commit
2a46431b3f
|
@ -21,9 +21,6 @@
|
||||||
|
|
||||||
taiga = @.taiga
|
taiga = @.taiga
|
||||||
|
|
||||||
trim = @.taiga.trim
|
|
||||||
typeIsArray = @.taiga.typeIsArray
|
|
||||||
|
|
||||||
module = angular.module("taigaCommon", [])
|
module = angular.module("taigaCommon", [])
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
@ -31,19 +28,15 @@ module = angular.module("taigaCommon", [])
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
CheckPermissionDirective = ->
|
CheckPermissionDirective = ->
|
||||||
showElementIfPermission = (element, permission, project) ->
|
render = ($el, project, permission) ->
|
||||||
element.show() if project.my_permissions.indexOf(permission) > -1
|
$el.show() if project.my_permissions.indexOf(permission) > -1
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
$el.hide()
|
$el.hide()
|
||||||
permission = $attrs.tgCheckPermission
|
permission = $attrs.tgCheckPermission
|
||||||
|
|
||||||
#Sometimes this directive from a self included html template
|
$scope.$watch "project", (project) ->
|
||||||
if $scope.project?
|
render($el, project, permission) if project?
|
||||||
showElementIfPermission($el, permission, $scope.project)
|
|
||||||
|
|
||||||
$scope.$on "project:loaded", (ctx, project) ->
|
|
||||||
showElementIfPermission($el, permission, project)
|
|
||||||
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
|
@ -21,22 +21,189 @@
|
||||||
|
|
||||||
taiga = @.taiga
|
taiga = @.taiga
|
||||||
sizeFormat = @.taiga.sizeFormat
|
sizeFormat = @.taiga.sizeFormat
|
||||||
|
bindOnce = @.taiga.bindOnce
|
||||||
|
|
||||||
module = angular.module("taigaCommon")
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
class AttachmentsController extends taiga.Controller
|
||||||
## Attachments Directive
|
@.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"]
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
AttachmentsDirective = ($repo, $rs, $confirm) ->
|
constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q) ->
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
_.bindAll(@)
|
||||||
$ctrl = $el.controller()
|
@.type = null
|
||||||
$scope.uploadingFiles = []
|
@.objectId = null
|
||||||
|
|
||||||
|
@.uploadingAttachments = []
|
||||||
|
@.attachments = []
|
||||||
|
@.attachmentsCount = 0
|
||||||
|
@.deprecatedAttachmentsCount = 0
|
||||||
|
@.showDeprecated = false
|
||||||
|
|
||||||
|
initialize: (type, objectId) ->
|
||||||
|
@.type = type
|
||||||
|
@.objectId = objectId
|
||||||
|
|
||||||
|
loadAttachments: ->
|
||||||
|
urlname = "attachments/#{@.type}"
|
||||||
|
id = @.objectId
|
||||||
|
|
||||||
|
return @rs.attachments.list(urlname, id).then (attachments) =>
|
||||||
|
@.attachments = _.sortBy(attachments, "order")
|
||||||
|
@.updateCounters()
|
||||||
|
return attachments
|
||||||
|
|
||||||
|
updateCounters: ->
|
||||||
|
@.attachmentsCount = @.attachments.length
|
||||||
|
@.deprecatedAttachmentsCount = _.filter(@.attachments, {is_deprecated: true}).length
|
||||||
|
|
||||||
|
_createAttachment: (attachment) ->
|
||||||
|
projectId = @scope.projectId
|
||||||
|
urlName = "attachments/#{@.type}"
|
||||||
|
|
||||||
|
promise = @rs.attachments.create(urlName, projectId, @.objectId, attachment)
|
||||||
|
promise = promise.then (data) =>
|
||||||
|
data.isCreatedRightNow = true
|
||||||
|
|
||||||
|
index = @.uploadingAttachments.indexOf(attachment)
|
||||||
|
@.uploadingAttachments.splice(index, 1)
|
||||||
|
@.attachments.push(data)
|
||||||
|
@rootscope.$broadcast("attachment:create")
|
||||||
|
|
||||||
|
promise = promise.then null, (data) ->
|
||||||
|
index = @.uploadingAttachments.indexOf(attachment)
|
||||||
|
@.uploadingAttachments.splice(index, 1)
|
||||||
|
@confirm.notify("error", null, "We have not been able to upload '#{attachment.name}'.")
|
||||||
|
return @q.reject(data)
|
||||||
|
|
||||||
|
return promise
|
||||||
|
|
||||||
|
# Create attachments in bulk
|
||||||
|
createAttachments: (attachments) ->
|
||||||
|
promises = _.map(attachments, (x) => @._createAttachment(x))
|
||||||
|
return @q.all.apply(null, promises).then =>
|
||||||
|
@.updateCounters()
|
||||||
|
|
||||||
|
# Add uploading attachment tracking.
|
||||||
|
addUploadingAttachments: (attachments) ->
|
||||||
|
@.uploadingAttachments = _.union(@.uploadingAttachments, attachments)
|
||||||
|
|
||||||
|
# Change order of attachment in a ordered list.
|
||||||
|
# This function is mainly executed after sortable ends.
|
||||||
|
reorderAttachment: (attachment, newIndex) ->
|
||||||
|
oldIndex = @.attachments.indexOf(attachment)
|
||||||
|
return if oldIndex == newIndex
|
||||||
|
|
||||||
|
@.attachments.splice(oldIndex, 1)
|
||||||
|
@.attachments.splice(newIndex, 0, attachment)
|
||||||
|
|
||||||
|
_.each(@.attachments, (x,i) -> x.order = i+1)
|
||||||
|
|
||||||
|
# Persist one concrete attachment.
|
||||||
|
# This function is mainly used when user clicks
|
||||||
|
# to save button for save one unique attachment.
|
||||||
|
updateAttachment: (attachment) ->
|
||||||
|
onSuccess = =>
|
||||||
|
@.updateCounters()
|
||||||
|
@rootscope.$broadcast("attachment:edit")
|
||||||
|
|
||||||
|
onError = =>
|
||||||
|
@confirm.notify("error")
|
||||||
|
return @q.reject()
|
||||||
|
|
||||||
|
return @repo.save(attachment).then(onSuccess, onError)
|
||||||
|
|
||||||
|
# Persist all pending modifications on attachments.
|
||||||
|
# This function is used mainly for persist the order
|
||||||
|
# after sorting.
|
||||||
|
saveAttachments: ->
|
||||||
|
return @repo.saveAll(@.attachments).then null, =>
|
||||||
|
for item in @.attachments
|
||||||
|
item.revert()
|
||||||
|
@.attachments = _.sorBy(@.attachments, "order")
|
||||||
|
|
||||||
|
# Remove one concrete attachment.
|
||||||
|
removeAttachment: (attachment) ->
|
||||||
|
title = "Delete attachment" #TODO: i18in
|
||||||
|
subtitle = "the attachment '#{attachment.name}'" #TODO: i18in
|
||||||
|
|
||||||
|
onSuccess = =>
|
||||||
|
index = @.attachments.indexOf(attachment)
|
||||||
|
@.attachments.splice(index, 1)
|
||||||
|
@.updateCounters()
|
||||||
|
@rootscope.$broadcast("attachment:delete")
|
||||||
|
|
||||||
|
onError = =>
|
||||||
|
@confirm.notify("error", null, "We have not been able to delete #{subtitle}.")
|
||||||
|
return @q.reject()
|
||||||
|
|
||||||
|
return @confirm.ask(title, subtitle).then =>
|
||||||
|
return @repo.remove(attachment).then(onSuccess, onError)
|
||||||
|
|
||||||
|
# Function used in template for filter visible attachments
|
||||||
|
filterAttachments: (item) ->
|
||||||
|
if @.showDeprecated
|
||||||
|
return true
|
||||||
|
return not item.is_deprecated
|
||||||
|
|
||||||
|
|
||||||
|
AttachmentsDirective = ($confirm) ->
|
||||||
|
template = _.template("""
|
||||||
|
<section class="attachments">
|
||||||
|
<div class="attachments-header">
|
||||||
|
<h3 class="attachments-title">
|
||||||
|
<span class="icon icon-attachments"></span>
|
||||||
|
<span class="attachments-num" tg-bind-html="ctrl.attachmentsCount"></span>
|
||||||
|
<span class="attachments-text">attachments</span>
|
||||||
|
|
||||||
|
<div tg-check-permission="modify_<%- type %>"
|
||||||
|
title="Add new attachment" class="button button-gray add-attach">
|
||||||
|
<span>+new file</span>
|
||||||
|
<input type="file" multiple="multiple"/>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="attachment-body sortable">
|
||||||
|
<div ng-repeat="attach in ctrl.attachments|filter:ctrl.filterAttachments track by attach.id"
|
||||||
|
tg-attachment="attach"
|
||||||
|
class="single-attachment">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ng-repeat="file in ctrl.uploadingAttachments" class="single-attachment">
|
||||||
|
<div class="attachment-name">
|
||||||
|
<a href="" tg-bo-title="file.name" tg-bo-bind="file.name"></a>
|
||||||
|
</div>
|
||||||
|
<div class="attachment-size">
|
||||||
|
<span tg-bo-bind="file.size" class="attachment-size"></span>
|
||||||
|
</div>
|
||||||
|
<div class="attachment-comments">
|
||||||
|
<span ng-bind="file.progressMessage"></span>
|
||||||
|
<div ng-style="{'width': file.progressPercent}" class="percentage"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="" title="show deprecated atachments" class="more-attachments"
|
||||||
|
ng-if="ctrl.deprecatedAttachmentsCount > 0">
|
||||||
|
<span class="text" data-type="show">+ show deprecated atachments</span>
|
||||||
|
<span class="text hidden" data-type="hide">- hide deprecated atachments</span>
|
||||||
|
<span class="more-attachments-num">
|
||||||
|
({{ctrl.deprecatedAttachmentsCount }} deprecated)
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>""")
|
||||||
|
|
||||||
|
|
||||||
|
link = ($scope, $el, $attrs, $ctrls) ->
|
||||||
|
$ctrl = $ctrls[0]
|
||||||
|
$model = $ctrls[1]
|
||||||
|
|
||||||
|
bindOnce $scope, $attrs.ngModel, (value) ->
|
||||||
|
$ctrl.initialize($attrs.type, value.id)
|
||||||
|
$ctrl.loadAttachments()
|
||||||
|
|
||||||
## Drag & drop
|
|
||||||
tdom = $el.find("div.attachment-body.sortable")
|
tdom = $el.find("div.attachment-body.sortable")
|
||||||
|
|
||||||
tdom.sortable({
|
tdom.sortable({
|
||||||
items: "div.single-attachment"
|
items: "div.single-attachment"
|
||||||
handle: "a.settings.icon.icon-drag-v"
|
handle: "a.settings.icon.icon-drag-v"
|
||||||
|
@ -49,101 +216,58 @@ AttachmentsDirective = ($repo, $rs, $confirm) ->
|
||||||
tdom.on "sortstop", (event, ui) ->
|
tdom.on "sortstop", (event, ui) ->
|
||||||
attachment = ui.item.scope().attach
|
attachment = ui.item.scope().attach
|
||||||
newIndex = ui.item.index()
|
newIndex = ui.item.index()
|
||||||
index = $scope.attachments.indexOf(attachment)
|
|
||||||
|
|
||||||
return if index == newIndex
|
$ctrl.reorderAttachment(attachment, newIndex)
|
||||||
|
$ctrl.saveAttachments()
|
||||||
|
|
||||||
# Move attachment to newIndex and recalculate order
|
$el.on "click", "a.add-attach", (event) ->
|
||||||
$scope.attachments.splice(index, 1)
|
event.preventDefault()
|
||||||
$scope.attachments.splice(newIndex, 0, attachment)
|
$el.find("input.add-attach").trigger("click")
|
||||||
_.forEach $scope.attachments, (attach, idx) ->
|
|
||||||
attach.order = idx+1
|
|
||||||
|
|
||||||
# Save or revert changes
|
$el.on "change", "input.add-attach", (event) ->
|
||||||
$repo.saveAll($scope.attachments).then null, ->
|
files = _.toArray(event.target.files)
|
||||||
_.forEach $scope.attachments, attach ->
|
return if files.length < 1
|
||||||
attach.revert()
|
|
||||||
_.sorBy($scope.attachments, 'order')
|
|
||||||
|
|
||||||
## Total attachments counter
|
$scope.$apply ->
|
||||||
$scope.$watch "attachmentsCount", (count) ->
|
$ctrl.addUploadingAttachments(files)
|
||||||
$el.find("span.attachments-num").html(count)
|
$ctrl.createAttachments(files)
|
||||||
|
|
||||||
## Show/Hide deprecated attachments
|
$el.on "click", ".more-attachments", (event) ->
|
||||||
$scope.showDeprecatedAttachments = false
|
|
||||||
|
|
||||||
$scope.$watch "deprecatedAttachmentsCount", (deprecatedAttachmentsCount) ->
|
|
||||||
$el.find("span.more-attachments-num").html("(#{deprecatedAttachmentsCount} deprecated)") # TODO: i18n
|
|
||||||
|
|
||||||
if deprecatedAttachmentsCount
|
|
||||||
$el.find("a.more-attachments").removeClass("hidden")
|
|
||||||
else
|
|
||||||
$el.find("a.more-attachments").addClass("hidden")
|
|
||||||
|
|
||||||
$el.on "click", "a.more-attachments", ->
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
target = angular.element(event.currentTarget)
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
$scope.showDeprecatedAttachments = not $scope.showDeprecatedAttachments
|
$scope.$apply ->
|
||||||
|
$ctrl.showDeprecated = not $ctrl.showDeprecated
|
||||||
|
|
||||||
if $scope.showDeprecatedAttachments
|
target.find("span.text").addClass("hidden")
|
||||||
target.find("span.text").html("- hide deprecated attachments") # TODO: i18n
|
if $ctrl.showDeprecated
|
||||||
.prop("title", "hide deprecated attachments") # TODO: i18n
|
target.find("span[data-type=hide]").removeClass("hidden")
|
||||||
$el.find("div.single-attachment.deprecated").removeClass("hidden")
|
target.find("more-attachments-num").addClass("hidden")
|
||||||
else
|
else
|
||||||
target.find("span.text").html("+ show deprecated attachments") # TODO: i18n
|
target.find("span[data-type=show]").removeClass("hidden")
|
||||||
.prop("title", "show deprecated attachments") # TODO: i18n
|
target.find("more-attachments-num").removeClass("hidden")
|
||||||
$el.find("div.single-attachment.deprecated").addClass("hidden")
|
|
||||||
|
|
||||||
$el.on "change", "input.add-attach", ->
|
|
||||||
files = _.map(event.target.files, (x) -> x)
|
|
||||||
return if files.length < 1
|
|
||||||
|
|
||||||
# Add files to uploadingFiles array
|
|
||||||
$scope.$apply =>
|
|
||||||
if not $scope.uploadingFiles or $scope.uploadingFiles.length == 0
|
|
||||||
$scope.uploadingFiles = files
|
|
||||||
else
|
|
||||||
$scope.uploadingFiles = scope.uploadingFiles.concat(files)
|
|
||||||
|
|
||||||
# Upload new files
|
|
||||||
urlName = $ctrl.attachmentsUrlName
|
|
||||||
projectId = $scope.projectId
|
|
||||||
objectId = $model.$modelValue.id
|
|
||||||
|
|
||||||
_.forEach files, (file) ->
|
|
||||||
promise = $rs.attachments.create(urlName, projectId, objectId, file)
|
|
||||||
|
|
||||||
promise.then (data) ->
|
|
||||||
data.isCreatedRightNow = true
|
|
||||||
|
|
||||||
index = $scope.uploadingFiles.indexOf(file)
|
|
||||||
$scope.uploadingFiles.splice(index, 1)
|
|
||||||
$ctrl.onCreateAttachment(data)
|
|
||||||
|
|
||||||
promise.then null, (data) ->
|
|
||||||
index = $scope.uploadingFiles.indexOf(file)
|
|
||||||
$scope.uploadingFiles.splice(index, 1)
|
|
||||||
$confirm.notify("error", null, "We have not been able to upload '#{file.name}'.") #TODO: i18in
|
|
||||||
|
|
||||||
## On destroy
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
|
templateFn = ($el, $attrs) ->
|
||||||
|
return template({type: $attrs.type})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
link: link,
|
require: ["tgAttachments", "ngModel"]
|
||||||
require: "ngModel"
|
controller: AttachmentsController
|
||||||
|
controllerAs: "ctrl"
|
||||||
|
restrict: "AE"
|
||||||
|
scope: true
|
||||||
|
link: link
|
||||||
|
template: templateFn
|
||||||
}
|
}
|
||||||
|
|
||||||
module.directive("tgAttachments", ["$tgRepo", "$tgResources", "$tgConfirm", AttachmentsDirective])
|
module.directive("tgAttachments", ["$tgConfirm", AttachmentsDirective])
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
AttachmentDirective = ->
|
||||||
## Attachment Directive
|
template = _.template("""
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
AttachmentDirective = ($log, $repo, $confirm) ->
|
|
||||||
singleAttachment = _.template("""
|
|
||||||
<div class="attachment-name">
|
<div class="attachment-name">
|
||||||
<a href="<%- url %>" title="<%- name %>" target="_blank">
|
<a href="<%- url %>" title="<%- name %>" target="_blank">
|
||||||
<span class="icon icon-documents"></span>
|
<span class="icon icon-documents"></span>
|
||||||
|
@ -164,9 +288,9 @@ AttachmentDirective = ($log, $repo, $confirm) ->
|
||||||
<a class="settings icon icon-drag-v" href="" title=""Drag"></a>
|
<a class="settings icon icon-drag-v" href="" title=""Drag"></a>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
""") #TODO: i18n
|
""")
|
||||||
|
|
||||||
singleAttachmentEditable = _.template("""
|
templateEdit = _.template("""
|
||||||
<div class="attachment-name">
|
<div class="attachment-name">
|
||||||
<span class="icon.icon-document"></span>
|
<span class="icon.icon-document"></span>
|
||||||
<a href="<%- url %>" title="<%- name %>" target="_blank"><%- name %></a>
|
<a href="<%- url %>" title="<%- name %>" target="_blank"><%- name %></a>
|
||||||
|
@ -188,13 +312,13 @@ AttachmentDirective = ($log, $repo, $confirm) ->
|
||||||
<a class="editable-settings icon icon-floppy" href="" title="Save"></a>
|
<a class="editable-settings icon icon-floppy" href="" title="Save"></a>
|
||||||
<a class="editable-settings icon icon-delete" href="" title="Cancel"></a>
|
<a class="editable-settings icon icon-delete" href="" title="Cancel"></a>
|
||||||
</div>
|
</div>
|
||||||
""") # TODO: i18n
|
""")
|
||||||
|
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs, $ctrl) ->
|
||||||
$ctrl = $el.controller()
|
render = (attachment, edit=false) ->
|
||||||
|
permissions = $scope.project.my_permissions
|
||||||
|
modifyPermission = permissions.indexOf("modify_#{$ctrl.type}") > -1
|
||||||
|
|
||||||
render = (attachment, isEditable=false) ->
|
|
||||||
modifyPermission = $scope.project.my_permissions.indexOf("modify_#{$attrs.permissionSuffix}") > -1
|
|
||||||
ctx = {
|
ctx = {
|
||||||
id: attachment.id
|
id: attachment.id
|
||||||
name: attachment.name
|
name: attachment.name
|
||||||
|
@ -205,30 +329,29 @@ AttachmentDirective = ($log, $repo, $confirm) ->
|
||||||
modifyPermission: modifyPermission
|
modifyPermission: modifyPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
if isEditable
|
if edit
|
||||||
html = singleAttachmentEditable(ctx)
|
html = templateEdit(ctx)
|
||||||
else
|
else
|
||||||
html = singleAttachment(ctx)
|
html = template(ctx)
|
||||||
|
|
||||||
$el.html(html)
|
$el.html(html)
|
||||||
|
|
||||||
if attachment.is_deprecated
|
if attachment.is_deprecated
|
||||||
$el.addClass("deprecated")
|
$el.addClass("deprecated")
|
||||||
if $scope.showDeprecatedAttachments
|
|
||||||
$el.removeClass("hidden")
|
|
||||||
else
|
|
||||||
$el.addClass("hidden")
|
|
||||||
else
|
|
||||||
$el.removeClass("deprecated")
|
|
||||||
$el.removeClass("hidden")
|
|
||||||
|
|
||||||
## Initialize
|
## Actions (on edit mode)
|
||||||
if not $attrs.tgAttachment?
|
$el.on "click", "a.editable-settings.icon-floppy", (event) ->
|
||||||
return $log.error "AttachmentDirective the directive need an attachment"
|
event.preventDefault()
|
||||||
|
|
||||||
attachment = $scope.$eval($attrs.tgAttachment)
|
attachment.description = $el.find("input[name='description']").val()
|
||||||
render(attachment, attachment.isCreatedRightNow)
|
attachment.is_deprecated = $el.find("input[name='is-deprecated']").prop("checked")
|
||||||
delete attachment.isCreatedRightNow
|
|
||||||
|
$scope.$apply ->
|
||||||
|
$ctrl.updateAttachment(attachment).then ->
|
||||||
|
render(attachment)
|
||||||
|
|
||||||
|
$el.on "click", "a.editable-settings.icon-delete", (event) ->
|
||||||
|
event.preventDefault()
|
||||||
|
render(attachment, false)
|
||||||
|
|
||||||
## Actions (on view mode)
|
## Actions (on view mode)
|
||||||
$el.on "click", "a.settings.icon-edit", (event) ->
|
$el.on "click", "a.settings.icon-edit", (event) ->
|
||||||
|
@ -237,49 +360,20 @@ AttachmentDirective = ($log, $repo, $confirm) ->
|
||||||
|
|
||||||
$el.on "click", "a.settings.icon-delete", (event) ->
|
$el.on "click", "a.settings.icon-delete", (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
$scope.$apply ->
|
||||||
|
$ctrl.removeAttachment(attachment)
|
||||||
|
|
||||||
title = "Delete attachment" #TODO: i18in
|
|
||||||
subtitle = "the attachment '#{attachment.name}'" #TODO: i18in
|
|
||||||
|
|
||||||
onSuccess = ->
|
|
||||||
$ctrl.onDeleteAttachment(attachment)
|
|
||||||
|
|
||||||
onError = ->
|
|
||||||
$confirm.notify("error", null, "We have not been able to delete #{subtitle}.") #TODO: i18in
|
|
||||||
|
|
||||||
$confirm.ask(title, subtitle).then ->
|
|
||||||
$repo.remove(attachment).then(onSuccess, onError)
|
|
||||||
|
|
||||||
## Actions (on edit mode)
|
|
||||||
$el.on "click", "a.editable-settings.icon-floppy", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
newDescription = $el.find("input[name='description']").val()
|
|
||||||
newIsDeprecated = $el.find("input[name='is-deprecated']").prop("checked")
|
|
||||||
|
|
||||||
if newDescription != attachment.description
|
|
||||||
attachment.description = newDescription
|
|
||||||
if newIsDeprecated != attachment.is_deprecated
|
|
||||||
attachment.is_deprecated = newIsDeprecated
|
|
||||||
|
|
||||||
onSuccess = ->
|
|
||||||
$ctrl.onEditAttachment(attachment)
|
|
||||||
render(attachment)
|
|
||||||
|
|
||||||
onError = ->
|
|
||||||
$confirm.notify("error")
|
|
||||||
|
|
||||||
$repo.save(attachment).then(onSuccess, onError)
|
|
||||||
|
|
||||||
$el.on "click", "a.editable-settings.icon-delete", (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
render(attachment)
|
|
||||||
|
|
||||||
## On destroy
|
|
||||||
$scope.$on "$destroy", ->
|
$scope.$on "$destroy", ->
|
||||||
$el.off()
|
$el.off()
|
||||||
|
|
||||||
return {link: link}
|
# Bootstrap
|
||||||
|
attachment = $scope.$eval($attrs.tgAttachment)
|
||||||
|
render(attachment, attachment.isCreatedRightNow)
|
||||||
|
|
||||||
module.directive("tgAttachment", ["$log", "$tgRepo", "$tgConfirm",
|
return {
|
||||||
AttachmentDirective])
|
link: link
|
||||||
|
require: "^tgAttachments"
|
||||||
|
restrict: "AE"
|
||||||
|
}
|
||||||
|
|
||||||
|
module.directive("tgAttachment", AttachmentDirective)
|
||||||
|
|
|
@ -102,44 +102,3 @@ class FiltersMixin
|
||||||
location.search(name, value)
|
location.search(name, value)
|
||||||
|
|
||||||
taiga.FiltersMixin = FiltersMixin
|
taiga.FiltersMixin = FiltersMixin
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
## Attachments Mixin
|
|
||||||
#############################################################################
|
|
||||||
# This mixin requires @rs ($tgResources), @scope and @log ($tgLog)
|
|
||||||
# The mixin required @..attachmentsUrlName (p.e. 'issues/attachments',see resources.coffee)
|
|
||||||
|
|
||||||
class AttachmentsMixin
|
|
||||||
loadAttachments: (objectId) ->
|
|
||||||
if not @.attachmentsUrlName
|
|
||||||
return @log.error "AttachmentsMixin: @.attachmentsUrlName is required"
|
|
||||||
|
|
||||||
@scope.attachmentsCount = 0
|
|
||||||
@scope.deprecatedAttachmentsCount = 0
|
|
||||||
|
|
||||||
return @rs.attachments.list(@.attachmentsUrlName, objectId).then (attachments) =>
|
|
||||||
@scope.attachments = _.sortBy(attachments, "order")
|
|
||||||
@.updateAttachmentsCounters()
|
|
||||||
return attachments
|
|
||||||
|
|
||||||
updateAttachmentsCounters: ->
|
|
||||||
@scope.attachmentsCount = @scope.attachments.length
|
|
||||||
@scope.deprecatedAttachmentsCount = _.filter(@scope.attachments, is_deprecated: true).length
|
|
||||||
|
|
||||||
onCreateAttachment: (attachment) ->
|
|
||||||
@scope.attachments[@scope.attachments.length] = attachment
|
|
||||||
@.updateAttachmentsCounters()
|
|
||||||
@scope.$emit("attachment:create")
|
|
||||||
|
|
||||||
onEditAttachment: (attachment) ->
|
|
||||||
@.updateAttachmentsCounters()
|
|
||||||
@scope.$emit("attachment:edit")
|
|
||||||
|
|
||||||
onDeleteAttachment: (attachment) ->
|
|
||||||
index = @scope.attachments.indexOf(attachment)
|
|
||||||
@scope.attachments.splice(index, 1)
|
|
||||||
@.updateAttachmentsCounters()
|
|
||||||
@scope.$emit("attachment:delete")
|
|
||||||
|
|
||||||
taiga.AttachmentsMixin = AttachmentsMixin
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ module = angular.module("taigaIssues")
|
||||||
## Issue Detail Controller
|
## Issue Detail Controller
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.AttachmentsMixin)
|
class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
@ -49,8 +49,6 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls) ->
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls) ->
|
||||||
@.attachmentsUrlName = "issues/attachments"
|
|
||||||
|
|
||||||
@scope.issueRef = @params.issueref
|
@scope.issueRef = @params.issueref
|
||||||
@scope.sectionName = "Issue Details"
|
@scope.sectionName = "Issue Details"
|
||||||
|
|
||||||
|
@ -118,8 +116,7 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin, tai
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @q.all([@.loadIssue(),
|
.then(=> @.loadIssue())
|
||||||
@.loadAttachments(@scope.issueId)]))
|
|
||||||
|
|
||||||
block: ->
|
block: ->
|
||||||
@rootscope.$broadcast("block", @scope.issue)
|
@rootscope.$broadcast("block", @scope.issue)
|
||||||
|
|
|
@ -88,10 +88,10 @@ urls = {
|
||||||
"history/wiki": "/api/v1/history/wiki"
|
"history/wiki": "/api/v1/history/wiki"
|
||||||
|
|
||||||
# Attachments
|
# Attachments
|
||||||
"userstories/attachments": "/api/v1/userstories/attachments"
|
"attachments/us": "/api/v1/userstories/attachments"
|
||||||
"issues/attachments": "/api/v1/issues/attachments"
|
"attachments/issue": "/api/v1/issues/attachments"
|
||||||
"tasks/attachments": "/api/v1/tasks/attachments"
|
"attachments/task": "/api/v1/tasks/attachments"
|
||||||
"wiki/attachments": "/api/v1/wiki/attachments"
|
"attachments/wiki_page": "/api/v1/wiki/attachments"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize api urls service
|
# Initialize api urls service
|
||||||
|
|
|
@ -30,7 +30,7 @@ module = angular.module("taigaTasks")
|
||||||
## Task Detail Controller
|
## Task Detail Controller
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.AttachmentsMixin)
|
class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
@ -46,8 +46,6 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taig
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls) ->
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls) ->
|
||||||
@.attachmentsUrlName = "tasks/attachments"
|
|
||||||
|
|
||||||
@scope.taskRef = @params.taskref
|
@scope.taskRef = @params.taskref
|
||||||
@scope.sectionName = "Task Details"
|
@scope.sectionName = "Task Details"
|
||||||
|
|
||||||
|
@ -110,7 +108,6 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taig
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @.loadTask())
|
.then(=> @.loadTask())
|
||||||
.then(=> @.loadAttachments(@scope.taskId))
|
|
||||||
|
|
||||||
block: ->
|
block: ->
|
||||||
@rootscope.$broadcast("block", @scope.task)
|
@rootscope.$broadcast("block", @scope.task)
|
||||||
|
|
|
@ -31,7 +31,7 @@ module = angular.module("taigaUserStories")
|
||||||
## User story Detail Controller
|
## User story Detail Controller
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.AttachmentsMixin)
|
class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
@ -47,7 +47,6 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin,
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls) ->
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls) ->
|
||||||
@.attachmentsUrlName = "userstories/attachments"
|
|
||||||
@scope.issueRef = @params.issueref
|
@scope.issueRef = @params.issueref
|
||||||
@scope.sectionName = "User Story Details"
|
@scope.sectionName = "User Story Details"
|
||||||
|
|
||||||
|
@ -114,8 +113,7 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin,
|
||||||
|
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @q.all([@.loadUs(),
|
.then(=> @.loadUs())
|
||||||
@.loadAttachments(@scope.usId)]))
|
|
||||||
|
|
||||||
block: ->
|
block: ->
|
||||||
@rootscope.$broadcast("block", @scope.us)
|
@rootscope.$broadcast("block", @scope.us)
|
||||||
|
|
|
@ -32,7 +32,7 @@ module = angular.module("taigaWiki")
|
||||||
## Wiki Detail Controller
|
## Wiki Detail Controller
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.AttachmentsMixin)
|
class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@.$inject = [
|
@.$inject = [
|
||||||
"$scope",
|
"$scope",
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
|
@ -50,7 +50,6 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taig
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @filter, @log, @appTitle,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @filter, @log, @appTitle,
|
||||||
@navUrls) ->
|
@navUrls) ->
|
||||||
@.attachmentsUrlName = "wiki/attachments"
|
|
||||||
@scope.projectSlug = @params.pslug
|
@scope.projectSlug = @params.pslug
|
||||||
@scope.wikiSlug = @params.slug
|
@scope.wikiSlug = @params.slug
|
||||||
@scope.sectionName = "Wiki"
|
@scope.sectionName = "Wiki"
|
||||||
|
@ -116,8 +115,7 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin, taig
|
||||||
return promise.then(=> @.loadProject())
|
return promise.then(=> @.loadProject())
|
||||||
.then(=> @.loadUsersAndRoles())
|
.then(=> @.loadUsersAndRoles())
|
||||||
.then(=> @q.all([@.loadWikiLinks(),
|
.then(=> @q.all([@.loadWikiLinks(),
|
||||||
@.loadWiki(),
|
@.loadWiki()]))
|
||||||
@.loadAttachments(@scope.wikiId)]))
|
|
||||||
|
|
||||||
edit: ->
|
edit: ->
|
||||||
ctx = {
|
ctx = {
|
||||||
|
|
|
@ -28,10 +28,7 @@ block content
|
||||||
section.us-content
|
section.us-content
|
||||||
textarea(placeholder="Write a description of your issue", ng-model="issue.description", tg-markitup)
|
textarea(placeholder="Write a description of your issue", ng-model="issue.description", tg-markitup)
|
||||||
|
|
||||||
- var attachModel = "issue"
|
tg-attachments(ng-model="issue", type="issue")
|
||||||
- var permissionSuffix = "issue"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
||||||
tg-history(ng-model="issue", type="issue", mode="edit")
|
tg-history(ng-model="issue", type="issue", mode="edit")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
|
|
|
@ -28,10 +28,7 @@ block content
|
||||||
|
|
||||||
section.us-content.wysiwyg(tg-bind-html="issue.description_html")
|
section.us-content.wysiwyg(tg-bind-html="issue.description_html")
|
||||||
|
|
||||||
- var attachModel = "issue"
|
tg-attachments(ng-model="issue", type="issue")
|
||||||
- var permissionSuffix = "issue"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
||||||
tg-history(ng-model="issue", type="issue")
|
tg-history(ng-model="issue", type="issue")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
|
|
|
@ -28,10 +28,7 @@ block content
|
||||||
section.us-content
|
section.us-content
|
||||||
textarea(placeholder="Write a description of your task", ng-model="task.description", tg-markitup)
|
textarea(placeholder="Write a description of your task", ng-model="task.description", tg-markitup)
|
||||||
|
|
||||||
- var attachModel = "task"
|
tg-attachments(ng-model="task", type="task")
|
||||||
- var permissionSuffix = "task"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
||||||
tg-history(ng-model="task", type="task", mode="edit")
|
tg-history(ng-model="task", type="task", mode="edit")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
|
|
|
@ -28,10 +28,7 @@ block content
|
||||||
|
|
||||||
section.us-content.wysiwyg(tg-bind-html="task.description_html")
|
section.us-content.wysiwyg(tg-bind-html="task.description_html")
|
||||||
|
|
||||||
- var attachModel = "task"
|
tg-attachments(ng-model="task", type="task")
|
||||||
- var permissionSuffix = "task"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
||||||
tg-history(ng-model="task", type="task")
|
tg-history(ng-model="task", type="task")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
|
|
|
@ -28,10 +28,7 @@ block content
|
||||||
section.us-content
|
section.us-content
|
||||||
textarea(placeholder="Write a description of your user story", ng-model="us.description", tg-markitup)
|
textarea(placeholder="Write a description of your user story", ng-model="us.description", tg-markitup)
|
||||||
|
|
||||||
- var attachModel = "us"
|
tg-attachments(ng-model="us", type="us")
|
||||||
- var permissionSuffix = "us"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
||||||
tg-history(ng-model="us", type="us", mode="edit")
|
tg-history(ng-model="us", type="us", mode="edit")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
|
|
|
@ -32,10 +32,7 @@ block content
|
||||||
|
|
||||||
include views/modules/related-tasks
|
include views/modules/related-tasks
|
||||||
|
|
||||||
- var attachModel = "us"
|
tg-attachments(ng-model="us", type="us")
|
||||||
- var permissionSuffix = "us"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
||||||
tg-history(ng-model="us", type="us")
|
tg-history(ng-model="us", type="us")
|
||||||
|
|
||||||
sidebar.menu-secondary.sidebar
|
sidebar.menu-secondary.sidebar
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
//- NOTE: You must to define 'var attachModel' 'var permissionSuffix' with the object model
|
|
||||||
//- that have attachments
|
|
||||||
|
|
||||||
section.attachments(tg-attachments, ng-model=attachModel, ng-if="#{attachModel}.id")
|
|
||||||
div.attachments-header
|
|
||||||
h3.attachments-title
|
|
||||||
span.icon.icon-attachments
|
|
||||||
span.attachments-num 0
|
|
||||||
span.attachments-text attachments
|
|
||||||
div.button.button-gray.add-attach(tg-check-permission="modify_"+permissionSuffix, title="Add new attachment")
|
|
||||||
span +new file
|
|
||||||
input.add-attach(type="file", multiple="multiple")
|
|
||||||
|
|
||||||
div.attachment-body.sortable
|
|
||||||
div.hidden.single-attachment(ng-repeat="attach in attachments",
|
|
||||||
tg-attachment="attach", permission-suffix=permissionSuffix)
|
|
||||||
|
|
||||||
//- See modules/common/attachments.coffee - AttachmentDirective
|
|
||||||
|
|
||||||
div.single-attachment(ng-repeat="file in uploadingFiles")
|
|
||||||
div.attachment-name
|
|
||||||
a(href="", tg-bo-title="file.name", tg-bo-bind="file.name")
|
|
||||||
div.attachment-size
|
|
||||||
span.attachment-size(tg-bo-bind="file.size")
|
|
||||||
div.attachment-comments
|
|
||||||
span(ng-bind="file.progressMessage")
|
|
||||||
div.percentage(ng-style="{'width': file.progressPercent}")
|
|
||||||
|
|
||||||
a.hidden.more-attachments(href="", title="show deprecated atachments")
|
|
||||||
span.text + show deprecated atachments
|
|
||||||
span.more-attachments-num (0 deprecated)
|
|
|
@ -21,6 +21,4 @@ block content
|
||||||
section.wysiwyg
|
section.wysiwyg
|
||||||
textarea(placeholder="Write a your wiki page", ng-model="wiki.content", tg-markitup)
|
textarea(placeholder="Write a your wiki page", ng-model="wiki.content", tg-markitup)
|
||||||
|
|
||||||
- var attachModel = "wiki"
|
tg-attachments(ng-model="wiki", type="wiki_page")
|
||||||
- var permissionSuffix = "wiki_page"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
|
@ -25,6 +25,4 @@ block content
|
||||||
|
|
||||||
section.wiki-content.wysiwyg(tg-bind-html="wiki.html")
|
section.wiki-content.wysiwyg(tg-bind-html="wiki.html")
|
||||||
|
|
||||||
- var attachModel = "wiki"
|
tg-attachments(ng-model="wiki", type="wiki_page")
|
||||||
- var permissionSuffix = "wiki_page"
|
|
||||||
include views/modules/attachments
|
|
||||||
|
|
|
@ -159,16 +159,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-attach {
|
.add-attach {
|
||||||
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 100%;
|
font-size: 99px;
|
||||||
|
height: 120%;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: -5px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue