Fixes #3773 - Inconstant alignment in the listing of attachments/submit button in new article area

Fixes #3774 - Broken dialog whiling uploading oversized attachment
This commit is contained in:
Mantas Masalskis 2021-10-04 21:05:32 +02:00 committed by Thorsten Eckel
parent 09fc94cf72
commit 94011846d6
8 changed files with 195 additions and 139 deletions

View file

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

View file

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

View file

@ -117,7 +117,7 @@ class App.TicketZoomArticleNew extends App.Controller
@tokanice(@type) @tokanice(@type)
if @defaults.body or @isIE10() if @defaults.body or @attachments or @isIE10()
@openTextarea(null, true) @openTextarea(null, true)
tokanice: (type = 'email') -> tokanice: (type = 'email') ->
@ -191,82 +191,30 @@ class App.TicketZoomArticleNew extends App.Controller
maxlength: 150000 maxlength: 150000
}) })
html5Upload.initialize( new App.Html5Upload(
uploadUrl: "#{App.Config.get('api_path')}/upload_caches/#{@form_id}" uploadUrl: "#{App.Config.get('api_path')}/upload_caches/#{@form_id}"
dropContainer: @$('.article-add').get(0) dropContainer: @$('.article-add')
cancelContainer: @cancelContainer cancelContainer: @cancelContainer
inputField: @$('.article-attachment input').get(0) inputField: @$('.article-attachment input')
key: 'File'
maxSimultaneousUploads: 1
onFileAdded: (file) =>
file.on( onFileStartCallback: =>
@callbackFileUploadStart?()
onStart: => onFileCompletedCallback: (response) =>
@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 @attachments.push response.data
@attachmentPlaceholder.removeClass('hide')
@attachmentUpload.addClass('hide')
# reset progress bar
@progressBar.width(parseInt(0) + '%')
@progressText.text('')
@renderAttachment(response.data) @renderAttachment(response.data)
@$('.article-attachment input').val('') @$('.article-attachment input').val('')
if @callbackFileUploadStop @callbackFileUploadStop?()
@callbackFileUploadStop()
# Called during upload progress, first parameter onFileAbortedCallback: =>
# is decimal value from 0 to 100. @callbackFileUploadStop?()
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 attachmentPlaceholder: @attachmentPlaceholder
onError: (message) => attachmentUpload: @attachmentUpload
@attachmentPlaceholder.removeClass('hide') progressBar: @progressBar
@attachmentUpload.addClass('hide') progressText: @progressText
@$('.article-attachment input').val('') ).render()
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')
)
)
)
@bindAttachmentDelete() @bindAttachmentDelete()

View file

@ -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')

View file

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

View file

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

View file

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

View file

@ -469,6 +469,36 @@ RSpec.describe 'Ticket Create', type: :system do
expect(page).to have_no_selector(:task_with, task_key) expect(page).to have_no_selector(:task_with, task_key)
end 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 end
context 'when closing taskbar tab for new ticket creation' do context 'when closing taskbar tab for new ticket creation' do