Browse Source

Fixes #3773 - Inconstant alignment in the listing of attachments/submit button in new article area
Fixes #3774 - Broken dialog whiling uploading oversized attachment

Mantas Masalskis 3 years ago
parent
commit
90eca0f1eb

+ 36 - 68
app/assets/javascripts/app/controllers/_ui_element/richtext.coffee

@@ -6,7 +6,7 @@ class App.UiElement.richtext
       attribute.value = attribute.value.text
 
     item = $( App.view('generic/richtext')(attribute: attribute, toolButtons: @toolButtons) )
-    @contenteditable = item.find('[contenteditable]').ce(
+    item.find('[contenteditable]').ce(
       mode:      attribute.type
       maxlength: attribute.maxlength
       buttons:   attribute.buttons
@@ -21,12 +21,12 @@ class App.UiElement.richtext
         new App[plugin.controller](params)
 
     if attribute.upload
-      @attachments = []
+      attachments = []
       item.append( $( App.view('generic/attachment')(attribute: attribute) ) )
 
-      renderFile = (file) =>
+      renderFile = (file) ->
         item.find('.attachments').append(App.view('generic/attachment_item')(file))
-        @attachments.push file
+        attachments.push file
 
       if params && params.attachments
         for file in params.attachments
@@ -46,10 +46,10 @@ class App.UiElement.richtext
       , form.form_id)
 
       # remove items
-      item.find('.attachments').on('click', '.js-delete', (e) =>
+      item.find('.attachments').on('click', '.js-delete', (e) ->
         id = $(e.currentTarget).data('id')
-        @attachments = _.filter(
-          @attachments,
+        attachments = _.filter(
+          attachments,
           (item) ->
             return if item.id.toString() is id.toString()
             item
@@ -71,67 +71,35 @@ class App.UiElement.richtext
           element.empty()
       )
 
-      @progressBar           = item.find('.attachmentUpload-progressBar')
-      @progressText          = item.find('.js-percentage')
-      @attachmentPlaceholder = item.find('.attachmentPlaceholder')
-      @attachmentUpload      = item.find('.attachmentUpload')
-      @attachmentsHolder     = item.find('.attachments')
-      @cancelContainer       = item.find('.js-cancel')
-
-      u = => html5Upload.initialize(
-        uploadUrl:              "#{App.Config.get('api_path')}/attachments"
-        dropContainer:          item.closest('form').get(0)
-        cancelContainer:        @cancelContainer
-        inputField:             item.find('input').get(0)
-        maxSimultaneousUploads: 1,
-        key:                    'File'
-        data:
-          form_id: item.closest('form').find('[name=form_id]').val()
-        onFileAdded: (file) =>
-
-          file.on(
-            onStart: =>
-              @attachmentPlaceholder.addClass('hide')
-              @attachmentUpload.removeClass('hide')
-              @cancelContainer.removeClass('hide')
-              item.find('[contenteditable]').trigger('fileUploadStart')
-              App.Log.debug 'UiElement.richtext', 'upload start'
-
-            onAborted: =>
-              @attachmentPlaceholder.removeClass('hide')
-              @attachmentUpload.addClass('hide')
-              item.find('input').val('')
-              item.find('[contenteditable]').trigger('fileUploadStop', ['aborted'])
-
-            # Called after received response from the server
-            onCompleted: (response) =>
-              response = JSON.parse(response)
-
-              @attachmentPlaceholder.removeClass('hide')
-              @attachmentUpload.addClass('hide')
-
-              # reset progress bar
-              @progressBar.width(parseInt(0) + '%')
-              @progressText.text('')
-
-              renderFile(response.data)
-              item.find('input').val('')
-              item.find('[contenteditable]').trigger('fileUploadStop', ['completed'])
-              App.Log.debug 'UiElement.richtext', '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')
-              App.Log.debug 'UiElement.richtext', 'uploadProgress ', parseInt(progress)
-
-          )
-      )
-      App.Delay.set(u, 100, undefined, 'form_upload')
+      App.Delay.set( ->
+        uploader = new App.Html5Upload(
+          uploadUrl:              "#{App.Config.get('api_path')}/attachments"
+          dropContainer:          item.closest('form')
+          cancelContainer:        item.find('.js-cancel')
+          inputField:             item.find('input')
+          data:
+            form_id: item.closest('form').find('[name=form_id]').val()
+
+          onFileStartCallback: ->
+            item.find('[contenteditable]').trigger('fileUploadStart')
+
+          onFileCompletedCallback: (response) ->
+            renderFile(response.data)
+            item.find('input').val('')
+            item.find('[contenteditable]').trigger('fileUploadStop', ['completed'])
+
+          onFileAbortedCallback: ->
+            item.find('input').val('')
+            item.find('[contenteditable]').trigger('fileUploadStop', ['aborted'])
+
+          attachmentPlaceholder: item.find('.attachmentPlaceholder')
+          attachmentUpload:      item.find('.attachmentUpload')
+          progressBar:           item.find('.attachmentUpload-progressBar')
+          progressText:          item.find('.js-percentage')
+        )
+
+        uploader.render()
+      , 100, undefined, 'form_upload')
 
     item
 

+ 8 - 2
app/assets/javascripts/app/controllers/agent_ticket_create.coffee

@@ -8,7 +8,7 @@ class App.TicketCreate extends App.Controller
   events:
     'click .type-tabs .tab':   'changeFormType'
     'submit form':             'submit'
-    'click .js-cancel':        'cancel'
+    'click .form-controls .js-cancel':        'cancel'
     'click .js-active-toggle': 'toggleButton'
 
   types: {
@@ -184,8 +184,11 @@ class App.TicketCreate extends App.Controller
     @controllerUnbind('ticket_create_rerender', (template) => @renderQueue(template))
 
   changed: =>
+    return true if @hasAttachments()
+
     formCurrent = @formParam( @$('.ticket-create') )
     diff = difference(@formDefault, formCurrent)
+
     return false if !diff || _.isEmpty(diff)
     return true
 
@@ -461,6 +464,9 @@ class App.TicketCreate extends App.Controller
   params: =>
     params = @formParam(@$('.main form'))
 
+  hasAttachments: =>
+    @$('.richtext .attachments .attachment').length > 0
+
   submit: (e) =>
     e.preventDefault()
 
@@ -563,7 +569,7 @@ class App.TicketCreate extends App.Controller
     # save ticket, create article
     # check attachment
     if article['body']
-      if @$('.richtext .attachments .attachment').length < 1
+      if !@hasAttachments()
         matchingWord = App.Utils.checkAttachmentReference(article['body'])
         if matchingWord
           if !confirm(App.i18n.translateContent('You use %s in text but no attachment is attached. Do you want to continue?', matchingWord))

+ 25 - 77
app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee

@@ -117,7 +117,7 @@ class App.TicketZoomArticleNew extends App.Controller
 
     @tokanice(@type)
 
-    if @defaults.body or @isIE10()
+    if @defaults.body or @attachments or @isIE10()
       @openTextarea(null, true)
 
   tokanice: (type = 'email') ->
@@ -191,82 +191,30 @@ class App.TicketZoomArticleNew extends App.Controller
       maxlength: 150000
     })
 
-    html5Upload.initialize(
-      uploadUrl:       "#{App.Config.get('api_path')}/upload_caches/#{@form_id}"
-      dropContainer:   @$('.article-add').get(0)
-      cancelContainer: @cancelContainer
-      inputField:      @$('.article-attachment input').get(0)
-      key:             'File'
-      maxSimultaneousUploads: 1
-      onFileAdded:            (file) =>
-
-        file.on(
-
-          onStart: =>
-            @attachmentPlaceholder.addClass('hide')
-            @attachmentUpload.removeClass('hide')
-            @cancelContainer.removeClass('hide')
-
-            if @callbackFileUploadStart
-              @callbackFileUploadStart()
-
-          onAborted: =>
-            @attachmentPlaceholder.removeClass('hide')
-            @attachmentUpload.addClass('hide')
-            @$('.article-attachment input').val('')
-
-            if @callbackFileUploadStop
-              @callbackFileUploadStop()
-
-          # Called after received response from the server
-          onCompleted: (response) =>
-
-            response = JSON.parse(response)
-            @attachments.push response.data
-
-            @attachmentPlaceholder.removeClass('hide')
-            @attachmentUpload.addClass('hide')
-
-            # reset progress bar
-            @progressBar.width(parseInt(0) + '%')
-            @progressText.text('')
-
-            @renderAttachment(response.data)
-            @$('.article-attachment input').val('')
-
-            if @callbackFileUploadStop
-              @callbackFileUploadStop()
-
-          # 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')
-
-          # Called when upload failed
-          onError: (message) =>
-            @attachmentPlaceholder.removeClass('hide')
-            @attachmentUpload.addClass('hide')
-            @$('.article-attachment input').val('')
-
-            if @callbackFileUploadStop
-              @callbackFileUploadStop()
-
-            new App.ControllerModal(
-              head: 'Upload Failed'
-              buttonCancel: 'Cancel'
-              buttonCancelClass: 'btn--danger'
-              buttonSubmit: false
-              message: message
-              shown: true
-              small: true
-              container: @el.closest('.content')
-            )
-        )
-    )
+    new App.Html5Upload(
+      uploadUrl:              "#{App.Config.get('api_path')}/upload_caches/#{@form_id}"
+      dropContainer:          @$('.article-add')
+      cancelContainer:        @cancelContainer
+      inputField:             @$('.article-attachment input')
+
+      onFileStartCallback: =>
+        @callbackFileUploadStart?()
+
+      onFileCompletedCallback: (response) =>
+        @attachments.push response.data
+        @renderAttachment(response.data)
+        @$('.article-attachment input').val('')
+
+        @callbackFileUploadStop?()
+
+      onFileAbortedCallback: =>
+        @callbackFileUploadStop?()
+
+      attachmentPlaceholder: @attachmentPlaceholder
+      attachmentUpload:      @attachmentUpload
+      progressBar:           @progressBar
+      progressText:          @progressText
+    ).render()
 
     @bindAttachmentDelete()
 

+ 98 - 0
app/assets/javascripts/app/lib/app_post/html5_upload.coffee

@@ -0,0 +1,98 @@
+class App.Html5Upload extends App.Controller
+  uploadUrl:              null
+  maxSimultaneousUploads: 1
+  key:                    'File'
+  data:                   null
+
+  onFileStartCallback:     null
+  onFileCompletedCallback: null
+  onFileAbortedCallback:   null
+
+  dropContainer:         null
+  cancelContainer:       null
+  inputField:            null
+  attachmentPlaceholder: null
+  attachmentUpload:      null
+  progressBar:           null
+  progressText:          null
+
+  render: =>
+    html5Upload.initialize(
+      uploadUrl:              @uploadUrl
+      dropContainer:          @dropContainer.get(0)
+      cancelContainer:        @cancelContainer
+      inputField:             @inputField.get(0)
+      maxSimultaneousUploads: @maxSimultaneousUploads
+      key:                    @key
+      data:                   @data
+      onFileAdded:            @onFileAdded
+    )
+
+  onFileAdded: (file) =>
+    file.on(
+      onStart:     @onFileStart
+      onAborted:   @onFileAborted
+      onCompleted: @onFileCompleted
+      onProgress:  @onFileProgress
+      onError:     @onFileError
+    )
+
+  onFileStart: =>
+    @attachmentPlaceholder.addClass('hide')
+    @attachmentUpload.removeClass('hide')
+    @cancelContainer.removeClass('hide')
+
+    App.Log.debug 'Html5Upload', 'upload start'
+    @onFileStartCallback?()
+
+  onFileProgress: (progress, fileSize, uploadedBytes) =>
+    progress = parseInt(progress)
+
+    @progressBar.width(progress + '%')
+    @progressText.text(progress)
+    # hide cancel on 90%
+    if progress >= 90
+      @cancelContainer.addClass('hide')
+
+    App.Log.debug 'Html5Upload', 'uploadProgress ', progress
+
+
+  onFileCompleted: (response) =>
+    response = JSON.parse(response)
+
+    @hideFileUploading()
+    @onFileCompletedCallback?(response)
+
+    App.Log.debug 'Html5Upload', 'upload complete', response.data
+
+  onFileAborted: =>
+    @hideFileUploading()
+    @onFileAbortedCallback?()
+
+    App.Log.debug 'Html5Upload', 'upload aborted'
+
+  onFileError: (message) =>
+    @hideFileUploading()
+    @inputField.val('')
+
+    @callbackFileUploadStop?()
+
+    new App.ControllerModal(
+      head: 'Upload Failed'
+      buttonCancel: 'Cancel'
+      buttonCancelClass: 'btn--danger'
+      buttonSubmit: false
+      message: message || 'Cannot upload file'
+      shown: true
+      small: true
+      container: @inputField.closest('.content')
+    )
+
+    App.Log.debug 'Html5Upload', 'upload error'
+
+  hideFileUploading: =>
+    @attachmentPlaceholder.removeClass('hide')
+    @attachmentUpload.addClass('hide')
+
+    @progressBar.width('0%')
+    @progressText.text('0')

+ 2 - 1
app/assets/javascripts/app/lib/base/html5Upload.js

@@ -255,7 +255,7 @@
                     manager.ajaxUpload(manager.uploadsQueue.shift());
                 }
             };
-            xhr.abort = function (event) {
+            xhr.onabort = function (event) {
                 console.log('Upload abort');
 
                 // Reduce number of active uploads:
@@ -269,6 +269,7 @@
             // Triggered when upload fails:
             xhr.onerror = function () {
                 console.log('Upload failed: ', upload.fileName);
+                upload.events.onError('Upload failed: ' + upload.fileName);
             };
 
             // Append additional data if provided:

+ 1 - 1
app/assets/javascripts/app/views/generic/attachment.jst.eco

@@ -17,7 +17,7 @@
         <%- @T('Uploading') %> (<span class="js-percentage">0</span>%) ...
       </div>
       <div class="attachmentUpload-cancel js-cancel">
-        <%- @Icon('diagonal-cross') %></div><%- @T('Cancel Upload') %>
+        <%- @Icon('diagonal-cross') %><%- @T('Cancel Upload') %>
       </div>
     </div>
     <div class="attachmentUpload-progressBar" style="width: 0%"></div>

+ 5 - 0
app/assets/stylesheets/zammad.scss

@@ -7063,6 +7063,11 @@ footer {
     .ticket-create .attachments:not(:empty) {
       margin-left: 0;
       margin-right: 0;
+      margin-bottom: 56px;
+    }
+
+    .ticket-create .attachment--row {
+      line-height: 1.45;
     }
 
     .attachment.attachment--row {

+ 30 - 0
spec/system/ticket/create_spec.rb

@@ -469,6 +469,36 @@ RSpec.describe 'Ticket Create', type: :system do
 
       expect(page).to have_no_selector(:task_with, task_key)
     end
+
+    it 'asks for confirmation if attachment was added' do
+      visit 'ticket/create'
+
+      within :active_content do
+        page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
+        await_empty_ajax_queue
+
+        find('.js-cancel').click
+      end
+
+      in_modal disappears: false do
+        expect(page).to have_text 'Tab has changed'
+      end
+    end
+  end
+
+  context 'when uploading attachment' do
+    it 'shows an error if server throws an error' do
+      allow(Store).to receive(:add) { raise 'Error' }
+      visit 'ticket/create'
+
+      within :active_content do
+        page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
+      end
+
+      in_modal disappears: false do
+        expect(page).to have_text 'Error'
+      end
+    end
   end
 
   context 'when closing taskbar tab for new ticket creation' do