New attachments components
- Attachments image gallery. - Upload attachments on US/issue/task lightbox. - Drag files from desktop. - Completly new attachment code. - Drag files in wysiwygstable
parent
298b528e49
commit
50af448305
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,18 +1,12 @@
|
|||
# Changelog #
|
||||
|
||||
## 1.10.0 ??? (unreleased)
|
||||
|
||||
## 1.10.0 ??? (Unreleased)
|
||||
|
||||
### Features
|
||||
- ...
|
||||
|
||||
### Misc
|
||||
- Lots of small and not so small bugfixes.
|
||||
|
||||
|
||||
## 1.9.1 Taiga Tribe (2016-01-05)
|
||||
|
||||
### Features
|
||||
- Statics folder hash.
|
||||
- Attachments image gallery.
|
||||
- Upload attachments on US/issue/task lightbox.
|
||||
- Drag files from desktop.
|
||||
- Drag files in wysiwyg.
|
||||
- [118n] Now taiga plugins can be translatable.
|
||||
- New Taiga plugins system.
|
||||
- Now superadmins can send notifications (live announcement) to the user (through taiga-events).
|
||||
|
|
|
@ -54,11 +54,12 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
"$tgEvents",
|
||||
"$tgAnalytics",
|
||||
"$translate",
|
||||
"$tgLoading"
|
||||
"$tgLoading",
|
||||
"tgResources"
|
||||
]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
|
||||
@location, @appMetaService, @navUrls, @events, @analytics, @translate, @loading) ->
|
||||
@location, @appMetaService, @navUrls, @events, @analytics, @translate, @loading, @rs2) ->
|
||||
bindMethods(@)
|
||||
|
||||
@scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME")
|
||||
|
@ -566,10 +567,10 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
|||
.timeout(200)
|
||||
.start()
|
||||
|
||||
@rs.userstories.getByRef(projectId, ref).then (us) =>
|
||||
@rootscope.$broadcast("usform:edit", us)
|
||||
|
||||
currentLoading.finish()
|
||||
return @rs.userstories.getByRef(projectId, ref).then (us) =>
|
||||
@rs2.attachments.list("us", us.id, projectId).then (attachments) =>
|
||||
@rootscope.$broadcast("usform:edit", us, attachments.toJS())
|
||||
currentLoading.finish()
|
||||
|
||||
deleteUserStory: (us) ->
|
||||
title = @translate.instant("US.TITLE_DELETE_ACTION")
|
||||
|
|
|
@ -96,22 +96,28 @@ module.factory("$selectedText", ["$window", "$document", SelectedText])
|
|||
## Permission directive, hide elements when necessary
|
||||
#############################################################################
|
||||
|
||||
CheckPermissionDirective = ->
|
||||
CheckPermissionDirective = (projectService) ->
|
||||
render = ($el, project, permission) ->
|
||||
$el.removeClass('hidden') if project.my_permissions.indexOf(permission) > -1
|
||||
$el.removeClass('hidden') if project.get('my_permissions').indexOf(permission) > -1
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$el.addClass('hidden')
|
||||
permission = $attrs.tgCheckPermission
|
||||
|
||||
$scope.$watch "project", (project) ->
|
||||
render($el, project, permission) if project?
|
||||
$scope.$watch ( () ->
|
||||
return projectService.project
|
||||
), () ->
|
||||
render($el, projectService.project, permission) if projectService.project
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link:link}
|
||||
|
||||
CheckPermissionDirective.$inject = [
|
||||
"tgProjectService"
|
||||
]
|
||||
|
||||
module.directive("tgCheckPermission", CheckPermissionDirective)
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -1,343 +0,0 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
# Copyright (C) 2014-2016 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014-2016 David Barragán Merino <bameda@dbarragan.com>
|
||||
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Xavi Julian <xavier.julian@kaleidos.net>
|
||||
#
|
||||
# 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: modules/common/attachments.coffee
|
||||
###
|
||||
|
||||
taiga = @.taiga
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
bindOnce = @.taiga.bindOnce
|
||||
bindMethods = @.taiga.bindMethods
|
||||
|
||||
module = angular.module("taigaCommon")
|
||||
|
||||
|
||||
class AttachmentsController extends taiga.Controller
|
||||
@.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q", "$translate"]
|
||||
|
||||
constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q, @translate) ->
|
||||
bindMethods(@)
|
||||
@.type = null
|
||||
@.objectId = null
|
||||
@.projectId = null
|
||||
|
||||
@.uploadingAttachments = []
|
||||
@.attachments = []
|
||||
@.attachmentsCount = 0
|
||||
@.deprecatedAttachmentsCount = 0
|
||||
@.showDeprecated = false
|
||||
|
||||
initialize: (type, objectId) ->
|
||||
@.type = type
|
||||
@.objectId = objectId
|
||||
@.projectId = @scope.projectId
|
||||
|
||||
loadAttachments: ->
|
||||
return @.attachments if not @.objectId
|
||||
|
||||
urlname = "attachments/#{@.type}"
|
||||
|
||||
return @rs.attachments.list(urlname, @.objectId, @.projectId).then (attachments) =>
|
||||
@.attachments = _.sortBy(attachments, "order")
|
||||
@.updateCounters()
|
||||
return attachments
|
||||
|
||||
updateCounters: ->
|
||||
@.attachmentsCount = @.attachments.length
|
||||
@.deprecatedAttachmentsCount = _.filter(@.attachments, {is_deprecated: true}).length
|
||||
|
||||
_createAttachment: (attachment) ->
|
||||
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) =>
|
||||
@scope.$emit("attachments:size-error") if data.status == 413
|
||||
|
||||
index = @.uploadingAttachments.indexOf(attachment)
|
||||
@.uploadingAttachments.splice(index, 1)
|
||||
|
||||
message = @translate.instant("ATTACHMENT.ERROR_UPLOAD_ATTACHMENT", {
|
||||
fileName: attachment.name, errorMessage: data.data._error_message})
|
||||
@confirm.notify("error", message)
|
||||
return @q.reject(data)
|
||||
|
||||
return promise
|
||||
|
||||
# Create attachments in bulk
|
||||
createAttachments: (attachments) ->
|
||||
promises = _.map(attachments, (x) => @._createAttachment(x))
|
||||
return @q.all(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 = (response) =>
|
||||
$scope.$emit("attachments:size-error") if response.status == 413
|
||||
@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 = _.sortBy(@.attachments, "order")
|
||||
|
||||
# Remove one concrete attachment.
|
||||
removeAttachment: (attachment) ->
|
||||
title = @translate.instant("ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT")
|
||||
message = @translate.instant("ATTACHMENT.MSG_LIGHTBOX_DELETE_ATTACHMENT", {fileName: attachment.name})
|
||||
|
||||
return @confirm.askOnDelete(title, message).then (askResponse) =>
|
||||
onSuccess = =>
|
||||
askResponse.finish()
|
||||
index = @.attachments.indexOf(attachment)
|
||||
@.attachments.splice(index, 1)
|
||||
@.updateCounters()
|
||||
@rootscope.$broadcast("attachment:delete")
|
||||
|
||||
onError = =>
|
||||
askResponse.finish(false)
|
||||
message = @translate.instant("ATTACHMENT.ERROR_DELETE_ATTACHMENT", {errorMessage: message})
|
||||
@confirm.notify("error", null, message)
|
||||
return @q.reject()
|
||||
|
||||
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 = ($config, $confirm, $templates, $translate) ->
|
||||
template = $templates.get("attachment/attachments.html", true)
|
||||
|
||||
link = ($scope, $el, $attrs, $ctrls) ->
|
||||
$ctrl = $ctrls[0]
|
||||
$model = $ctrls[1]
|
||||
|
||||
bindOnce $scope, $attrs.ngModel, (value) ->
|
||||
$ctrl.initialize($attrs.type, value.id)
|
||||
$ctrl.loadAttachments()
|
||||
|
||||
tdom = $el.find("div.attachment-body.sortable")
|
||||
tdom.sortable({
|
||||
items: "div.single-attachment"
|
||||
handle: "a.settings.icon.icon-drag-v"
|
||||
containment: ".attachments"
|
||||
dropOnEmpty: true
|
||||
scroll: false
|
||||
tolerance: "pointer"
|
||||
placeholder: "sortable-placeholder single-attachment"
|
||||
})
|
||||
|
||||
tdom.on "sortstop", (event, ui) ->
|
||||
attachment = ui.item.scope().attach
|
||||
newIndex = ui.item.index()
|
||||
|
||||
$ctrl.reorderAttachment(attachment, newIndex)
|
||||
$ctrl.saveAttachments().then ->
|
||||
$scope.$emit("attachment:edit")
|
||||
|
||||
showSizeInfo = ->
|
||||
$el.find(".size-info").removeClass("hidden")
|
||||
|
||||
$scope.$on "attachments:size-error", ->
|
||||
showSizeInfo()
|
||||
|
||||
$el.on "change", ".attachments-header input", (event) ->
|
||||
files = _.toArray(event.target.files)
|
||||
|
||||
return if files.length < 1
|
||||
|
||||
$scope.$apply ->
|
||||
$ctrl.addUploadingAttachments(files)
|
||||
$ctrl.createAttachments(files)
|
||||
|
||||
$el.on "click", ".more-attachments", (event) ->
|
||||
event.preventDefault()
|
||||
target = angular.element(event.currentTarget)
|
||||
|
||||
$scope.$apply ->
|
||||
$ctrl.showDeprecated = not $ctrl.showDeprecated
|
||||
|
||||
target.find("span.text").addClass("hidden")
|
||||
if $ctrl.showDeprecated
|
||||
target.find("span[data-type=hide]").removeClass("hidden")
|
||||
target.find("more-attachments-num").addClass("hidden")
|
||||
else
|
||||
target.find("span[data-type=show]").removeClass("hidden")
|
||||
target.find("more-attachments-num").removeClass("hidden")
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
templateFn = ($el, $attrs) ->
|
||||
maxFileSize = $config.get("maxUploadFileSize", null)
|
||||
maxFileSize = sizeFormat(maxFileSize) if maxFileSize
|
||||
maxFileSizeMsg = if maxFileSize then $translate.instant("ATTACHMENT.MAX_UPLOAD_SIZE", {maxFileSize: maxFileSize}) else ""
|
||||
ctx = {
|
||||
type: $attrs.type
|
||||
maxFileSize: maxFileSize
|
||||
maxFileSizeMsg: maxFileSizeMsg
|
||||
}
|
||||
return template(ctx)
|
||||
|
||||
return {
|
||||
require: ["tgAttachments", "ngModel"]
|
||||
controller: AttachmentsController
|
||||
controllerAs: "ctrl"
|
||||
restrict: "AE"
|
||||
scope: true
|
||||
link: link
|
||||
template: templateFn
|
||||
}
|
||||
|
||||
module.directive("tgAttachments", ["$tgConfig", "$tgConfirm", "$tgTemplate", "$translate", AttachmentsDirective])
|
||||
|
||||
|
||||
AttachmentDirective = ($template, $compile, $translate, $rootScope) ->
|
||||
template = $template.get("attachment/attachment.html", true)
|
||||
templateEdit = $template.get("attachment/attachment-edit.html", true)
|
||||
|
||||
link = ($scope, $el, $attrs, $ctrl) ->
|
||||
render = (attachment, edit=false) ->
|
||||
permissions = $scope.project.my_permissions
|
||||
modifyPermission = permissions.indexOf("modify_#{$ctrl.type}") > -1
|
||||
|
||||
ctx = {
|
||||
id: attachment.id
|
||||
name: attachment.name
|
||||
title : $translate.instant("ATTACHMENT.TITLE", {
|
||||
fileName: attachment.name,
|
||||
date: moment(attachment.created_date).format($translate.instant("ATTACHMENT.DATE"))})
|
||||
url: attachment.url
|
||||
size: sizeFormat(attachment.size)
|
||||
description: attachment.description
|
||||
isDeprecated: attachment.is_deprecated
|
||||
modifyPermission: modifyPermission
|
||||
}
|
||||
|
||||
if edit
|
||||
html = $compile(templateEdit(ctx))($scope)
|
||||
else
|
||||
html = $compile(template(ctx))($scope)
|
||||
|
||||
$el.html(html)
|
||||
|
||||
if attachment.is_deprecated
|
||||
$el.addClass("deprecated")
|
||||
$el.find("input:checkbox").prop('checked', true)
|
||||
else
|
||||
$el.removeClass("deprecated")
|
||||
|
||||
saveAttachment = ->
|
||||
attachment.description = $el.find("input[name='description']").val()
|
||||
attachment.is_deprecated = $el.find("input[name='is-deprecated']").prop("checked")
|
||||
attachment.isCreatedRightNow = false
|
||||
|
||||
$scope.$apply ->
|
||||
$ctrl.updateAttachment(attachment).then ->
|
||||
render(attachment, false)
|
||||
|
||||
## Actions (on edit mode)
|
||||
$el.on "click", "a.editable-settings.icon-floppy", (event) ->
|
||||
event.preventDefault()
|
||||
saveAttachment()
|
||||
|
||||
$el.on "keyup", "input[name=description]", (event) ->
|
||||
if event.keyCode == 13
|
||||
saveAttachment()
|
||||
else if event.keyCode == 27
|
||||
$scope.$apply -> render(attachment, false)
|
||||
|
||||
$el.on "click", "a.editable-settings.icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
render(attachment, false)
|
||||
|
||||
## Actions (on view mode)
|
||||
$el.on "click", "a.settings.icon-edit", (event) ->
|
||||
event.preventDefault()
|
||||
render(attachment, true)
|
||||
$el.find("input[name='description']").focus().select()
|
||||
|
||||
$el.on "click", "a.settings.icon-delete", (event) ->
|
||||
event.preventDefault()
|
||||
$scope.$apply ->
|
||||
$ctrl.removeAttachment(attachment)
|
||||
|
||||
$el.on "click", "div.attachment-name a", (event) ->
|
||||
if null != attachment.name.match(/\.(jpe?g|png|gif|gifv|webm)/i)
|
||||
event.preventDefault()
|
||||
$scope.$apply ->
|
||||
$rootScope.$broadcast("attachment:preview", attachment)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
# Bootstrap
|
||||
attachment = $scope.$eval($attrs.tgAttachment)
|
||||
render(attachment, attachment.isCreatedRightNow)
|
||||
if attachment.isCreatedRightNow
|
||||
$el.find("input[name='description']").focus().select()
|
||||
|
||||
return {
|
||||
link: link
|
||||
require: "^tgAttachments"
|
||||
restrict: "AE"
|
||||
}
|
||||
|
||||
module.directive("tgAttachment", ["$tgTemplate", "$compile", "$translate", "$rootScope", AttachmentDirective])
|
|
@ -607,8 +607,79 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading,
|
|||
template: template
|
||||
}
|
||||
|
||||
module.directive("tgEditableDescription", ["$rootScope", "$tgRepo", "$tgConfirm", "$compile", "$tgLoading",
|
||||
"$selectedText", "$tgQqueue", "$tgTemplate", EditableDescriptionDirective])
|
||||
module.directive("tgEditableDescription", [
|
||||
"$rootScope",
|
||||
"$tgRepo",
|
||||
"$tgConfirm",
|
||||
"$compile",
|
||||
"$tgLoading",
|
||||
"$selectedText",
|
||||
"$tgQqueue",
|
||||
"$tgTemplate", EditableDescriptionDirective])
|
||||
|
||||
|
||||
|
||||
EditableWysiwyg = (attachmentsService) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
|
||||
isInEditMode = ->
|
||||
return $el.find('textarea').is(':visible')
|
||||
|
||||
$el.on 'dragover', (e) ->
|
||||
textarea = $el.find('textarea').focus()
|
||||
|
||||
return false
|
||||
|
||||
$el.on 'drop', (e) ->
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
if isInEditMode()
|
||||
dataTransfer = e.dataTransfer || (e.originalEvent && e.originalEvent.dataTransfer)
|
||||
|
||||
textarea = $el.find('textarea')
|
||||
|
||||
textarea.addClass('in-progress')
|
||||
|
||||
type = $model.$modelValue['_name']
|
||||
|
||||
if type == "userstories"
|
||||
type = "us"
|
||||
else if type == "tasks"
|
||||
type = "task"
|
||||
else if type == "issues"
|
||||
type = "issue"
|
||||
else if type == "wiki"
|
||||
type = "wiki_page"
|
||||
|
||||
file = dataTransfer.files[0]
|
||||
|
||||
return if !attachmentsService.validate(file)
|
||||
|
||||
attachmentsService.upload(
|
||||
file,
|
||||
$model.$modelValue.id,
|
||||
$model.$modelValue.project,
|
||||
type
|
||||
).then (result) ->
|
||||
textarea = $el.find('textarea')
|
||||
|
||||
if taiga.isImage(result.get('name'))
|
||||
url = ' + ')'
|
||||
else
|
||||
url = '[' + result.get('name') + '](' + result.get('url') + ')'
|
||||
|
||||
$.markItUp({ replaceWith: url })
|
||||
|
||||
textarea.removeClass('in-progress')
|
||||
|
||||
return {
|
||||
link: link
|
||||
restrict: "EA"
|
||||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgEditableWysiwyg", ["tgAttachmentsService", EditableWysiwyg])
|
||||
|
||||
|
||||
#############################################################################
|
||||
|
|
|
@ -68,3 +68,9 @@ momentFromNow = ->
|
|||
return ""
|
||||
|
||||
module.filter("momentFromNow", momentFromNow)
|
||||
|
||||
|
||||
sizeFormat = =>
|
||||
return @.taiga.sizeFormat
|
||||
|
||||
module.filter("sizeFormat", sizeFormat)
|
||||
|
|
|
@ -27,6 +27,7 @@ module = angular.module("taigaCommon")
|
|||
bindOnce = @.taiga.bindOnce
|
||||
timeout = @.taiga.timeout
|
||||
debounce = @.taiga.debounce
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
|
||||
#############################################################################
|
||||
## Common Lightbox Services
|
||||
|
@ -265,13 +266,30 @@ module.directive("tgBlockingMessageInput", ["$log", "$tgTemplate", "$compile", B
|
|||
## Create/Edit Userstory Lightbox Directive
|
||||
#############################################################################
|
||||
|
||||
CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading, $translate) ->
|
||||
CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService, $loading, $translate, $confirm, $q, attachmentsService) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.createEditUs = {}
|
||||
$scope.isNew = true
|
||||
|
||||
attachmentsToAdd = Immutable.List()
|
||||
attachmentsToDelete = Immutable.List()
|
||||
|
||||
resetAttachments = () ->
|
||||
attachmentsToAdd = Immutable.List()
|
||||
attachmentsToDelete = Immutable.List()
|
||||
|
||||
$scope.addAttachment = (attachment) ->
|
||||
attachmentsToAdd = attachmentsToAdd.push(attachment)
|
||||
|
||||
$scope.deleteAttachment = (attachment) ->
|
||||
attachmentsToDelete = attachmentsToDelete.push(attachment)
|
||||
|
||||
$scope.$on "usform:new", (ctx, projectId, status, statusList) ->
|
||||
$scope.isNew = true
|
||||
$scope.usStatusList = statusList
|
||||
$scope.attachments = Immutable.List()
|
||||
|
||||
resetAttachments()
|
||||
|
||||
$scope.us = $model.make_model("userstories", {
|
||||
project: projectId
|
||||
|
@ -293,10 +311,13 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
|
||||
lightboxService.open($el)
|
||||
|
||||
$scope.$on "usform:edit", (ctx, us) ->
|
||||
$scope.$on "usform:edit", (ctx, us, attachments) ->
|
||||
$scope.us = us
|
||||
$scope.attachments = Immutable.fromJS(attachments)
|
||||
$scope.isNew = false
|
||||
|
||||
resetAttachments()
|
||||
|
||||
# Update texts for edition
|
||||
$el.find(".button-green").html($translate.instant("COMMON.SAVE"))
|
||||
$el.find(".title").html($translate.instant("LIGHTBOX.CREATE_EDIT_US.EDIT_US"))
|
||||
|
@ -321,6 +342,18 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
|
||||
lightboxService.open($el)
|
||||
|
||||
createAttachments = (obj) ->
|
||||
promises = _.map attachmentsToAdd.toJS(), (attachment) ->
|
||||
attachmentsService.upload(attachment.file, obj.id, $scope.us.project, 'us')
|
||||
|
||||
return $q.all(promises)
|
||||
|
||||
deleteAttachments = (obj) ->
|
||||
promises = _.map attachmentsToDelete.toJS(), (attachment) ->
|
||||
return attachmentsService.delete("us", attachment.id)
|
||||
|
||||
return $q.all(promises)
|
||||
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
|
@ -339,6 +372,12 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
|||
promise = $repo.save($scope.us)
|
||||
broadcastEvent = "usform:edit:success"
|
||||
|
||||
promise.then (data) ->
|
||||
createAttachments(data)
|
||||
deleteAttachments(data)
|
||||
|
||||
return data
|
||||
|
||||
promise.then (data) ->
|
||||
currentLoading.finish()
|
||||
lightboxService.close($el)
|
||||
|
@ -380,6 +419,9 @@ module.directive("tgLbCreateEditUserstory", [
|
|||
"lightboxService",
|
||||
"$tgLoading",
|
||||
"$translate",
|
||||
"$tgConfirm",
|
||||
"$q",
|
||||
"tgAttachmentsService",
|
||||
CreateEditUserstoryDirective
|
||||
])
|
||||
|
||||
|
@ -632,30 +674,14 @@ module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboar
|
|||
## Attachment Preview Lighbox
|
||||
#############################################################################
|
||||
|
||||
AttachmentPreviewLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile) ->
|
||||
AttachmentPreviewLightboxDirective = (lightboxService, $template, $compile) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
template = $template.get("common/lightbox/lightbox-attachment-preview.html", true)
|
||||
|
||||
$scope.$on "attachment:preview", (event, attachment) ->
|
||||
lightboxService.open($el)
|
||||
render(attachment)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
render = (attachment) ->
|
||||
ctx = {
|
||||
url: attachment.url,
|
||||
title: attachment.description,
|
||||
name: attachment.name
|
||||
}
|
||||
|
||||
html = template(ctx)
|
||||
html = $compile(html)($scope)
|
||||
$el.html(html)
|
||||
lightboxService.open($el)
|
||||
|
||||
return {
|
||||
link: link
|
||||
templateUrl: 'common/lightbox/lightbox-attachment-preview.html',
|
||||
link: link,
|
||||
scope: true
|
||||
}
|
||||
|
||||
module.directive("tgLbAttachmentPreview", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", AttachmentPreviewLightboxDirective])
|
||||
module.directive("tgLbAttachmentPreview", ["lightboxService", "$tgTemplate", "$compile", AttachmentPreviewLightboxDirective])
|
||||
|
|
|
@ -32,12 +32,15 @@ module = angular.module("taigaIssues")
|
|||
## Issue Create Lightbox Directive
|
||||
#############################################################################
|
||||
|
||||
CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading) ->
|
||||
CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading, $q, attachmentsService) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
form = $el.find("form").checksley()
|
||||
$scope.issue = {}
|
||||
$scope.attachments = Immutable.List()
|
||||
|
||||
$scope.$on "issueform:new", (ctx, project)->
|
||||
attachmentsToAdd = Immutable.List()
|
||||
|
||||
$el.find(".tag-input").val("")
|
||||
|
||||
lightboxService.open($el)
|
||||
|
@ -55,6 +58,21 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading)
|
|||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
|
||||
createAttachments = (obj) ->
|
||||
promises = _.map attachmentsToAdd.toJS(), (attachment) ->
|
||||
return attachmentsService.upload(attachment.file, obj.id, $scope.issue.project, 'issue')
|
||||
|
||||
return $q.all(promises)
|
||||
|
||||
attachmentsToAdd = Immutable.List()
|
||||
|
||||
resetAttachments = () ->
|
||||
attachmentsToAdd = Immutable.List()
|
||||
|
||||
$scope.addAttachment = (attachment) ->
|
||||
attachmentsToAdd = attachmentsToAdd.push(attachment)
|
||||
|
||||
submit = debounce 2000, (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
|
@ -67,6 +85,9 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading)
|
|||
|
||||
promise = $repo.create("issues", $scope.issue)
|
||||
|
||||
promise.then (data) ->
|
||||
return createAttachments(data)
|
||||
|
||||
promise.then (data) ->
|
||||
currentLoading.finish()
|
||||
$rootscope.$broadcast("issueform:new:success", data)
|
||||
|
@ -85,7 +106,7 @@ CreateIssueDirective = ($repo, $confirm, $rootscope, lightboxService, $loading)
|
|||
|
||||
return {link:link}
|
||||
|
||||
module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", "$tgLoading",
|
||||
module.directive("tgLbCreateIssue", ["$tgRepo", "$tgConfirm", "$rootScope", "lightboxService", "$tgLoading", "$q", "tgAttachmentsService",
|
||||
CreateIssueDirective])
|
||||
|
||||
|
||||
|
|
|
@ -416,7 +416,7 @@ module.directive("tgKanbanArchivedStatusIntro", ["$translate", KanbanArchivedSta
|
|||
## Kanban User Story Directive
|
||||
#############################################################################
|
||||
|
||||
KanbanUserstoryDirective = ($rootscope, $loading, $rs) ->
|
||||
KanbanUserstoryDirective = ($rootscope, $loading, $rs, $rs2) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
$el.disableSelection()
|
||||
|
||||
|
@ -440,8 +440,9 @@ KanbanUserstoryDirective = ($rootscope, $loading, $rs) ->
|
|||
|
||||
us = $model.$modelValue
|
||||
$rs.userstories.getByRef(us.project, us.ref).then (editingUserStory) =>
|
||||
$rootscope.$broadcast("usform:edit", editingUserStory)
|
||||
currentLoading.finish()
|
||||
$rs2.attachments.list("us", us.id, us.project).then (attachments) =>
|
||||
$rootscope.$broadcast("usform:edit", editingUserStory, attachments.toJS())
|
||||
currentLoading.finish()
|
||||
|
||||
$scope.getTemplateUrl = () ->
|
||||
if $scope.us.isPlaceholder
|
||||
|
@ -458,7 +459,7 @@ KanbanUserstoryDirective = ($rootscope, $loading, $rs) ->
|
|||
require: "ngModel"
|
||||
}
|
||||
|
||||
module.directive("tgKanbanUserstory", ["$rootScope", "$tgLoading", "$tgResources", KanbanUserstoryDirective])
|
||||
module.directive("tgKanbanUserstory", ["$rootScope", "$tgLoading", "$tgResources", "tgResources", KanbanUserstoryDirective])
|
||||
|
||||
#############################################################################
|
||||
## Kanban Squish Column Directive
|
||||
|
|
|
@ -193,10 +193,8 @@ RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading,
|
|||
return {link: link}
|
||||
module.directive("tgRelatedTaskCreateForm", ["$tgRepo", "$compile", "$tgConfirm", "$tgModel", "$tgLoading", "$tgAnalytics", "$tgTemplate", RelatedTaskCreateFormDirective])
|
||||
|
||||
RelatedTaskCreateButtonDirective = ($repo, $compile, $confirm, $tgmodel) ->
|
||||
template = _.template("""
|
||||
<a ng-show="!newRelatedTaskFormOpen" class="icon icon-plus related-tasks-buttons ng-animate-disabled"></a>
|
||||
""")
|
||||
RelatedTaskCreateButtonDirective = ($repo, $compile, $confirm, $tgmodel, $template) ->
|
||||
template = $template.get("common/components/add-button.html", true)
|
||||
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$scope.$watch "project", (val) ->
|
||||
|
@ -207,14 +205,14 @@ RelatedTaskCreateButtonDirective = ($repo, $compile, $confirm, $tgmodel) ->
|
|||
else
|
||||
$el.html("")
|
||||
|
||||
$el.on "click", ".icon", (event)->
|
||||
$el.on "click", ".add-button", (event)->
|
||||
$scope.$emit("related-tasks:add-new-clicked")
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
return {link: link}
|
||||
module.directive("tgRelatedTaskCreateButton", ["$tgRepo", "$compile", "$tgConfirm", "$tgModel", RelatedTaskCreateButtonDirective])
|
||||
module.directive("tgRelatedTaskCreateButton", ["$tgRepo", "$compile", "$tgConfirm", "$tgModel", "$tgTemplate", RelatedTaskCreateButtonDirective])
|
||||
|
||||
RelatedTasksDirective = ($repo, $rs, $rootscope) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
|
|
|
@ -213,7 +213,6 @@ module.run([
|
|||
"$tgIssuesResourcesProvider",
|
||||
"$tgWikiResourcesProvider",
|
||||
"$tgSearchResourcesProvider",
|
||||
"$tgAttachmentsResourcesProvider",
|
||||
"$tgMdRenderResourcesProvider",
|
||||
"$tgHistoryResourcesProvider",
|
||||
"$tgKanbanResourcesProvider",
|
||||
|
|
|
@ -26,10 +26,36 @@ taiga = @.taiga
|
|||
bindOnce = @.taiga.bindOnce
|
||||
debounce = @.taiga.debounce
|
||||
|
||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate) ->
|
||||
CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxService, $translate, $q, attachmentsService) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.isNew = true
|
||||
|
||||
attachmentsToAdd = Immutable.List()
|
||||
attachmentsToDelete = Immutable.List()
|
||||
|
||||
resetAttachments = () ->
|
||||
attachmentsToAdd = Immutable.List()
|
||||
attachmentsToDelete = Immutable.List()
|
||||
|
||||
$scope.addAttachment = (attachment) ->
|
||||
attachmentsToAdd = attachmentsToAdd.push(attachment)
|
||||
|
||||
$scope.deleteAttachment = (attachment) ->
|
||||
attachmentsToDelete = attachmentsToDelete.push(attachment)
|
||||
|
||||
createAttachments = (obj) ->
|
||||
promises = _.map attachmentsToAdd.toJS(), (attachment) ->
|
||||
attachmentsService.upload(attachment.file, obj.id, $scope.task.project, 'task')
|
||||
|
||||
return $q.all(promises)
|
||||
|
||||
deleteAttachments = (obj) ->
|
||||
console.log attachmentsToDelete.toJS()
|
||||
promises = _.map attachmentsToDelete.toJS(), (attachment) ->
|
||||
return attachmentsService.delete("task", attachment.id)
|
||||
|
||||
return $q.all(promises)
|
||||
|
||||
$scope.$on "taskform:new", (ctx, sprintId, usId) ->
|
||||
$scope.task = {
|
||||
project: $scope.projectId
|
||||
|
@ -41,6 +67,9 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
|||
tags: []
|
||||
}
|
||||
$scope.isNew = true
|
||||
$scope.attachments = Immutable.List()
|
||||
|
||||
resetAttachments()
|
||||
|
||||
# Update texts for creation
|
||||
create = $translate.instant("COMMON.CREATE")
|
||||
|
@ -52,10 +81,14 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
|||
$el.find(".tag-input").val("")
|
||||
lightboxService.open($el)
|
||||
|
||||
$scope.$on "taskform:edit", (ctx, task) ->
|
||||
$scope.$on "taskform:edit", (ctx, task, attachments) ->
|
||||
$scope.task = task
|
||||
$scope.isNew = false
|
||||
|
||||
$scope.attachments = Immutable.fromJS(attachments)
|
||||
|
||||
resetAttachments()
|
||||
|
||||
# Update texts for edition
|
||||
save = $translate.instant("COMMON.SAVE")
|
||||
edit = $translate.instant("LIGHTBOX.CREATE_EDIT_TASK.ACTION_EDIT")
|
||||
|
@ -83,6 +116,12 @@ CreateEditTaskDirective = ($repo, $model, $rs, $rootscope, $loading, lightboxSer
|
|||
promise = $repo.save($scope.task)
|
||||
broadcastEvent = "taskform:edit:success"
|
||||
|
||||
promise.then (data) ->
|
||||
createAttachments(data)
|
||||
deleteAttachments(data)
|
||||
|
||||
return data
|
||||
|
||||
currentLoading = $loading()
|
||||
.target(submitButton)
|
||||
.start()
|
||||
|
@ -155,7 +194,9 @@ module.directive("tgLbCreateEditTask", [
|
|||
"$rootScope",
|
||||
"$tgLoading",
|
||||
"lightboxService",
|
||||
"$translate"
|
||||
"$translate",
|
||||
"$q",
|
||||
"tgAttachmentsService",
|
||||
CreateEditTaskDirective
|
||||
])
|
||||
|
||||
|
|
|
@ -307,7 +307,7 @@ module.directive("tgTaskboard", ["$rootScope", TaskboardDirective])
|
|||
## Taskboard Task Directive
|
||||
#############################################################################
|
||||
|
||||
TaskboardTaskDirective = ($rootscope, $loading, $rs) ->
|
||||
TaskboardTaskDirective = ($rootscope, $loading, $rs, $rs2) ->
|
||||
link = ($scope, $el, $attrs, $model) ->
|
||||
$el.disableSelection()
|
||||
|
||||
|
@ -330,14 +330,16 @@ TaskboardTaskDirective = ($rootscope, $loading, $rs) ->
|
|||
.start()
|
||||
|
||||
task = $scope.task
|
||||
|
||||
$rs.tasks.getByRef(task.project, task.ref).then (editingTask) =>
|
||||
$rootscope.$broadcast("taskform:edit", editingTask)
|
||||
currentLoading.finish()
|
||||
$rs2.attachments.list("task", editingTask.id, editingTask.project).then (attachments) =>
|
||||
$rootscope.$broadcast("taskform:edit", editingTask, attachments.toJS())
|
||||
currentLoading.finish()
|
||||
|
||||
return {link:link}
|
||||
|
||||
|
||||
module.directive("tgTaskboardTask", ["$rootScope", "$tgLoading", "$tgResources", TaskboardTaskDirective])
|
||||
module.directive("tgTaskboardTask", ["$rootScope", "$tgLoading", "$tgResources", "tgResources", TaskboardTaskDirective])
|
||||
|
||||
#############################################################################
|
||||
## Taskboard Squish Column Directive
|
||||
|
|
|
@ -195,6 +195,21 @@ _.mixin
|
|||
delete obj[key]; obj
|
||||
, obj).value()
|
||||
|
||||
isImage = (name) ->
|
||||
return name.match(/\.(jpe?g|png|gif|gifv|webm)/i) != null
|
||||
|
||||
patch = (oldImmutable, newImmutable) ->
|
||||
pathObj = {}
|
||||
|
||||
newImmutable.forEach (newValue, key) ->
|
||||
if newValue != oldImmutable.get(key)
|
||||
if newValue.toJS
|
||||
pathObj[key] = newValue.toJS()
|
||||
else
|
||||
pathObj[key] = newValue
|
||||
|
||||
return pathObj
|
||||
|
||||
taiga = @.taiga
|
||||
taiga.nl2br = nl2br
|
||||
taiga.bindMethods = bindMethods
|
||||
|
@ -218,3 +233,5 @@ taiga.sizeFormat = sizeFormat
|
|||
taiga.stripTags = stripTags
|
||||
taiga.replaceTags = replaceTags
|
||||
taiga.defineImmutableProperty = defineImmutableProperty
|
||||
taiga.isImage = isImage
|
||||
taiga.patch = patch
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
|
@ -361,6 +361,7 @@
|
|||
"DEPRECATED": "(deprecated)",
|
||||
"DEPRECATED_FILE": "Deprecated?",
|
||||
"ADD": "Add new attachment. {{maxFileSizeMsg}}",
|
||||
"DROP": "Drop attachments here!",
|
||||
"MAX_FILE_SIZE": "[Max. size: {{maxFileSize}}]",
|
||||
"SHOW_DEPRECATED": "+ show deprecated atachments",
|
||||
"HIDE_DEPRECATED": "- hide deprecated atachments",
|
||||
|
@ -371,6 +372,7 @@
|
|||
"TITLE_LIGHTBOX_DELETE_ATTACHMENT": "Delete attachment...",
|
||||
"MSG_LIGHTBOX_DELETE_ATTACHMENT": "the attachment '{{fileName}}'",
|
||||
"ERROR_DELETE_ATTACHMENT": "We have not been able to delete: {{errorMessage}}",
|
||||
"ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) is too heavy for our Oompa Loompas, try it with a smaller than ({{maxFileSize}})",
|
||||
"FIELDS": {
|
||||
"IS_DEPRECATED": "is deprecated"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
.attachment-gallery {
|
||||
display: flex;
|
||||
flex-basis: 25%;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
margin-top: 1rem;
|
||||
.single-attachment {
|
||||
margin-bottom: .5rem;
|
||||
margin-right: .5rem;
|
||||
max-width: 200px;
|
||||
&:hover {
|
||||
.icon-delete {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.attachment-image {
|
||||
display: inline-block;
|
||||
}
|
||||
img {
|
||||
height: 150px;
|
||||
margin-bottom: .2rem;
|
||||
width: 200px;
|
||||
&:hover {
|
||||
filter: saturate(150%) hue-rotate(60deg);
|
||||
transition: all .3s cubic-bezier(.01, .7, 1, 1);
|
||||
}
|
||||
}
|
||||
&.deprecated {
|
||||
img {
|
||||
opacity: .5;
|
||||
}
|
||||
.attachment-name {
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
.attachment-data {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.attachment-name {
|
||||
@extend %light;
|
||||
@include ellipsis(175px);
|
||||
display: inline-block;
|
||||
}
|
||||
.icon-delete {
|
||||
color: $red-light;
|
||||
margin-left: auto;
|
||||
opacity: 0;
|
||||
transition: opacity .3s ease-in;
|
||||
transition-delay: .2s;
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
.loading-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 150px;
|
||||
justify-content: center;
|
||||
margin: 0 .5rem .5rem 0;
|
||||
width: 200px;
|
||||
}
|
||||
.loading-spinner {
|
||||
margin: 0 auto;
|
||||
max-height: 3rem;
|
||||
max-width: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
.attachment-list {
|
||||
.single-attachment {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $whitish;
|
||||
display: flex;
|
||||
padding: .5rem 0 .5rem .5rem;
|
||||
position: relative;
|
||||
&:hover {
|
||||
.settings {
|
||||
opacity: 1;
|
||||
transition: opacity .2s ease-in;
|
||||
}
|
||||
}
|
||||
&.deprecated {
|
||||
color: $gray-light;
|
||||
.attachment-name a {
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
.attachment-name {
|
||||
@include ellipsis(90%);
|
||||
flex-basis: 25%;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.attachment-comments,
|
||||
.editable-attachment-comment {
|
||||
flex: 2;
|
||||
flex-basis: 50%;
|
||||
margin-right: .5rem;
|
||||
span {
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
.attachment-size {
|
||||
flex-basis: 125px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.attachment-settings {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-basis: 10%;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
justify-content: space-around;
|
||||
margin-left: auto;
|
||||
.settings,
|
||||
.editable-settings {
|
||||
@extend %large;
|
||||
color: $gray-light;
|
||||
&:hover {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
.settings {
|
||||
opacity: 0;
|
||||
}
|
||||
.editable-settings {
|
||||
opacity: 1;
|
||||
}
|
||||
.icon-delete {
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
.icon-drag-v {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
.icon-delete {
|
||||
@extend %large;
|
||||
color: $gray-light;
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
.editable-attachment-deprecated {
|
||||
display: flex;
|
||||
padding-left: 1rem;
|
||||
span {
|
||||
color: $gray-light;
|
||||
}
|
||||
input {
|
||||
margin-right: .2rem;
|
||||
vertical-align: middle;
|
||||
&:checked+span {
|
||||
color: $grayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.percentage {
|
||||
background: rgba($primary, .1);
|
||||
bottom: 0;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 45%;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
.attachments {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.attachments-header {
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
background: $whitish;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.attachments-title {
|
||||
@extend %medium;
|
||||
@extend %bold;
|
||||
color: $grayer;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
.options {
|
||||
display: flex;
|
||||
}
|
||||
label {
|
||||
cursor: pointer;
|
||||
margin-left: 1rem;
|
||||
&.add-attachment-button {
|
||||
background: $gray;
|
||||
border: 0;
|
||||
display: inline-block;
|
||||
padding: .5rem;
|
||||
transition: background .25s;
|
||||
&:hover {
|
||||
background: $primary-light;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
fill: $white;
|
||||
height: 1.25rem;
|
||||
margin-bottom: -.2rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
}
|
||||
button {
|
||||
margin-right: .2rem;
|
||||
&:hover,
|
||||
&.is-active {
|
||||
svg {
|
||||
fill: $primary-light;
|
||||
}
|
||||
}
|
||||
svg {
|
||||
fill: $gray-light;
|
||||
height: 1.6rem;
|
||||
margin-bottom: -.2rem;
|
||||
width: 1.6rem;
|
||||
}
|
||||
}
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.attachments-empty {
|
||||
@extend %large;
|
||||
@extend %bold;
|
||||
border: 3px dashed $whitish;
|
||||
color: $gray-light;
|
||||
margin-top: .5rem;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.single-attachment {
|
||||
@extend %small;
|
||||
&.ui-sortable-helper {
|
||||
background: lighten($primary, 60%);
|
||||
box-shadow: 1px 1px 10px rgba($black, .1);
|
||||
transition: background .2s ease-in;
|
||||
}
|
||||
&.sortable-placeholder {
|
||||
background: $whitish;
|
||||
height: 40px;
|
||||
}
|
||||
.attachment-name {
|
||||
@extend %bold;
|
||||
padding-right: 1rem;
|
||||
.icon {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
svg {
|
||||
height: 1.2rem;
|
||||
width: 1.2rem;
|
||||
}
|
||||
}
|
||||
.attachment-size {
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.more-attachments {
|
||||
@extend %small;
|
||||
border-bottom: 1px solid $gray-light;
|
||||
display: block;
|
||||
padding: 1rem 0 1rem 1rem;
|
||||
span {
|
||||
color: $gray-light;
|
||||
}
|
||||
.more-attachments-num {
|
||||
color: $primary;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
&:hover {
|
||||
background: lighten($primary, 60%);
|
||||
transition: background .2s ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-preview {
|
||||
img {
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
###
|
||||
# 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: attachment-link.directive.coffee
|
||||
###
|
||||
|
||||
AttachmentLinkDirective = ($parse, lightboxFactory) ->
|
||||
link = (scope, el, attrs) ->
|
||||
attachment = $parse(attrs.tgAttachmentLink)(scope)
|
||||
|
||||
el.on "click", (event) ->
|
||||
if taiga.isImage(attachment.getIn(['file', 'name']))
|
||||
event.preventDefault()
|
||||
|
||||
scope.$apply ->
|
||||
lightboxFactory.create('tg-lb-attachment-preview', {
|
||||
class: 'lightbox lightbox-block'
|
||||
}, {
|
||||
file: attachment.get('file')
|
||||
})
|
||||
|
||||
scope.$on "$destroy", -> el.off()
|
||||
return {
|
||||
link: link
|
||||
}
|
||||
|
||||
AttachmentLinkDirective.$inject = [
|
||||
"$parse",
|
||||
"tgLightboxFactory"
|
||||
]
|
||||
|
||||
angular.module("taigaComponents").directive("tgAttachmentLink", AttachmentLinkDirective)
|
|
@ -0,0 +1,39 @@
|
|||
###
|
||||
# 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: attachment-gallery.directive.coffee
|
||||
###
|
||||
|
||||
AttachmentGalleryDirective = () ->
|
||||
link = (scope, el, attrs, ctrl) ->
|
||||
|
||||
return {
|
||||
scope: {},
|
||||
bindToController: {
|
||||
attachment: "=",
|
||||
onDelete: "&",
|
||||
onUpdate: "&",
|
||||
type: "="
|
||||
},
|
||||
controller: "Attachment",
|
||||
controllerAs: "vm",
|
||||
templateUrl: "components/attachment/attachment-gallery.html",
|
||||
link: link
|
||||
}
|
||||
|
||||
AttachmentGalleryDirective.$inject = []
|
||||
|
||||
angular.module("taigaComponents").directive("tgAttachmentGallery", AttachmentGalleryDirective)
|
|
@ -0,0 +1,36 @@
|
|||
.single-attachment(
|
||||
ng-class="{deprecated: vm.attachment.getIn(['file', 'is_deprecated'])}",
|
||||
ng-if="vm.attachment.getIn(['file', 'id'])",
|
||||
)
|
||||
a.attachment-image(
|
||||
tg-attachment-link="vm.attachment"
|
||||
href="{{::vm.attachment.getIn(['file', 'url'])}}"
|
||||
title="{{::vm.attachment.getIn(['file', 'name'])}}"
|
||||
target="_blank"
|
||||
download="{{::vm.attachment.getIn(['file', 'name'])}}"
|
||||
)
|
||||
img(
|
||||
alt="{{::vm.attachment.getIn(['file', 'name'])}}"
|
||||
ng-src="{{::vm.attachment.getIn(['file', 'thumbnail_card_url'])}}"
|
||||
ng-if="vm.attachment.getIn(['file', 'thumbnail_card_url'])"
|
||||
)
|
||||
img(
|
||||
alt="{{::vm.attachment.getIn(['file', 'name'])}}"
|
||||
src="/#{v}/images/attachment-gallery.png"
|
||||
ng-if="!vm.attachment.getIn(['file', 'thumbnail_card_url'])"
|
||||
)
|
||||
.attachment-data
|
||||
a.attachment-name(
|
||||
tg-attachment-link="vm.attachment"
|
||||
href="{{::vm.attachment.getIn(['file', 'url'])}}"
|
||||
title="{{::vm.attachment.get(['file', 'name'])}}"
|
||||
target="_blank"
|
||||
download="{{::vm.attachment.getIn(['file', 'name'])}}"
|
||||
)
|
||||
span {{::vm.attachment.getIn(['file', 'name'])}}
|
||||
|
||||
a.icon-delete(
|
||||
href=""
|
||||
title="{{'COMMON.DELETE' | translate}}"
|
||||
ng-click="vm.delete()"
|
||||
)
|
|
@ -0,0 +1,59 @@
|
|||
###
|
||||
# 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: attchment.controller.coffee
|
||||
###
|
||||
|
||||
class AttachmentController
|
||||
@.$inject = [
|
||||
'tgAttachmentsService',
|
||||
'$translate'
|
||||
]
|
||||
|
||||
constructor: (@attachmentsService, @translate) ->
|
||||
@.form = {}
|
||||
@.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)
|
||||
|
||||
delete: () ->
|
||||
@.onDelete({attachment: @.attachment})
|
||||
|
||||
save: () ->
|
||||
@.attachment = @.attachment.set('loading', true)
|
||||
|
||||
attachment = @.attachment.merge({
|
||||
editable: false,
|
||||
loading: false
|
||||
})
|
||||
|
||||
attachment = attachment.mergeIn(['file'], {
|
||||
description: @.form.description,
|
||||
is_deprecated: !!@.form.is_deprecated
|
||||
})
|
||||
|
||||
@.onUpdate({attachment: attachment})
|
||||
|
||||
angular.module('taigaComponents').controller('Attachment', AttachmentController)
|
|
@ -0,0 +1,140 @@
|
|||
###
|
||||
# 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: attchment.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "AttachmentController", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
scope = null
|
||||
mocks = {}
|
||||
|
||||
_mockAttachmentsService = ->
|
||||
mocks.attachmentsService = {}
|
||||
|
||||
$provide.value("tgAttachmentsService", mocks.attachmentsService)
|
||||
|
||||
_mockTranslate = ->
|
||||
mocks.translate = {
|
||||
instant: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$translate", mocks.translate)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockAttachmentsService()
|
||||
_mockTranslate()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_, $rootScope) ->
|
||||
$controller = _$controller_
|
||||
scope = $rootScope.$new()
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaComponents"
|
||||
|
||||
_setup()
|
||||
|
||||
it "change edit mode", () ->
|
||||
attachment = Immutable.fromJS({
|
||||
file: {
|
||||
description: 'desc',
|
||||
is_deprecated: false
|
||||
}
|
||||
})
|
||||
|
||||
ctrl = $controller("Attachment", {
|
||||
$scope: scope
|
||||
}, {
|
||||
attachment : attachment
|
||||
})
|
||||
|
||||
ctrl.editable = false
|
||||
ctrl.editMode(true)
|
||||
|
||||
expect(ctrl.attachment.get('editable')).to.be.true
|
||||
|
||||
it "delete", () ->
|
||||
attachment = Immutable.fromJS({
|
||||
file: {
|
||||
description: 'desc',
|
||||
is_deprecated: false
|
||||
}
|
||||
})
|
||||
|
||||
ctrl = $controller("Attachment", {
|
||||
$scope: scope
|
||||
}, {
|
||||
attachment : attachment
|
||||
})
|
||||
|
||||
ctrl.onDelete = sinon.spy()
|
||||
|
||||
onDelete = sinon.match (value) ->
|
||||
return value.attachment == attachment
|
||||
, "onDelete"
|
||||
|
||||
ctrl.delete()
|
||||
|
||||
expect(ctrl.onDelete).to.be.calledWith(onDelete)
|
||||
|
||||
it "save", () ->
|
||||
attachment = Immutable.fromJS({
|
||||
file: {
|
||||
description: 'desc',
|
||||
is_deprecated: false
|
||||
},
|
||||
loading: false,
|
||||
editable: false
|
||||
})
|
||||
|
||||
ctrl = $controller("Attachment", {
|
||||
$scope: scope
|
||||
}, {
|
||||
attachment : attachment
|
||||
})
|
||||
|
||||
ctrl.onUpdate = sinon.spy()
|
||||
|
||||
onUpdate = sinon.match (value) ->
|
||||
value = value.attachment.toJS()
|
||||
|
||||
return (
|
||||
value.file.description == "ok" &&
|
||||
value.file.is_deprecated
|
||||
)
|
||||
, "onUpdate"
|
||||
|
||||
ctrl.form = {
|
||||
description: "ok"
|
||||
is_deprecated: true
|
||||
}
|
||||
|
||||
ctrl.save()
|
||||
|
||||
attachment = ctrl.attachment.toJS()
|
||||
|
||||
expect(ctrl.onUpdate).to.be.calledWith(onUpdate)
|
|
@ -0,0 +1,39 @@
|
|||
###
|
||||
# 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: attachment.directive.coffee
|
||||
###
|
||||
|
||||
AttachmentDirective = () ->
|
||||
link = (scope, el, attrs, ctrl) ->
|
||||
|
||||
return {
|
||||
scope: {},
|
||||
bindToController: {
|
||||
attachment: "=",
|
||||
onDelete: "&",
|
||||
onUpdate: "&",
|
||||
type: "="
|
||||
},
|
||||
controller: "Attachment",
|
||||
controllerAs: "vm",
|
||||
templateUrl: "components/attachment/attachment.html",
|
||||
link: link
|
||||
}
|
||||
|
||||
AttachmentDirective.$inject = []
|
||||
|
||||
angular.module("taigaComponents").directive("tgAttachment", AttachmentDirective)
|
|
@ -0,0 +1,80 @@
|
|||
form.single-attachment(
|
||||
ng-class="{deprecated: vm.attachment.getIn(['file', 'is_deprecated'])}",
|
||||
ng-if="vm.attachment.getIn(['file', 'id'])",
|
||||
ng-submit="vm.save()"
|
||||
)
|
||||
|
||||
.attachment-name
|
||||
a(
|
||||
tg-attachment-link="vm.attachment"
|
||||
href="{{::vm.attachment.getIn(['file', 'url'])}}"
|
||||
title="{{::vm.attachment.get(['file', 'name'])}}"
|
||||
target="_blank"
|
||||
download="{{::vm.attachment.getIn(['file', 'name'])}}"
|
||||
)
|
||||
span.icon
|
||||
include ../../../svg/attachment.svg
|
||||
span {{::vm.attachment.getIn(['file', 'name'])}}
|
||||
|
||||
.attachment-comments(ng-if="!vm.attachment.get('editable') && vm.attachment.getIn(['file', 'description'])")
|
||||
span.deprecated-file(ng-if="vm.attachment.getIn(['file', 'is_deprecated'])") {{'ATTACHMENT.DEPRECATED' | translate}}
|
||||
span {{vm.attachment.getIn(['file', 'description'])}}
|
||||
|
||||
.attachment-size
|
||||
span {{::vm.attachment.getIn(['file', 'size']) | sizeFormat}}
|
||||
|
||||
.editable.editable-attachment-comment(ng-if="vm.attachment.get('editable')")
|
||||
input(
|
||||
type="text",
|
||||
name="description",
|
||||
maxlength="140",
|
||||
ng-model="vm.form.description",
|
||||
tg-auto-select,
|
||||
ng-keydown="$event.which === 27 && vm.editMode(false)"
|
||||
placeholder="{{'ATTACHMENT.DESCRIPTION' | translate}}"
|
||||
)
|
||||
|
||||
.editable.editable-attachment-deprecated(ng-if="vm.attachment.get('editable')")
|
||||
input(
|
||||
type="checkbox"
|
||||
ng-model="vm.form.is_deprecated"
|
||||
name="is-deprecated"
|
||||
id="attach-{{::vm.attachment.getIn(['file', 'id'])}}-is-deprecated"
|
||||
)
|
||||
label.is_deprecated(
|
||||
for="attach-{{::vm.attachment.getIn(['file', 'id'])}}-is-deprecated"
|
||||
translate="{{'ATTACHMENT.DEPRECATED_FILE' | translate}}")
|
||||
|
||||
.attachment-settings(ng-if="vm.attachment.get('editable')")
|
||||
div(tg-loading="vm.attachment.get('loading')")
|
||||
a.editable-settings.icon.icon-floppy(
|
||||
href=""
|
||||
title="{{'COMMON.SAVE' | translate}}"
|
||||
ng-click="vm.save()"
|
||||
)
|
||||
|
||||
div
|
||||
a.editable-settings.icon.icon-delete(
|
||||
href=""
|
||||
title="{{'COMMON.CANCEL' | translate}}"
|
||||
ng-click="vm.editMode(false)"
|
||||
)
|
||||
|
||||
.attachment-settings(
|
||||
ng-if="!vm.attachment.get('editable')"
|
||||
tg-check-permission="modify_{{vm.type}}"
|
||||
)
|
||||
a.settings.icon.icon-edit(
|
||||
href=""
|
||||
title="{{'COMMON.EDIT' | translate}}"
|
||||
ng-click="vm.editMode(true)"
|
||||
)
|
||||
a.settings.icon.icon-delete(
|
||||
href=""
|
||||
title="{{'COMMON.DELETE' | translate}}"
|
||||
ng-click="vm.delete()"
|
||||
)
|
||||
a.settings.icon.icon-drag-v(
|
||||
href=""
|
||||
title="{{'COMMON.DRAG' | translate}}"
|
||||
)
|
|
@ -0,0 +1,46 @@
|
|||
###
|
||||
# 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: attachments-drop.directive.coffee
|
||||
###
|
||||
|
||||
AttachmentsDropDirective = ($parse) ->
|
||||
link = (scope, el, attrs) ->
|
||||
eventAttr = $parse(attrs.tgAttachmentsDrop)
|
||||
|
||||
el.on 'dragover', (e) ->
|
||||
e.preventDefault()
|
||||
return false
|
||||
|
||||
el.on 'drop', (e) ->
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
|
||||
dataTransfer = e.dataTransfer || (e.originalEvent && e.originalEvent.dataTransfer);
|
||||
|
||||
scope.$apply () -> eventAttr(scope, {files: dataTransfer.files})
|
||||
|
||||
scope.$on "$destroy", -> el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
}
|
||||
|
||||
AttachmentsDropDirective.$inject = [
|
||||
"$parse"
|
||||
]
|
||||
|
||||
angular.module("taigaComponents").directive("tgAttachmentsDrop", AttachmentsDropDirective)
|
|
@ -0,0 +1,150 @@
|
|||
###
|
||||
# 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.controller.coffee
|
||||
###
|
||||
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
|
||||
class AttachmentsFullController
|
||||
@.$inject = [
|
||||
"tgAttachmentsService",
|
||||
"$rootScope",
|
||||
"$translate",
|
||||
"$tgConfirm",
|
||||
"$tgConfig",
|
||||
"$tgStorage"
|
||||
]
|
||||
|
||||
constructor: (@attachmentsService, @rootScope, @translate, @confirm, @config, @storage) ->
|
||||
@.deprecatedsVisible = false
|
||||
@.uploadingAttachments = []
|
||||
|
||||
@.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()
|
||||
|
||||
return attachment.merge({
|
||||
loading: false,
|
||||
editable: false,
|
||||
file: file
|
||||
})
|
||||
|
||||
@.generate()
|
||||
|
||||
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()
|
||||
|
||||
addAttachment: (file) ->
|
||||
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: true,
|
||||
loading: false
|
||||
})
|
||||
|
||||
@.attachments = @.attachments.push(attachment)
|
||||
@.generate()
|
||||
@rootScope.$broadcast("attachment:create")
|
||||
resolve(@.attachments)
|
||||
else
|
||||
reject(file)
|
||||
|
||||
addAttachments: (files) ->
|
||||
_.forEach files, @.addAttachment.bind(this)
|
||||
|
||||
deleteAttachment: (toDeleteAttachment) ->
|
||||
title = @translate.instant("ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT")
|
||||
message = @translate.instant("ATTACHMENT.MSG_LIGHTBOX_DELETE_ATTACHMENT", {
|
||||
fileName: toDeleteAttachment.getIn(['file', 'name'])
|
||||
})
|
||||
|
||||
return @confirm.askOnDelete(title, message)
|
||||
.then (askResponse) =>
|
||||
onError = () =>
|
||||
message = @translate.instant("ATTACHMENT.ERROR_DELETE_ATTACHMENT", {errorMessage: message})
|
||||
@confirm.notify("error", null, message)
|
||||
askResponse.finish(false)
|
||||
|
||||
onSuccess = () =>
|
||||
@.attachments = @.attachments.filter (attachment) -> attachment != toDeleteAttachment
|
||||
@.generate()
|
||||
|
||||
askResponse.finish()
|
||||
|
||||
return @attachmentsService.delete(@.type, toDeleteAttachment.getIn(['file', 'id'])).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()
|
||||
|
||||
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()
|
||||
|
||||
angular.module("taigaComponents").controller("AttachmentsFull", AttachmentsFullController)
|
|
@ -0,0 +1,328 @@
|
|||
###
|
||||
# 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.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "AttachmentsController", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockAttachmentsService = ->
|
||||
mocks.attachmentsService = {
|
||||
upload: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgAttachmentsService", mocks.attachmentsService)
|
||||
|
||||
_mockConfirm = ->
|
||||
mocks.confirm = {}
|
||||
|
||||
$provide.value("$tgConfirm", mocks.confirm)
|
||||
|
||||
_mockTranslate = ->
|
||||
mocks.translate = {
|
||||
instant: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$translate", mocks.translate)
|
||||
|
||||
_mockConfig = ->
|
||||
mocks.config = {
|
||||
get: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgConfig", mocks.config)
|
||||
|
||||
_mockStorage = ->
|
||||
mocks.storage = {
|
||||
get: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgStorage", mocks.storage)
|
||||
|
||||
_mockRootScope = ->
|
||||
mocks.rootScope = {}
|
||||
|
||||
$provide.value("$rootScope", mocks.rootScope)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockAttachmentsService()
|
||||
_mockConfirm()
|
||||
_mockTranslate()
|
||||
_mockConfig()
|
||||
_mockStorage()
|
||||
_mockRootScope()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_) ->
|
||||
$controller = _$controller_
|
||||
|
||||
_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
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.attachments = attachments
|
||||
|
||||
ctrl.generate()
|
||||
|
||||
expect(ctrl.deprecatedsCount).to.be.equal(3)
|
||||
|
||||
it "toggle deprecated visibility", () ->
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.deprecatedsVisible = false
|
||||
|
||||
ctrl.generate = sinon.spy()
|
||||
|
||||
ctrl.toggleDeprecatedsVisible()
|
||||
|
||||
expect(ctrl.deprecatedsVisible).to.be.true
|
||||
expect(ctrl.generate).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()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.attachments = Immutable.List()
|
||||
|
||||
ctrl.addAttachment(file).then null, () ->
|
||||
expect(ctrl.attachments.count()).to.be.equal(0)
|
||||
|
||||
it "add attachments", () ->
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.attachments = Immutable.List()
|
||||
ctrl.addAttachment = sinon.spy()
|
||||
|
||||
files = [
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
|
||||
ctrl.addAttachments(files)
|
||||
|
||||
expect(ctrl.addAttachment).to.have.callCount(3)
|
||||
|
||||
describe "deleteattachments", () ->
|
||||
it "success attachment", (done) ->
|
||||
askResponse = {
|
||||
finish: sinon.spy()
|
||||
}
|
||||
|
||||
mocks.translate.instant.withArgs('ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT').returns('title')
|
||||
mocks.translate.instant.withArgs('ATTACHMENT.MSG_LIGHTBOX_DELETE_ATTACHMENT').returns('message')
|
||||
|
||||
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) ->
|
||||
askResponse = {
|
||||
finish: sinon.spy()
|
||||
}
|
||||
|
||||
mocks.translate.instant.withArgs('ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT').returns('title')
|
||||
mocks.translate.instant.withArgs('ATTACHMENT.MSG_LIGHTBOX_DELETE_ATTACHMENT').returns('message')
|
||||
mocks.translate.instant.withArgs('ATTACHMENT.ERROR_DELETE_ATTACHMENT').returns('error')
|
||||
|
||||
mocks.confirm.askOnDelete = sinon.stub()
|
||||
mocks.confirm.askOnDelete.withArgs('title', 'message').promise().resolve(askResponse)
|
||||
|
||||
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()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
ctrl = $controller("AttachmentsFull")
|
||||
|
||||
ctrl.type = 'us'
|
||||
ctrl.attachments = attachments
|
||||
|
||||
ctrl.updateAttachment(attachment).then () ->
|
||||
expect(ctrl.attachments.get(1).toJS()).to.be.eql(attachment.toJS())
|
|
@ -0,0 +1,42 @@
|
|||
###
|
||||
# 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.directive.coffee
|
||||
###
|
||||
|
||||
bindOnce = @.taiga.bindOnce
|
||||
|
||||
AttachmentsFullDirective = () ->
|
||||
link = (scope, el, attrs, ctrl) ->
|
||||
bindOnce scope, 'vm.objId', (value) ->
|
||||
ctrl.loadAttachments()
|
||||
|
||||
return {
|
||||
scope: {},
|
||||
bindToController: {
|
||||
type: "@",
|
||||
objId: "="
|
||||
projectId: "="
|
||||
},
|
||||
controller: "AttachmentsFull",
|
||||
controllerAs: "vm",
|
||||
templateUrl: "components/attachments-full/attachments-full.html",
|
||||
link: link
|
||||
}
|
||||
|
||||
AttachmentsFullDirective.$inject = []
|
||||
|
||||
angular.module("taigaComponents").directive("tgAttachmentsFull", AttachmentsFullDirective)
|
|
@ -0,0 +1,97 @@
|
|||
section.attachments(tg-attachments-drop="vm.addAttachments(files)")
|
||||
.attachments-header
|
||||
h3.attachments-title #[span.attachments-num {{vm.attachments.size}}] #[span.attachments-text(translate="ATTACHMENT.SECTION_NAME")]
|
||||
.options
|
||||
button.view-gallery(
|
||||
ng-class="{'is-active': vm.mode == 'gallery'}"
|
||||
ng-if="vm.attachments.size",
|
||||
ng-click="vm.setMode('gallery')"
|
||||
)
|
||||
include ../../../svg/gallery.svg
|
||||
button.view-list(
|
||||
ng-class="{'is-active': vm.mode == 'list'}"
|
||||
ng-if="vm.attachments.size",
|
||||
ng-click="vm.setMode('list')"
|
||||
)
|
||||
include ../../../svg/list.svg
|
||||
.add-attach(
|
||||
tg-check-permission="modify_{{vm.type}}"
|
||||
title!="{{'ATTACHMENT.ADD' | translate}}"
|
||||
)
|
||||
span.size-info(
|
||||
ng-if="vm.maxFileSize",
|
||||
translate="ATTACHMENT.MAX_FILE_SIZE",
|
||||
translate-values="{ 'maxFileSize': vm.maxFileSize}"
|
||||
)
|
||||
|
||||
label.add-attachment-button(for="add-attach")
|
||||
include ../../../svg/add.svg
|
||||
|
||||
input(
|
||||
id="add-attach",
|
||||
type="file",
|
||||
ng-model="files",
|
||||
multiple="multiple",
|
||||
tg-file-change="vm.addAttachments(files)"
|
||||
)
|
||||
.attachments-empty(ng-if="!vm.attachments.size")
|
||||
div {{'ATTACHMENT.DROP' | translate}}
|
||||
.attachment-list.sortable(ng-if="vm.mode == 'list'")
|
||||
div(tg-attachments-sortable="vm.reorderAttachment(attachment, index)")
|
||||
div(
|
||||
tg-repeat="attachment in vm.attachmentsVisible track by attachment.getIn(['file', 'id'])",
|
||||
tg-bind-scope
|
||||
)
|
||||
tg-attachment(
|
||||
attachment="attachment",
|
||||
on-delete="vm.deleteAttachment(attachment)",
|
||||
on-update="vm.updateAttachment(attachment)",
|
||||
type="vm.type"
|
||||
)
|
||||
|
||||
.single-attachment(ng-repeat="file in vm.uploadingAttachments")
|
||||
.attachment-name
|
||||
span.icon
|
||||
include ../../../svg/attachment.svg
|
||||
span {{attachment.get('name')}}
|
||||
.attachment-size
|
||||
span {{attachment.get('size') | sizeFormat}}
|
||||
|
||||
.attachment-comments
|
||||
span {{file.progressMessage}}
|
||||
.percentage(ng-style="{'width': file.progressPercent}")
|
||||
|
||||
a.more-attachments(
|
||||
href="",
|
||||
title="{{'ATTACHMENT.SHOW_DEPRECATED' | translate}}",
|
||||
ng-if="vm.deprecatedsCount > 0",
|
||||
ng-click="vm.toggleDeprecatedsVisible()"
|
||||
)
|
||||
span.text(
|
||||
ng-show="!vm.deprecatedsVisible",
|
||||
translate="ATTACHMENT.SHOW_DEPRECATED"
|
||||
)
|
||||
span.text(
|
||||
ng-show="vm.deprecatedsVisible",
|
||||
translate="ATTACHMENT.HIDE_DEPRECATED"
|
||||
)
|
||||
span.more-attachments-num(
|
||||
translate="ATTACHMENT.COUNT_DEPRECATED",
|
||||
translate-values="{counter: '{{vm.deprecatedsCount}}'}"
|
||||
)
|
||||
|
||||
.attachment-gallery(ng-if="vm.mode == 'gallery'")
|
||||
tg-attachment-gallery.attachment-gallery-container(
|
||||
tg-repeat="attachment in vm.attachmentsVisible track by attachment.getIn(['file', 'id'])"
|
||||
attachment="attachment",
|
||||
on-delete="vm.deleteAttachment(attachment)",
|
||||
on-update="vm.updateAttachment(attachment)",
|
||||
type="vm.type"
|
||||
)
|
||||
.single-attachment(ng-repeat="file in vm.uploadingAttachments")
|
||||
.loading-container
|
||||
img.loading-spinner(
|
||||
src="/#{v}/svg/spinner-circle.svg",
|
||||
alt="{{'COMMON.LOADING' | translate}}"
|
||||
)
|
||||
.attachment-data {{file.progressMessage}}
|
|
@ -0,0 +1,47 @@
|
|||
###
|
||||
# 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-simple.controller.coffee
|
||||
###
|
||||
|
||||
class AttachmentsSimpleController
|
||||
@.$inject = [
|
||||
"tgAttachmentsService"
|
||||
]
|
||||
|
||||
constructor: (@attachmentsService) ->
|
||||
|
||||
addAttachment: (file) ->
|
||||
attachment = Immutable.fromJS({
|
||||
file: file,
|
||||
name: file.name,
|
||||
size: file.size
|
||||
})
|
||||
|
||||
if @attachmentsService.validate(file)
|
||||
@.attachments = @.attachments.push(attachment)
|
||||
|
||||
@.onAdd({attachment: attachment}) if @.onAdd
|
||||
|
||||
addAttachments: (files) ->
|
||||
_.forEach files, @.addAttachment.bind(this)
|
||||
|
||||
deleteAttachment: (toDeleteAttachment) ->
|
||||
@.attachments = @.attachments.filter (attachment) -> attachment != toDeleteAttachment
|
||||
|
||||
@.onDelete({attachment: toDeleteAttachment}) if @.onDelete
|
||||
|
||||
angular.module("taigaComponents").controller("AttachmentsSimple", AttachmentsSimpleController)
|
|
@ -0,0 +1,96 @@
|
|||
###
|
||||
# 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: attchment.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "AttachmentsSimple", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
mocks = {}
|
||||
scope = null
|
||||
|
||||
_mockAttachmentsService = ->
|
||||
mocks.attachmentsService = {}
|
||||
|
||||
$provide.value("tgAttachmentsService", mocks.attachmentsService)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockAttachmentsService()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_, $rootScope) ->
|
||||
$controller = _$controller_
|
||||
scope = $rootScope.$new()
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaComponents"
|
||||
|
||||
_setup()
|
||||
|
||||
it "add attachment", () ->
|
||||
file = {
|
||||
name: 'name',
|
||||
size: 1000
|
||||
}
|
||||
|
||||
mocks.attachmentsService.validate = sinon.stub()
|
||||
mocks.attachmentsService.validate.withArgs(file).returns(true)
|
||||
|
||||
ctrl = $controller("AttachmentsSimple", {
|
||||
$scope: scope
|
||||
}, {
|
||||
attachments: Immutable.List()
|
||||
})
|
||||
|
||||
ctrl.onAdd = sinon.spy()
|
||||
|
||||
ctrl.addAttachment(file)
|
||||
|
||||
expect(ctrl.attachments.size).to.be.equal(1)
|
||||
expect(ctrl.onAdd).to.have.been.calledOnce
|
||||
|
||||
it "delete attachment", () ->
|
||||
attachments = Immutable.fromJS([
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
])
|
||||
|
||||
ctrl = $controller("AttachmentsSimple", {
|
||||
$scope: scope
|
||||
}, {
|
||||
attachments: attachments
|
||||
})
|
||||
|
||||
ctrl.onDelete = sinon.spy()
|
||||
|
||||
|
||||
attachment = attachments.get(1)
|
||||
|
||||
ctrl.deleteAttachment(attachment)
|
||||
|
||||
expect(ctrl.attachments.size).to.be.equal(2)
|
||||
expect(ctrl.onDelete.withArgs({attachment: attachment})).to.have.been.calledOnce
|
|
@ -0,0 +1,38 @@
|
|||
###
|
||||
# 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-simple.directive.coffee
|
||||
###
|
||||
|
||||
AttachmentsSimpleDirective = () ->
|
||||
link = (scope, el, attrs, ctrl) ->
|
||||
|
||||
return {
|
||||
scope: {},
|
||||
bindToController: {
|
||||
attachments: "=",
|
||||
onAdd: "&",
|
||||
onDelete: "&"
|
||||
},
|
||||
controller: "AttachmentsSimple",
|
||||
controllerAs: "vm",
|
||||
templateUrl: "components/attachments-simple/attachments-simple.html",
|
||||
link: link
|
||||
}
|
||||
|
||||
AttachmentsSimpleDirective.$inject = []
|
||||
|
||||
angular.module("taigaComponents").directive("tgAttachmentsSimple", AttachmentsSimpleDirective)
|
|
@ -0,0 +1,38 @@
|
|||
//- section.attachments(tg-attachments-drop="vm.addAttachments(files)")
|
||||
|
||||
section.attachments(tg-attachments-drop="vm.addAttachments(files)")
|
||||
.attachments-header
|
||||
h3.attachments-title #[span.attachments-num {{vm.attachments.size}}] #[span.attachments-text(translate="ATTACHMENT.SECTION_NAME")]
|
||||
.add-attach(title!="{{'ATTACHMENT.ADD' | translate}}")
|
||||
span.size-info(
|
||||
translate="ATTACHMENT.MAX_FILE_SIZE"
|
||||
translate-values="{ 'maxFileSize': vm.maxFileSizeFormated}"
|
||||
ng-if="vm.maxFileSize"
|
||||
)
|
||||
label.add-attachment-button(for="add-attach")
|
||||
include ../../../svg/add.svg
|
||||
input(
|
||||
id="add-attach"
|
||||
type="file"
|
||||
multiple="multiple"
|
||||
ng-model="files"
|
||||
tg-file-change="vm.addAttachments(files)"
|
||||
)
|
||||
.attachments-empty(ng-if="!vm.attachments.size")
|
||||
div {{'ATTACHMENT.DROP' | translate}}
|
||||
.attachment-body.attachment-list
|
||||
.single-attachment(tg-repeat="attachment in vm.attachments track by $index")
|
||||
.attachment-name
|
||||
span.icon
|
||||
include ../../../svg/attachment.svg
|
||||
span {{attachment.get('name')}}
|
||||
.attachment-size
|
||||
span {{attachment.get('size') | sizeFormat}}
|
||||
|
||||
.attachment-settings
|
||||
a.settings.attachment-delete(
|
||||
href="#"
|
||||
title="{{'COMMON.DELETE' | translate}}"
|
||||
ng-click="vm.deleteAttachment(attachment)"
|
||||
)
|
||||
include ../../../svg/remove.svg
|
|
@ -0,0 +1,52 @@
|
|||
###
|
||||
# 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-sortable.directive.coffee
|
||||
###
|
||||
|
||||
AttachmentSortableDirective = ($parse) ->
|
||||
link = (scope, el, attrs) ->
|
||||
callback = $parse(attrs.tgAttachmentsSortable)
|
||||
|
||||
el.sortable({
|
||||
items: "div[tg-bind-scope]"
|
||||
handle: "a.settings.icon.icon-drag-v"
|
||||
containment: ".attachments"
|
||||
dropOnEmpty: true
|
||||
helper: 'clone'
|
||||
scroll: false
|
||||
tolerance: "pointer"
|
||||
placeholder: "sortable-placeholder single-attachment"
|
||||
})
|
||||
|
||||
el.on "sortstop", (event, ui) ->
|
||||
attachment = ui.item.scope().attachment
|
||||
newIndex = ui.item.index()
|
||||
|
||||
scope.$apply () ->
|
||||
callback(scope, {attachment: attachment, index: newIndex})
|
||||
|
||||
scope.$on "$destroy", -> el.off()
|
||||
|
||||
return {
|
||||
link: link
|
||||
}
|
||||
|
||||
AttachmentSortableDirective.$inject = [
|
||||
"$parse"
|
||||
]
|
||||
|
||||
angular.module("taigaComponents").directive("tgAttachmentsSortable", AttachmentSortableDirective)
|
|
@ -0,0 +1,30 @@
|
|||
###
|
||||
# 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: auto-select.directive.coffee
|
||||
###
|
||||
|
||||
AutoSelectDirective = ($timeout) ->
|
||||
return {
|
||||
link: (scope, elm) ->
|
||||
$timeout () -> elm[0].select()
|
||||
}
|
||||
|
||||
AutoSelectDirective.$inject = [
|
||||
'$timeout'
|
||||
]
|
||||
|
||||
angular.module("taigaComponents").directive("tgAutoSelect", AutoSelectDirective)
|
|
@ -0,0 +1,39 @@
|
|||
###
|
||||
# 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: file-change.directive.coffee
|
||||
###
|
||||
|
||||
FileChangeDirective = ($parse) ->
|
||||
link = (scope, el, attrs, ctrl) ->
|
||||
eventAttr = $parse(attrs.tgFileChange)
|
||||
|
||||
el.on 'change', (event) ->
|
||||
scope.$apply () -> eventAttr(scope, {files: event.currentTarget.files})
|
||||
|
||||
scope.$on "$destroy", -> el.off()
|
||||
|
||||
return {
|
||||
require: "ngModel",
|
||||
restrict: "A",
|
||||
link: link
|
||||
}
|
||||
|
||||
FileChangeDirective.$inject = [
|
||||
"$parse"
|
||||
]
|
||||
|
||||
angular.module("taigaComponents").directive("tgFileChange", FileChangeDirective)
|
|
@ -5,6 +5,7 @@
|
|||
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Xavi Julian <xavier.julian@kaleidos.net>
|
||||
# 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
|
||||
|
@ -19,29 +20,57 @@
|
|||
# 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: modules/resources/attachments.coffee
|
||||
# File: attachments-resource.service.coffee
|
||||
###
|
||||
|
||||
|
||||
taiga = @.taiga
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
|
||||
|
||||
resourceProvider = ($rootScope, $config, $urls, $model, $repo, $auth, $q) ->
|
||||
Resource = (urlsService, http, config, $rootScope, $q, storage) ->
|
||||
service = {}
|
||||
|
||||
service.list = (urlName, objectId, projectId) ->
|
||||
params = {object_id: objectId, project: projectId}
|
||||
return $repo.queryMany(urlName, params)
|
||||
service.list = (type, objectId, projectId) ->
|
||||
urlname = "attachments/#{type}"
|
||||
|
||||
params = {object_id: objectId, project: projectId}
|
||||
httpOptions = {
|
||||
headers: {
|
||||
"x-disable-pagination": "1"
|
||||
}
|
||||
}
|
||||
|
||||
url = urlsService.resolve(urlname)
|
||||
|
||||
return http.get(url, params, httpOptions)
|
||||
.then (result) -> Immutable.fromJS(result.data)
|
||||
|
||||
service.delete = (type, id) ->
|
||||
urlname = "attachments/#{type}"
|
||||
|
||||
url = urlsService.resolve(urlname) + "/#{id}"
|
||||
|
||||
return http.delete(url)
|
||||
|
||||
service.patch = (type, id, patch) ->
|
||||
urlname = "attachments/#{type}"
|
||||
|
||||
url = urlsService.resolve(urlname) + "/#{id}"
|
||||
|
||||
return http.patch(url, patch)
|
||||
|
||||
service.create = (type, projectId, objectId, file) ->
|
||||
urlname = "attachments/#{type}"
|
||||
|
||||
url = urlsService.resolve(urlname)
|
||||
|
||||
service.create = (urlName, projectId, objectId, file) ->
|
||||
defered = $q.defer()
|
||||
|
||||
if file is undefined
|
||||
defered.reject(null)
|
||||
return defered.promise
|
||||
|
||||
maxFileSize = $config.get("maxUploadFileSize", null)
|
||||
maxFileSize = config.get("maxUploadFileSize", null)
|
||||
|
||||
if maxFileSize and file.size > maxFileSize
|
||||
response = {
|
||||
status: 413,
|
||||
|
@ -64,13 +93,13 @@ resourceProvider = ($rootScope, $config, $urls, $model, $repo, $auth, $q) ->
|
|||
|
||||
status = evt.target.status
|
||||
try
|
||||
data = JSON.parse(evt.target.responseText)
|
||||
attachment = JSON.parse(evt.target.responseText)
|
||||
catch
|
||||
data = {}
|
||||
attachment = {}
|
||||
|
||||
if status >= 200 and status < 400
|
||||
model = $model.make_model(urlName, data)
|
||||
defered.resolve(model)
|
||||
attachment = Immutable.fromJS(attachment)
|
||||
defered.resolve(attachment)
|
||||
else
|
||||
response = {
|
||||
status: status,
|
||||
|
@ -93,17 +122,26 @@ resourceProvider = ($rootScope, $config, $urls, $model, $repo, $auth, $q) ->
|
|||
xhr.addEventListener("load", uploadComplete, false)
|
||||
xhr.addEventListener("error", uploadFailed, false)
|
||||
|
||||
xhr.open("POST", $urls.resolve(urlName))
|
||||
xhr.setRequestHeader("Authorization", "Bearer #{$auth.getToken()}")
|
||||
token = storage.get('token')
|
||||
|
||||
xhr.open("POST", url)
|
||||
xhr.setRequestHeader("Authorization", "Bearer #{token}")
|
||||
xhr.setRequestHeader('Accept', 'application/json')
|
||||
xhr.send(data)
|
||||
|
||||
return defered.promise
|
||||
|
||||
return (instance) ->
|
||||
instance.attachments = service
|
||||
return () ->
|
||||
return {"attachments": service}
|
||||
|
||||
Resource.$inject = [
|
||||
"$tgUrls",
|
||||
"$tgHttp",
|
||||
"$tgConfig",
|
||||
"$rootScope",
|
||||
"$q",
|
||||
"$tgStorage"
|
||||
]
|
||||
|
||||
module = angular.module("taigaResources")
|
||||
module.factory("$tgAttachmentsResourcesProvider", ["$rootScope", "$tgConfig", "$tgUrls", "$tgModel", "$tgRepo",
|
||||
"$tgAuth", "$q", resourceProvider])
|
||||
module = angular.module("taigaResources2")
|
||||
module.factory("tgAttachmentsResource", Resource)
|
|
@ -24,7 +24,8 @@ services = [
|
|||
"tgUserstoriesResource",
|
||||
"tgTasksResource",
|
||||
"tgIssuesResource",
|
||||
"tgExternalAppsResource"
|
||||
"tgExternalAppsResource",
|
||||
"tgAttachmentsResource"
|
||||
]
|
||||
|
||||
Resources = ($injector) ->
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
###
|
||||
# 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: attachments.service.coffee
|
||||
###
|
||||
|
||||
sizeFormat = @.taiga.sizeFormat
|
||||
|
||||
class AttachmentsService
|
||||
@.$inject = [
|
||||
"$tgConfirm",
|
||||
"$tgConfig",
|
||||
"$translate",
|
||||
"tgResources"
|
||||
]
|
||||
|
||||
constructor: (@confirm, @config, @translate, @rs) ->
|
||||
@.maxFileSize = @.getMaxFileSize()
|
||||
|
||||
if @.maxFileSize
|
||||
@.maxFileSizeFormated = sizeFormat(@.maxFileSize)
|
||||
|
||||
sizeError: (file) ->
|
||||
message = @translate.instant("ATTACHMENT.ERROR_MAX_SIZE_EXCEEDED", {
|
||||
fileName: file.name,
|
||||
fileSize: sizeFormat(file.size),
|
||||
maxFileSize: @.maxFileSizeFormated
|
||||
})
|
||||
|
||||
@confirm.notify("error", message)
|
||||
|
||||
validate: (file) ->
|
||||
if @.maxFileSize && file.size > @.maxFileSize
|
||||
@.sizeError(file)
|
||||
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
getMaxFileSize: () ->
|
||||
return @config.get("maxUploadFileSize", null)
|
||||
|
||||
list: (type, objId, projectId) ->
|
||||
return @rs.attachments.list(type, objId, projectId).then (attachments) =>
|
||||
return attachments.sortBy (attachment) => attachment.get('order')
|
||||
|
||||
delete: (type, id) ->
|
||||
return @rs.attachments.delete(type, id)
|
||||
|
||||
saveError: (file, data) ->
|
||||
message = ""
|
||||
|
||||
if file
|
||||
message = @translate.instant("ATTACHMENT.ERROR_UPLOAD_ATTACHMENT", {
|
||||
fileName: file.name, errorMessage: data.data._error_message
|
||||
})
|
||||
|
||||
@confirm.notify("error", message)
|
||||
|
||||
upload: (file, objId, projectId, type) ->
|
||||
promise = @rs.attachments.create(type, projectId, objId, file)
|
||||
|
||||
promise.then null, @.saveError.bind(this, file)
|
||||
|
||||
return promise
|
||||
|
||||
patch: (id, type, patch) ->
|
||||
promise = @rs.attachments.patch(type, id, patch)
|
||||
|
||||
promise.then null, @.saveError.bind(this, null)
|
||||
|
||||
return promise
|
||||
|
||||
angular.module("taigaCommon").service("tgAttachmentsService", AttachmentsService)
|
|
@ -0,0 +1,178 @@
|
|||
###
|
||||
# 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: attachments.service.spec.coffee
|
||||
###
|
||||
|
||||
describe "tgAttachmentsService", ->
|
||||
attachmentsService = provide = null
|
||||
mocks = {}
|
||||
|
||||
_mockTgConfirm = () ->
|
||||
mocks.confirm = {
|
||||
notify: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "$tgConfirm", mocks.confirm
|
||||
|
||||
_mockTgConfig = () ->
|
||||
mocks.config = {
|
||||
get: sinon.stub()
|
||||
}
|
||||
|
||||
mocks.config.get.withArgs('maxUploadFileSize', null).returns(3000)
|
||||
|
||||
provide.value "$tgConfig", mocks.config
|
||||
|
||||
_mockRs = () ->
|
||||
mocks.rs = {}
|
||||
|
||||
provide.value "tgResources", mocks.rs
|
||||
|
||||
_mockTranslate = () ->
|
||||
mocks.translate = {
|
||||
instant: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "$translate", mocks.translate
|
||||
|
||||
|
||||
_inject = (callback) ->
|
||||
inject (_tgAttachmentsService_) ->
|
||||
attachmentsService = _tgAttachmentsService_
|
||||
callback() if callback
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockTgConfirm()
|
||||
_mockTgConfig()
|
||||
_mockRs()
|
||||
_mockTranslate()
|
||||
|
||||
return null
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaCommon"
|
||||
_setup()
|
||||
_inject()
|
||||
|
||||
it "maxFileSize formated", () ->
|
||||
expect(attachmentsService.maxFileSizeFormated).to.be.equal("2.9 KB")
|
||||
|
||||
it "sizeError, send notification", () ->
|
||||
file = {
|
||||
name: 'test',
|
||||
size: 3000
|
||||
}
|
||||
|
||||
mocks.translate.instant.withArgs('ATTACHMENT.ERROR_MAX_SIZE_EXCEEDED').returns('message')
|
||||
|
||||
attachmentsService.sizeError(file)
|
||||
|
||||
expect(mocks.confirm.notify).to.have.been.calledWith('error', 'message')
|
||||
|
||||
it "invalid, validate", () ->
|
||||
file = {
|
||||
name: 'test',
|
||||
size: 4000
|
||||
}
|
||||
|
||||
result = attachmentsService.validate(file)
|
||||
|
||||
expect(result).to.be.false
|
||||
|
||||
it "valid, validate", () ->
|
||||
file = {
|
||||
name: 'test',
|
||||
size: 1000
|
||||
}
|
||||
|
||||
result = attachmentsService.validate(file)
|
||||
|
||||
expect(result).to.be.true
|
||||
|
||||
it "get max file size", () ->
|
||||
result = attachmentsService.getMaxFileSize()
|
||||
|
||||
expect(result).to.be.equal(3000)
|
||||
|
||||
it "delete", () ->
|
||||
mocks.rs.attachments = {
|
||||
delete: sinon.stub()
|
||||
}
|
||||
|
||||
attachmentsService.delete('us', 2)
|
||||
|
||||
expect(mocks.rs.attachments.delete).to.have.been.calledWith('us', 2)
|
||||
|
||||
it "upload", (done) ->
|
||||
file = {
|
||||
id: 1
|
||||
}
|
||||
|
||||
objId = 2
|
||||
projectId = 2
|
||||
type = 'us'
|
||||
|
||||
mocks.rs.attachments = {
|
||||
create: sinon.stub().promise()
|
||||
}
|
||||
|
||||
mocks.rs.attachments.create.withArgs('us', type, objId, file).resolve()
|
||||
|
||||
attachmentsService.sizeError = sinon.spy()
|
||||
|
||||
attachmentsService.upload(file, objId, projectId, 'us').then () ->
|
||||
expect(mocks.rs.attachments.create).to.have.been.calledOnce
|
||||
done()
|
||||
|
||||
it "patch", (done) ->
|
||||
file = {
|
||||
id: 1
|
||||
}
|
||||
|
||||
objId = 2
|
||||
type = 'us'
|
||||
|
||||
patch = {
|
||||
id: 2
|
||||
}
|
||||
|
||||
mocks.rs.attachments = {
|
||||
patch: sinon.stub().promise()
|
||||
}
|
||||
|
||||
mocks.rs.attachments.patch.withArgs('us', objId, patch).resolve()
|
||||
|
||||
attachmentsService.sizeError = sinon.spy()
|
||||
|
||||
attachmentsService.patch(objId, 'us', patch).then () ->
|
||||
expect(mocks.rs.attachments.patch).to.have.been.calledOnce
|
||||
done()
|
||||
|
||||
it "error", () ->
|
||||
mocks.translate.instant.withArgs("ATTACHMENT.ERROR_MAX_SIZE_EXCEEDED").returns("msg")
|
||||
|
||||
attachmentsService.sizeError({
|
||||
name: 'name',
|
||||
size: 123
|
||||
})
|
||||
|
||||
expect(mocks.confirm.notify).to.have.been.calledWith('error', 'msg')
|
|
@ -21,9 +21,11 @@ class LightboxFactory
|
|||
@.$inject = ["$rootScope", "$compile"]
|
||||
constructor: (@rootScope, @compile) ->
|
||||
|
||||
create: (name, attrs) ->
|
||||
create: (name, attrs, scopeAttrs) ->
|
||||
scope = @rootScope.$new()
|
||||
|
||||
scope = _.merge(scope, scopeAttrs)
|
||||
|
||||
elm = $("<div>")
|
||||
.attr(name, true)
|
||||
.attr("tg-bind-scope", true)
|
||||
|
|
|
@ -67,6 +67,9 @@ class ProjectService
|
|||
@._section = null
|
||||
@._sectionsBreadcrumb = Immutable.List()
|
||||
|
||||
hasPermission: (permission) ->
|
||||
return @._project.get('my_permissions').indexOf(permission) != -1
|
||||
|
||||
fetchProject: () ->
|
||||
pslug = @.project.get('slug')
|
||||
|
||||
|
|
|
@ -140,3 +140,20 @@ describe "tgProjectService", ->
|
|||
expect(projectService.activeMembers.size).to.be.equal(0);
|
||||
expect(projectService.section).to.be.null;
|
||||
expect(projectService.sectionsBreadcrumb.size).to.be.equal(0);
|
||||
|
||||
it "has permissions", () ->
|
||||
project = Immutable.Map({
|
||||
id: 1,
|
||||
my_permissions: [
|
||||
'test1',
|
||||
'test2'
|
||||
]
|
||||
})
|
||||
|
||||
projectService._project = project
|
||||
|
||||
perm1 = projectService.hasPermission('test2')
|
||||
perm2 = projectService.hasPermission('test3')
|
||||
|
||||
expect(perm1).to.be.true
|
||||
expect(perm2).to.be.false
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
.attachment-name
|
||||
span.icon.icon-document
|
||||
a(href!="<%- url %>", title!="<%- title %>", target="_blank", download!="<%- name %>")
|
||||
| <%- name %>
|
||||
.attachment-size
|
||||
span <%- size %>
|
||||
|
||||
.editable.editable-attachment-comment
|
||||
input(type="text", name="description", maxlength="140",
|
||||
value!="<%- description %>", placeholder="{{'ATTACHMENT.DESCRIPTION' | translate}}")
|
||||
|
||||
.editable.editable-attachment-deprecated
|
||||
input(type="checkbox", name="is-deprecated",
|
||||
id!="attach-<%- id %>-is-deprecated")
|
||||
label(for!="attach-<%- id %>-is-deprecated", translate="{{'ATTACHMENT.DEPRECATED_FILE' | translate}}")
|
||||
|
||||
.attachment-settings
|
||||
a.editable-settings.icon.icon-floppy(href="", title="{{'COMMON.SAVE' | translate}}")
|
||||
a.editable-settings.icon.icon-delete(href="", title="{{'COMMON.CANCEL' | translate}}")
|
|
@ -1,19 +0,0 @@
|
|||
.attachment-name
|
||||
a(href!="<%- url %>", title!="<%- title %>", target="_blank", download!="<%- name %>")
|
||||
span.icon.icon-documents
|
||||
span <%- name %>
|
||||
.attachment-size
|
||||
span <%- size %>
|
||||
|
||||
.attachment-comments
|
||||
<% if (isDeprecated){ %>
|
||||
span.deprecated-file {{'ATTACHMENT.DEPRECATED' | translate}}
|
||||
<% } %>
|
||||
span <%- description %>
|
||||
|
||||
<% if (modifyPermission) {%>
|
||||
.attachment-settings
|
||||
a.settings.icon.icon-edit(href="", title="{{'COMMON.EDIT' | translate}}")
|
||||
a.settings.icon.icon-delete(href="", title="{{'COMMON.DELETE' | translate}}")
|
||||
a.settings.icon.icon-drag-v(href="", title="{{'COMMON.DRAG' | translate}}")
|
||||
<% } %>
|
|
@ -1,30 +0,0 @@
|
|||
section.attachments
|
||||
.attachments-header
|
||||
h3.attachments-title
|
||||
span.attachments-num(tg-bind-html="ctrl.attachmentsCount")
|
||||
span.attachments-text(translate="ATTACHMENT.SECTION_NAME")
|
||||
.add-attach(tg-check-permission!="modify_<%- type %>", title!="{{'ATTACHMENT.ADD' | translate}}")
|
||||
<% if (maxFileSize){ %>
|
||||
span.size-info.hidden(translate="ATTACHMENT.MAX_FILE_SIZE", translate-values!="{ 'maxFileSize': '<%- maxFileSize %>'}")
|
||||
<% }; %>
|
||||
label(for="add-attach", class="icon icon-plus related-tasks-buttons")
|
||||
input(id="add-attach", type="file", multiple="multiple")
|
||||
|
||||
.attachment-body.sortable
|
||||
.single-attachment(ng-repeat="attach in ctrl.attachments|filter:ctrl.filterAttachments track by attach.id" tg-attachment="attach", tg-bind-scope)
|
||||
|
||||
.single-attachment(ng-repeat="file in ctrl.uploadingAttachments")
|
||||
.attachment-name
|
||||
a(href="", tg-bo-title="file.name", tg-bo-bind="file.name")
|
||||
.attachment-size
|
||||
span.attachment-size(tg-bo-bind="file.size")
|
||||
.attachment-comments
|
||||
span(ng-bind="file.progressMessage")
|
||||
.percentage(ng-style="{'width': file.progressPercent}")
|
||||
|
||||
a.more-attachments(href="", title="{{'ATTACHMENT.SHOW_DEPRECATED' | translate}}", ng-if="ctrl.deprecatedAttachmentsCount > 0")
|
||||
span.text(data-type="show", translate="ATTACHMENT.SHOW_DEPRECATED")
|
||||
span.text.hidden(data-type="hide", translate="ATTACHMENT.HIDE_DEPRECATED")
|
||||
span.more-attachments-num(translate="ATTACHMENT.COUNT_DEPRECATED", translate-values="{counter: '{{ctrl.deprecatedAttachmentsCount}}'}")
|
||||
|
||||
div.lightbox.lightbox-block(tg-lb-attachment-preview)
|
|
@ -0,0 +1,4 @@
|
|||
a.add-button(
|
||||
href=""
|
||||
)
|
||||
include ../../../svg/add.svg
|
|
@ -1,11 +1,11 @@
|
|||
include wysiwyg.jade
|
||||
|
||||
.view-description
|
||||
section.us-content.wysiwyg(tg-bind-html="item.description_html || noDescriptionMsg")
|
||||
span.edit.icon.icon-edit
|
||||
|
||||
.edit-description
|
||||
textarea(ng-attr-placeholder="{{'COMMON.DESCRIPTION.EMPTY' | translate}}", ng-model="item.description", tg-markitup="tg-markitup")
|
||||
a.help-markdown(href="https://taiga.io/support/taiga-markdown-syntax/", target="_blank", title="{{'COMMON.WYSIWYG.MARKDOWN_HELP' | translate}}")
|
||||
span.icon.icon-help
|
||||
span(translate="COMMON.WYSIWYG.MARKDOWN_HELP")
|
||||
+wysihelp
|
||||
span.save-container
|
||||
a.save.icon.icon-floppy(href="", title="{{'COMMON.SAVE' | translate}}")
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
mixin wysihelp
|
||||
div.wysiwyg-help
|
||||
span.drag-drop-help Attach files by dragging & dropping on the textarea above.
|
||||
a.help-markdown(href="https://taiga.io/support/taiga-markdown-syntax/", target="_blank", title="{{'COMMON.WYSIWYG.MARKDOWN_HELP' | translate}}")
|
||||
span.icon.icon-help
|
||||
span(translate="COMMON.WYSIWYG.MARKDOWN_HELP")
|
|
@ -1,4 +1,6 @@
|
|||
section.history(tg-check-permission!="modify_<%- type %>")
|
||||
include ../components/wysiwyg.jade
|
||||
|
||||
section.history
|
||||
ul.history-tabs
|
||||
li
|
||||
a(href="#", class="active", data-section-class="history-comments")
|
||||
|
@ -10,13 +12,12 @@ section.history(tg-check-permission!="modify_<%- type %>")
|
|||
span.tab-title(translate="ACTIVITY.TITLE")
|
||||
section.history-comments
|
||||
.comments-list
|
||||
div(tg-toggle-comment, class="add-comment")
|
||||
textarea(ng-attr-placeholder="{{'COMMENTS.TYPE_NEW_COMMENT' | translate}}", ng-model!="<%- ngmodel %>.comment", tg-markitup="tg-markitup")
|
||||
<% if (mode !== "edit") { %>
|
||||
a(class="help-markdown", href="https://taiga.io/support/taiga-markdown-syntax/", target="_blank", title="{{'COMMON.WYSIWYG.MARKDOWN_HELP' | translate}}")
|
||||
span.icon.icon-help
|
||||
span(translate="COMMON.WYSIWYG.MARKDOWN_HELP")
|
||||
button(type="button", ng-disabled!="!<%- ngmodel %>.comment.length" title="{{'COMMENTS.COMMENT' | translate}}", translate="COMMENTS.COMMENT", class="button button-green save-comment")
|
||||
<% } %>
|
||||
div(tg-editable-wysiwyg, ng-model!="<%- ngmodel %>")
|
||||
div(tg-check-permission!="modify_<%- type %>", tg-toggle-comment, class="add-comment")
|
||||
textarea(ng-attr-placeholder="{{'COMMENTS.TYPE_NEW_COMMENT' | translate}}", ng-model!="<%- ngmodel %>.comment", tg-markitup="tg-markitup")
|
||||
<% if (mode !== "edit") { %>
|
||||
+wysihelp
|
||||
button(type="button", ng-disabled!="!<%- ngmodel %>.comment.length" title="{{'COMMENTS.COMMENT' | translate}}", translate="COMMENTS.COMMENT", class="button button-green save-comment")
|
||||
<% } %>
|
||||
section.history-activity.hidden
|
||||
.changes-list
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
a.close(href="", title="{{'COMMON.CLOSE' | translate}}")
|
||||
span.icon.icon-delete
|
||||
|
||||
a(href!="<%- url %>", title!="<%- title %>", target="_blank", download!="<%- name %>")
|
||||
img(src!="<%- url %>")
|
||||
a(href="{{::file.get('url')}}", title="{{::file.get('description')}}", target="_blank", download="{{::file.get('name')}}")
|
||||
img(src="{{::file.get('url')}}")
|
|
@ -1,4 +1,7 @@
|
|||
fieldset.blocked-note.hidden
|
||||
textarea(name="blocked_note",
|
||||
ng-attr-placeholder="{{'COMMON.BLOCKED_NOTE' | translate}}",
|
||||
ng-model!="<%- ngmodel %>")
|
||||
input(
|
||||
type="text"
|
||||
name="blocked_note"
|
||||
ng-attr-placeholder="{{'COMMON.BLOCKED_NOTE' | translate}}"
|
||||
ng-model!="<%- ngmodel %>"
|
||||
)
|
||||
|
|
|
@ -16,6 +16,14 @@ form
|
|||
fieldset
|
||||
div.tags-block(tg-lb-tag-line, ng-model="issue.tags")
|
||||
|
||||
fieldset
|
||||
section
|
||||
tg-attachments-simple(
|
||||
attachments="attachments",
|
||||
on-add="addAttachment(attachment)"
|
||||
on-delete="deleteAttachment(attachment)"
|
||||
)
|
||||
|
||||
fieldset
|
||||
textarea.description(ng-attr-placeholder="{{'COMMON.FIELDS.DESCRIPTION' | translate}}", ng-model="issue.description")
|
||||
|
||||
|
|
|
@ -18,6 +18,14 @@ form
|
|||
fieldset
|
||||
div.tags-block(tg-lb-tag-line, ng-model="task.tags")
|
||||
|
||||
fieldset
|
||||
section
|
||||
tg-attachments-simple(
|
||||
attachments="attachments",
|
||||
on-add="addAttachment(attachment)"
|
||||
on-delete="deleteAttachment(attachment)"
|
||||
)
|
||||
|
||||
fieldset
|
||||
textarea.description(ng-attr-placeholder="{{'LIGHTBOX.CREATE_EDIT_TASK.PLACEHOLDER_SHORT_DESCRIPTION' | translate}}", ng-model="task.description")
|
||||
|
||||
|
|
|
@ -18,6 +18,14 @@ form
|
|||
fieldset
|
||||
textarea.description(name="description", ng-model="us.description",
|
||||
ng-attr-placeholder="{{'LIGHTBOX.CREATE_EDIT_US.PLACEHOLDER_DESCRIPTION' | translate}}")
|
||||
fieldset
|
||||
section
|
||||
tg-attachments-simple(
|
||||
attachments="attachments",
|
||||
on-add="addAttachment(attachment)"
|
||||
on-delete="deleteAttachment(attachment)"
|
||||
)
|
||||
|
||||
div.settings
|
||||
fieldset.team-requirement
|
||||
input(type="checkbox", name="team_requirement", ng-model="us.team_requirement",
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
section.related-tasks(tg-related-tasks)
|
||||
div.related-tasks-header
|
||||
.related-tasks-header
|
||||
span.related-tasks-title(translate="COMMON.RELATED_TASKS")
|
||||
div(tg-related-task-create-button)
|
||||
div.related-tasks-body
|
||||
div.row.single-related-task(ng-repeat="task in tasks", ng-class="{closed: task.is_closed, blocked: task.is_blocked, iocaine: task.is_iocaine}",
|
||||
tg-related-task-row, ng-model="task")
|
||||
div.row.single-related-task.related-task-create-form(tg-related-task-create-form)
|
||||
.related-tasks-body
|
||||
.row.single-related-task(
|
||||
ng-repeat="task in tasks"
|
||||
ng-class="{closed: task.is_closed, blocked: task.is_blocked, iocaine: task.is_iocaine}"
|
||||
tg-related-task-row
|
||||
ng-model="task"
|
||||
)
|
||||
.row.single-related-task.related-task-create-form(tg-related-task-create-form)
|
||||
|
|
|
@ -60,7 +60,7 @@ div.wrapper(
|
|||
|
||||
div.tags-block(tg-tag-line, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
section.duty-content(tg-editable-description, ng-model="issue", required-perm="modify_issue")
|
||||
section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="issue", required-perm="modify_issue")
|
||||
|
||||
// Custom Fields
|
||||
tg-custom-attributes-values(
|
||||
|
@ -70,7 +70,12 @@ div.wrapper(
|
|||
required-edition-perm="modify_issue"
|
||||
)
|
||||
|
||||
tg-attachments(ng-model="issue", type="issue")
|
||||
tg-attachments-full(
|
||||
obj-id="issue.id"
|
||||
type="issue",
|
||||
project-id="projectId"
|
||||
)
|
||||
|
||||
tg-history(ng-model="issue", type="issue")
|
||||
|
||||
sidebar.menu-secondary.sidebar.ticket-data
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
doctype html
|
||||
|
||||
div.wrapper.issues(tg-issues, ng-controller="IssuesController as ctrl", ng-init="section='issues'")
|
||||
div.wrapper.issues.lightbox-generic-form(tg-issues, ng-controller="IssuesController as ctrl", ng-init="section='issues'")
|
||||
tg-project-menu
|
||||
sidebar.menu-secondary.extrabar.filters-bar(tg-issues-filters)
|
||||
include ../includes/modules/issues-filters
|
||||
|
|
|
@ -1,27 +1,46 @@
|
|||
div(class="tasks")
|
||||
div(class="task-name")
|
||||
span(class="icon icon-iocaine")
|
||||
a(tg-nav="project-tasks-detail:project=project.slug,ref=task.ref" title!="#<%- task.ref %> <%- task.subject %>" class="clickable")
|
||||
span #<%- task.ref %>
|
||||
.tasks
|
||||
.task-name
|
||||
.icon.icon-iocaine
|
||||
a.clickable(
|
||||
tg-nav="project-tasks-detail:project=project.slug,ref=task.ref"
|
||||
title!="#<%- task.ref %> <%- task.subject %>")
|
||||
span #<%- task.ref %>
|
||||
span <%- task.subject %>
|
||||
div(class="task-settings")
|
||||
.task-settings
|
||||
<% if(perms.modify_task) { %>
|
||||
a(href="" title="{{'COMMON.EDIT' | translate}}" class="icon icon-edit")
|
||||
a.icon.icon-edit(
|
||||
href=""
|
||||
title="{{'COMMON.EDIT' | translate}}"
|
||||
)
|
||||
<% } %>
|
||||
<% if(perms.delete_task) { %>
|
||||
a(href="" title="{{'COMMON.DELETE' | translate}}" class="icon icon-delete delete-task")
|
||||
a.icon.icon-delete.delete-task(
|
||||
href=""
|
||||
title="{{'COMMON.DELETE' | translate}}"
|
||||
)
|
||||
<% } %>
|
||||
|
||||
div(tg-related-task-status="task" ng-model="task" class="status")
|
||||
a(href="" title="{{'TASK.TITLE_SELECT_STATUS' | translate}}" class="task-status")
|
||||
span(class="task-status-bind")
|
||||
.status(
|
||||
tg-related-task-status="task"
|
||||
ng-model="task"
|
||||
)
|
||||
a.task-status(
|
||||
href=""
|
||||
title="{{'TASK.TITLE_SELECT_STATUS' | translate}}"
|
||||
)
|
||||
span.task-status-bind
|
||||
<% if(perms.modify_task) { %>
|
||||
span(class="icon icon-arrow-bottom")
|
||||
span.icon.icon-arrow-bottom
|
||||
<% } %>
|
||||
|
||||
div(tg-related-task-assigned-to-inline-edition="task" class="assigned-to")
|
||||
div(title="{{'COMMON.FIELDS.ASSIGNED_TO' | translate}}" class="task-assignedto <% if(perms.modify_task) { %>editable<% } %>")
|
||||
figure(class="avatar")
|
||||
.assigned-to(
|
||||
tg-related-task-assigned-to-inline-edition="task"
|
||||
)
|
||||
.task-assignedto(
|
||||
title="{{'COMMON.FIELDS.ASSIGNED_TO' | translate}}"
|
||||
class="<% if(perms.modify_task) { %>editable<% } %>"
|
||||
)
|
||||
figure.avatar
|
||||
<% if(perms.modify_task) { %>
|
||||
span(class="icon icon-arrow-bottom")
|
||||
span.icon.icon-arrow-bottom
|
||||
<% } %>
|
||||
|
|
|
@ -75,7 +75,7 @@ div.wrapper(
|
|||
|
||||
div.tags-block(tg-tag-line, ng-model="task", required-perm="modify_task")
|
||||
|
||||
section.duty-content(tg-editable-description, ng-model="task", required-perm="modify_task")
|
||||
section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="task", required-perm="modify_task")
|
||||
|
||||
// Custom Fields
|
||||
tg-custom-attributes-values(
|
||||
|
@ -85,7 +85,12 @@ div.wrapper(
|
|||
required-edition-perm="modify_task"
|
||||
)
|
||||
|
||||
tg-attachments(ng-model="task", type="task")
|
||||
tg-attachments-full(
|
||||
obj-id="task.id"
|
||||
type="task",
|
||||
project-id="projectId"
|
||||
)
|
||||
|
||||
tg-history(ng-model="task", type="task")
|
||||
|
||||
sidebar.menu-secondary.sidebar.ticket-data
|
||||
|
|
|
@ -68,7 +68,7 @@ div.wrapper(
|
|||
|
||||
div.tags-block(tg-tag-line, ng-model="us", required-perm="modify_us")
|
||||
|
||||
section.duty-content(tg-editable-description, ng-model="us", required-perm="modify_us")
|
||||
section.duty-content(tg-editable-description, tg-editable-wysiwyg, ng-model="us", required-perm="modify_us")
|
||||
|
||||
// Custom Fields
|
||||
tg-custom-attributes-values(
|
||||
|
@ -80,10 +80,12 @@ div.wrapper(
|
|||
|
||||
include ../includes/modules/related-tasks
|
||||
|
||||
tg-attachments(
|
||||
ng-model="us"
|
||||
type="us"
|
||||
tg-attachments-full(
|
||||
obj-id="us.id"
|
||||
type="us",
|
||||
project-id="projectId"
|
||||
)
|
||||
|
||||
tg-history(
|
||||
ng-model="us"
|
||||
type="us"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
include ../common/components/wysiwyg.jade
|
||||
|
||||
.view-wiki-content
|
||||
section.wysiwyg(tg-bind-html='wiki.html')
|
||||
span.edit.icon.icon-edit(title="{{'COMMON.EDIT' | translate}}", ng-if="wiki")
|
||||
|
@ -5,11 +7,7 @@
|
|||
.edit-wiki-content(style='display: none;')
|
||||
textarea(ng-attr-placeholder="{{'WIKI.PLACEHOLDER_PAGE' | translate}}",
|
||||
ng-model='wiki.content', tg-markitup='tg-markitup')
|
||||
|
||||
a.help-markdown(href='https://taiga.io/support/taiga-markdown-syntax/', target='_blank',
|
||||
title="{{'COMMON.WYSIWYG.MARKDOWN_HELP' | translate}}")
|
||||
span.icon.icon-help
|
||||
span(translate="COMMON.WYSIWYG.MARKDOWN_HELP")
|
||||
+wysihelp
|
||||
|
||||
span.action-container
|
||||
a.save.icon.icon-floppy(href='', title="{{'COMMON.SAVE' | translate}}")
|
||||
|
|
|
@ -16,7 +16,12 @@ div.wrapper(ng-controller="WikiDetailController as ctrl",
|
|||
h2.wiki-title(ng-bind='wikiTitle')
|
||||
section.wiki-content(tg-editable-wiki-content, ng-model="wiki")
|
||||
|
||||
tg-attachments(ng-model="wiki", type="wiki_page", ng-if="wiki.id")
|
||||
tg-attachments-full(
|
||||
ng-if="wiki.id"
|
||||
obj-id="wiki.id"
|
||||
type="wiki_page",
|
||||
project-id="projectId"
|
||||
)
|
||||
|
||||
a.remove(href="", ng-click="ctrl.delete()", ng-if="wiki.id", title="{{'WIKI.REMOVE' | translate}}", tg-check-permission="delete_wiki_page")
|
||||
span.icon.icon-delete
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
.wysiwyg-help {
|
||||
background: $whitish;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: -.5rem;
|
||||
padding: .25rem .5rem;
|
||||
}
|
||||
|
||||
.drag-drop-help {
|
||||
@extend %xsmall;
|
||||
color: $gray;
|
||||
}
|
||||
|
||||
.help-markdown,
|
||||
.help-button {
|
||||
@extend %xsmall;
|
||||
&:hover {
|
||||
span {
|
||||
transition: color .2s linear;
|
||||
}
|
||||
.icon {
|
||||
color: $primary-light;
|
||||
transition: color .2s linear;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
margin-right: .2rem;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
a.help-markdown,
|
||||
a.help-button {
|
||||
@extend %small;
|
||||
color: $gray-light;
|
||||
&:hover {
|
||||
span {
|
||||
color: $grayer;
|
||||
transition: color .2s linear;
|
||||
}
|
||||
.icon {
|
||||
color: $primary-light;
|
||||
transition: color .2s linear;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
color: $gray-light;
|
||||
margin-right: .2rem;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,15 @@ textarea {
|
|||
transition: border .3s linear;
|
||||
}
|
||||
}
|
||||
button,
|
||||
button:active,
|
||||
button:focus {
|
||||
background: none;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
outline-style: none;
|
||||
outline-width: 0;
|
||||
}
|
||||
textarea {
|
||||
min-height: 10rem;
|
||||
resize: vertical;
|
||||
|
|
|
@ -141,3 +141,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin cursor-progress {
|
||||
.in-progress {
|
||||
cursor: progress;
|
||||
}
|
||||
}
|
|
@ -166,6 +166,7 @@
|
|||
}
|
||||
|
||||
.duty-content {
|
||||
@include cursor-progress;
|
||||
position: relative;
|
||||
&:hover {
|
||||
.view-description {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
}
|
||||
|
||||
.wiki-content {
|
||||
@include cursor-progress;
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
&.editable {
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
.attachments {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.attachments-header {
|
||||
align-content: space-between;
|
||||
align-items: center;
|
||||
background: $whitish;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
.attachments-title {
|
||||
@extend %medium;
|
||||
@extend %bold;
|
||||
color: $grayer;
|
||||
}
|
||||
.attachments-num,
|
||||
.attachments-text {
|
||||
margin-right: .1rem;
|
||||
}
|
||||
.icon {
|
||||
@extend %large;
|
||||
color: $grayer;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $primary;
|
||||
transition: color .2s ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.single-attachment {
|
||||
@extend %small;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $whitish;
|
||||
display: flex;
|
||||
padding: .5rem 0 .5rem 1rem;
|
||||
position: relative;
|
||||
&:hover {
|
||||
.attachment-settings {
|
||||
.settings {
|
||||
opacity: 1;
|
||||
transition: opacity .2s ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.ui-sortable-helper {
|
||||
background: lighten($primary, 60%);
|
||||
box-shadow: 1px 1px 10px rgba($black, .1);
|
||||
transition: background .2s ease-in;
|
||||
}
|
||||
&.deprecated {
|
||||
color: $gray-light;
|
||||
.attachment-name a {
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
&.sortable-placeholder {
|
||||
background: $whitish;
|
||||
height: 40px;
|
||||
}
|
||||
.attachment-name {
|
||||
@extend %bold;
|
||||
@include ellipsis(200px);
|
||||
flex-basis: 35%;
|
||||
flex-grow: 1;
|
||||
padding-right: 1rem;
|
||||
.icon {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
.attachment-size {
|
||||
color: $gray-light;
|
||||
flex-basis: 15%;
|
||||
flex-grow: 1;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
.attachment-comments,
|
||||
.editable-attachment-comment {
|
||||
flex-basis: 35%;
|
||||
flex-grow: 1;
|
||||
span {
|
||||
color: $gray;
|
||||
}
|
||||
}
|
||||
.editable-attachment-comment {
|
||||
@extend %small;
|
||||
}
|
||||
.attachment-settings {
|
||||
flex-basis: 15%;
|
||||
flex-grow: 1;
|
||||
.settings,
|
||||
.editable-settings {
|
||||
@extend %large;
|
||||
color: $gray-light;
|
||||
display: block;
|
||||
position: absolute;
|
||||
&:hover {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
.settings {
|
||||
opacity: 0;
|
||||
top: .5rem;
|
||||
}
|
||||
.editable-settings {
|
||||
opacity: 1;
|
||||
top: 1rem;
|
||||
}
|
||||
.icon-edit,
|
||||
.icon-floppy {
|
||||
right: 3.5rem;
|
||||
}
|
||||
.icon-delete {
|
||||
right: 2rem;
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
.icon-drag-v {
|
||||
cursor: move;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.icon-delete {
|
||||
@extend %large;
|
||||
color: $gray-light;
|
||||
&:hover {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
.editable-attachment-deprecated {
|
||||
padding-left: 1rem;
|
||||
span {
|
||||
color: $gray-light;
|
||||
}
|
||||
input {
|
||||
margin-right: .2rem;
|
||||
vertical-align: middle;
|
||||
&:checked+span {
|
||||
color: $grayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.percentage {
|
||||
background: rgba($primary, .1);
|
||||
bottom: 0;
|
||||
height: 40px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 45%;
|
||||
}
|
||||
}
|
||||
|
||||
.more-attachments {
|
||||
@extend %small;
|
||||
border-bottom: 1px solid $gray-light;
|
||||
display: block;
|
||||
padding: 1rem 0 1rem 1rem;
|
||||
span {
|
||||
color: $gray-light;
|
||||
}
|
||||
.more-attachments-num {
|
||||
color: $primary;
|
||||
margin-left: .5rem;
|
||||
}
|
||||
&:hover {
|
||||
background: lighten($primary, 60%);
|
||||
transition: background .2s ease-in;
|
||||
}
|
||||
}
|
||||
|
||||
.add-attach {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
span {
|
||||
@extend %small;
|
||||
color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-preview img {
|
||||
max-height: 95vh;
|
||||
max-width: 95vw;
|
||||
}
|
|
@ -67,10 +67,12 @@
|
|||
}
|
||||
}
|
||||
.add-comment {
|
||||
@include cursor-progress;
|
||||
@include clearfix;
|
||||
&.active {
|
||||
.button-green {
|
||||
display: block;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
textarea {
|
||||
height: 6rem;
|
||||
|
@ -89,7 +91,6 @@
|
|||
textarea {
|
||||
background: $white;
|
||||
height: 5rem;
|
||||
margin-bottom: .5rem;
|
||||
min-height: 41px;
|
||||
}
|
||||
.help-markdown {
|
||||
|
|
|
@ -18,12 +18,9 @@
|
|||
}
|
||||
|
||||
textarea {
|
||||
margin-bottom: 1rem;
|
||||
max-height: 9rem;
|
||||
min-height: 7rem;
|
||||
min-height: 4.5rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
label {
|
||||
@extend %xsmall;
|
||||
background: $whitish;
|
||||
|
@ -47,12 +44,12 @@
|
|||
.settings {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
fieldset {
|
||||
margin-right: .5rem;
|
||||
&:hover {
|
||||
color: $white;
|
||||
transition: all .2s ease-in;
|
||||
transition-delay: .2s;
|
||||
}
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
|
@ -98,6 +95,26 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
.attachments {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.attachment-body {
|
||||
max-height: 7.5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.attachment-delete {
|
||||
right: .5rem;
|
||||
svg {
|
||||
fill: $gray-light;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
&:hover {
|
||||
svg {
|
||||
fill: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightbox-generic-bulk {
|
||||
|
@ -493,7 +510,7 @@
|
|||
.ticket-role-points {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
max-width: calc(100% * (1/5) - .2rem);
|
||||
max-width: calc(100% * (1/6) - .2rem);
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
@ -502,6 +519,6 @@
|
|||
}
|
||||
}
|
||||
.points-per-role {
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,26 @@
|
|||
background: $whitish;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
.related-tasks-title {
|
||||
@extend %medium;
|
||||
@extend %bold;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.icon {
|
||||
@extend %large;
|
||||
color: $grayer;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $primary;
|
||||
transition: color .2s ease-in;
|
||||
.add-button {
|
||||
background: $grayer;
|
||||
border: 0;
|
||||
display: inline-block;
|
||||
padding: .5rem;
|
||||
transition: background .25s;
|
||||
&:hover,
|
||||
&.is-active {
|
||||
background: $primary-light;
|
||||
}
|
||||
svg {
|
||||
fill: $white;
|
||||
height: 1.25rem;
|
||||
margin-bottom: -.2rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1000 1000">
|
||||
<path transform="translate(91.675 74.66) scale(17.01355)" d="M15 36C8.92 36 4 31.07 4 25s4.92-11 11-11h21c4.42 0 8 3.58 8 8s-3.58 8-8 8H19c-2.76 0-5-2.24-5-5s2.24-5 5-5h15v3H19c-1.1 0-2 .89-2 2s.9 2 2 2h17c2.76 0 5-2.24 5-5s-2.24-5-5-5H15c-4.42 0-8 3.58-8 8s3.58 8 8 8h19v3H15z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 400 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
||||
<path d="M107.855 133.83h359.073v333.794H107.855zM533.072 133.83h359.074v333.794H533.072zM107.855 532.376h359.073V866.17H107.855zM533.072 532.376h359.074V866.17H533.072z" />
|
||||
</svg>
|
After Width: | Height: | Size: 250 B |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000">
|
||||
<path transform="matrix(31.97294 0 0 31.97294 5313.926 639.882)" d="M-162.75-2.5h3.75v-3.75h-3.75zm0 7.5h3.75V1.25h-3.75zm0-15h3.75v-3.75h-3.75zm7.5 7.5h16.875v-3.75h-16.875zm0 7.5h16.875V1.25h-16.875zm0-18.75V-10h16.875v-3.75z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 307 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="200"><path d="M211,504L-64,554L273,510Z" fill="#448e54" stroke="#448e54" stroke-width="1.51"/><path d="M395,96L413,270L481,52Z" fill="#449c57" stroke="#449c57" stroke-width="1.51"/><path d="M481,52L413,270L488,358Z" fill="#2b8e4c" stroke="#2b8e4c" stroke-width="1.51"/><path d="M273,510L-64,554L495,543Z" fill="#2d8249" stroke="#2d8249" stroke-width="1.51"/><path d="M-302,-260L105,-294L513,-357Z" fill="#c4e6a4" stroke="#c4e6a4" stroke-width="1.51"/><path d="M105,-294L298,-267L513,-357Z" fill="#9fcf8d" stroke="#9fcf8d" stroke-width="1.51"/><path d="M282,-153L481,52L534,-109Z" fill="#72af69" stroke="#72af69" stroke-width="1.51"/><path d="M513,-357L298,-267L534,-109Z" fill="#8bb777" stroke="#8bb777" stroke-width="1.51"/><path d="M298,-267L282,-153L534,-109Z" fill="#8abe77" stroke="#8abe77" stroke-width="1.51"/><path d="M481,52L488,358L534,-109Z" fill="#469757" stroke="#469757" stroke-width="1.51"/><path d="M488,358L495,543L534,-109Z" fill="#218346" stroke="#218346" stroke-width="1.51"/><path d="M282,-153L395,96L481,52Z" fill="#69af67" stroke="#69af67" stroke-width="1.51"/><path d="M488,358L273,510L495,543Z" fill="#10723b" stroke="#10723b" stroke-width="1.51"/><path d="M413,270L273,510L488,358Z" fill="#208041" stroke="#208041" stroke-width="1.51"/><path d="M282,-153L148,102L395,96Z" fill="#72c072" stroke="#72c072" stroke-width="1.51"/><path d="M181,356L273,510L413,270Z" fill="#2d904c" stroke="#2d904c" stroke-width="1.51"/><path d="M395,96L148,102L413,270Z" fill="#4cab5f" stroke="#4cab5f" stroke-width="1.51"/><path d="M148,102L181,356L413,270Z" fill="#45aa5d" stroke="#45aa5d" stroke-width="1.51"/><path d="M-302,-260L-45,-237L105,-294Z" fill="#e6f5ad" stroke="#e6f5ad" stroke-width="1.51"/><path d="M181,356L211,504L273,510Z" fill="#348f4f" stroke="#348f4f" stroke-width="1.51"/><path d="M161,-135L148,102L282,-153Z" fill="#92d081" stroke="#92d081" stroke-width="1.51"/><path d="M161,-135L282,-153L298,-267Z" fill="#9cd386" stroke="#9cd386" stroke-width="1.51"/><path d="M105,-294L161,-135L298,-267Z" fill="#aedc91" stroke="#aedc91" stroke-width="1.51"/><path d="M148,102L-14,145L181,356Z" fill="#6ebf71" stroke="#6ebf71" stroke-width="1.51"/><path d="M-14,145L-48,271L181,356Z" fill="#74bd70" stroke="#74bd70" stroke-width="1.51"/><path d="M181,356L-64,554L211,504Z" fill="#4e995a" stroke="#4e995a" stroke-width="1.51"/><path d="M-111,-28L-14,145L148,102Z" fill="#9cd687" stroke="#9cd687" stroke-width="1.51"/><path d="M-111,-28L148,102L161,-135Z" fill="#a5da8a" stroke="#a5da8a" stroke-width="1.51"/><path d="M-48,271L-64,554L181,356Z" fill="#69aa65" stroke="#69aa65" stroke-width="1.51"/><path d="M105,-294L-45,-237L161,-135Z" fill="#c3e69b" stroke="#c3e69b" stroke-width="1.51"/><path d="M-45,-237L-111,-28L161,-135Z" fill="#c6e89a" stroke="#c6e89a" stroke-width="1.51"/><path d="M-274,131L-276,222L-48,271Z" fill="#a2d48a" stroke="#a2d48a" stroke-width="1.51"/><path d="M-217,430L-64,554L-48,271Z" fill="#7eb16f" stroke="#7eb16f" stroke-width="1.51"/><path d="M-218,-122L-111,-28L-45,-237Z" fill="#dbf1a4" stroke="#dbf1a4" stroke-width="1.51"/><path d="M-274,131L-48,271L-14,145Z" fill="#9cd385" stroke="#9cd385" stroke-width="1.51"/><path d="M-111,-28L-274,131L-14,145Z" fill="#b2df93" stroke="#b2df93" stroke-width="1.51"/><path d="M-276,222L-217,430L-48,271Z" fill="#92c47d" stroke="#92c47d" stroke-width="1.51"/><path d="M-302,-260L-218,-122L-45,-237Z" fill="#ecf8b1" stroke="#ecf8b1" stroke-width="1.51"/><path d="M-218,-122L-274,131L-111,-28Z" fill="#cdeba0" stroke="#cdeba0" stroke-width="1.51"/><path d="M-302,-260L-274,131L-218,-122Z" fill="#dff2b2" stroke="#dff2b2" stroke-width="1.51"/><path d="M-302,-260L-276,222L-274,131Z" fill="#cae9ab" stroke="#cae9ab" stroke-width="1.51"/></svg>
|
After Width: | Height: | Size: 3.7 KiB |
|
@ -56,7 +56,7 @@ $dropdown-color: rgba(darken($primary-dark, 20%), 1);
|
|||
%mono {font-family: 'courier new', 'monospace';}
|
||||
|
||||
%lightbox {
|
||||
background: rgba($white, .95);
|
||||
background: rgba($white, 1);
|
||||
}
|
||||
|
||||
// Background images
|
||||
|
|
|
@ -53,7 +53,7 @@ $dropdown-color: rgba(darken($primary-dark, 20%), 1);
|
|||
|
||||
// lightbox
|
||||
%lightbox {
|
||||
background: rgba($white, .95);
|
||||
background: rgba($white, .98);
|
||||
}
|
||||
|
||||
// Background images
|
||||
|
|
|
@ -62,7 +62,7 @@ $dropdown-color: rgba(darken($grayer, 20%), 1);
|
|||
|
||||
// lightbox
|
||||
%lightbox {
|
||||
background: rgba($white, .95);
|
||||
background: rgba($white, .98);
|
||||
}
|
||||
|
||||
// Background images
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
var utils = require('../utils');
|
||||
var helper = module.exports;
|
||||
|
||||
var chai = require('chai');
|
||||
var chaiAsPromised = require('chai-as-promised');
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
var expect = chai.expect;
|
||||
|
||||
helper.assignToLightbox = function() {
|
||||
let el = $('div[tg-lb-assignedto]');
|
||||
|
||||
|
@ -37,3 +43,23 @@ helper.assignToLightbox = function() {
|
|||
|
||||
return obj;
|
||||
};
|
||||
|
||||
helper.lightboxAttachment = async function() {
|
||||
let el = $('tg-attachments-simple');
|
||||
|
||||
let addAttachment = el.$('#add-attach');
|
||||
|
||||
let countAttachments = await el.$$('.single-attachment').count();
|
||||
|
||||
var fileToUpload1 = utils.common.uploadImagePath();
|
||||
var fileToUpload2 = utils.common.uploadFilePath();
|
||||
|
||||
await utils.common.uploadFile(addAttachment, fileToUpload1)
|
||||
await utils.common.uploadFile(addAttachment, fileToUpload2)
|
||||
|
||||
el.$$('.attachment-delete').get(0).click()
|
||||
|
||||
let newCountAttachments = await el.$$('.single-attachment').count();
|
||||
|
||||
expect(countAttachments + 1).to.be.equal(newCountAttachments);
|
||||
}
|
||||
|
|
|
@ -256,14 +256,14 @@ helper.delete = function() {
|
|||
};
|
||||
|
||||
helper.attachment = function() {
|
||||
let el = $('tg-attachments');
|
||||
let el = $('tg-attachments-full');
|
||||
|
||||
let obj = {
|
||||
el:el,
|
||||
upload: async function(filePath, name) {
|
||||
let addAttach = el.$('#add-attach');
|
||||
|
||||
let countAttachments = await $$('div[tg-attachment]').count();
|
||||
let countAttachments = await $$('tg-attachment').count();
|
||||
|
||||
let toggleInput = function() {
|
||||
$('#add-attach').toggle();
|
||||
|
@ -274,37 +274,37 @@ helper.attachment = function() {
|
|||
await browser.waitForAngular();
|
||||
|
||||
await browser.wait(async () => {
|
||||
let newCountAttachments = await $$('div[tg-attachment]').count();
|
||||
let newCountAttachments = await $$('tg-attachment').count();
|
||||
|
||||
return newCountAttachments == countAttachments + 1;
|
||||
}, 5000);
|
||||
|
||||
await el.$$('div[tg-attachment] .editable-attachment-comment input').last().sendKeys(name);
|
||||
await el.$$('tg-attachment .editable-attachment-comment input').last().sendKeys(name);
|
||||
await browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
await browser.executeScript(toggleInput);
|
||||
await browser.waitForAngular();
|
||||
},
|
||||
|
||||
renameLastAttchment: async function (name) {
|
||||
await browser.actions().mouseMove(el.$$('div[tg-attachment]').last()).perform();
|
||||
await el.$$('div[tg-attachment] .attachment-settings .icon-edit').last().click();
|
||||
await el.$$('div[tg-attachment] .editable-attachment-comment input').last().sendKeys(name);
|
||||
await browser.actions().mouseMove(el.$$('tg-attachment').last()).perform();
|
||||
await el.$$('tg-attachment .attachment-settings .icon-edit').last().click();
|
||||
await el.$$('tg-attachment .editable-attachment-comment input').last().sendKeys(name);
|
||||
await browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
return browser.waitForAngular();
|
||||
},
|
||||
|
||||
getFirstAttachmentName: async function () {
|
||||
let name = await el.$$('div[tg-attachment] .attachment-comments').first().getText();
|
||||
let name = await el.$$('tg-attachment .attachment-comments').first().getText();
|
||||
return name;
|
||||
},
|
||||
|
||||
getLastAttachmentName: async function () {
|
||||
let name = await el.$$('div[tg-attachment] .attachment-comments').last().getText();
|
||||
let name = await el.$$('tg-attachment .attachment-comments').last().getText();
|
||||
return name;
|
||||
},
|
||||
|
||||
countAttachments: async function(){
|
||||
return await el.$$('div[tg-attachment]').count();
|
||||
return await el.$$('tg-attachment').count();
|
||||
},
|
||||
|
||||
countDeprecatedAttachments: async function(){
|
||||
|
@ -321,10 +321,10 @@ helper.attachment = function() {
|
|||
},
|
||||
|
||||
deprecateLastAttachment: async function() {
|
||||
await browser.actions().mouseMove(el.$$('div[tg-attachment]').last()).perform();
|
||||
await el.$$('div[tg-attachment] .attachment-settings .icon-edit').last().click();
|
||||
await el.$$('div[tg-attachment] .editable-attachment-deprecated input').last().click();
|
||||
await el.$$('div[tg-attachment] .attachment-settings .editable-settings.icon-floppy').last().click();
|
||||
await browser.actions().mouseMove(el.$$('tg-attachment').last()).perform();
|
||||
await el.$$('tg-attachment .attachment-settings .icon-edit').last().click();
|
||||
await el.$$('tg-attachment .editable-attachment-deprecated input').last().click();
|
||||
await el.$$('tg-attachment .attachment-settings .editable-settings.icon-floppy').last().click();
|
||||
await browser.waitForAngular();
|
||||
},
|
||||
|
||||
|
@ -333,7 +333,7 @@ helper.attachment = function() {
|
|||
},
|
||||
|
||||
deleteLastAttachment: async function() {
|
||||
let attachment = await $$('div[tg-attachment]').last();
|
||||
let attachment = await $$('tg-attachment').last();
|
||||
|
||||
await browser.actions().mouseMove(attachment).perform();
|
||||
|
||||
|
@ -359,11 +359,23 @@ helper.attachment = function() {
|
|||
},
|
||||
|
||||
dragLastAttchmentToFirstPosition: async function() {
|
||||
await browser.actions().mouseMove(el.$$('div[tg-attachment]').last()).perform();
|
||||
let lastDraggableAttachment = el.$$('div[tg-attachment] .attachment-settings .icon-drag-v').last();
|
||||
let destination = el.$$('div[tg-attachment] .attachment-settings .icon-drag-v').first();
|
||||
await browser.actions().mouseMove(el.$$('tg-attachment').last()).perform();
|
||||
let lastDraggableAttachment = el.$$('tg-attachment .attachment-settings .icon-drag-v').last();
|
||||
let destination = el.$$('tg-attachment .attachment-settings .icon-drag-v').first();
|
||||
await utils.common.drag(lastDraggableAttachment, destination);
|
||||
}
|
||||
},
|
||||
|
||||
galleryImages: function() {
|
||||
return $$('tg-attachment-gallery');
|
||||
},
|
||||
|
||||
gallery: function() {
|
||||
$('.view-gallery').click();
|
||||
},
|
||||
|
||||
list: function() {
|
||||
$('.view-list').click();
|
||||
},
|
||||
};
|
||||
|
||||
return obj;
|
||||
|
|
|
@ -249,6 +249,18 @@ shared.attachmentTesting = async function() {
|
|||
newAttachmentsLength = await attachmentHelper.countAttachments();
|
||||
expect(newAttachmentsLength).to.be.equal(attachmentsLength + deprecatedAttachmentsLength);
|
||||
|
||||
// Gallery
|
||||
attachmentHelper.gallery();
|
||||
|
||||
let countImages = await attachmentHelper.galleryImages().count();
|
||||
|
||||
commonUtil.takeScreenshot('attachments', 'gallery');
|
||||
|
||||
expect(countImages).to.be.above(0);
|
||||
|
||||
attachmentHelper.list();
|
||||
|
||||
|
||||
// Deleting
|
||||
attachmentsLength = await attachmentHelper.countAttachments();
|
||||
await attachmentHelper.deleteLastAttachment();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var utils = require('../utils');
|
||||
var backlogHelper = require('../helpers').backlog;
|
||||
var commonHelper = require('../helpers').common;
|
||||
|
||||
var chai = require('chai');
|
||||
var chaiAsPromised = require('chai-as-promised');
|
||||
|
@ -57,9 +58,12 @@ describe('backlog', function() {
|
|||
//settings
|
||||
createUSLightbox.settings(0).click();
|
||||
|
||||
|
||||
await utils.common.waitTransitionTime(createUSLightbox.settings(0));
|
||||
});
|
||||
|
||||
it('upload attachments', commonHelper.lightboxAttachment);
|
||||
|
||||
it('screenshots', function() {
|
||||
utils.common.takeScreenshot('backlog', 'create-us-filled');
|
||||
});
|
||||
|
||||
|
@ -150,6 +154,8 @@ describe('backlog', function() {
|
|||
editUSLightbox.settings(1).click();
|
||||
});
|
||||
|
||||
it('upload attachments', commonHelper.lightboxAttachment);
|
||||
|
||||
it('send form', async function() {
|
||||
editUSLightbox.submit();
|
||||
|
||||
|
|
|
@ -41,7 +41,11 @@ describe('issues list', function() {
|
|||
|
||||
await createIssueLightbox.tags().sendKeys('bbb');
|
||||
browser.actions().sendKeys(protractor.Key.ENTER).perform();
|
||||
});
|
||||
|
||||
it('upload attachments', commonHelper.lightboxAttachment);
|
||||
|
||||
it('screenshots', function() {
|
||||
utils.common.takeScreenshot('issues', 'create-issue-filled');
|
||||
});
|
||||
|
||||
|
|
|
@ -67,6 +67,12 @@ describe('kanban', function() {
|
|||
createUSLightbox.settings(1).click();
|
||||
});
|
||||
|
||||
it('upload attachments', commonHelper.lightboxAttachment);
|
||||
|
||||
it('screenshots', function() {
|
||||
utils.common.takeScreenshot('kanban', 'create-us-filled');
|
||||
})
|
||||
|
||||
it('send form', async function() {
|
||||
createUSLightbox.submit();
|
||||
|
||||
|
@ -134,6 +140,8 @@ describe('kanban', function() {
|
|||
createUSLightbox.settings(1).click();
|
||||
});
|
||||
|
||||
it('upload attachments', commonHelper.lightboxAttachment);
|
||||
|
||||
it('send form', async function() {
|
||||
createUSLightbox.submit();
|
||||
|
||||
|
|
|
@ -438,7 +438,7 @@ gulp.task("jslibs-deploy", function() {
|
|||
.pipe(gulp.dest(paths.distVersion + "js/"));
|
||||
});
|
||||
|
||||
gulp.task("app-watch", ["coffee-lint", "coffee", "conf", "locales", "app-loader"]);
|
||||
gulp.task("app-watch", ["coffee", "conf", "locales", "app-loader"]);
|
||||
|
||||
gulp.task("app-deploy", ["coffee", "conf", "locales", "app-loader"], function() {
|
||||
return gulp.src(paths.distVersion + "js/app.js")
|
||||
|
|
Loading…
Reference in New Issue