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:
parent
ffa8814d02
commit
90eca0f1eb
8 changed files with 195 additions and 139 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
98
app/assets/javascripts/app/lib/app_post/html5_upload.coffee
Normal file
98
app/assets/javascripts/app/lib/app_post/html5_upload.coffee
Normal 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')
|
|
@ -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:
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue