diff --git a/app/assets/javascripts/app/controllers/article_view/item.coffee b/app/assets/javascripts/app/controllers/article_view/item.coffee new file mode 100644 index 000000000000..3b4bf5f9c547 --- /dev/null +++ b/app/assets/javascripts/app/controllers/article_view/item.coffee @@ -0,0 +1,477 @@ +class App.ArticleViewItem extends App.ControllerObserver + model: 'TicketArticle' + observe: + from: true + to: true + cc: true + subject: true + body: true + internal: true + preferences: true + + elements: + '.textBubble-content': 'textBubbleContent' + '.textBubble-content img': 'textBubbleImages' + '.textBubble-overflowContainer': 'textBubbleOverflowContainer' + + events: + 'click .article-meta-permanent': 'toggleMetaWithDelay' + 'click .textBubble': 'toggleMetaWithDelay' + 'click .textBubble a': 'stopPropagation' + 'click .js-toggleFold': 'toggleFold' + 'click .richtext-content img': 'imageView' + 'click .attachments img': 'imageView' + 'click .file-calendar .js-preview': 'calendarView' + 'click .js-securityRetryProcess': 'retrySecurityProcess' + 'click .js-retryWhatsAppAttachmentDownload': 'retryWhatsAppAttachmentDownload' + + constructor: -> + super + @seeMoreOpen = false + + # set expand of text area only once + @controllerBind('ui::ticket::shown', (data) => + return if data.ticket_id.toString() isnt @ticket.id.toString() + + # set highlighter + @setHighlighter() + + # set see more + @setSeeMore() + ) + + setHighlighter: => + return if @el.is(':hidden') + # use delay do no ui blocking + #@highlighter.loadHighlights(@object_id) + d = => + if @highlighter + @highlighter.loadHighlights(@object_id) + @delay(d, 200) + + render: (article) => + + # set @el attributes + @el.addClass("ticket-article-item #{article.sender.name.toLowerCase()}") + @el.attr('data-id', article.id) + @el.attr('id', "article-#{article.id}") + if article.internal + @el.addClass('is-internal') + else + @el.removeClass('is-internal') + + # check if email link needs to be updated + links = clone(article.preferences.links) || [] + if article.type.name is 'email' + link = + name: __('Raw') + url: "#{@Config.get('api_path')}/ticket_article_plain/#{article.id}" + target: '_blank' + links.push link + + # attachments prepare + attachments = App.TicketArticle.contentAttachments(article) + if article.attachments + for attachment in article.attachments + + dispositionParams = '' + if attachment?.preferences['Content-Type'] isnt 'text/html' + dispositionParams = '?disposition=attachment' + + attachment.url = "#{App.Config.get('api_path')}/ticket_attachment/#{article.ticket_id}/#{article.id}/#{attachment.id}#{dispositionParams}" + attachment.preview_url = "#{App.Config.get('api_path')}/ticket_attachment/#{article.ticket_id}/#{article.id}/#{attachment.id}?view=preview" + + if attachment && attachment.preferences && attachment.preferences['original-format'] is true + link = + url: "#{App.Config.get('api_path')}/ticket_attachment/#{article.ticket_id}/#{article.id}/#{attachment.id}?disposition=attachment" + name: __('Original Formatting') + target: '_blank' + links.push link + + # prepare html body + if article.content_type is 'text/html' + body = article.body + if article.preferences && article.preferences.signature_detection + signatureDetected = '' + body = body.replace(signatureDetected, '') + body = body.split('
') + body.splice(article.preferences.signature_detection, 0, signatureDetected) + body = body.join('
') + else + body = App.Utils.signatureIdentifyByHtml(body) + article['html'] = body + else + + # client signature detection + bodyHtml = App.Utils.text2html(article.body) + article['html'] = App.Utils.signatureIdentifyByPlaintext(bodyHtml) + + # if no signature detected or within first 25 lines, check if signature got detected in backend + if article['html'] is bodyHtml || (article.preferences && article.preferences.signature_detection < 25) + signatureDetected = false + body = article.body + if article.preferences && article.preferences.signature_detection + signatureDetected = '########SIGNATURE########' + # coffeelint: disable=no_unnecessary_double_quotes + body = body.split("\n") + body.splice(article.preferences.signature_detection, 0, signatureDetected) + body = body.join("\n") + # coffeelint: enable=no_unnecessary_double_quotes + if signatureDetected + body = App.Utils.textCleanup(body) + article['html'] = App.Utils.text2html(body) + article['html'] = article['html'].replace(signatureDetected, '') + + if article.preferences.delivery_message + @html App.view('ticket_zoom/article_view_delivery_failed')( + ticket: @ticket + article: article + attachments: attachments + links: links + ) + return + if article.sender.name is 'System' && article.type.name isnt 'note' + #if article.sender.name is 'System' && article.preferences.perform_origin is 'trigger' + @html App.view('ticket_zoom/article_view_system')( + ticket: @ticket + article: article + attachments: attachments + links: links + ) + return + + if article.preferences?.whatsapp + icon = null + msg = null + if article.preferences?.whatsapp?.timestamp_read + icon = 'double-checkmark' + msg = __('read by the customer') + else if article.preferences?.whatsapp?.timestamp_delivered + icon = 'double-checkmark-outline' + msg = __('delivered to the customer') + else if article.preferences?.whatsapp?.timestamp_sent + icon = 'checkmark-outline' + msg = __('sent to the customer') + + article['delivery_status_icon'] = icon + article['delivery_status_message'] = msg + + @html App.view('ticket_zoom/article_view')( + ticket: @ticket + article: article + attachments: App.view('generic/attachments')(attachments: attachments, has_body: !!article.html) + links: links + ) + + new App.WidgetAvatar( + el: @$('.js-avatar') + object_id: article.origin_by_id || article.created_by_id + size: 40 + ) + + @articleActions = new App.TicketZoomArticleActions( + el: @$('.js-article-actions') + ticket: @ticket + article: article + lastAttributes: @lastAttributes + form_id: @form_id + ) + + # set see more + @shown = false + a = => + @setSeeMore() + @delay(a, 50) + + # set highlighter + @setHighlighter() + + # set see more options + setSeeMore: => + return if @el.is(':hidden') + return if @shown + @shown = true + + @textBubbleImages.each (i, el) => + if !el.complete + $(el).one 'load', @measureSeeMore + + @measureSeeMore() + + measureSeeMore: => + maxHeight = 560 + minHeight = 90 + bubbleContent = @textBubbleContent + bubbleOverflowContainer = @textBubbleOverflowContainer + + # expand if see more is already clicked + if @seeMoreOpen + bubbleContent.css('height', 'auto') + else + # reset bubble height and "see more" opacity + bubbleContent.css('height', '') + bubbleOverflowContainer.css('opacity', '') + + # remember offset of "see more" + signatureMarker = bubbleContent.find('.js-signatureMarker') + if !signatureMarker.get(0) + signatureMarker = bubbleContent.find('div [data-signature=true]') + offsetTop = signatureMarker.position() + + # safari - workaround + # in safari sometimes the marker is directly on top via .top and inspector but it isn't + # in this case use the next element + if offsetTop && offsetTop.top is 0 + offsetTop = signatureMarker.next('div, p, br').position() + + # remember bubble content height + bubbleContentHeight = bubbleContent.height() + + # get marker height + if offsetTop + markerHeight = offsetTop.top + + # if signature marker exists and height is within maxHeight + if markerHeight && markerHeight < maxHeight + newHeight = markerHeight + 30 + if newHeight < minHeight + newHeight = minHeight + + bubbleContent.attr('data-height', bubbleContentHeight + 30) + bubbleContent.attr('data-height-origin', newHeight) + bubbleContent.css('height', "#{newHeight}px") + bubbleOverflowContainer.removeClass('hide') + + # if height is higher then maxHeight + else if bubbleContentHeight > maxHeight + bubbleContent.attr('data-height', bubbleContentHeight + 30) + bubbleContent.attr('data-height-origin', maxHeight) + newHeight = if @seeMoreOpen then 'auto' else "#{maxHeight}px" + bubbleContent.css('height', newHeight) + bubbleOverflowContainer.toggleClass('is-open', @seeMoreOpen).find('.js-toggleFold').html(@label) + bubbleOverflowContainer.removeClass('hide') + else + bubbleOverflowContainer.addClass('hide') + + retrySecurityProcess: (e) -> + e.preventDefault() + e.stopPropagation() + + article_id = $(e.target).closest('.ticket-article-item').data('id') + article = App.TicketArticle.find(article_id) + + @ajax( + id: 'retrySecurityProcess' + type: 'POST' + url: "#{@apiPath}/ticket_articles/#{article_id}/retry_security_process" + processData: true + success: (encryption_data, status, xhr) => + for data in encryption_data + continue if article.preferences.security.type isnt data.type + + if data.sign.success + @notify + type: 'success' + msg: App.i18n.translateContent('The signature was successfully verified.') + else if data.sign.comment + comment = App.i18n.translateContent('Signature verification failed!') + ' ' + App.i18n.translateContent(data.sign.comment || '', data.sign.commentPlaceholders) + @notify + type: 'error' + msg: comment + timeout: 2000 + + if data.encryption.success + @notify + type: 'success' + msg: App.i18n.translateContent('Decryption was successful.') + else if data.encryption.comment + comment = App.i18n.translateContent('Decryption failed!') + ' ' + App.i18n.translateContent(data.encryption.comment || '', data.encryption.commentPlaceholders) + @notify + type: 'error' + msg: comment + timeout: 2000 + + error: (xhr) => + @notify + type: 'error' + msg: App.i18n.translateContent('The retried security process failed!') + ) + + retryWhatsAppAttachmentDownload: (e) -> + e.preventDefault() + e.stopPropagation() + + article_id = $(e.target).closest('.ticket-article-item').data('id') + + @ajax( + id: 'retryWhatsAppAttachmentDownload' + type: 'POST' + url: "#{@apiPath}/ticket_articles/#{article_id}/retry_whatsapp_attachment_download" + processData: true + success: (data, status, xhr) => + @notify + type: 'success' + msg: App.i18n.translateContent('Downloading attachments…') + + error: (data, status, xhr) => + details = data.responseJSON || {} + @notify + type: 'error' + msg: App.i18n.translateContent(details.error) + ) + + stopPropagation: (e) -> + e.stopPropagation() + + toggleMetaWithDelay: (e) => + # allow double click select + # by adding a delay to the toggle + delay = 300 + + article = $(e.target).closest('.ticket-article-item') + if @elementContainsSelection(article.get(0)) + @stopPropagation(e) + return false + + if @lastClick and +new Date - @lastClick < delay + clearTimeout(@toggleMetaTimeout) + else + @toggleMetaTimeout = setTimeout(@toggleMeta, delay, e) + @lastClick = +new Date + + toggleMeta: (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') + + toggleFold: (e) -> + e.preventDefault() + e.stopPropagation() + + bubbleContent = @textBubbleContent + bubbleOverflowContainer = @textBubbleOverflowContainer + + if @seeMoreOpen + @label = App.i18n.translateContent('See more') + height = bubbleContent.attr('data-height-origin') + @seeMoreOpen = false + else + @label = App.i18n.translateContent('See less') + height = bubbleContent.attr('data-height') + @seeMoreOpen = true + + bubbleOverflowContainer.toggleClass('is-open', @seeMoreOpen).find('.js-toggleFold').html(@label) + + bubbleContent.velocity + properties: + height: height + options: + duration: 300 + + 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 + + remove: => + @el.remove() + + imageView: (e) -> + # take care of images surrounded by a link + if e.target && e.target.parentNode && e.target.parentNode.nodeName.toLowerCase() == 'a' + return false + + e.preventDefault() + e.stopPropagation() + new App.TicketZoomArticleImageView(image: $(e.target).get(0).outerHTML, parentElement: $(e.currentTarget)) + + calendarView: (e) -> + e.preventDefault() + e.stopPropagation() + parentElement = $(e.target).closest('.attachment.file-calendar') + new App.TicketZoomArticleCalendarView(calendar: parentElement.get(0).outerHTML) + + updateFormId: (newFormId) -> + @articleActions?.form_id = newFormId diff --git a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee index a7ad3f9a99e5..aad60d885a65 100644 --- a/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +++ b/app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee @@ -14,7 +14,7 @@ class App.TicketZoomArticleView extends App.Controller controllerKey = ticket_article_id.toString() if !@articleController[controllerKey] el = $('
') - @articleController[controllerKey] = new ArticleViewItem( + @articleController[controllerKey] = new App.ArticleViewItem( ticket: @ticket object_id: ticket_article_id el: el @@ -66,480 +66,3 @@ class App.TicketZoomArticleView extends App.Controller for id, viewItem of @articleController viewItem.updateFormId(newFormId) -class ArticleViewItem extends App.ControllerObserver - model: 'TicketArticle' - observe: - from: true - to: true - cc: true - subject: true - body: true - internal: true - preferences: true - - elements: - '.textBubble-content': 'textBubbleContent' - '.textBubble-content img': 'textBubbleImages' - '.textBubble-overflowContainer': 'textBubbleOverflowContainer' - - events: - 'click .article-meta-permanent': 'toggleMetaWithDelay' - 'click .textBubble': 'toggleMetaWithDelay' - 'click .textBubble a': 'stopPropagation' - 'click .js-toggleFold': 'toggleFold' - 'click .richtext-content img': 'imageView' - 'click .attachments img': 'imageView' - 'click .file-calendar .js-preview': 'calendarView' - 'click .js-securityRetryProcess': 'retrySecurityProcess' - 'click .js-retryWhatsAppAttachmentDownload': 'retryWhatsAppAttachmentDownload' - - constructor: -> - super - @seeMoreOpen = false - - # set expand of text area only once - @controllerBind('ui::ticket::shown', (data) => - return if data.ticket_id.toString() isnt @ticket.id.toString() - - # set highlighter - @setHighlighter() - - # set see more - @setSeeMore() - ) - - setHighlighter: => - return if @el.is(':hidden') - # use delay do no ui blocking - #@highlighter.loadHighlights(@object_id) - d = => - if @highlighter - @highlighter.loadHighlights(@object_id) - @delay(d, 200) - - render: (article) => - - # set @el attributes - @el.addClass("ticket-article-item #{article.sender.name.toLowerCase()}") - @el.attr('data-id', article.id) - @el.attr('id', "article-#{article.id}") - if article.internal - @el.addClass('is-internal') - else - @el.removeClass('is-internal') - - # check if email link needs to be updated - links = clone(article.preferences.links) || [] - if article.type.name is 'email' - link = - name: __('Raw') - url: "#{@Config.get('api_path')}/ticket_article_plain/#{article.id}" - target: '_blank' - links.push link - - # attachments prepare - attachments = App.TicketArticle.contentAttachments(article) - if article.attachments - for attachment in article.attachments - - dispositionParams = '' - if attachment?.preferences['Content-Type'] isnt 'text/html' - dispositionParams = '?disposition=attachment' - - attachment.url = "#{App.Config.get('api_path')}/ticket_attachment/#{article.ticket_id}/#{article.id}/#{attachment.id}#{dispositionParams}" - attachment.preview_url = "#{App.Config.get('api_path')}/ticket_attachment/#{article.ticket_id}/#{article.id}/#{attachment.id}?view=preview" - - if attachment && attachment.preferences && attachment.preferences['original-format'] is true - link = - url: "#{App.Config.get('api_path')}/ticket_attachment/#{article.ticket_id}/#{article.id}/#{attachment.id}?disposition=attachment" - name: __('Original Formatting') - target: '_blank' - links.push link - - # prepare html body - if article.content_type is 'text/html' - body = article.body - if article.preferences && article.preferences.signature_detection - signatureDetected = '' - body = body.replace(signatureDetected, '') - body = body.split('
') - body.splice(article.preferences.signature_detection, 0, signatureDetected) - body = body.join('
') - else - body = App.Utils.signatureIdentifyByHtml(body) - article['html'] = body - else - - # client signature detection - bodyHtml = App.Utils.text2html(article.body) - article['html'] = App.Utils.signatureIdentifyByPlaintext(bodyHtml) - - # if no signature detected or within first 25 lines, check if signature got detected in backend - if article['html'] is bodyHtml || (article.preferences && article.preferences.signature_detection < 25) - signatureDetected = false - body = article.body - if article.preferences && article.preferences.signature_detection - signatureDetected = '########SIGNATURE########' - # coffeelint: disable=no_unnecessary_double_quotes - body = body.split("\n") - body.splice(article.preferences.signature_detection, 0, signatureDetected) - body = body.join("\n") - # coffeelint: enable=no_unnecessary_double_quotes - if signatureDetected - body = App.Utils.textCleanup(body) - article['html'] = App.Utils.text2html(body) - article['html'] = article['html'].replace(signatureDetected, '') - - if article.preferences.delivery_message - @html App.view('ticket_zoom/article_view_delivery_failed')( - ticket: @ticket - article: article - attachments: attachments - links: links - ) - return - if article.sender.name is 'System' && article.type.name isnt 'note' - #if article.sender.name is 'System' && article.preferences.perform_origin is 'trigger' - @html App.view('ticket_zoom/article_view_system')( - ticket: @ticket - article: article - attachments: attachments - links: links - ) - return - - if article.preferences?.whatsapp - icon = null - msg = null - if article.preferences?.whatsapp?.timestamp_read - icon = 'double-checkmark' - msg = __('read by the customer') - else if article.preferences?.whatsapp?.timestamp_delivered - icon = 'double-checkmark-outline' - msg = __('delivered to the customer') - else if article.preferences?.whatsapp?.timestamp_sent - icon = 'checkmark-outline' - msg = __('sent to the customer') - - article['delivery_status_icon'] = icon - article['delivery_status_message'] = msg - - @html App.view('ticket_zoom/article_view')( - ticket: @ticket - article: article - attachments: App.view('generic/attachments')(attachments: attachments, has_body: !!article.html) - links: links - ) - - new App.WidgetAvatar( - el: @$('.js-avatar') - object_id: article.origin_by_id || article.created_by_id - size: 40 - ) - - @articleActions = new App.TicketZoomArticleActions( - el: @$('.js-article-actions') - ticket: @ticket - article: article - lastAttributes: @lastAttributes - form_id: @form_id - ) - - # set see more - @shown = false - a = => - @setSeeMore() - @delay(a, 50) - - # set highlighter - @setHighlighter() - - # set see more options - setSeeMore: => - return if @el.is(':hidden') - return if @shown - @shown = true - - @textBubbleImages.each (i, el) => - if !el.complete - $(el).one 'load', @measureSeeMore - - @measureSeeMore() - - measureSeeMore: => - maxHeight = 560 - minHeight = 90 - bubbleContent = @textBubbleContent - bubbleOverflowContainer = @textBubbleOverflowContainer - - # expand if see more is already clicked - if @seeMoreOpen - bubbleContent.css('height', 'auto') - else - # reset bubble height and "see more" opacity - bubbleContent.css('height', '') - bubbleOverflowContainer.css('opacity', '') - - # remember offset of "see more" - signatureMarker = bubbleContent.find('.js-signatureMarker') - if !signatureMarker.get(0) - signatureMarker = bubbleContent.find('div [data-signature=true]') - offsetTop = signatureMarker.position() - - # safari - workaround - # in safari sometimes the marker is directly on top via .top and inspector but it isn't - # in this case use the next element - if offsetTop && offsetTop.top is 0 - offsetTop = signatureMarker.next('div, p, br').position() - - # remember bubble content height - bubbleContentHeight = bubbleContent.height() - - # get marker height - if offsetTop - markerHeight = offsetTop.top - - # if signature marker exists and height is within maxHeight - if markerHeight && markerHeight < maxHeight - newHeight = markerHeight + 30 - if newHeight < minHeight - newHeight = minHeight - - bubbleContent.attr('data-height', bubbleContentHeight + 30) - bubbleContent.attr('data-height-origin', newHeight) - bubbleContent.css('height', "#{newHeight}px") - bubbleOverflowContainer.removeClass('hide') - - # if height is higher then maxHeight - else if bubbleContentHeight > maxHeight - bubbleContent.attr('data-height', bubbleContentHeight + 30) - bubbleContent.attr('data-height-origin', maxHeight) - newHeight = if @seeMoreOpen then 'auto' else "#{maxHeight}px" - bubbleContent.css('height', newHeight) - bubbleOverflowContainer.toggleClass('is-open', @seeMoreOpen).find('.js-toggleFold').html(@label) - bubbleOverflowContainer.removeClass('hide') - else - bubbleOverflowContainer.addClass('hide') - - retrySecurityProcess: (e) -> - e.preventDefault() - e.stopPropagation() - - article_id = $(e.target).closest('.ticket-article-item').data('id') - article = App.TicketArticle.find(article_id) - - @ajax( - id: 'retrySecurityProcess' - type: 'POST' - url: "#{@apiPath}/ticket_articles/#{article_id}/retry_security_process" - processData: true - success: (encryption_data, status, xhr) => - for data in encryption_data - continue if article.preferences.security.type isnt data.type - - if data.sign.success - @notify - type: 'success' - msg: App.i18n.translateContent('The signature was successfully verified.') - else if data.sign.comment - comment = App.i18n.translateContent('Signature verification failed!') + ' ' + App.i18n.translateContent(data.sign.comment || '', data.sign.commentPlaceholders) - @notify - type: 'error' - msg: comment - timeout: 2000 - - if data.encryption.success - @notify - type: 'success' - msg: App.i18n.translateContent('Decryption was successful.') - else if data.encryption.comment - comment = App.i18n.translateContent('Decryption failed!') + ' ' + App.i18n.translateContent(data.encryption.comment || '', data.encryption.commentPlaceholders) - @notify - type: 'error' - msg: comment - timeout: 2000 - - error: (xhr) => - @notify - type: 'error' - msg: App.i18n.translateContent('The retried security process failed!') - ) - - retryWhatsAppAttachmentDownload: (e) -> - e.preventDefault() - e.stopPropagation() - - article_id = $(e.target).closest('.ticket-article-item').data('id') - - @ajax( - id: 'retryWhatsAppAttachmentDownload' - type: 'POST' - url: "#{@apiPath}/ticket_articles/#{article_id}/retry_whatsapp_attachment_download" - processData: true - success: (data, status, xhr) => - @notify - type: 'success' - msg: App.i18n.translateContent('Downloading attachments…') - - error: (data, status, xhr) => - details = data.responseJSON || {} - @notify - type: 'error' - msg: App.i18n.translateContent(details.error) - ) - - stopPropagation: (e) -> - e.stopPropagation() - - toggleMetaWithDelay: (e) => - # allow double click select - # by adding a delay to the toggle - delay = 300 - - article = $(e.target).closest('.ticket-article-item') - if @elementContainsSelection(article.get(0)) - @stopPropagation(e) - return false - - if @lastClick and +new Date - @lastClick < delay - clearTimeout(@toggleMetaTimeout) - else - @toggleMetaTimeout = setTimeout(@toggleMeta, delay, e) - @lastClick = +new Date - - toggleMeta: (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') - - toggleFold: (e) -> - e.preventDefault() - e.stopPropagation() - - bubbleContent = @textBubbleContent - bubbleOverflowContainer = @textBubbleOverflowContainer - - if @seeMoreOpen - @label = App.i18n.translateContent('See more') - height = bubbleContent.attr('data-height-origin') - @seeMoreOpen = false - else - @label = App.i18n.translateContent('See less') - height = bubbleContent.attr('data-height') - @seeMoreOpen = true - - bubbleOverflowContainer.toggleClass('is-open', @seeMoreOpen).find('.js-toggleFold').html(@label) - - bubbleContent.velocity - properties: - height: height - options: - duration: 300 - - 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 - - remove: => - @el.remove() - - imageView: (e) -> - # take care of images surrounded by a link - if e.target && e.target.parentNode && e.target.parentNode.nodeName.toLowerCase() == 'a' - return false - - e.preventDefault() - e.stopPropagation() - new App.TicketZoomArticleImageView(image: $(e.target).get(0).outerHTML, parentElement: $(e.currentTarget)) - - calendarView: (e) -> - e.preventDefault() - e.stopPropagation() - parentElement = $(e.target).closest('.attachment.file-calendar') - new App.TicketZoomArticleCalendarView(calendar: parentElement.get(0).outerHTML) - - updateFormId: (newFormId) -> - @articleActions?.form_id = newFormId diff --git a/i18n/zammad.pot b/i18n/zammad.pot index 3ba79e8acc51..48a860a86de9 100644 --- a/i18n/zammad.pot +++ b/i18n/zammad.pot @@ -3533,7 +3533,7 @@ msgstr "" msgid "Decrement seconds value" msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee msgid "Decryption failed!" msgstr "" @@ -3541,7 +3541,7 @@ msgstr "" msgid "Decryption failed! %s" msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleSecurityBadge.vue msgid "Decryption was successful." msgstr "" @@ -4730,7 +4730,7 @@ msgstr "" msgid "Download and install the %s Migration Plugin on your %s instance." msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee msgid "Downloading attachments…" msgstr "" @@ -9331,7 +9331,7 @@ msgstr "" msgid "Origin By" msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleMetadataDialog.vue msgid "Original Formatting" msgstr "" @@ -10135,7 +10135,7 @@ msgstr "" msgid "REST API access using the username/email address and password is currently disabled. Please contact your administrator." msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleMetadataDialog.vue msgid "Raw" msgstr "" @@ -11067,15 +11067,15 @@ msgid "See HTTPLog for details." msgstr "" #: app/assets/javascripts/app/controllers/_ui_element/ticket_duplicate_detection.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/assets/javascripts/app/controllers/data_privacy.coffee -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleBubble.vue msgid "See less" msgstr "" #: app/assets/javascripts/app/controllers/_ui_element/ticket_duplicate_detection.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/assets/javascripts/app/controllers/data_privacy.coffee -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee #: app/assets/javascripts/app/views/data_privacy/tasks.jst.eco #: app/assets/javascripts/app/views/generic/ticket_duplicate_detection/warning.jst.eco #: app/assets/javascripts/app/views/ticket_zoom/article_view.jst.eco @@ -11628,7 +11628,7 @@ msgstr "" msgid "Signature" msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee msgid "Signature verification failed!" msgstr "" @@ -13128,7 +13128,7 @@ msgstr "" msgid "The retried attachment download failed." msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleSecurityBadge.vue msgid "The retried security process failed!" msgstr "" @@ -13184,7 +13184,7 @@ msgstr "" msgid "The signature part of this PGP email is missing or has a wrong content type according to RFC 3156." msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleSecurityBadge.vue msgid "The signature was successfully verified." msgstr "" @@ -16215,7 +16215,7 @@ msgstr "" msgid "delete" msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleMetadataDialog.vue msgid "delivered to the customer" msgstr "" @@ -16930,7 +16930,7 @@ msgstr "" msgid "reached" msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleMetadataDialog.vue msgid "read by the customer" msgstr "" @@ -17013,7 +17013,7 @@ msgstr "" msgid "select visible options" msgstr "" -#: app/assets/javascripts/app/controllers/ticket_zoom/article_view.coffee +#: app/assets/javascripts/app/controllers/article_view/item.coffee #: app/frontend/apps/mobile/pages/ticket/components/TicketDetailView/ArticleMetadataDialog.vue msgid "sent to the customer" msgstr ""