Split of ticket zoom controller into separate files.
This commit is contained in:
parent
c360fabbb3
commit
a206644a85
8 changed files with 1329 additions and 1296 deletions
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,212 @@
|
||||||
|
class App.TicketZoomArticleActions extends App.Controller
|
||||||
|
events:
|
||||||
|
'click [data-type=public]': 'public_internal'
|
||||||
|
'click [data-type=internal]': 'public_internal'
|
||||||
|
'click [data-type=reply]': 'reply'
|
||||||
|
'click [data-type=replyAll]': 'replyAll'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
actions = @actionRow(@article)
|
||||||
|
|
||||||
|
if actions
|
||||||
|
@html App.view('ticket_zoom/article_view_actions')(
|
||||||
|
article: @article
|
||||||
|
actions: actions
|
||||||
|
)
|
||||||
|
else
|
||||||
|
@html ''
|
||||||
|
|
||||||
|
public_internal: (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
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
|
||||||
|
article.updateAttributes(
|
||||||
|
internal: internal
|
||||||
|
)
|
||||||
|
|
||||||
|
# runntime update
|
||||||
|
if internal
|
||||||
|
$(e.target).closest('.ticket-article-item').addClass('is-internal')
|
||||||
|
else
|
||||||
|
$(e.target).closest('.ticket-article-item').removeClass('is-internal')
|
||||||
|
|
||||||
|
@render()
|
||||||
|
|
||||||
|
actionRow: (article) ->
|
||||||
|
if @isRole('Customer')
|
||||||
|
return []
|
||||||
|
|
||||||
|
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 []
|
||||||
|
if article.type.name is 'email' || article.type.name is 'phone' || article.type.name is 'web'
|
||||||
|
actions.push {
|
||||||
|
name: 'reply'
|
||||||
|
type: 'reply'
|
||||||
|
icon: 'reply'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
recipients = []
|
||||||
|
if article.sender.name is 'Agent'
|
||||||
|
if article.to
|
||||||
|
localRecipients = emailAddresses.parseAddressList(article.to)
|
||||||
|
if localRecipients
|
||||||
|
recipients = recipients.concat localRecipients
|
||||||
|
else
|
||||||
|
if article.from
|
||||||
|
localRecipients = emailAddresses.parseAddressList(article.from)
|
||||||
|
if localRecipients
|
||||||
|
recipients = recipients.concat localRecipients
|
||||||
|
if article.cc
|
||||||
|
localRecipients = emailAddresses.parseAddressList(article.cc)
|
||||||
|
if localRecipients
|
||||||
|
recipients = recipients.concat localRecipients
|
||||||
|
if recipients.length > 1
|
||||||
|
actions.push {
|
||||||
|
name: 'reply all'
|
||||||
|
type: 'replyAll'
|
||||||
|
icon: 'reply-all'
|
||||||
|
href: '#'
|
||||||
|
}
|
||||||
|
actions.push {
|
||||||
|
name: 'split'
|
||||||
|
type: 'split'
|
||||||
|
icon: 'split'
|
||||||
|
href: '#ticket/create/' + article.ticket_id + '/' + article.id
|
||||||
|
}
|
||||||
|
actions
|
||||||
|
|
||||||
|
replyAll: (e) =>
|
||||||
|
@reply(e, true)
|
||||||
|
|
||||||
|
reply: (e, all = false) =>
|
||||||
|
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 )
|
||||||
|
customer = App.User.find( article.created_by_id )
|
||||||
|
|
||||||
|
@el.closest('.article-add').ScrollTo()
|
||||||
|
|
||||||
|
# empty form
|
||||||
|
articleNew = {
|
||||||
|
to: ''
|
||||||
|
cc: ''
|
||||||
|
body: ''
|
||||||
|
in_reply_to: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
#@el.closest('[name="in_reply_to"]').val('')
|
||||||
|
|
||||||
|
if article.message_id
|
||||||
|
articleNew.in_reply_to = article.message_id
|
||||||
|
|
||||||
|
if type.name is 'twitter status'
|
||||||
|
|
||||||
|
# set to in body
|
||||||
|
to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
|
||||||
|
articleNew.body = '@' + to
|
||||||
|
|
||||||
|
else if type.name is 'twitter direct-message'
|
||||||
|
|
||||||
|
# show to
|
||||||
|
to = customer.accounts['twitter'].username || customer.accounts['twitter'].uid
|
||||||
|
articleNew.to = to
|
||||||
|
|
||||||
|
else if type.name is 'email' || type.name is 'phone' || type.name is 'web'
|
||||||
|
|
||||||
|
if article.sender.name is 'Agent'
|
||||||
|
articleNew.to = article.to
|
||||||
|
else
|
||||||
|
articleNew.to = article.from
|
||||||
|
|
||||||
|
# if sender is customer but in article.from is no email, try to get
|
||||||
|
# customers email via customer user
|
||||||
|
if articleNew.to && !articleNew.to.match(/@/)
|
||||||
|
articleNew.to = article.created_by.email
|
||||||
|
|
||||||
|
# filter for uniq recipients
|
||||||
|
recipientAddresses = {}
|
||||||
|
recipient = emailAddresses.parseAddressList(articleNew.to)
|
||||||
|
if recipient && recipient[0]
|
||||||
|
recipientAddresses[ recipient[0].address.toString().toLowerCase() ] = true
|
||||||
|
if all
|
||||||
|
addAddresses = (lineNew, addressLine) ->
|
||||||
|
localAddresses = App.EmailAddress.all()
|
||||||
|
recipients = emailAddresses.parseAddressList(addressLine)
|
||||||
|
if recipients
|
||||||
|
for recipient in recipients
|
||||||
|
if recipient.address
|
||||||
|
|
||||||
|
# check if addess is not local
|
||||||
|
localAddess = false
|
||||||
|
for address in localAddresses
|
||||||
|
if recipient.address.toString().toLowerCase() == address.email.toString().toLowerCase()
|
||||||
|
localAddess = true
|
||||||
|
if !localAddess
|
||||||
|
|
||||||
|
# filter for uniq recipients
|
||||||
|
if !recipientAddresses[ recipient.address.toString().toLowerCase() ]
|
||||||
|
recipientAddresses[ recipient.address.toString().toLowerCase() ] = true
|
||||||
|
|
||||||
|
# add recipient
|
||||||
|
if lineNew
|
||||||
|
lineNew = lineNew + ', '
|
||||||
|
lineNew = lineNew + recipient.address
|
||||||
|
lineNew
|
||||||
|
|
||||||
|
if article.from
|
||||||
|
articleNew.cc = addAddresses(articleNew.cc, article.from)
|
||||||
|
if article.to
|
||||||
|
articleNew.cc = addAddresses(articleNew.cc, article.to)
|
||||||
|
if article.cc
|
||||||
|
articleNew.cc = addAddresses(articleNew.cc, article.cc)
|
||||||
|
|
||||||
|
# get current body
|
||||||
|
body = @el.closest('[data-name="body"]').html() || ''
|
||||||
|
|
||||||
|
# check if quote need to be added
|
||||||
|
selectedText = App.ClipBoard.getSelected()
|
||||||
|
if selectedText
|
||||||
|
|
||||||
|
# clean selection
|
||||||
|
selectedText = App.Utils.textCleanup( selectedText )
|
||||||
|
|
||||||
|
# convert to html
|
||||||
|
selectedText = App.Utils.text2html( selectedText )
|
||||||
|
if selectedText
|
||||||
|
selectedText = "<div><br><br/></div><div><blockquote type=\"cite\">#{selectedText}</blockquote></div><div><br></div>"
|
||||||
|
|
||||||
|
# add selected text to body
|
||||||
|
body = selectedText + body
|
||||||
|
|
||||||
|
articleNew.body = body
|
||||||
|
|
||||||
|
App.Event.trigger('ui::ticket::setArticleType', { ticket: @ticket, type: type, article: articleNew } )
|
|
@ -0,0 +1,520 @@
|
||||||
|
class App.TicketZoomArticleNew extends App.Controller
|
||||||
|
elements:
|
||||||
|
'.js-textarea': 'textarea'
|
||||||
|
'.attachmentPlaceholder': 'attachmentPlaceholder'
|
||||||
|
'.attachmentPlaceholder-inputHolder': 'attachmentInputHolder'
|
||||||
|
'.attachmentPlaceholder-hint': 'attachmentHint'
|
||||||
|
'.article-add': 'articleNewEdit'
|
||||||
|
'.attachments': 'attachmentsHolder'
|
||||||
|
'.attachmentUpload': 'attachmentUpload'
|
||||||
|
'.attachmentUpload-progressBar': 'progressBar'
|
||||||
|
'.js-percentage': 'progressText'
|
||||||
|
'.js-cancel': 'cancelContainer'
|
||||||
|
'.textBubble': 'textBubble'
|
||||||
|
'.editControls-item': 'editControlItem'
|
||||||
|
#'.editControls': 'editControls'
|
||||||
|
#'.recipient-picker': 'recipientPicker'
|
||||||
|
#'.recipient-list': 'recipientList'
|
||||||
|
#'.recipient-list .list-arrow': 'recipientListArrow'
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click .js-toggleVisibility': 'toggleVisibility'
|
||||||
|
'click .js-articleTypeItem': 'selectArticleType'
|
||||||
|
'click .js-selectedArticleType': 'showSelectableArticleType'
|
||||||
|
'click .recipient-picker': 'toggle_recipients'
|
||||||
|
'click .recipient-list': 'stopPropagation'
|
||||||
|
'click .list-entry-type div': 'change_type'
|
||||||
|
'submit .recipient-list form': 'add_recipient'
|
||||||
|
'focus .js-textarea': 'openTextarea'
|
||||||
|
'input .js-textarea': 'detectEmptyTextarea'
|
||||||
|
#'dragenter': 'onDragenter'
|
||||||
|
#'dragleave': 'onDragleave'
|
||||||
|
#'drop': 'onFileDrop'
|
||||||
|
#'change input[type=file]': 'onFilePick'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
# gets referenced in @setArticleType
|
||||||
|
@type = @defaults['type'] || 'note'
|
||||||
|
@articleTypes = [
|
||||||
|
{
|
||||||
|
name: 'note'
|
||||||
|
icon: 'note'
|
||||||
|
attributes: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email'
|
||||||
|
icon: 'email'
|
||||||
|
attributes: ['to','cc']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'facebook'
|
||||||
|
icon: 'facebook'
|
||||||
|
attributes: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter'
|
||||||
|
icon: 'twitter'
|
||||||
|
attributes: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phone'
|
||||||
|
icon: 'phone'
|
||||||
|
attributes: []
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if @isRole('Customer')
|
||||||
|
@type = 'note'
|
||||||
|
@articleTypes = [
|
||||||
|
{
|
||||||
|
name: 'note'
|
||||||
|
icon: 'note'
|
||||||
|
attributes: []
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@textareaHeight =
|
||||||
|
open: 148
|
||||||
|
closed: 20
|
||||||
|
|
||||||
|
@dragEventCounter = 0
|
||||||
|
@attachments = []
|
||||||
|
|
||||||
|
@render()
|
||||||
|
|
||||||
|
if @defaults.body or @isIE10()
|
||||||
|
@openTextarea(null, true)
|
||||||
|
|
||||||
|
# set article type and expand text area
|
||||||
|
@bind(
|
||||||
|
'ui::ticket::setArticleType'
|
||||||
|
(data) =>
|
||||||
|
if data.ticket.id is @ticket.id
|
||||||
|
#@setArticleType(data.type.name)
|
||||||
|
|
||||||
|
@openTextarea(null, true)
|
||||||
|
for key, value of data.article
|
||||||
|
if key is 'body'
|
||||||
|
@$('[data-name="' + key + '"]').html(value)
|
||||||
|
else
|
||||||
|
@$('[name="' + key + '"]').val(value)
|
||||||
|
|
||||||
|
# preselect article type
|
||||||
|
@setArticleType( 'email' )
|
||||||
|
)
|
||||||
|
|
||||||
|
# reset new article screen
|
||||||
|
@bind(
|
||||||
|
'ui::ticket::taskReset'
|
||||||
|
(data) =>
|
||||||
|
if data.ticket_id is @ticket.id
|
||||||
|
@type = 'note'
|
||||||
|
@defaults = {}
|
||||||
|
@render()
|
||||||
|
)
|
||||||
|
|
||||||
|
isIE10: ->
|
||||||
|
Function('/*@cc_on return document.documentMode===10@*/')()
|
||||||
|
|
||||||
|
stopPropagation: (e) ->
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
release: =>
|
||||||
|
if @subscribeIdTextModule
|
||||||
|
App.Ticket.unsubscribe(@subscribeIdTextModule)
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
|
||||||
|
ticket = App.Ticket.fullLocal( @ticket.id )
|
||||||
|
|
||||||
|
@html App.view('ticket_zoom/article_new')(
|
||||||
|
ticket: ticket
|
||||||
|
articleTypes: @articleTypes
|
||||||
|
article: @defaults
|
||||||
|
isCustomer: @isRole('Customer')
|
||||||
|
)
|
||||||
|
@setArticleType(@type)
|
||||||
|
|
||||||
|
new App.WidgetAvatar(
|
||||||
|
el: @$('.js-avatar')
|
||||||
|
user_id: App.Session.get('id')
|
||||||
|
size: 40
|
||||||
|
position: 'right'
|
||||||
|
class: 'zIndex-5'
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_attributes = [
|
||||||
|
{ name: 'customer_id', display: 'Recipients', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateUser: false },
|
||||||
|
]
|
||||||
|
|
||||||
|
controller = new App.ControllerForm(
|
||||||
|
el: @$('.recipients')
|
||||||
|
model:
|
||||||
|
configure_attributes: configure_attributes,
|
||||||
|
)
|
||||||
|
|
||||||
|
@$('[data-name="body"]').ce({
|
||||||
|
mode: 'richtext'
|
||||||
|
multiline: true
|
||||||
|
maxlength: 5000
|
||||||
|
})
|
||||||
|
|
||||||
|
html5Upload.initialize(
|
||||||
|
uploadUrl: App.Config.get('api_path') + '/ticket_attachment_upload',
|
||||||
|
dropContainer: @el.get(0),
|
||||||
|
cancelContainer: @cancelContainer,
|
||||||
|
inputField: @$('.article-attachment input').get(0),
|
||||||
|
key: 'File',
|
||||||
|
data: { form_id: @form_id },
|
||||||
|
maxSimultaneousUploads: 1,
|
||||||
|
onFileAdded: (file) =>
|
||||||
|
|
||||||
|
file.on(
|
||||||
|
|
||||||
|
onStart: =>
|
||||||
|
@attachmentPlaceholder.addClass('hide')
|
||||||
|
@attachmentUpload.removeClass('hide')
|
||||||
|
@cancelContainer.removeClass('hide')
|
||||||
|
console.log('upload start')
|
||||||
|
|
||||||
|
onAborted: =>
|
||||||
|
@attachmentPlaceholder.removeClass('hide')
|
||||||
|
@attachmentUpload.addClass('hide')
|
||||||
|
|
||||||
|
# Called after received response from the server
|
||||||
|
onCompleted: (response) =>
|
||||||
|
|
||||||
|
response = JSON.parse(response)
|
||||||
|
@attachments.push response.data
|
||||||
|
|
||||||
|
@attachmentPlaceholder.removeClass('hide')
|
||||||
|
@attachmentUpload.addClass('hide')
|
||||||
|
|
||||||
|
@renderAttachment(response.data)
|
||||||
|
console.log('upload complete', response.data )
|
||||||
|
|
||||||
|
# Called during upload progress, first parameter
|
||||||
|
# is decimal value from 0 to 100.
|
||||||
|
onProgress: (progress, fileSize, uploadedBytes) =>
|
||||||
|
@progressBar.width(parseInt(progress) + "%")
|
||||||
|
@progressText.text(parseInt(progress))
|
||||||
|
# hide cancel on 90%
|
||||||
|
if parseInt(progress) >= 90
|
||||||
|
@cancelContainer.addClass('hide')
|
||||||
|
console.log('uploadProgress ', parseInt(progress))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# show text module UI
|
||||||
|
if !@isRole('Customer')
|
||||||
|
textModule = new App.WidgetTextModule(
|
||||||
|
el: @$('.js-textarea').parent()
|
||||||
|
data:
|
||||||
|
ticket: ticket
|
||||||
|
)
|
||||||
|
callback = (ticket) =>
|
||||||
|
textModule.reload(
|
||||||
|
ticket: ticket
|
||||||
|
)
|
||||||
|
@subscribeIdTextModule = ticket.subscribe( callback )
|
||||||
|
|
||||||
|
toggle_recipients: =>
|
||||||
|
if !@pickRecipientsCatcher
|
||||||
|
@show_recipients()
|
||||||
|
else
|
||||||
|
@hide_recipients()
|
||||||
|
|
||||||
|
show_recipients: ->
|
||||||
|
padding = 15
|
||||||
|
|
||||||
|
@recipientPicker.addClass('is-open')
|
||||||
|
@recipientList.removeClass('hide')
|
||||||
|
|
||||||
|
pickerDimensions = @recipientPicker.get(0).getBoundingClientRect()
|
||||||
|
availableHeight = @recipientPicker.scrollParent().outerHeight()
|
||||||
|
|
||||||
|
top = pickerDimensions.height/2 - @recipientList.height()/2
|
||||||
|
bottomDistance = availableHeight - padding - (pickerDimensions.top + top + @recipientList.height())
|
||||||
|
|
||||||
|
if bottomDistance < 0
|
||||||
|
top += bottomDistance
|
||||||
|
|
||||||
|
arrowCenter = -top + pickerDimensions.height/2
|
||||||
|
|
||||||
|
@recipientListArrow.css('top', arrowCenter)
|
||||||
|
@recipientList.css('top', top)
|
||||||
|
|
||||||
|
$.Velocity.hook(@recipientList, 'transformOriginX', "0")
|
||||||
|
$.Velocity.hook(@recipientList, 'transformOriginY', "#{ arrowCenter }px")
|
||||||
|
|
||||||
|
@recipientList.velocity
|
||||||
|
properties:
|
||||||
|
scale: [ 1, 0 ]
|
||||||
|
opacity: [ 1, 0 ]
|
||||||
|
options:
|
||||||
|
speed: 300
|
||||||
|
easing: [ 0.34, 1.61, 0.7, 1 ]
|
||||||
|
|
||||||
|
@pickRecipientsCatcher = new App.clickCatcher
|
||||||
|
holder: @el.offsetParent()
|
||||||
|
callback: @hide_recipients
|
||||||
|
zIndexScale: 6
|
||||||
|
|
||||||
|
hide_recipients: =>
|
||||||
|
@pickRecipientsCatcher.remove()
|
||||||
|
@pickRecipientsCatcher = null
|
||||||
|
|
||||||
|
@recipientPicker.removeClass('is-open')
|
||||||
|
|
||||||
|
@recipientList.velocity
|
||||||
|
properties:
|
||||||
|
scale: [ 0, 1 ]
|
||||||
|
opacity: [ 0, 1 ]
|
||||||
|
options:
|
||||||
|
speed: 300
|
||||||
|
easing: [ 500, 20 ]
|
||||||
|
complete: -> @recipientList.addClass('hide')
|
||||||
|
|
||||||
|
change_type: (e) ->
|
||||||
|
$(e.target).addClass('active').siblings('.active').removeClass('active')
|
||||||
|
# store $(this).data('value')
|
||||||
|
|
||||||
|
add_recipient: (e) ->
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
console.log "add recipient", e
|
||||||
|
# store recipient
|
||||||
|
|
||||||
|
toggleVisibility: ->
|
||||||
|
if @articleNewEdit.hasClass 'is-public'
|
||||||
|
@articleNewEdit
|
||||||
|
.removeClass 'is-public'
|
||||||
|
.addClass 'is-internal'
|
||||||
|
|
||||||
|
@$('[name="internal"]').val 'true'
|
||||||
|
else
|
||||||
|
@articleNewEdit
|
||||||
|
.addClass 'is-public'
|
||||||
|
.removeClass 'is-internal'
|
||||||
|
|
||||||
|
|
||||||
|
@$('[name="internal"]').val ''
|
||||||
|
|
||||||
|
showSelectableArticleType: =>
|
||||||
|
@el.find('.js-articleTypes').removeClass('is-hidden')
|
||||||
|
|
||||||
|
@selectTypeCatcher = new App.clickCatcher
|
||||||
|
holder: @el.offsetParent()
|
||||||
|
callback: @hideSelectableArticleType
|
||||||
|
zIndexScale: 6
|
||||||
|
|
||||||
|
selectArticleType: (e) =>
|
||||||
|
articleTypeToSet = $(e.target).closest('.pop-selectable').data('value')
|
||||||
|
@setArticleType( articleTypeToSet )
|
||||||
|
@hideSelectableArticleType()
|
||||||
|
|
||||||
|
@selectTypeCatcher.remove()
|
||||||
|
@selectTypeCatcher = null
|
||||||
|
|
||||||
|
hideSelectableArticleType: =>
|
||||||
|
@el.find('.js-articleTypes').addClass('is-hidden')
|
||||||
|
|
||||||
|
setArticleType: (type) ->
|
||||||
|
typeIcon = @$('.js-selectedType')
|
||||||
|
@type = type
|
||||||
|
@$('[name="type"]').val(type)
|
||||||
|
@articleNewEdit.attr('data-type', type)
|
||||||
|
typeIcon.find('use').attr 'xlink:href', '#icon-'+ @type
|
||||||
|
|
||||||
|
# show/hide attributes
|
||||||
|
for articleType in @articleTypes
|
||||||
|
if articleType.name is type
|
||||||
|
@$('.form-group').addClass('hide')
|
||||||
|
for name in articleType.attributes
|
||||||
|
@$("[name=#{name}]").closest('.form-group').removeClass('hide')
|
||||||
|
|
||||||
|
# check if signature need to be added
|
||||||
|
body = @$('[data-name="body"]').html() || ''
|
||||||
|
signature = undefined
|
||||||
|
if @ticket.group.signature_id
|
||||||
|
signature = App.Signature.find( @ticket.group.signature_id )
|
||||||
|
if signature && signature.body && @type is 'email'
|
||||||
|
signatureFinished = App.Utils.text2html(
|
||||||
|
App.Utils.replaceTags( signature.body, { user: App.Session.get(), ticket: @ticket } )
|
||||||
|
)
|
||||||
|
if App.Utils.signatureCheck( body, signatureFinished )
|
||||||
|
if !App.Utils.lastLineEmpty(body)
|
||||||
|
body = body + '<br>'
|
||||||
|
body = body + "<div data-signature=\"true\" data-signature-id=\"#{signature.id}\">#{signatureFinished}</div>"
|
||||||
|
@$('[data-name="body"]').html(body)
|
||||||
|
|
||||||
|
# remove old signature
|
||||||
|
else
|
||||||
|
@$('[data-name="body"]').find("[data-signature=true]").remove()
|
||||||
|
|
||||||
|
detectEmptyTextarea: =>
|
||||||
|
if !@textarea.text().trim()
|
||||||
|
@addTextareaCatcher()
|
||||||
|
else
|
||||||
|
@removeTextareaCatcher()
|
||||||
|
|
||||||
|
openTextarea: (event, withoutAnimation) =>
|
||||||
|
if !@articleNewEdit.hasClass('is-open')
|
||||||
|
duration = 300
|
||||||
|
|
||||||
|
if withoutAnimation
|
||||||
|
duration = 0
|
||||||
|
|
||||||
|
@articleNewEdit.addClass('is-open')
|
||||||
|
|
||||||
|
@textarea.velocity
|
||||||
|
properties:
|
||||||
|
minHeight: "#{ @textareaHeight.open - 38 }px"
|
||||||
|
options:
|
||||||
|
duration: duration
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
complete: => @addTextareaCatcher()
|
||||||
|
|
||||||
|
@textBubble.velocity
|
||||||
|
properties:
|
||||||
|
paddingBottom: 28
|
||||||
|
options:
|
||||||
|
duration: duration
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
|
||||||
|
# scroll to bottom
|
||||||
|
@textarea.velocity "scroll",
|
||||||
|
container: @textarea.scrollParent()
|
||||||
|
offset: 99999
|
||||||
|
duration: 300
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
queue: false
|
||||||
|
|
||||||
|
@editControlItem
|
||||||
|
.removeClass('is-hidden')
|
||||||
|
.velocity
|
||||||
|
properties:
|
||||||
|
opacity: [ 1, 0 ]
|
||||||
|
translateX: [ 0, 20 ]
|
||||||
|
translateZ: 0
|
||||||
|
options:
|
||||||
|
duration: 300
|
||||||
|
stagger: 50
|
||||||
|
drag: true
|
||||||
|
|
||||||
|
# move attachment text to the left bottom (bottom happens automatically)
|
||||||
|
@attachmentPlaceholder.velocity
|
||||||
|
properties:
|
||||||
|
translateX: -@attachmentInputHolder.position().left + "px"
|
||||||
|
options:
|
||||||
|
duration: duration
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
|
||||||
|
@attachmentHint.velocity
|
||||||
|
properties:
|
||||||
|
opacity: 0
|
||||||
|
options:
|
||||||
|
duration: duration
|
||||||
|
|
||||||
|
addTextareaCatcher: =>
|
||||||
|
if @articleNewEdit.is(':visible')
|
||||||
|
@textareaCatcher = new App.clickCatcher
|
||||||
|
holder: @articleNewEdit.offsetParent()
|
||||||
|
callback: @closeTextarea
|
||||||
|
zIndexScale: 4
|
||||||
|
|
||||||
|
removeTextareaCatcher: ->
|
||||||
|
return if !@textareaCatcher
|
||||||
|
@textareaCatcher.remove()
|
||||||
|
@textareaCatcher = null
|
||||||
|
|
||||||
|
closeTextarea: =>
|
||||||
|
@removeTextareaCatcher()
|
||||||
|
if !@textarea.text().trim() && !@attachments.length && not @isIE10()
|
||||||
|
|
||||||
|
@textarea.velocity
|
||||||
|
properties:
|
||||||
|
minHeight: "#{ @textareaHeight.closed }px"
|
||||||
|
options:
|
||||||
|
duration: 300
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
complete: => @articleNewEdit.removeClass('is-open')
|
||||||
|
|
||||||
|
@textBubble.velocity
|
||||||
|
properties:
|
||||||
|
paddingBottom: 10
|
||||||
|
options:
|
||||||
|
duration: 300
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
|
||||||
|
@attachmentPlaceholder.velocity
|
||||||
|
properties:
|
||||||
|
translateX: 0
|
||||||
|
options:
|
||||||
|
duration: 300
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
|
||||||
|
@attachmentHint.velocity
|
||||||
|
properties:
|
||||||
|
opacity: 1
|
||||||
|
options:
|
||||||
|
duration: 300
|
||||||
|
|
||||||
|
@editControlItem
|
||||||
|
.velocity
|
||||||
|
properties:
|
||||||
|
opacity: [ 0, 1 ]
|
||||||
|
translateX: [ 20, 0 ]
|
||||||
|
translateZ: 0
|
||||||
|
options:
|
||||||
|
duration: 100
|
||||||
|
stagger: 50
|
||||||
|
drag: true
|
||||||
|
complete: (elements) => $(elements).addClass('is-hidden')
|
||||||
|
|
||||||
|
onDragenter: (event) =>
|
||||||
|
# on the first event,
|
||||||
|
# open textarea (it will only open if its closed)
|
||||||
|
@openTextarea() if @dragEventCounter is 0
|
||||||
|
|
||||||
|
@dragEventCounter++
|
||||||
|
@articleNewEdit.parent().addClass('is-dropTarget')
|
||||||
|
|
||||||
|
onDragleave: (event) =>
|
||||||
|
@dragEventCounter--
|
||||||
|
|
||||||
|
@articleNewEdit.parent().removeClass('is-dropTarget') if @dragEventCounter is 0
|
||||||
|
|
||||||
|
renderAttachment: (file) =>
|
||||||
|
@attachmentsHolder.append App.view('generic/attachment_item')
|
||||||
|
fileName: file.filename
|
||||||
|
fileSize: @humanFileSize( file.size )
|
||||||
|
store_id: file.store_id
|
||||||
|
@attachmentsHolder.on(
|
||||||
|
'click'
|
||||||
|
"[data-id=#{file.store_id}]", (e) =>
|
||||||
|
@attachments = _.filter(
|
||||||
|
@attachments,
|
||||||
|
(item) ->
|
||||||
|
return if item.id isnt file.store_id
|
||||||
|
item
|
||||||
|
)
|
||||||
|
store_id = $(e.currentTarget).data('id')
|
||||||
|
|
||||||
|
# delete attachment from storage
|
||||||
|
App.Ajax.request(
|
||||||
|
type: 'DELETE'
|
||||||
|
url: App.Config.get('api_path') + '/ticket_attachment_upload'
|
||||||
|
data: JSON.stringify( { store_id: store_id } ),
|
||||||
|
processData: false
|
||||||
|
success: (data, status, xhr) =>
|
||||||
|
)
|
||||||
|
|
||||||
|
# remove attachment from dom
|
||||||
|
element = $(e.currentTarget).closest('.attachments')
|
||||||
|
$(e.currentTarget).closest('.attachment').remove()
|
||||||
|
# empty .attachment (remove spaces) to keep css working, thanks @mrflix :-o
|
||||||
|
if element.find('.attachment').length == 0
|
||||||
|
element.empty()
|
||||||
|
)
|
|
@ -0,0 +1,251 @@
|
||||||
|
class App.TicketZoomArticleView extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
@article_controller = {}
|
||||||
|
|
||||||
|
execute: (params) ->
|
||||||
|
for ticket_article_id in params.ticket_article_ids
|
||||||
|
if !@article_controller[ticket_article_id]
|
||||||
|
el = $('<div></div>')
|
||||||
|
@article_controller[ticket_article_id] = new ArticleViewItem(
|
||||||
|
ticket: @ticket
|
||||||
|
ticket_article_id: ticket_article_id
|
||||||
|
el: el
|
||||||
|
ui: @ui
|
||||||
|
)
|
||||||
|
@el.append( el )
|
||||||
|
|
||||||
|
class ArticleViewItem extends App.Controller
|
||||||
|
events:
|
||||||
|
'click .show_toogle': 'show_toogle'
|
||||||
|
'click .textBubble': 'toggle_meta_with_delay'
|
||||||
|
'click .textBubble a': 'stopPropagation'
|
||||||
|
'click .js-unfold': 'unfold'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
@seeMore = false
|
||||||
|
|
||||||
|
@render()
|
||||||
|
|
||||||
|
# set article type and expand text area
|
||||||
|
@bind(
|
||||||
|
'ui::ticket::shown'
|
||||||
|
(data) =>
|
||||||
|
if data.ticket_id is @ticket.id
|
||||||
|
@setSeeMore()
|
||||||
|
)
|
||||||
|
|
||||||
|
# subscribe to changes
|
||||||
|
@subscribeId = App.TicketArticle.full( @ticket_article_id, @render, false, true )
|
||||||
|
|
||||||
|
release: =>
|
||||||
|
App.User.TicketArticle(@subscribeId)
|
||||||
|
|
||||||
|
render: (article) =>
|
||||||
|
|
||||||
|
# get articles
|
||||||
|
@article = App.TicketArticle.fullLocal( @ticket_article_id )
|
||||||
|
|
||||||
|
# prepare html body
|
||||||
|
if @article.content_type is 'text/html'
|
||||||
|
@article['html'] = @article.body
|
||||||
|
else
|
||||||
|
@article['html'] = App.Utils.textCleanup( @article.body )
|
||||||
|
@article['html'] = App.Utils.text2html( @article.body )
|
||||||
|
|
||||||
|
@html App.view('ticket_zoom/article_view')(
|
||||||
|
ticket: @ticket
|
||||||
|
article: @article
|
||||||
|
isCustomer: @isRole('Customer')
|
||||||
|
)
|
||||||
|
|
||||||
|
new App.WidgetAvatar(
|
||||||
|
el: @$('.js-avatar')
|
||||||
|
user_id: @article.created_by_id
|
||||||
|
size: 40
|
||||||
|
)
|
||||||
|
|
||||||
|
new App.TicketZoomArticleActions(
|
||||||
|
el: @$('.js-article-actions')
|
||||||
|
ticket: @ticket
|
||||||
|
article: @article
|
||||||
|
)
|
||||||
|
|
||||||
|
# show frontend times
|
||||||
|
@frontendTimeUpdate()
|
||||||
|
|
||||||
|
# set see more option
|
||||||
|
@setSeeMore()
|
||||||
|
|
||||||
|
# set see more options
|
||||||
|
setSeeMore: =>
|
||||||
|
maxHeight = 560
|
||||||
|
bubble = @$('.textBubble-content')
|
||||||
|
|
||||||
|
# expand if see more is already clicked
|
||||||
|
if @seeMore
|
||||||
|
bubble.css('height', 'auto')
|
||||||
|
bubble.parent().find('.textBubble-overflowContainer').addClass('hide')
|
||||||
|
return
|
||||||
|
|
||||||
|
# reset bubble heigth and "see more" opacity
|
||||||
|
bubble.css('height', '')
|
||||||
|
bubble.parent().find('.textBubble-overflowContainer').css('opacity', '')
|
||||||
|
|
||||||
|
# remember offset of "see more"
|
||||||
|
offsetTop = bubble.find('.js-signatureMarker').position()
|
||||||
|
|
||||||
|
# remember bubble heigth
|
||||||
|
heigth = bubble.height()
|
||||||
|
if offsetTop
|
||||||
|
bubble.attr('data-height', heigth)
|
||||||
|
bubble.css('height', "#{offsetTop.top + 30}px")
|
||||||
|
bubble.parent().find('.textBubble-overflowContainer').removeClass('hide')
|
||||||
|
else if heigth > maxHeight
|
||||||
|
bubble.attr('data-height', heigth)
|
||||||
|
bubble.css('height', "#{maxHeight}px")
|
||||||
|
bubble.parent().find('.textBubble-overflowContainer').removeClass('hide')
|
||||||
|
else
|
||||||
|
bubble.parent().find('.textBubble-overflowContainer').addClass('hide')
|
||||||
|
|
||||||
|
show_toogle: (e) ->
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
#$(e.target).hide()
|
||||||
|
if $(e.target).next('div')[0]
|
||||||
|
if $(e.target).next('div').hasClass('hide')
|
||||||
|
$(e.target).next('div').removeClass('hide')
|
||||||
|
$(e.target).text( App.i18n.translateContent('Fold in') )
|
||||||
|
else
|
||||||
|
$(e.target).text( App.i18n.translateContent('See more') )
|
||||||
|
$(e.target).next('div').addClass('hide')
|
||||||
|
|
||||||
|
stopPropagation: (e) ->
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
toggle_meta_with_delay: (e) =>
|
||||||
|
# allow double click select
|
||||||
|
# by adding a delay to the toggle
|
||||||
|
|
||||||
|
if @lastClick and +new Date - @lastClick < 150
|
||||||
|
clearTimeout(@toggleMetaTimeout)
|
||||||
|
else
|
||||||
|
@toggleMetaTimeout = setTimeout(@toggle_meta, 150, e)
|
||||||
|
@lastClick = +new Date
|
||||||
|
|
||||||
|
toggle_meta: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
animSpeed = 300
|
||||||
|
article = $(e.target).closest('.ticket-article-item')
|
||||||
|
metaTopClip = article.find('.article-meta-clip.top')
|
||||||
|
metaBottomClip = article.find('.article-meta-clip.bottom')
|
||||||
|
metaTop = article.find('.article-content-meta.top')
|
||||||
|
metaBottom = article.find('.article-content-meta.bottom')
|
||||||
|
|
||||||
|
if @elementContainsSelection( article.get(0) )
|
||||||
|
@stopPropagation(e)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if !metaTop.hasClass('hide')
|
||||||
|
article.removeClass('state--folde-out')
|
||||||
|
|
||||||
|
# scroll back up
|
||||||
|
article.velocity "scroll",
|
||||||
|
container: article.scrollParent()
|
||||||
|
offset: -article.offset().top - metaTop.outerHeight()
|
||||||
|
duration: animSpeed
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
|
||||||
|
metaTop.velocity
|
||||||
|
properties:
|
||||||
|
translateY: 0
|
||||||
|
opacity: [ 0, 1 ]
|
||||||
|
options:
|
||||||
|
speed: animSpeed
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
complete: -> metaTop.addClass('hide')
|
||||||
|
|
||||||
|
metaBottom.velocity
|
||||||
|
properties:
|
||||||
|
translateY: [ -metaBottom.outerHeight(), 0 ]
|
||||||
|
opacity: [ 0, 1 ]
|
||||||
|
options:
|
||||||
|
speed: animSpeed
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
complete: -> metaBottom.addClass('hide')
|
||||||
|
|
||||||
|
metaTopClip.velocity({ height: 0 }, animSpeed, 'easeOutQuad')
|
||||||
|
metaBottomClip.velocity({ height: 0 }, animSpeed, 'easeOutQuad')
|
||||||
|
else
|
||||||
|
article.addClass('state--folde-out')
|
||||||
|
metaBottom.removeClass('hide')
|
||||||
|
metaTop.removeClass('hide')
|
||||||
|
|
||||||
|
# balance out the top meta height by scrolling down
|
||||||
|
article.velocity("scroll",
|
||||||
|
container: article.scrollParent()
|
||||||
|
offset: -article.offset().top + metaTop.outerHeight()
|
||||||
|
duration: animSpeed
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
)
|
||||||
|
|
||||||
|
metaTop.velocity
|
||||||
|
properties:
|
||||||
|
translateY: [ 0, metaTop.outerHeight() ]
|
||||||
|
opacity: [ 1, 0 ]
|
||||||
|
options:
|
||||||
|
speed: animSpeed
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
|
||||||
|
metaBottom.velocity
|
||||||
|
properties:
|
||||||
|
translateY: [ 0, -metaBottom.outerHeight() ]
|
||||||
|
opacity: [ 1, 0 ]
|
||||||
|
options:
|
||||||
|
speed: animSpeed
|
||||||
|
easing: 'easeOutQuad'
|
||||||
|
|
||||||
|
metaTopClip.velocity({ height: metaTop.outerHeight() }, animSpeed, 'easeOutQuad')
|
||||||
|
metaBottomClip.velocity({ height: metaBottom.outerHeight() }, animSpeed, 'easeOutQuad')
|
||||||
|
|
||||||
|
unfold: (e) ->
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
@seeMore = true
|
||||||
|
|
||||||
|
container = $(e.currentTarget).parents('.textBubble-content')
|
||||||
|
overflowContainer = container.find('.textBubble-overflowContainer')
|
||||||
|
|
||||||
|
overflowContainer.velocity
|
||||||
|
properties:
|
||||||
|
opacity: 0
|
||||||
|
options:
|
||||||
|
duration: 300
|
||||||
|
|
||||||
|
container.velocity
|
||||||
|
properties:
|
||||||
|
height: container.attr('data-height')
|
||||||
|
options:
|
||||||
|
duration: 300
|
||||||
|
complete: -> overflowContainer.addClass('hide');
|
||||||
|
|
||||||
|
isOrContains: (node, container) ->
|
||||||
|
while node
|
||||||
|
if node is container
|
||||||
|
return true
|
||||||
|
node = node.parentNode
|
||||||
|
false
|
||||||
|
|
||||||
|
elementContainsSelection: (el) ->
|
||||||
|
sel = window.getSelection()
|
||||||
|
if sel.rangeCount > 0 && sel.toString()
|
||||||
|
for i in [0..sel.rangeCount-1]
|
||||||
|
if !@isOrContains(sel.getRangeAt(i).commonAncestorContainer, el)
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
false
|
|
@ -0,0 +1,19 @@
|
||||||
|
class App.TicketZoomMeta extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
@ticket = App.Ticket.fullLocal( @ticket.id )
|
||||||
|
@subscribeId = @ticket.subscribe(@render)
|
||||||
|
@render(@ticket)
|
||||||
|
|
||||||
|
render: (ticket) =>
|
||||||
|
@html App.view('ticket_zoom/meta')(
|
||||||
|
ticket: ticket
|
||||||
|
isCustomer: @isRole('Customer')
|
||||||
|
)
|
||||||
|
|
||||||
|
# show frontend times
|
||||||
|
@frontendTimeUpdate()
|
||||||
|
|
||||||
|
release: =>
|
||||||
|
App.Ticket.unsubscribe( @subscribeId )
|
|
@ -0,0 +1,71 @@
|
||||||
|
class App.TicketZoomOverviewNavigator extends App.Controller
|
||||||
|
events:
|
||||||
|
'click a': 'open'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
# rebuild overview navigator if overview has changed
|
||||||
|
@bind 'ticket_overview_rebuild', (data) =>
|
||||||
|
execute = =>
|
||||||
|
@render()
|
||||||
|
@delay(execute, 600, 'overview-navigator')
|
||||||
|
|
||||||
|
@render()
|
||||||
|
|
||||||
|
render: (overview) =>
|
||||||
|
if !@overview_id
|
||||||
|
@html('')
|
||||||
|
return
|
||||||
|
|
||||||
|
# get overview data
|
||||||
|
worker = App.TaskManager.worker( 'TicketOverview' )
|
||||||
|
return if !worker
|
||||||
|
overview = worker.overview(@overview_id)
|
||||||
|
return if !overview
|
||||||
|
current_position = 0
|
||||||
|
next = false
|
||||||
|
previous = false
|
||||||
|
for ticket_id in overview.ticket_ids
|
||||||
|
current_position += 1
|
||||||
|
next = overview.ticket_ids[current_position]
|
||||||
|
previous = overview.ticket_ids[current_position-2]
|
||||||
|
break if ticket_id is @ticket_id
|
||||||
|
|
||||||
|
# get next/previous ticket
|
||||||
|
if next
|
||||||
|
next = App.Ticket.find(next)
|
||||||
|
if previous
|
||||||
|
previous = App.Ticket.find(previous)
|
||||||
|
|
||||||
|
@html App.view('ticket_zoom/overview_navigator')(
|
||||||
|
title: overview.overview.name
|
||||||
|
total_count: overview.tickets_count
|
||||||
|
current_position: current_position
|
||||||
|
next: next
|
||||||
|
previous: previous
|
||||||
|
)
|
||||||
|
|
||||||
|
open: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
# get requested object and location
|
||||||
|
id = $(e.target).data('id')
|
||||||
|
url = $(e.target).attr('href')
|
||||||
|
if !id
|
||||||
|
id = $(e.target).closest('a').data('id')
|
||||||
|
url = $(e.target).closest('a').attr('href')
|
||||||
|
|
||||||
|
# return if we are unable to get id
|
||||||
|
return if !id
|
||||||
|
|
||||||
|
# open task via task manager to get overview information
|
||||||
|
App.TaskManager.execute(
|
||||||
|
key: 'Ticket-' + id
|
||||||
|
controller: 'TicketZoom'
|
||||||
|
params:
|
||||||
|
ticket_id: id
|
||||||
|
overview_id: @overview_id
|
||||||
|
show: true
|
||||||
|
)
|
||||||
|
@navigate url
|
|
@ -0,0 +1,179 @@
|
||||||
|
class App.TicketZoomSidebar extends App.Controller
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
ticket = App.Ticket.fullLocal( @ticket.id )
|
||||||
|
@subscribeId = ticket.subscribe(@render)
|
||||||
|
@render(ticket)
|
||||||
|
|
||||||
|
release: =>
|
||||||
|
App.Ticket.unsubscribe( @subscribeId )
|
||||||
|
|
||||||
|
render: (ticket) =>
|
||||||
|
|
||||||
|
editTicket = (el) =>
|
||||||
|
el.append('<form class="edit"></form>')
|
||||||
|
@editEl = el
|
||||||
|
|
||||||
|
show = (ticket) =>
|
||||||
|
el.find('.edit').html('')
|
||||||
|
|
||||||
|
defaults = ticket.attributes()
|
||||||
|
task_state = @taskGet('ticket')
|
||||||
|
modelDiff = App.Utils.formDiff( task_state, defaults )
|
||||||
|
#if @isRole('Customer')
|
||||||
|
# delete defaults['state_id']
|
||||||
|
# delete defaults['state']
|
||||||
|
if !_.isEmpty( task_state )
|
||||||
|
defaults = _.extend( defaults, task_state )
|
||||||
|
|
||||||
|
new App.ControllerForm(
|
||||||
|
el: el.find('.edit')
|
||||||
|
model: App.Ticket
|
||||||
|
screen: 'edit'
|
||||||
|
params: App.Ticket.find(ticket.id)
|
||||||
|
handlers: [
|
||||||
|
@ticketFormChanges
|
||||||
|
]
|
||||||
|
filter: @form_meta.filter
|
||||||
|
params: defaults
|
||||||
|
#bookmarkable: true
|
||||||
|
)
|
||||||
|
#console.log('Ichanges', modelDiff, task_state, ticket.attributes())
|
||||||
|
#@markFormDiff( modelDiff )
|
||||||
|
|
||||||
|
show(ticket)
|
||||||
|
@bind(
|
||||||
|
'ui::ticket::taskReset'
|
||||||
|
(data) =>
|
||||||
|
if data.ticket_id is ticket.id
|
||||||
|
show(ticket)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !@isRole('Customer')
|
||||||
|
el.append('<div class="tags"></div>')
|
||||||
|
new App.WidgetTag(
|
||||||
|
el: el.find('.tags')
|
||||||
|
object_type: 'Ticket'
|
||||||
|
object: ticket
|
||||||
|
tags: @tags
|
||||||
|
)
|
||||||
|
el.append('<div class="links"></div>')
|
||||||
|
new App.WidgetLink(
|
||||||
|
el: el.find('.links')
|
||||||
|
object_type: 'Ticket'
|
||||||
|
object: ticket
|
||||||
|
links: @links
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
|
||||||
|
showTicketHistory = =>
|
||||||
|
new App.TicketHistory(
|
||||||
|
ticket_id: ticket.id
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
showTicketMerge = =>
|
||||||
|
new App.TicketMerge(
|
||||||
|
ticket: ticket
|
||||||
|
task_key: @task_key
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
changeCustomer = (e, el) =>
|
||||||
|
new App.TicketCustomer(
|
||||||
|
ticket: ticket
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
@sidebarItems = [
|
||||||
|
{
|
||||||
|
head: 'Ticket'
|
||||||
|
name: 'ticket'
|
||||||
|
icon: 'message'
|
||||||
|
callback: editTicket
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if !@isRole('Customer')
|
||||||
|
@sidebarItems[0]['actions'] = [
|
||||||
|
{
|
||||||
|
name: 'ticket-history'
|
||||||
|
title: 'History'
|
||||||
|
callback: showTicketHistory
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ticket-merge'
|
||||||
|
title: 'Merge'
|
||||||
|
callback: showTicketMerge
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Change Customer'
|
||||||
|
name: 'customer-change'
|
||||||
|
callback: changeCustomer
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if !@isRole('Customer')
|
||||||
|
editCustomer = (e, el) =>
|
||||||
|
new App.ControllerGenericEdit(
|
||||||
|
id: ticket.customer_id
|
||||||
|
genericObject: 'User'
|
||||||
|
screen: 'edit'
|
||||||
|
pageData:
|
||||||
|
title: 'Users'
|
||||||
|
object: 'User'
|
||||||
|
objects: 'Users'
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
showCustomer = (el) =>
|
||||||
|
new App.WidgetUser(
|
||||||
|
el: el
|
||||||
|
user_id: ticket.customer_id
|
||||||
|
)
|
||||||
|
@sidebarItems.push {
|
||||||
|
head: 'Customer'
|
||||||
|
name: 'customer'
|
||||||
|
icon: 'person'
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: 'Change Customer'
|
||||||
|
name: 'customer-change'
|
||||||
|
callback: changeCustomer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Edit Customer'
|
||||||
|
name: 'customer-edit'
|
||||||
|
callback: editCustomer
|
||||||
|
},
|
||||||
|
]
|
||||||
|
callback: showCustomer
|
||||||
|
}
|
||||||
|
if ticket.organization_id
|
||||||
|
editOrganization = (e, el) =>
|
||||||
|
new App.ControllerGenericEdit(
|
||||||
|
id: ticket.organization_id,
|
||||||
|
genericObject: 'Organization'
|
||||||
|
pageData:
|
||||||
|
title: 'Organizations'
|
||||||
|
object: 'Organization'
|
||||||
|
objects: 'Organizations'
|
||||||
|
container: @el.closest('.content')
|
||||||
|
)
|
||||||
|
showOrganization = (el) =>
|
||||||
|
new App.WidgetOrganization(
|
||||||
|
el: el
|
||||||
|
organization_id: ticket.organization_id
|
||||||
|
)
|
||||||
|
@sidebarItems.push {
|
||||||
|
head: 'Organization'
|
||||||
|
name: 'organization'
|
||||||
|
icon: 'group'
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: 'Edit Organization'
|
||||||
|
name: 'organization-edit'
|
||||||
|
callback: editOrganization
|
||||||
|
},
|
||||||
|
]
|
||||||
|
callback: showOrganization
|
||||||
|
}
|
||||||
|
new App.Sidebar(
|
||||||
|
el: @el
|
||||||
|
sidebarState: @sidebarState
|
||||||
|
items: @sidebarItems
|
||||||
|
)
|
|
@ -0,0 +1,47 @@
|
||||||
|
class App.TicketZoomTitle extends App.Controller
|
||||||
|
events:
|
||||||
|
'blur .ticket-title-update': 'update'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
@ticket = App.Ticket.fullLocal( @ticket.id )
|
||||||
|
@subscribeId = @ticket.subscribe(@render)
|
||||||
|
@render(@ticket)
|
||||||
|
|
||||||
|
render: (ticket) =>
|
||||||
|
|
||||||
|
# check if render is needed
|
||||||
|
if @lastTitle && @lastTitle is ticket.title
|
||||||
|
return
|
||||||
|
@lastTitle = ticket.title
|
||||||
|
|
||||||
|
@html App.view('ticket_zoom/title')(
|
||||||
|
ticket: ticket
|
||||||
|
)
|
||||||
|
|
||||||
|
@$('.ticket-title-update').ce({
|
||||||
|
mode: 'textonly'
|
||||||
|
multiline: false
|
||||||
|
maxlength: 250
|
||||||
|
})
|
||||||
|
|
||||||
|
update: (e) =>
|
||||||
|
title = $(e.target).ceg() || ''
|
||||||
|
|
||||||
|
# update title
|
||||||
|
if title isnt @ticket.title
|
||||||
|
@ticket.title = title
|
||||||
|
|
||||||
|
# reset article - should not be resubmited on next ticket update
|
||||||
|
@ticket.article = undefined
|
||||||
|
|
||||||
|
@ticket.save()
|
||||||
|
|
||||||
|
App.TaskManager.mute( @task_key )
|
||||||
|
|
||||||
|
# update taskbar with new meta data
|
||||||
|
App.Event.trigger 'task:render'
|
||||||
|
|
||||||
|
release: =>
|
||||||
|
App.Ticket.unsubscribe( @subscribeId )
|
Loading…
Reference in a new issue