refresh attachments when the user drags in a wysiwyg
parent
1393139cf9
commit
7456e84c7e
|
@ -627,7 +627,7 @@ module.directive("tgEditableDescription", [
|
|||
|
||||
|
||||
|
||||
EditableWysiwyg = (attachmentsService) ->
|
||||
EditableWysiwyg = (attachmentsService, attachmentsFullService) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
|
||||
isInEditMode = ->
|
||||
|
@ -637,19 +637,11 @@ EditableWysiwyg = (attachmentsService) ->
|
|||
uploadFile = (file, type) ->
|
||||
return if !attachmentsService.validate(file)
|
||||
|
||||
attachmentsService.upload(
|
||||
file,
|
||||
$model.$modelValue.id,
|
||||
$model.$modelValue.project,
|
||||
type
|
||||
).then (result) ->
|
||||
if taiga.isImage(result.get('name'))
|
||||
url = ' + ')'
|
||||
return attachmentsFullService.addAttachment($model.$modelValue.project, $model.$modelValue.id, type, file).then (result) ->
|
||||
if taiga.isImage(result.getIn(['file', 'name']))
|
||||
return '![' + result.getIn(['file', 'name']) + '](' + result.getIn(['file', 'url']) + ')'
|
||||
else
|
||||
url = '[' + result.get('name') + '](' + result.get('url') + ')'
|
||||
|
||||
|
||||
return url
|
||||
return '[' + result.getIn(['file', 'name']) + '](' + result.getIn(['file', 'url']) + ')'
|
||||
|
||||
$el.on 'dragover', (e) ->
|
||||
textarea = $el.find('textarea').focus()
|
||||
|
@ -694,7 +686,7 @@ EditableWysiwyg = (attachmentsService) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgEditableWysiwyg", ["tgAttachmentsService", EditableWysiwyg])
|
||||
module.directive("tgEditableWysiwyg", ["tgAttachmentsService", "tgAttachmentsFullService", EditableWysiwyg])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -28,21 +28,22 @@ class AttachmentController
|
|||
@.form.description = @.attachment.getIn(['file', 'description'])
|
||||
@.form.is_deprecated = @.attachment.get(['file', 'is_deprecated'])
|
||||
|
||||
@.loading = false
|
||||
|
||||
@.title = @translate.instant("ATTACHMENT.TITLE", {
|
||||
fileName: @.attachment.get('name'),
|
||||
date: moment(@.attachment.get('created_date')).format(@translate.instant("ATTACHMENT.DATE"))
|
||||
})
|
||||
|
||||
editMode: (mode) ->
|
||||
@.attachment = @.attachment.set('editable', mode)
|
||||
attachment = @.attachment.set('editable', mode)
|
||||
@.onUpdate({attachment: attachment})
|
||||
|
||||
delete: () ->
|
||||
@.onDelete({attachment: @.attachment})
|
||||
|
||||
save: () ->
|
||||
@.attachment = @.attachment.set('loading', true)
|
||||
attachment = @.attachment.set('loading', true)
|
||||
|
||||
@.onUpdate({attachment: attachment})
|
||||
|
||||
attachment = @.attachment.merge({
|
||||
editable: false,
|
||||
|
|
|
@ -72,10 +72,18 @@ describe "AttachmentController", ->
|
|||
attachment : attachment
|
||||
})
|
||||
|
||||
ctrl.editable = false
|
||||
ctrl.onUpdate = sinon.spy()
|
||||
|
||||
onUpdate = sinon.match (value) ->
|
||||
value = value.attachment.toJS()
|
||||
|
||||
return value.editable
|
||||
|
||||
, "onUpdate"
|
||||
|
||||
ctrl.editMode(true)
|
||||
|
||||
expect(ctrl.attachment.get('editable')).to.be.true
|
||||
expect(ctrl.onUpdate).to.be.calledWith(onUpdate)
|
||||
|
||||
it "delete", () ->
|
||||
attachment = Immutable.fromJS({
|
||||
|
@ -119,6 +127,12 @@ describe "AttachmentController", ->
|
|||
|
||||
ctrl.onUpdate = sinon.spy()
|
||||
|
||||
onUpdateLoading = sinon.match (value) ->
|
||||
value = value.attachment.toJS()
|
||||
|
||||
return value.loading
|
||||
, "onUpdateLoading"
|
||||
|
||||
onUpdate = sinon.match (value) ->
|
||||
value = value.attachment.toJS()
|
||||
|
||||
|
@ -137,4 +151,5 @@ describe "AttachmentController", ->
|
|||
|
||||
attachment = ctrl.attachment.toJS()
|
||||
|
||||
expect(ctrl.onUpdate).to.be.calledWith(onUpdateLoading)
|
||||
expect(ctrl.onUpdate).to.be.calledWith(onUpdate)
|
||||
|
|
|
@ -21,82 +21,46 @@ sizeFormat = @.taiga.sizeFormat
|
|||
|
||||
class AttachmentsFullController
|
||||
@.$inject = [
|
||||
"tgAttachmentsService",
|
||||
"$rootScope",
|
||||
"$translate",
|
||||
"$tgConfirm",
|
||||
"$tgConfig",
|
||||
"$tgStorage"
|
||||
"$tgStorage",
|
||||
"tgAttachmentsFullService"
|
||||
]
|
||||
|
||||
constructor: (@attachmentsService, @rootScope, @translate, @confirm, @config, @storage) ->
|
||||
@.deprecatedsVisible = false
|
||||
@.uploadingAttachments = []
|
||||
|
||||
constructor: (@translate, @confirm, @config, @storage, @attachmentsFullService) ->
|
||||
@.mode = @storage.get('attachment-mode', 'list')
|
||||
|
||||
@.maxFileSize = @config.get("maxUploadFileSize", null)
|
||||
@.maxFileSize = sizeFormat(@.maxFileSize) if @.maxFileSize
|
||||
@.maxFileSizeMsg = if @.maxFileSize then @translate.instant("ATTACHMENT.MAX_UPLOAD_SIZE", {maxFileSize: @.maxFileSize}) else ""
|
||||
|
||||
loadAttachments: ->
|
||||
@attachmentsService.list(@.type, @.objId, @.projectId).then (files) =>
|
||||
@.attachments = files.map (file) ->
|
||||
attachment = Immutable.Map()
|
||||
taiga.defineImmutableProperty @, 'attachments', () => return @attachmentsFullService.attachments
|
||||
taiga.defineImmutableProperty @, 'deprecatedsCount', () => return @attachmentsFullService.deprecatedsCount
|
||||
taiga.defineImmutableProperty @, 'attachmentsVisible', () => return @attachmentsFullService.attachmentsVisible
|
||||
taiga.defineImmutableProperty @, 'deprecatedsVisible', () => return @attachmentsFullService.deprecatedsVisible
|
||||
|
||||
return attachment.merge({
|
||||
loading: false,
|
||||
editable: false,
|
||||
file: file
|
||||
})
|
||||
uploadingAttachments: () ->
|
||||
return @attachmentsFullService.uploadingAttachments
|
||||
|
||||
@.generate()
|
||||
addAttachment: (file) ->
|
||||
editable = (@.mode == 'list')
|
||||
|
||||
@attachmentsFullService.addAttachment(@.projectId, @.objId, @.type, file, editable)
|
||||
|
||||
setMode: (mode) ->
|
||||
@.mode = mode
|
||||
|
||||
@storage.set('attachment-mode', mode)
|
||||
|
||||
generate: () ->
|
||||
@.deprecatedsCount = @.attachments.count (it) -> it.getIn(['file', 'is_deprecated'])
|
||||
|
||||
if @.deprecatedsVisible
|
||||
@.attachmentsVisible = @.attachments
|
||||
else
|
||||
@.attachmentsVisible = @.attachments.filter (it) -> !it.getIn(['file', 'is_deprecated'])
|
||||
|
||||
toggleDeprecatedsVisible: () ->
|
||||
@.deprecatedsVisible = !@.deprecatedsVisible
|
||||
@.generate()
|
||||
@attachmentsFullService.toggleDeprecatedsVisible()
|
||||
|
||||
addAttachment: (file, editable = true) ->
|
||||
return new Promise (resolve, reject) =>
|
||||
if @attachmentsService.validate(file)
|
||||
@.uploadingAttachments.push(file)
|
||||
addAttachments: (files) ->
|
||||
_.forEach files, (file) => @.addAttachment(file)
|
||||
|
||||
|
||||
promise = @attachmentsService.upload(file, @.objId, @.projectId, @.type)
|
||||
promise.then (file) =>
|
||||
@.uploadingAttachments = @.uploadingAttachments.filter (uploading) ->
|
||||
return uploading.name != file.get('name')
|
||||
|
||||
attachment = Immutable.Map()
|
||||
|
||||
attachment = attachment.merge({
|
||||
file: file,
|
||||
editable: editable,
|
||||
loading: false
|
||||
})
|
||||
|
||||
@.attachments = @.attachments.push(attachment)
|
||||
@.generate()
|
||||
@rootScope.$broadcast("attachment:create")
|
||||
resolve(@.attachments)
|
||||
else
|
||||
reject(file)
|
||||
|
||||
addAttachments: (files, editable) ->
|
||||
_.forEach files, (file) => @.addAttachment(file, editable)
|
||||
loadAttachments: ->
|
||||
@attachmentsFullService.loadAttachments(@.type, @.objId, @.projectId)
|
||||
|
||||
deleteAttachment: (toDeleteAttachment) ->
|
||||
title = @translate.instant("ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT")
|
||||
|
@ -111,40 +75,14 @@ class AttachmentsFullController
|
|||
@confirm.notify("error", null, message)
|
||||
askResponse.finish(false)
|
||||
|
||||
onSuccess = () =>
|
||||
@.attachments = @.attachments.filter (attachment) -> attachment != toDeleteAttachment
|
||||
@.generate()
|
||||
onSuccess = () => askResponse.finish()
|
||||
|
||||
askResponse.finish()
|
||||
|
||||
return @attachmentsService.delete(@.type, toDeleteAttachment.getIn(['file', 'id'])).then(onSuccess, onError)
|
||||
@attachmentsFullService.deleteAttachment(toDeleteAttachment, @.type).then(onSuccess, onError)
|
||||
|
||||
reorderAttachment: (attachment, newIndex) ->
|
||||
oldIndex = @.attachments.findIndex (it) -> it == attachment
|
||||
return if oldIndex == newIndex
|
||||
|
||||
attachments = @.attachments.remove(oldIndex)
|
||||
attachments = attachments.splice(newIndex, 0, attachment)
|
||||
attachments = attachments.map (x, i) -> x.setIn(['file', 'order'], i + 1)
|
||||
|
||||
promises = attachments.map (attachment) =>
|
||||
patch = {order: attachment.getIn(['file', 'order'])}
|
||||
|
||||
return @attachmentsService.patch(attachment.getIn(['file', 'id']), @.type, patch)
|
||||
|
||||
return Promise.all(promises.toJS()).then () =>
|
||||
@.attachments = attachments
|
||||
@.generate()
|
||||
@attachmentsFullService.reorderAttachment(@.type, attachment, newIndex)
|
||||
|
||||
updateAttachment: (toUpdateAttachment) ->
|
||||
index = @.attachments.findIndex (attachment) ->
|
||||
return attachment.getIn(['file', 'id']) == toUpdateAttachment.getIn(['file', 'id'])
|
||||
oldAttachment = @.attachments.get(index)
|
||||
|
||||
patch = taiga.patch(oldAttachment.get('file'), toUpdateAttachment.get('file'))
|
||||
|
||||
return @attachmentsService.patch(toUpdateAttachment.getIn(['file', 'id']), @.type, patch).then () =>
|
||||
@.attachments = @.attachments.set(index, toUpdateAttachment)
|
||||
@.generate()
|
||||
@attachmentsFullService.updateAttachment(toUpdateAttachment, @.type)
|
||||
|
||||
angular.module("taigaComponents").controller("AttachmentsFull", AttachmentsFullController)
|
||||
|
|
|
@ -22,13 +22,6 @@ describe "AttachmentsController", ->
|
|||
$controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockAttachmentsService = ->
|
||||
mocks.attachmentsService = {
|
||||
upload: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgAttachmentsService", mocks.attachmentsService)
|
||||
|
||||
_mockConfirm = ->
|
||||
mocks.confirm = {}
|
||||
|
||||
|
@ -55,21 +48,20 @@ describe "AttachmentsController", ->
|
|||
|
||||
$provide.value("$tgStorage", mocks.storage)
|
||||
|
||||
_mockRootScope = ->
|
||||
mocks.rootScope = {}
|
||||
_mockAttachmetsFullService = ->
|
||||
mocks.attachmentsFullService = {}
|
||||
|
||||
$provide.value("$rootScope", mocks.rootScope)
|
||||
$provide.value("tgAttachmentsFullService", mocks.attachmentsFullService)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockAttachmentsService()
|
||||
_mockConfirm()
|
||||
_mockTranslate()
|
||||
_mockConfig()
|
||||
_mockStorage()
|
||||
_mockRootScope()
|
||||
_mockAttachmetsFullService()
|
||||
|
||||
return null
|
||||
|
||||
|
@ -86,100 +78,31 @@ describe "AttachmentsController", ->
|
|||
|
||||
_setup()
|
||||
|
||||
it "generate, refresh deprecated counter", () ->
|
||||
attachments = Immutable.fromJS([
|
||||
{
|
||||
file: {
|
||||
is_deprecated: false
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: true
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: true
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: false
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.attachments = attachments
|
||||
|
||||
ctrl.generate()
|
||||
|
||||
expect(ctrl.deprecatedsCount).to.be.equal(3)
|
||||
|
||||
it "toggle deprecated visibility", () ->
|
||||
|
||||
mocks.attachmentsFullService.toggleDeprecatedsVisible = sinon.spy()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.deprecatedsVisible = false
|
||||
|
||||
ctrl.generate = sinon.spy()
|
||||
|
||||
ctrl.toggleDeprecatedsVisible()
|
||||
|
||||
expect(ctrl.deprecatedsVisible).to.be.true
|
||||
expect(ctrl.generate).to.be.calledOnce
|
||||
expect(mocks.attachmentsFullService.toggleDeprecatedsVisible).to.be.calledOnce
|
||||
|
||||
describe "add attachments", () ->
|
||||
it "valid attachment", (done) ->
|
||||
file = Immutable.fromJS({
|
||||
file: {},
|
||||
name: 'test',
|
||||
size: 3000
|
||||
})
|
||||
|
||||
mocks.attachmentsService.validate = sinon.stub()
|
||||
mocks.attachmentsService.validate.withArgs(file).returns(true)
|
||||
|
||||
mocks.attachmentsService.upload = sinon.stub()
|
||||
mocks.attachmentsService.upload.promise().resolve(file)
|
||||
|
||||
mocks.rootScope.$broadcast = sinon.spy()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
ctrl.attachments = Immutable.List()
|
||||
|
||||
ctrl.addAttachment(file).then () ->
|
||||
expect(mocks.rootScope.$broadcast).have.been.calledWith('attachment:create')
|
||||
expect(ctrl.attachments.count()).to.be.equal(1)
|
||||
done()
|
||||
|
||||
it "invalid attachment", () ->
|
||||
file = Immutable.fromJS({
|
||||
file: {},
|
||||
name: 'test',
|
||||
size: 3000
|
||||
})
|
||||
|
||||
mocks.attachmentsService.validate = sinon.stub()
|
||||
mocks.attachmentsService.validate.withArgs(file).returns(false)
|
||||
|
||||
mocks.attachmentsService.upload = sinon.stub()
|
||||
mocks.attachmentsService.upload.promise().resolve(file)
|
||||
|
||||
mocks.rootScope.$broadcast = sinon.spy()
|
||||
it "add attachment", () ->
|
||||
mocks.attachmentsFullService.addAttachment = sinon.spy()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.attachments = Immutable.List()
|
||||
file = Immutable.Map()
|
||||
|
||||
ctrl.addAttachment(file).then null, () ->
|
||||
expect(ctrl.attachments.count()).to.be.equal(0)
|
||||
ctrl.projectId = 3
|
||||
ctrl.objId = 30
|
||||
ctrl.type = 'us'
|
||||
ctrl.mode = 'list'
|
||||
|
||||
ctrl.addAttachment(file)
|
||||
|
||||
expect(mocks.attachmentsFullService.addAttachment).to.have.been.calledWith(3, 30, 'us', file, true)
|
||||
|
||||
it "add attachments", () ->
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
@ -199,6 +122,11 @@ describe "AttachmentsController", ->
|
|||
|
||||
describe "deleteattachments", () ->
|
||||
it "success attachment", (done) ->
|
||||
deleteFile = Immutable.Map()
|
||||
|
||||
mocks.attachmentsFullService.deleteAttachment = sinon.stub()
|
||||
mocks.attachmentsFullService.deleteAttachment.withArgs(deleteFile, 'us').promise().resolve()
|
||||
|
||||
askResponse = {
|
||||
finish: sinon.spy()
|
||||
}
|
||||
|
@ -209,37 +137,20 @@ describe "AttachmentsController", ->
|
|||
mocks.confirm.askOnDelete = sinon.stub()
|
||||
mocks.confirm.askOnDelete.withArgs('title', 'message').promise().resolve(askResponse)
|
||||
|
||||
mocks.attachmentsService.delete = sinon.stub()
|
||||
mocks.attachmentsService.delete.withArgs('us', 2).promise().resolve()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.type = 'us'
|
||||
ctrl.generate = sinon.spy()
|
||||
ctrl.attachments = Immutable.fromJS([
|
||||
{
|
||||
file: {id: 1}
|
||||
},
|
||||
{
|
||||
file: {id: 2}
|
||||
},
|
||||
{
|
||||
file: {id: 3}
|
||||
},
|
||||
{
|
||||
file: {id: 4}
|
||||
}
|
||||
])
|
||||
|
||||
deleteFile = ctrl.attachments.get(1)
|
||||
|
||||
ctrl.deleteAttachment(deleteFile).then () ->
|
||||
expect(ctrl.generate).have.been.calledOnce
|
||||
expect(ctrl.attachments.size).to.be.equal(3)
|
||||
expect(askResponse.finish).have.been.calledOnce
|
||||
done()
|
||||
|
||||
it "error attachment", (done) ->
|
||||
deleteFile = Immutable.Map()
|
||||
|
||||
mocks.attachmentsFullService.deleteAttachment = sinon.stub()
|
||||
mocks.attachmentsFullService.deleteAttachment.withArgs(deleteFile, 'us').promise().reject()
|
||||
|
||||
askResponse = {
|
||||
finish: sinon.spy()
|
||||
}
|
||||
|
@ -253,76 +164,52 @@ describe "AttachmentsController", ->
|
|||
|
||||
mocks.confirm.notify = sinon.spy()
|
||||
|
||||
mocks.attachmentsService.delete = sinon.stub()
|
||||
mocks.attachmentsService.delete.promise().reject()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.type = 'us'
|
||||
ctrl.generate = sinon.spy()
|
||||
ctrl.attachments = Immutable.fromJS([
|
||||
{
|
||||
file: {id: 1}
|
||||
},
|
||||
{
|
||||
file: {id: 2}
|
||||
},
|
||||
{
|
||||
file: {id: 3}
|
||||
},
|
||||
{
|
||||
file: {id: 4}
|
||||
}
|
||||
])
|
||||
|
||||
deleteFile = ctrl.attachments.get(1)
|
||||
|
||||
ctrl.deleteAttachment(deleteFile).then () ->
|
||||
expect(ctrl.attachments.size).to.be.equal(4)
|
||||
expect(askResponse.finish.withArgs(false)).have.been.calledOnce
|
||||
expect(mocks.confirm.notify.withArgs('error', null, 'error'))
|
||||
done()
|
||||
|
||||
it "reorder attachments", (done) ->
|
||||
attachments = Immutable.fromJS([
|
||||
{file: {id: 0, is_deprecated: false, order: 0}},
|
||||
{file: {id: 1, is_deprecated: true, order: 1}},
|
||||
{file: {id: 2, is_deprecated: true, order: 2}},
|
||||
{file: {id: 3, is_deprecated: false, order: 3}},
|
||||
{file: {id: 4, is_deprecated: true, order: 4}}
|
||||
])
|
||||
|
||||
mocks.attachmentsService.patch = sinon.stub()
|
||||
mocks.attachmentsService.patch.promise().resolve()
|
||||
it "loadAttachments", () ->
|
||||
mocks.attachmentsFullService.loadAttachments = sinon.spy()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.projectId = 3
|
||||
ctrl.objId = 30
|
||||
ctrl.type = 'us'
|
||||
ctrl.attachments = attachments
|
||||
|
||||
ctrl.reorderAttachment(attachments.get(1), 0).then () ->
|
||||
expect(ctrl.attachments.get(0)).to.be.equal(attachments.get(1))
|
||||
done()
|
||||
ctrl.loadAttachments()
|
||||
|
||||
expect(mocks.attachmentsFullService.loadAttachments).to.have.been.calledWith('us', 30, 3)
|
||||
|
||||
it "reorder attachments", () ->
|
||||
mocks.attachmentsFullService.reorderAttachment = sinon.spy()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
file = Immutable.Map()
|
||||
|
||||
ctrl.projectId = 3
|
||||
ctrl.objId = 30
|
||||
ctrl.type = 'us'
|
||||
|
||||
ctrl.reorderAttachment(file, 5)
|
||||
|
||||
expect(mocks.attachmentsFullService.reorderAttachment).to.have.been.calledWith('us', file, 5)
|
||||
|
||||
it "update attachment", () ->
|
||||
attachments = Immutable.fromJS([
|
||||
{file: {id: 0, is_deprecated: false, order: 0}},
|
||||
{file: {id: 1, is_deprecated: true, order: 1}},
|
||||
{file: {id: 2, is_deprecated: true, order: 2}},
|
||||
{file: {id: 3, is_deprecated: false, order: 3}},
|
||||
{file: {id: 4, is_deprecated: true, order: 4}}
|
||||
])
|
||||
|
||||
attachment = attachments.get(1)
|
||||
attachment = attachment.setIn(['file', 'is_deprecated'], false)
|
||||
|
||||
mocks.attachmentsService.patch = sinon.stub()
|
||||
mocks.attachmentsService.patch.withArgs(1, 'us', {is_deprecated: false}).promise().resolve()
|
||||
mocks.attachmentsFullService.updateAttachment = sinon.spy()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.type = 'us'
|
||||
ctrl.attachments = attachments
|
||||
file = Immutable.Map()
|
||||
|
||||
ctrl.updateAttachment(attachment).then () ->
|
||||
expect(ctrl.attachments.get(1).toJS()).to.be.eql(attachment.toJS())
|
||||
ctrl.type = 'us'
|
||||
|
||||
ctrl.updateAttachment(file, 5)
|
||||
|
||||
expect(mocks.attachmentsFullService.updateAttachment).to.have.been.calledWith(file, 'us')
|
||||
|
|
|
@ -34,7 +34,8 @@ section.attachments(tg-attachments-drop="vm.addAttachments(files, false)")
|
|||
multiple="multiple",
|
||||
tg-file-change="vm.addAttachments(files, true)"
|
||||
)
|
||||
.attachments-empty(ng-if="!vm.attachments.size")
|
||||
|
||||
.attachments-empty(ng-if="!vm.attachments.size && !vm.uploadingAttachments().length")
|
||||
div {{'ATTACHMENT.DROP' | translate}}
|
||||
.attachment-list.sortable(ng-if="vm.mode == 'list'")
|
||||
div(tg-attachments-sortable="vm.reorderAttachment(attachment, index)")
|
||||
|
@ -49,13 +50,13 @@ section.attachments(tg-attachments-drop="vm.addAttachments(files, false)")
|
|||
type="vm.type"
|
||||
)
|
||||
|
||||
.single-attachment(ng-repeat="file in vm.uploadingAttachments")
|
||||
.single-attachment(ng-repeat="file in vm.uploadingAttachments()")
|
||||
.attachment-name
|
||||
span.icon
|
||||
include ../../../svg/attachment.svg
|
||||
span {{attachment.get('name')}}
|
||||
span {{file.name}}
|
||||
.attachment-size
|
||||
span {{attachment.get('size') | sizeFormat}}
|
||||
span {{file.size | sizeFormat}}
|
||||
|
||||
.attachment-comments
|
||||
span {{file.progressMessage}}
|
||||
|
@ -88,7 +89,7 @@ section.attachments(tg-attachments-drop="vm.addAttachments(files, false)")
|
|||
on-update="vm.updateAttachment(attachment)",
|
||||
type="vm.type"
|
||||
)
|
||||
.single-attachment(ng-repeat="file in vm.uploadingAttachments")
|
||||
.single-attachment(ng-repeat="file in vm.uploadingAttachments()")
|
||||
.loading-container
|
||||
img.loading-spinner(
|
||||
src="/#{v}/svg/spinner-circle.svg",
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: attachments-full.service.coffee
|
||||
###
|
||||
|
||||
class AttachmentsFullService extends taiga.Service
|
||||
@.$inject = [
|
||||
"tgAttachmentsService",
|
||||
"$rootScope"
|
||||
]
|
||||
|
||||
constructor: (@attachmentsService, @rootScope) ->
|
||||
@._attachments = Immutable.List()
|
||||
@._deprecatedsCount = 0
|
||||
@._attachmentsVisible = Immutable.List()
|
||||
@._deprecatedsVisible = false
|
||||
@.uploadingAttachments = []
|
||||
|
||||
taiga.defineImmutableProperty @, 'attachments', () => return @._attachments
|
||||
taiga.defineImmutableProperty @, 'deprecatedsCount', () => return @._deprecatedsCount
|
||||
taiga.defineImmutableProperty @, 'attachmentsVisible', () => return @._attachmentsVisible
|
||||
taiga.defineImmutableProperty @, 'deprecatedsVisible', () => return @._deprecatedsVisible
|
||||
|
||||
toggleDeprecatedsVisible: () ->
|
||||
@._deprecatedsVisible = !@._deprecatedsVisible
|
||||
@.regenerate()
|
||||
|
||||
regenerate: () ->
|
||||
@._deprecatedsCount = @._attachments.count (it) -> it.getIn(['file', 'is_deprecated'])
|
||||
|
||||
if @._deprecatedsVisible
|
||||
@._attachmentsVisible = @._attachments
|
||||
else
|
||||
@._attachmentsVisible = @._attachments.filter (it) -> !it.getIn(['file', 'is_deprecated'])
|
||||
|
||||
addAttachment: (projectId, objId, type, file, editable = true) ->
|
||||
return new Promise (resolve, reject) =>
|
||||
if @attachmentsService.validate(file)
|
||||
@.uploadingAttachments.push(file)
|
||||
|
||||
promise = @attachmentsService.upload(file, objId, projectId, type)
|
||||
promise.then (file) =>
|
||||
@.uploadingAttachments = @.uploadingAttachments.filter (uploading) ->
|
||||
return uploading.name != file.get('name')
|
||||
|
||||
attachment = Immutable.Map()
|
||||
|
||||
attachment = attachment.merge({
|
||||
file: file,
|
||||
editable: editable,
|
||||
loading: false
|
||||
})
|
||||
|
||||
@._attachments = @._attachments.push(attachment)
|
||||
|
||||
@.regenerate()
|
||||
|
||||
@rootScope.$broadcast("attachment:create")
|
||||
|
||||
resolve(attachment)
|
||||
else
|
||||
reject(file)
|
||||
|
||||
loadAttachments: (type, objId, projectId)->
|
||||
@attachmentsService.list(type, objId, projectId).then (files) =>
|
||||
@._attachments = files.map (file) ->
|
||||
attachment = Immutable.Map()
|
||||
|
||||
return attachment.merge({
|
||||
loading: false,
|
||||
editable: false,
|
||||
file: file
|
||||
})
|
||||
|
||||
@.regenerate()
|
||||
|
||||
deleteAttachment: (toDeleteAttachment, type) ->
|
||||
onSuccess = () =>
|
||||
@._attachments = @._attachments.filter (attachment) -> attachment != toDeleteAttachment
|
||||
|
||||
@.regenerate()
|
||||
|
||||
return @attachmentsService.delete(type, toDeleteAttachment.getIn(['file', 'id'])).then(onSuccess)
|
||||
|
||||
reorderAttachment: (type, attachment, newIndex) ->
|
||||
oldIndex = @.attachments.findIndex (it) -> it == attachment
|
||||
return if oldIndex == newIndex
|
||||
|
||||
attachments = @.attachments.remove(oldIndex)
|
||||
attachments = attachments.splice(newIndex, 0, attachment)
|
||||
attachments = attachments.map (x, i) -> x.setIn(['file', 'order'], i + 1)
|
||||
|
||||
promises = []
|
||||
attachments.forEach (attachment) =>
|
||||
patch = {order: attachment.getIn(['file', 'order'])}
|
||||
|
||||
promises.push @attachmentsService.patch(attachment.getIn(['file', 'id']), type, patch)
|
||||
|
||||
return Promise.all(promises).then () =>
|
||||
@._attachments = attachments
|
||||
|
||||
@.regenerate()
|
||||
|
||||
updateAttachment: (toUpdateAttachment, type) ->
|
||||
index = @._attachments.findIndex (attachment) ->
|
||||
return attachment.getIn(['file', 'id']) == toUpdateAttachment.getIn(['file', 'id'])
|
||||
|
||||
oldAttachment = @._attachments.get(index)
|
||||
|
||||
patch = taiga.patch(oldAttachment.get('file'), toUpdateAttachment.get('file'))
|
||||
|
||||
return @attachmentsService.patch(toUpdateAttachment.getIn(['file', 'id']), type, patch).then () =>
|
||||
@._attachments = @._attachments.set(index, toUpdateAttachment)
|
||||
|
||||
@.regenerate()
|
||||
|
||||
angular.module("taigaComponents").service("tgAttachmentsFullService", AttachmentsFullService)
|
|
@ -0,0 +1,215 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# File: attchments-full.service.spec.coffee
|
||||
###
|
||||
|
||||
describe "tgAttachmentsFullService", ->
|
||||
$provide = null
|
||||
attachmentsFullService = null
|
||||
mocks = {}
|
||||
|
||||
_mockAttachmentsService = ->
|
||||
mocks.attachmentsService = {
|
||||
upload: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgAttachmentsService", mocks.attachmentsService)
|
||||
|
||||
_mockRootScope = ->
|
||||
mocks.rootScope = {}
|
||||
|
||||
$provide.value("$rootScope", mocks.rootScope)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockAttachmentsService()
|
||||
_mockRootScope()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_tgAttachmentsFullService_) ->
|
||||
attachmentsFullService = _tgAttachmentsFullService_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaComponents"
|
||||
|
||||
_setup()
|
||||
|
||||
it "generate, refresh deprecated counter", () ->
|
||||
attachments = Immutable.fromJS([
|
||||
{
|
||||
file: {
|
||||
is_deprecated: false
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: true
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: true
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: false
|
||||
}
|
||||
},
|
||||
{
|
||||
file: {
|
||||
is_deprecated: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
attachmentsFullService._attachments = attachments
|
||||
|
||||
attachmentsFullService.regenerate()
|
||||
|
||||
expect(attachmentsFullService._deprecatedsCount).to.be.equal(3)
|
||||
|
||||
it "toggle deprecated visibility", () ->
|
||||
attachmentsFullService._deprecatedsVisible = false
|
||||
|
||||
attachmentsFullService.regenerate = sinon.spy()
|
||||
|
||||
attachmentsFullService.toggleDeprecatedsVisible()
|
||||
|
||||
expect(attachmentsFullService.deprecatedsVisible).to.be.true
|
||||
expect(attachmentsFullService.regenerate).to.be.calledOnce
|
||||
|
||||
describe "add attachments", () ->
|
||||
it "valid attachment", (done) ->
|
||||
projectId = 1
|
||||
objId = 2
|
||||
type = "issue"
|
||||
|
||||
file = Immutable.fromJS({
|
||||
file: {},
|
||||
name: 'test',
|
||||
size: 3000
|
||||
})
|
||||
|
||||
mocks.attachmentsService.validate = sinon.stub()
|
||||
mocks.attachmentsService.validate.withArgs(file).returns(true)
|
||||
|
||||
mocks.attachmentsService.upload = sinon.stub()
|
||||
mocks.attachmentsService.upload.promise().resolve(file)
|
||||
|
||||
mocks.rootScope.$broadcast = sinon.spy()
|
||||
|
||||
attachmentsFullService._attachments = Immutable.List()
|
||||
|
||||
attachmentsFullService.addAttachment(projectId, objId, type, file).then () ->
|
||||
expect(mocks.rootScope.$broadcast).have.been.calledWith('attachment:create')
|
||||
expect(attachmentsFullService.attachments.count()).to.be.equal(1)
|
||||
done()
|
||||
|
||||
it "invalid attachment", () ->
|
||||
file = Immutable.fromJS({
|
||||
file: {},
|
||||
name: 'test',
|
||||
size: 3000
|
||||
})
|
||||
|
||||
mocks.attachmentsService.validate = sinon.stub()
|
||||
mocks.attachmentsService.validate.withArgs(file).returns(false)
|
||||
|
||||
mocks.attachmentsService.upload = sinon.stub()
|
||||
mocks.attachmentsService.upload.promise().resolve(file)
|
||||
|
||||
mocks.rootScope.$broadcast = sinon.spy()
|
||||
|
||||
attachmentsFullService._attachments = Immutable.List()
|
||||
|
||||
attachmentsFullService.addAttachment(file).then null, () ->
|
||||
expect(attachmentsFullService._attachments.count()).to.be.equal(0)
|
||||
|
||||
describe "deleteattachments", () ->
|
||||
it "success attachment", (done) ->
|
||||
mocks.attachmentsService.delete = sinon.stub()
|
||||
mocks.attachmentsService.delete.withArgs('us', 2).promise().resolve()
|
||||
|
||||
attachmentsFullService.regenerate = sinon.spy()
|
||||
attachmentsFullService._attachments = Immutable.fromJS([
|
||||
{
|
||||
file: {id: 1}
|
||||
},
|
||||
{
|
||||
file: {id: 2}
|
||||
},
|
||||
{
|
||||
file: {id: 3}
|
||||
},
|
||||
{
|
||||
file: {id: 4}
|
||||
}
|
||||
])
|
||||
|
||||
deleteFile = attachmentsFullService._attachments.get(1)
|
||||
|
||||
attachmentsFullService.deleteAttachment(deleteFile, 'us').then () ->
|
||||
expect(attachmentsFullService.regenerate).have.been.calledOnce
|
||||
expect(attachmentsFullService.attachments.size).to.be.equal(3)
|
||||
done()
|
||||
|
||||
it "reorder attachments", (done) ->
|
||||
attachments = Immutable.fromJS([
|
||||
{file: {id: 0, is_deprecated: false, order: 0}},
|
||||
{file: {id: 1, is_deprecated: true, order: 1}},
|
||||
{file: {id: 2, is_deprecated: true, order: 2}},
|
||||
{file: {id: 3, is_deprecated: false, order: 3}},
|
||||
{file: {id: 4, is_deprecated: true, order: 4}}
|
||||
])
|
||||
|
||||
mocks.attachmentsService.patch = sinon.stub()
|
||||
mocks.attachmentsService.patch.promise().resolve()
|
||||
|
||||
attachmentsFullService._attachments = attachments
|
||||
|
||||
attachmentsFullService.reorderAttachment('us', attachments.get(1), 0).then () ->
|
||||
expect(attachmentsFullService.attachments.get(0)).to.be.equal(attachments.get(1))
|
||||
done()
|
||||
|
||||
it "update attachment", () ->
|
||||
attachments = Immutable.fromJS([
|
||||
{file: {id: 0, is_deprecated: false, order: 0}},
|
||||
{file: {id: 1, is_deprecated: true, order: 1}},
|
||||
{file: {id: 2, is_deprecated: true, order: 2}},
|
||||
{file: {id: 3, is_deprecated: false, order: 3}},
|
||||
{file: {id: 4, is_deprecated: true, order: 4}}
|
||||
])
|
||||
|
||||
attachment = attachments.get(1)
|
||||
attachment = attachment.setIn(['file', 'is_deprecated'], false)
|
||||
|
||||
mocks.attachmentsService.patch = sinon.stub()
|
||||
mocks.attachmentsService.patch.withArgs(1, 'us', {is_deprecated: false}).promise().resolve()
|
||||
|
||||
attachmentsFullService._attachments = attachments
|
||||
|
||||
attachmentsFullService.updateAttachment(attachment, 'us').then () ->
|
||||
expect(attachmentsFullService.attachments.get(1).toJS()).to.be.eql(attachment.toJS())
|
Loading…
Reference in New Issue