From d95f1c474a26b759a0d23b5b6e874e76e534eafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 30 Jan 2015 14:11:06 +0100 Subject: [PATCH 01/19] Basic custom fields structure --- app/coffee/app.coffee | 2 ++ app/coffee/modules/base.coffee | 1 + .../admin/admin-project-values-us-extras.jade | 20 +++++++++++++++++++ .../modules/admin-submenu-project-values.jade | 5 +++++ .../modules/admin/admin-us-extras.jade | 18 +++++++++++++++++ .../modules/admin/admin-custom-fields.scss | 3 +++ main-sass.js | 1 + 7 files changed, 50 insertions(+) create mode 100644 app/partials/admin/admin-project-values-us-extras.jade create mode 100644 app/partials/includes/modules/admin/admin-us-extras.jade create mode 100644 app/styles/modules/admin/admin-custom-fields.scss diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index e7f49f72..aaeb7abd 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -87,6 +87,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "admin/admin-project-values-us-status.html"}) $routeProvider.when("/project/:pslug/admin/project-values/us-points", {templateUrl: "admin/admin-project-values-us-points.html"}) + $routeProvider.when("/project/:pslug/admin/project-values/us-extras", + {templateUrl: "admin/admin-project-values-us-extras.html"}) $routeProvider.when("/project/:pslug/admin/project-values/task-status", {templateUrl: "admin/admin-project-values-task-status.html"}) $routeProvider.when("/project/:pslug/admin/project-values/issue-status", diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 3e44ac58..9324f20c 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -86,6 +86,7 @@ urls = { "project-admin-project-profile-export": "/project/:project/admin/project-profile/export" "project-admin-project-values-us-status": "/project/:project/admin/project-values/us-status" "project-admin-project-values-us-points": "/project/:project/admin/project-values/us-points" + "project-admin-project-values-us-extras": "/project/:project/admin/project-values/us-extras" "project-admin-project-values-task-status": "/project/:project/admin/project-values/task-status" "project-admin-project-values-issue-status": "/project/:project/admin/project-values/issue-status" "project-admin-project-values-issue-types": "/project/:project/admin/project-values/issue-types" diff --git a/app/partials/admin/admin-project-values-us-extras.jade b/app/partials/admin/admin-project-values-us-extras.jade new file mode 100644 index 00000000..88649141 --- /dev/null +++ b/app/partials/admin/admin-project-values-us-extras.jade @@ -0,0 +1,20 @@ +div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl", + ng-init="section='admin'; resource='userstories'; type='points'; sectionName='Us points'", + type="points") + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + include ../includes/modules/admin-menu + + sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-us-extras") + include ../includes/modules/admin-submenu-project-values + + section.main.admin-common + include ../includes/components/mainTitle + p.admin-subtitle Specify here user story custom fields. The new field will appear on your user story detail. + + div.webhooks-options + a.button.button-green.hidden.add-custom-us(href="",title="Add a custom field in user stories") Add custom field + + include ../includes/modules/admin/admin-us-extras + +div.lightbox.lightbox-generic-notion.notion-admin-project-values-us-points(id="notion-admin-project-values-us-points", tg-lb-notion) + include ../includes/modules/help-notions/lightbox-notion-admin-project-values-us-points diff --git a/app/partials/includes/modules/admin-submenu-project-values.jade b/app/partials/includes/modules/admin-submenu-project-values.jade index 47cc1134..681b6520 100644 --- a/app/partials/includes/modules/admin-submenu-project-values.jade +++ b/app/partials/includes/modules/admin-submenu-project-values.jade @@ -14,6 +14,11 @@ section.admin-submenu span.title US points span.icon.icon-arrow-right + li#adminmenu-values-us-extras + a(href="", tg-nav="project-admin-project-values-us-extras:project=project.slug") + span.title US extras + span.icon.icon-arrow-right + li#adminmenu-values-task-status a(href="", tg-nav="project-admin-project-values-task-status:project=project.slug") span.title Task statuses diff --git a/app/partials/includes/modules/admin/admin-us-extras.jade b/app/partials/includes/modules/admin/admin-us-extras.jade new file mode 100644 index 00000000..e4758637 --- /dev/null +++ b/app/partials/includes/modules/admin/admin-us-extras.jade @@ -0,0 +1,18 @@ +section.custom-fields-table.basic-table + div.table-header + div.row + div.custom-name Name + div.custom-description Description + div.custom-options + div.table-body + div.row + div.custom-name + div.custom-description Description + div.custom-options + form.row + fieldset.custom-name + input(type="text") + fieldset.custom-description Description + input(type="text") + fieldset.custom-options + diff --git a/app/styles/modules/admin/admin-custom-fields.scss b/app/styles/modules/admin/admin-custom-fields.scss new file mode 100644 index 00000000..95f74aba --- /dev/null +++ b/app/styles/modules/admin/admin-custom-fields.scss @@ -0,0 +1,3 @@ +.custom-fields-table { + +} diff --git a/main-sass.js b/main-sass.js index 8db0d5bd..909931e1 100644 --- a/main-sass.js +++ b/main-sass.js @@ -125,6 +125,7 @@ exports.files = function () { 'modules/admin/admin-membership-table', 'modules/admin/admin-project-profile', 'modules/admin/default-values', + 'modules/admin/admin-custom-fields', 'modules/admin/project-values', 'modules/admin/third-parties', 'modules/admin/admin-third-parties-webhooks', From e2696d090e3b5cb77952b2c0a6a695453d85b8ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 30 Jan 2015 15:17:20 +0100 Subject: [PATCH 02/19] Basic custom field styles --- .../modules/admin/admin-us-extras.jade | 16 +++++--- .../modules/admin/admin-custom-fields.scss | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/app/partials/includes/modules/admin/admin-us-extras.jade b/app/partials/includes/modules/admin/admin-us-extras.jade index e4758637..37a24a0f 100644 --- a/app/partials/includes/modules/admin/admin-us-extras.jade +++ b/app/partials/includes/modules/admin/admin-us-extras.jade @@ -5,14 +5,20 @@ section.custom-fields-table.basic-table div.custom-description Description div.custom-options div.table-body - div.row - div.custom-name + div.row.single-custom-field + div.custom-name Name div.custom-description Description div.custom-options + div.custom-options-wrapper + a.edit-webhook.icon.icon-edit(href="", title="Edit Custom Field") + a.delete-webhook.icon.icon-delete(href="", title="Delete Custom Field") form.row fieldset.custom-name - input(type="text") - fieldset.custom-description Description - input(type="text") + input(type="text", placeholder="Set your custom field name") + fieldset.custom-description + input(type="text", placeholder="Set your custom field description") fieldset.custom-options + div.custom-options-wrapper + a.edit-webhook.icon.icon-floppy(href="", title="Save Custom Field") + a.delete-webhook.icon.icon-delete(href="", title="Delete Custom Field") diff --git a/app/styles/modules/admin/admin-custom-fields.scss b/app/styles/modules/admin/admin-custom-fields.scss index 95f74aba..b7cfb36f 100644 --- a/app/styles/modules/admin/admin-custom-fields.scss +++ b/app/styles/modules/admin/admin-custom-fields.scss @@ -1,3 +1,40 @@ .custom-fields-table { + .row { + border-bottom: 0; + padding: .5rem 0; + &:hover { + .custom-options-wrapper { + opacity: 1; + transition: opacity .2s linear; + } + } + } + .table-header { + @extend %bold; + border-bottom: 1px solid $gray-light; + } + .single-custom-field { + border-bottom: 1px solid $whitish; + color: $gray; + } + .custom-name, + .custom-description { + color: $gray; + margin-right: .5rem; + } + .custom-name { + flex-grow: 1; + } + .custom-description { + flex-grow: 2; + } + .custom-options { + flex-basis: 200px; + flex-grow: 0; + } + .custom-options-wrapper { + opacity: 0; + transition: opacity .3s linear; + } } From 0f4168f68c98ed8605d672f7d1d55789c44089d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 2 Feb 2015 08:29:54 +0100 Subject: [PATCH 03/19] Us custom fields --- .../admin/admin-project-values-us-extras.jade | 4 +-- .../modules/admin/admin-us-extras.jade | 6 ++-- .../modules/admin/admin-custom-fields.scss | 31 +++++++++++++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/app/partials/admin/admin-project-values-us-extras.jade b/app/partials/admin/admin-project-values-us-extras.jade index 88649141..d5674a0b 100644 --- a/app/partials/admin/admin-project-values-us-extras.jade +++ b/app/partials/admin/admin-project-values-us-extras.jade @@ -11,8 +11,8 @@ div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl", include ../includes/components/mainTitle p.admin-subtitle Specify here user story custom fields. The new field will appear on your user story detail. - div.webhooks-options - a.button.button-green.hidden.add-custom-us(href="",title="Add a custom field in user stories") Add custom field + div.custom-field-options + a.button.button-green.add-custom-field(href="",title="Add a custom field in user stories") Add custom field include ../includes/modules/admin/admin-us-extras diff --git a/app/partials/includes/modules/admin/admin-us-extras.jade b/app/partials/includes/modules/admin/admin-us-extras.jade index 37a24a0f..a844a656 100644 --- a/app/partials/includes/modules/admin/admin-us-extras.jade +++ b/app/partials/includes/modules/admin/admin-us-extras.jade @@ -6,13 +6,13 @@ section.custom-fields-table.basic-table div.custom-options div.table-body div.row.single-custom-field - div.custom-name Name - div.custom-description Description + div.custom-name Custom field name + div.custom-description Custom field looong Description div.custom-options div.custom-options-wrapper a.edit-webhook.icon.icon-edit(href="", title="Edit Custom Field") a.delete-webhook.icon.icon-delete(href="", title="Delete Custom Field") - form.row + form.row.single-custom-field fieldset.custom-name input(type="text", placeholder="Set your custom field name") fieldset.custom-description diff --git a/app/styles/modules/admin/admin-custom-fields.scss b/app/styles/modules/admin/admin-custom-fields.scss index b7cfb36f..93017908 100644 --- a/app/styles/modules/admin/admin-custom-fields.scss +++ b/app/styles/modules/admin/admin-custom-fields.scss @@ -1,3 +1,8 @@ +.custom-field-options { + margin-bottom: 1rem; + text-align: right; +} + .custom-fields-table { .row { border-bottom: 0; @@ -23,18 +28,38 @@ margin-right: .5rem; } .custom-name { - flex-grow: 1; + flex-basis: 25%; + flex-shrink: 0; } .custom-description { - flex-grow: 2; + @include ellipsis(100%); + flex-basis: 90%; + flex-grow: 8; } .custom-options { - flex-basis: 200px; + flex-basis: 100px; flex-grow: 0; + flex-shrink: 0; + text-align: center; + a { + color: $gray-light; + margin-right: 0.5rem; + transition: color .2s linear; + vertical-align: middle; + &:hover { + color: $green-taiga; + transition: color .2s linear; + } + } } .custom-options-wrapper { opacity: 0; transition: opacity .3s linear; } + form { + .custom-options-wrapper { + opacity: 1; + } + } } From d209718681e24d9363783cf9a47de4801df376db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 2 Feb 2015 08:32:17 +0100 Subject: [PATCH 04/19] Consistency styles --- app/styles/modules/admin/admin-custom-fields.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/styles/modules/admin/admin-custom-fields.scss b/app/styles/modules/admin/admin-custom-fields.scss index 93017908..782cd448 100644 --- a/app/styles/modules/admin/admin-custom-fields.scss +++ b/app/styles/modules/admin/admin-custom-fields.scss @@ -18,6 +18,11 @@ @extend %bold; border-bottom: 1px solid $gray-light; } + .table-body { + .custom-description { + color: $gray-light; + } + } .single-custom-field { border-bottom: 1px solid $whitish; color: $gray; From 9a7c900f2d1e709fc81b589c869ffbd06f457d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 2 Feb 2015 08:48:17 +0100 Subject: [PATCH 05/19] Add interactions --- .../modules/admin/admin-us-extras.jade | 1 + .../modules/admin/admin-custom-fields.scss | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/partials/includes/modules/admin/admin-us-extras.jade b/app/partials/includes/modules/admin/admin-us-extras.jade index a844a656..1655914a 100644 --- a/app/partials/includes/modules/admin/admin-us-extras.jade +++ b/app/partials/includes/modules/admin/admin-us-extras.jade @@ -6,6 +6,7 @@ section.custom-fields-table.basic-table div.custom-options div.table-body div.row.single-custom-field + span.icon.icon-drag-v div.custom-name Custom field name div.custom-description Custom field looong Description div.custom-options diff --git a/app/styles/modules/admin/admin-custom-fields.scss b/app/styles/modules/admin/admin-custom-fields.scss index 782cd448..bba0da2d 100644 --- a/app/styles/modules/admin/admin-custom-fields.scss +++ b/app/styles/modules/admin/admin-custom-fields.scss @@ -8,6 +8,10 @@ border-bottom: 0; padding: .5rem 0; &:hover { + background: rgba($fresh-taiga, .05); + cursor: move; + transition: background .2s linear; + .icon-drag-v, .custom-options-wrapper { opacity: 1; transition: opacity .2s linear; @@ -27,6 +31,18 @@ border-bottom: 1px solid $whitish; color: $gray; } + .icon-drag-v { + color: $gray-light; + opacity: 0; + padding: 0 .5rem; + transition: color .2s linear; + vertical-align: middle; + &:hover { + color: $gray; + cursor: move; + transition: color .2s linear; + } + } .custom-name, .custom-description { color: $gray; @@ -48,7 +64,7 @@ text-align: center; a { color: $gray-light; - margin-right: 0.5rem; + margin-right: .5rem; transition: color .2s linear; vertical-align: middle; &:hover { @@ -62,6 +78,10 @@ transition: opacity .3s linear; } form { + &.row:hover { + background: none; + cursor: default; + } .custom-options-wrapper { opacity: 1; } From f8929c3470d5b8470037d6d7b0ac2d92fe0e6438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 2 Feb 2015 09:05:21 +0100 Subject: [PATCH 06/19] Add drag&drop icon --- .../modules/admin/admin-us-extras.jade | 12 ++++--- .../modules/admin/admin-custom-fields.scss | 31 ++++++++++++------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/app/partials/includes/modules/admin/admin-us-extras.jade b/app/partials/includes/modules/admin/admin-us-extras.jade index 1655914a..94b2fef8 100644 --- a/app/partials/includes/modules/admin/admin-us-extras.jade +++ b/app/partials/includes/modules/admin/admin-us-extras.jade @@ -1,14 +1,18 @@ section.custom-fields-table.basic-table div.table-header div.row - div.custom-name Name - div.custom-description Description + div.custom-name + span Name + div.custom-description + span Description div.custom-options div.table-body div.row.single-custom-field span.icon.icon-drag-v - div.custom-name Custom field name - div.custom-description Custom field looong Description + div.custom-name + span Custom field name + div.custom-description + span Custom field looong Description div.custom-options div.custom-options-wrapper a.edit-webhook.icon.icon-edit(href="", title="Edit Custom Field") diff --git a/app/styles/modules/admin/admin-custom-fields.scss b/app/styles/modules/admin/admin-custom-fields.scss index bba0da2d..d110cf23 100644 --- a/app/styles/modules/admin/admin-custom-fields.scss +++ b/app/styles/modules/admin/admin-custom-fields.scss @@ -7,7 +7,18 @@ .row { border-bottom: 0; padding: .5rem 0; - &:hover { + + } + .table-header { + @extend %bold; + border-bottom: 1px solid $gray-light; + .custom-name span, + .custom-description span { + padding-left: 1.1rem; + } + } + .table-body { + .row:hover { background: rgba($fresh-taiga, .05); cursor: move; transition: background .2s linear; @@ -17,12 +28,12 @@ transition: opacity .2s linear; } } - } - .table-header { - @extend %bold; - border-bottom: 1px solid $gray-light; - } - .table-body { + form { + &.row:hover { + background: none; + cursor: default; + } + } .custom-description { color: $gray-light; } @@ -34,7 +45,7 @@ .icon-drag-v { color: $gray-light; opacity: 0; - padding: 0 .5rem; + padding: 0 .1rem; transition: color .2s linear; vertical-align: middle; &:hover { @@ -78,10 +89,6 @@ transition: opacity .3s linear; } form { - &.row:hover { - background: none; - cursor: default; - } .custom-options-wrapper { opacity: 1; } From 05a89bdf32d1bb931fb2d4245a86ab913ac90c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 2 Feb 2015 11:34:29 +0100 Subject: [PATCH 07/19] Custom fields in US detail --- .../modules/common/custom-fields.jade | 21 +++++ app/partials/us/us-detail.jade | 3 + app/styles/modules/common/custom-fields.scss | 76 +++++++++++++++++++ main-sass.js | 1 + 4 files changed, 101 insertions(+) create mode 100644 app/partials/includes/modules/common/custom-fields.jade create mode 100644 app/styles/modules/common/custom-fields.scss diff --git a/app/partials/includes/modules/common/custom-fields.jade b/app/partials/includes/modules/common/custom-fields.jade new file mode 100644 index 00000000..cf2be606 --- /dev/null +++ b/app/partials/includes/modules/common/custom-fields.jade @@ -0,0 +1,21 @@ +section.duty-custom-fields + div.custom-fields-header + span Custom Fields + // Remove .open class on click on this button in both .icon and .custom-fields-body to close + a.icon.icon-arrow-bottom.open + div.custom-fields-body.open + div.custom-field-single + div.custom-field-data + span.custom-field-name Name + span.custom-field-description This is the description + div.custom-field-value + span Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec. + div.custom-field-options + a.icon.icon-edit(href="", title="Edit Custom Field") + form.custom-field-single + div.custom-field-data + label.custom-field-name(for="custom-field-description") Name + div.custom-field-value + input#custom-field-description(type="text", placeholder="This is the description") + div.custom-field-options + a.icon.icon-floppy(href="", title="Save Custom Field") diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 5a78e15c..3c21553c 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -39,6 +39,9 @@ div.wrapper(ng-controller="UserStoryDetailController as ctrl", section.duty-content(tg-editable-description, ng-model="us", required-perm="modify_us") + // IF Custom Fields + include ../includes/modules/common/custom-fields + include ../includes/modules/related-tasks tg-attachments(ng-model="us", type="us") diff --git a/app/styles/modules/common/custom-fields.scss b/app/styles/modules/common/custom-fields.scss new file mode 100644 index 00000000..01105689 --- /dev/null +++ b/app/styles/modules/common/custom-fields.scss @@ -0,0 +1,76 @@ +.duty-custom-fields { + margin-bottom: 2rem; + .custom-fields-header { + @extend %bold; + align-content: space-between; + align-items: center; + background: $whitish; + display: flex; + justify-content: space-between; + padding: .5rem 1rem; + .icon-arrow-bottom { + @extend %large; + cursor: pointer; + transform: rotate(-90deg); + transition: transform .2s linear; + &.open { + transform: rotate(0); + transition: transform .2s linear; + } + } + } + .custom-fields-body { + @include slide(1000px, hidden, $min: 0); + } + .custom-field-single { + align-content: center; + align-items: center; + border-bottom: 1px solid $whitish; + display: flex; + padding: 1rem; + &:last-child { + border-bottom: 0; + } + &:hover { + .custom-field-options { + opacity: 1; + } + } + .custom-field-options { + opacity: 0; + transition: opacity .2s linear; + a { + color: $gray-light; + } + a:hover { + color: $green-taiga; + } + } + } + .custom-field-data { + flex: 0; + flex-basis: 200px; + .custom-field-name { + @extend %bold; + display: block; + } + .custom-field-description { + @extend %small; + color: $gray-light; + display: block; + line-height: .9rem; + } + } + .custom-field-value { + flex: 1; + padding-right: 2rem; + } + form { + label { + cursor: pointer; + } + input { + width: 100%; + } + } +} diff --git a/main-sass.js b/main-sass.js index 909931e1..f729d1e6 100644 --- a/main-sass.js +++ b/main-sass.js @@ -75,6 +75,7 @@ exports.files = function () { 'modules/common/history', 'modules/common/wizard', 'modules/common/external-reference', + 'modules/common/custom-fields', //Project modules 'modules/home-projects-list', From c45a51f3130b4381ab3d2880e96571669ea1b3dd Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 18 Feb 2015 15:16:11 +0100 Subject: [PATCH 08/19] Detail integration --- .../modules/common/custom-field-values.coffee | 190 ++++++++++++++++++ app/coffee/modules/resources.coffee | 6 + .../resources/custom-field-values.coffee | 35 ++++ .../custom-attribute-value-edit.jade | 14 ++ .../custom-attribute-value.jade | 17 ++ .../custom-attributes-values.jade | 7 + .../modules/common/custom-fields.jade | 21 -- app/partials/issue/issues-detail.jade | 3 + app/partials/task/task-detail.jade | 3 + app/partials/us/us-detail.jade | 4 +- 10 files changed, 277 insertions(+), 23 deletions(-) create mode 100644 app/coffee/modules/common/custom-field-values.coffee create mode 100644 app/coffee/modules/resources/custom-field-values.coffee create mode 100644 app/partials/custom-attributes/custom-attribute-value-edit.jade create mode 100644 app/partials/custom-attributes/custom-attribute-value.jade create mode 100644 app/partials/custom-attributes/custom-attributes-values.jade delete mode 100644 app/partials/includes/modules/common/custom-fields.jade diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee new file mode 100644 index 00000000..23689430 --- /dev/null +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -0,0 +1,190 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# File: modules/common/custom-field-values.coffee +### + +taiga = @.taiga +bindMethods = @.taiga.bindMethods +bindOnce = @.taiga.bindOnce +debounce = @.taiga.debounce +generateHash = taiga.generateHash + +module = angular.module("taigaCommon") + + +class CustomAttributesValuesController extends taiga.Controller + @.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"] + + constructor: (@scope, @rootscope, @repo, @rs, @confirm, @q) -> + bindMethods(@) + @.type = null + @.objectId = null + @.projectId = null + @.customAttributes = [] + @.customAttributesValues = null + + initialize: (type, objectId) -> + @.project = @scope.project + @.type = type + @.objectId = objectId + @.projectId = @scope.projectId + + loadCustomAttributesValues: -> + return @.customAttributesValues if not @.objectId + return @rs.customAttributesValues.get(@.type, @.objectId).then (customAttributesValues) => + @.customAttributes = @.project["#{@.type}_custom_attributes"] + @.customAttributesValues = customAttributesValues + return customAttributesValues + + getAttributeValue: (attribute) -> + attributeValue = _.clone(attribute, false) + attributeValue.value = @.customAttributesValues.attributes_values[attribute.id] + return attributeValue + + updateAttributeValue: (attributeValue) -> + onSuccess = => + onError = (response) => + @confirm.notify("error") + return @q.reject() + + # We need to update the full array so angular understand the model is modified + attributesValues = _.clone(@.customAttributesValues.attributes_values, true) + attributesValues[attributeValue.id] = attributeValue.value + @.customAttributesValues.attributes_values = attributesValues + @.customAttributesValues.id = @.objectId + return @repo.save(@.customAttributesValues).then(onSuccess, onError) + +CustomAttributesValuesDirective = ($templates, $storage) -> + template = $templates.get("custom-attributes/custom-attributes-values.html", true) + collapsedHash = generateHash(["custom-attributes-collapsed"]) + + link = ($scope, $el, $attrs, $ctrls) -> + $ctrl = $ctrls[0] + $model = $ctrls[1] + + bindOnce $scope, $attrs.ngModel, (value) -> + $ctrl.initialize($attrs.type, value.id) + $ctrl.loadCustomAttributesValues() + + $el.on "click", ".custom-fields-header a", -> + collapsed = not($storage.get(collapsedHash) or false) + $storage.set(collapsedHash, collapsed) + if collapsed + $el.find(".custom-fields-header a").removeClass("open") + $el.find(".custom-fields-body").removeClass("open") + else + $el.find(".custom-fields-header a").addClass("open") + $el.find(".custom-fields-body").addClass("open") + + $scope.$on "$destroy", -> + $el.off() + + templateFn = ($el, $attrs) -> + collapsed = $storage.get(collapsedHash) or false + + return template({ + requiredEditionPerm: $attrs.requiredEditionPerm + collapsed: collapsed + }) + + return { + require: ["tgCustomAttributesValues", "ngModel"] + controller: CustomAttributesValuesController + controllerAs: "ctrl" + restrict: "AE" + scope: true + link: link + template: templateFn + } + +module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", CustomAttributesValuesDirective]) + + +CustomAttributeValueDirective = ($template) -> + template = $template.get("custom-attributes/custom-attribute-value.html", true) + templateEdit = $template.get("custom-attributes/custom-attribute-value-edit.html", true) + + link = ($scope, $el, $attrs, $ctrl) -> + render = (attributeValue, edit=false) -> + ctx = { + id: attributeValue.id + name: attributeValue.name + description: attributeValue.description + value: attributeValue.value + isEditable: isEditable() + } + + if edit + html = templateEdit(ctx) + else + html = template(ctx) + + $el.html(html) + + isEditable = -> + permissions = $scope.project.my_permissions + requiredEditionPerm = $attrs.requiredEditionPerm + return permissions.indexOf(requiredEditionPerm) > -1 + + saveAttributeValue = -> + attributeValue.value = $el.find("input").val() + + $scope.$apply -> + $ctrl.updateAttributeValue(attributeValue).then -> + render(attributeValue, false) + + $el.on "keyup", "input[name=description]", (event) -> + if event.keyCode == 13 + submit(event) + else if event.keyCode == 27 + render(attributeValue, false) + + ## Actions (on view mode) + $el.on "click", ".custom-field-value.read-mode", -> + return if not isEditable() + render(attributeValue, true) + $el.find("input[name='description']").focus().select() + + $el.on "click", "a.icon-edit", (event) -> + event.preventDefault() + render(attributeValue, true) + $el.find("input[name='description']").focus().select() + + ## Actions (on edit mode) + submit = debounce 2000, (event) => + event.preventDefault() + saveAttributeValue() + + $el.on "submit", "form", submit + $el.on "click", "a.icon-floppy", submit + + $scope.$on "$destroy", -> + $el.off() + + # Bootstrap + attributeValue = $scope.$eval($attrs.tgCustomAttributeValue) + render(attributeValue) + + return { + link: link + require: "^tgCustomAttributesValues" + restrict: "AE" + } + +module.directive("tgCustomAttributeValue", ["$tgTemplate", CustomAttributeValueDirective]) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 9f067e78..3c84802c 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -102,6 +102,11 @@ urls = { "attachments/task": "/tasks/attachments" "attachments/wiki_page": "/wiki/attachments" + # Custom field values + "custom-attributes-values/userstory": "/userstories/custom-attributes-values" + "custom-attributes-values/issue": "/issues/custom-attributes-values" + "custom-attributes-values/task": "/tasks/custom-attributes-values" + # Feedback "feedback": "/feedback" @@ -145,6 +150,7 @@ module.run([ "$tgWikiResourcesProvider", "$tgSearchResourcesProvider", "$tgAttachmentsResourcesProvider", + "$tgCustomAttributesValuesResourcesProvider", "$tgMdRenderResourcesProvider", "$tgHistoryResourcesProvider", "$tgKanbanResourcesProvider", diff --git a/app/coffee/modules/resources/custom-field-values.coffee b/app/coffee/modules/resources/custom-field-values.coffee new file mode 100644 index 00000000..cb0415b3 --- /dev/null +++ b/app/coffee/modules/resources/custom-field-values.coffee @@ -0,0 +1,35 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# File: modules/resources/custom-field-values.coffee +### + +taiga = @.taiga + +resourceProvider = ($rootScope, $config, $urls, $model, $repo, $auth, $q) -> + service = {} + + service.get = (type, objectId) -> + return $repo.queryOne("custom-attributes-values/#{type}", objectId) + + return (instance) -> + instance.customAttributesValues = service + +module = angular.module("taigaResources") +module.factory("$tgCustomAttributesValuesResourcesProvider", ["$rootScope", "$tgConfig", "$tgUrls", "$tgModel", "$tgRepo", + "$tgAuth", "$q", resourceProvider]) diff --git a/app/partials/custom-attributes/custom-attribute-value-edit.jade b/app/partials/custom-attributes/custom-attribute-value-edit.jade new file mode 100644 index 00000000..675fd11e --- /dev/null +++ b/app/partials/custom-attributes/custom-attribute-value-edit.jade @@ -0,0 +1,14 @@ +form.custom-field-single + div.custom-field-data + label.custom-field-name(for="custom-field-description") + <%- name %> + <% if (description){ %> + span.custom-field-description + <%- description %> + <% } %> + + div.custom-field-value + input#custom-field-description(name="description", type="text", placeholder!="<%- value %>") + + div.custom-field-options + a.icon.icon-floppy(href="", title="Save Custom Field") diff --git a/app/partials/custom-attributes/custom-attribute-value.jade b/app/partials/custom-attributes/custom-attribute-value.jade new file mode 100644 index 00000000..b2502c04 --- /dev/null +++ b/app/partials/custom-attributes/custom-attribute-value.jade @@ -0,0 +1,17 @@ +div.custom-field-single + div.custom-field-data + span.custom-field-name + <%- name %> + <% if (description){ %> + span.custom-field-description + <%- description %> + <% } %> + + div.custom-field-value.read-mode + span + <%- value %> + + <% if (isEditable) { %> + div.custom-field-options + a.icon.icon-edit(href="", title="Edit Custom Field") + <% } %> diff --git a/app/partials/custom-attributes/custom-attributes-values.jade b/app/partials/custom-attributes/custom-attributes-values.jade new file mode 100644 index 00000000..7062caed --- /dev/null +++ b/app/partials/custom-attributes/custom-attributes-values.jade @@ -0,0 +1,7 @@ +section.duty-custom-fields(ng-show="ctrl.customAttributesValues") + div.custom-fields-header + span Custom Fields + // Remove .open class on click on this button in both .icon and .custom-fields-body to close + a.icon.icon-arrow-bottom(class!="<% if (!collapsed) { %>open<% } %>") + div.custom-fields-body(class!="<% if (!collapsed) { %>open<% } %>") + div(ng-repeat="att in ctrl.customAttributes", tg-custom-attribute-value="ctrl.getAttributeValue(att)", required-edition-perm!="<%- requiredEditionPerm %>") diff --git a/app/partials/includes/modules/common/custom-fields.jade b/app/partials/includes/modules/common/custom-fields.jade deleted file mode 100644 index cf2be606..00000000 --- a/app/partials/includes/modules/common/custom-fields.jade +++ /dev/null @@ -1,21 +0,0 @@ -section.duty-custom-fields - div.custom-fields-header - span Custom Fields - // Remove .open class on click on this button in both .icon and .custom-fields-body to close - a.icon.icon-arrow-bottom.open - div.custom-fields-body.open - div.custom-field-single - div.custom-field-data - span.custom-field-name Name - span.custom-field-description This is the description - div.custom-field-value - span Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec. - div.custom-field-options - a.icon.icon-edit(href="", title="Edit Custom Field") - form.custom-field-single - div.custom-field-data - label.custom-field-name(for="custom-field-description") Name - div.custom-field-value - input#custom-field-description(type="text", placeholder="This is the description") - div.custom-field-options - a.icon.icon-floppy(href="", title="Save Custom Field") diff --git a/app/partials/issue/issues-detail.jade b/app/partials/issue/issues-detail.jade index 8c90aeb9..ab528bc9 100644 --- a/app/partials/issue/issues-detail.jade +++ b/app/partials/issue/issues-detail.jade @@ -35,6 +35,9 @@ div.wrapper(ng-controller="IssueDetailController as ctrl", section.duty-content(tg-editable-description, ng-model="issue", required-perm="modify_issue") + // Custom Fields + tg-custom-attributes-values(ng-model="issue", type="issue", project="project", required-edition-perm="modify_issue") + tg-attachments(ng-model="issue", type="issue") tg-history(ng-model="issue", type="issue") diff --git a/app/partials/task/task-detail.jade b/app/partials/task/task-detail.jade index dd0075a0..4d149454 100644 --- a/app/partials/task/task-detail.jade +++ b/app/partials/task/task-detail.jade @@ -40,6 +40,9 @@ div.wrapper(ng-controller="TaskDetailController as ctrl", section.duty-content(tg-editable-description, ng-model="task", required-perm="modify_task") + // Custom Fields + tg-custom-attributes-values(ng-model="task", type="task", project="project", required-edition-perm="modify_task") + tg-attachments(ng-model="task", type="task") tg-history(ng-model="task", type="task") diff --git a/app/partials/us/us-detail.jade b/app/partials/us/us-detail.jade index 3c21553c..8c8708a1 100644 --- a/app/partials/us/us-detail.jade +++ b/app/partials/us/us-detail.jade @@ -39,8 +39,8 @@ div.wrapper(ng-controller="UserStoryDetailController as ctrl", section.duty-content(tg-editable-description, ng-model="us", required-perm="modify_us") - // IF Custom Fields - include ../includes/modules/common/custom-fields + // Custom Fields + tg-custom-attributes-values(ng-model="us", type="userstory", project="project", required-edition-perm="modify_us") include ../includes/modules/related-tasks From c919ae22d45d266c952328198a9f7ea7df9bc43c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 19 Feb 2015 13:58:07 +0100 Subject: [PATCH 09/19] Show custom attributes when project have got --- app/partials/custom-attributes/custom-attributes-values.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/partials/custom-attributes/custom-attributes-values.jade b/app/partials/custom-attributes/custom-attributes-values.jade index 7062caed..cadbb100 100644 --- a/app/partials/custom-attributes/custom-attributes-values.jade +++ b/app/partials/custom-attributes/custom-attributes-values.jade @@ -1,4 +1,4 @@ -section.duty-custom-fields(ng-show="ctrl.customAttributesValues") +section.duty-custom-fields(ng-show="ctrl.customAttributes.length") div.custom-fields-header span Custom Fields // Remove .open class on click on this button in both .icon and .custom-fields-body to close From 74331bbbc0d380129b3ddfeed88bab6b8016861b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 20 Feb 2015 08:57:11 +0100 Subject: [PATCH 10/19] Fixing issuaes when displaying custom fields on detail --- app/coffee/modules/common/custom-field-values.coffee | 10 ++++++---- .../custom-attributes/custom-attribute-value-edit.jade | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee index 23689430..26aabacf 100644 --- a/app/coffee/modules/common/custom-field-values.coffee +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -116,21 +116,22 @@ CustomAttributesValuesDirective = ($templates, $storage) -> module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", CustomAttributesValuesDirective]) -CustomAttributeValueDirective = ($template) -> +CustomAttributeValueDirective = ($template, $selectedText) -> template = $template.get("custom-attributes/custom-attribute-value.html", true) templateEdit = $template.get("custom-attributes/custom-attribute-value-edit.html", true) link = ($scope, $el, $attrs, $ctrl) -> render = (attributeValue, edit=false) -> + value = attributeValue.value ctx = { id: attributeValue.id name: attributeValue.name description: attributeValue.description - value: attributeValue.value + value: value isEditable: isEditable() } - if edit + if edit or not value html = templateEdit(ctx) else html = template(ctx) @@ -158,6 +159,7 @@ CustomAttributeValueDirective = ($template) -> ## Actions (on view mode) $el.on "click", ".custom-field-value.read-mode", -> return if not isEditable() + return if $selectedText.get().length render(attributeValue, true) $el.find("input[name='description']").focus().select() @@ -187,4 +189,4 @@ CustomAttributeValueDirective = ($template) -> restrict: "AE" } -module.directive("tgCustomAttributeValue", ["$tgTemplate", CustomAttributeValueDirective]) +module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", CustomAttributeValueDirective]) diff --git a/app/partials/custom-attributes/custom-attribute-value-edit.jade b/app/partials/custom-attributes/custom-attribute-value-edit.jade index 675fd11e..63fdd4b7 100644 --- a/app/partials/custom-attributes/custom-attribute-value-edit.jade +++ b/app/partials/custom-attributes/custom-attribute-value-edit.jade @@ -8,7 +8,7 @@ form.custom-field-single <% } %> div.custom-field-value - input#custom-field-description(name="description", type="text", placeholder!="<%- value %>") + input#custom-field-description(name="description", type="text", value!="<%- value %>") div.custom-field-options a.icon.icon-floppy(href="", title="Save Custom Field") From e1c0e2e7afa3bb0e7fd48ca1098fdd143b8d4a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 20 Feb 2015 08:57:49 +0100 Subject: [PATCH 11/19] Edit basic custom field style and visual behavior --- .../modules/common/custom-field-values.coffee | 2 ++ app/styles/modules/common/custom-fields.scss | 13 +++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee index 26aabacf..231929a9 100644 --- a/app/coffee/modules/common/custom-field-values.coffee +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -145,6 +145,7 @@ CustomAttributeValueDirective = ($template, $selectedText) -> saveAttributeValue = -> attributeValue.value = $el.find("input").val() + $el.find('.custom-field-single').removeClass('editable') $scope.$apply -> $ctrl.updateAttributeValue(attributeValue).then -> @@ -167,6 +168,7 @@ CustomAttributeValueDirective = ($template, $selectedText) -> event.preventDefault() render(attributeValue, true) $el.find("input[name='description']").focus().select() + $el.find('.custom-field-single').addClass('editable') ## Actions (on edit mode) submit = debounce 2000, (event) => diff --git a/app/styles/modules/common/custom-fields.scss b/app/styles/modules/common/custom-fields.scss index 01105689..2856188a 100644 --- a/app/styles/modules/common/custom-fields.scss +++ b/app/styles/modules/common/custom-fields.scss @@ -23,19 +23,20 @@ @include slide(1000px, hidden, $min: 0); } .custom-field-single { - align-content: center; - align-items: center; border-bottom: 1px solid $whitish; display: flex; padding: 1rem; - &:last-child { - border-bottom: 0; - } &:hover { .custom-field-options { opacity: 1; } } + &.editable { + .custom-field-options { + align-items: center; + display: flex; + } + } .custom-field-options { opacity: 0; transition: opacity .2s linear; @@ -63,7 +64,7 @@ } .custom-field-value { flex: 1; - padding-right: 2rem; + padding: 0 1rem 0 2rem; } form { label { From 9f6f080a3932b69e2d011a6afe093f098c29fca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Fri, 20 Feb 2015 09:02:26 +0100 Subject: [PATCH 12/19] Fixed custom attribute edition template --- app/coffee/modules/common/custom-field-values.coffee | 2 -- app/partials/custom-attributes/custom-attribute-value-edit.jade | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee index 231929a9..26aabacf 100644 --- a/app/coffee/modules/common/custom-field-values.coffee +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -145,7 +145,6 @@ CustomAttributeValueDirective = ($template, $selectedText) -> saveAttributeValue = -> attributeValue.value = $el.find("input").val() - $el.find('.custom-field-single').removeClass('editable') $scope.$apply -> $ctrl.updateAttributeValue(attributeValue).then -> @@ -168,7 +167,6 @@ CustomAttributeValueDirective = ($template, $selectedText) -> event.preventDefault() render(attributeValue, true) $el.find("input[name='description']").focus().select() - $el.find('.custom-field-single').addClass('editable') ## Actions (on edit mode) submit = debounce 2000, (event) => diff --git a/app/partials/custom-attributes/custom-attribute-value-edit.jade b/app/partials/custom-attributes/custom-attribute-value-edit.jade index 63fdd4b7..d3c3aabc 100644 --- a/app/partials/custom-attributes/custom-attribute-value-edit.jade +++ b/app/partials/custom-attributes/custom-attribute-value-edit.jade @@ -1,4 +1,4 @@ -form.custom-field-single +form.custom-field-single.editable div.custom-field-data label.custom-field-name(for="custom-field-description") <%- name %> From 4d2fe26be9453474f127203f69ffa1b08c9a2cf6 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 20 Feb 2015 09:46:31 +0100 Subject: [PATCH 13/19] Fixing hash generation --- app/coffee/modules/common/custom-field-values.coffee | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee index 26aabacf..a9aecefd 100644 --- a/app/coffee/modules/common/custom-field-values.coffee +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -72,7 +72,8 @@ class CustomAttributesValuesController extends taiga.Controller CustomAttributesValuesDirective = ($templates, $storage) -> template = $templates.get("custom-attributes/custom-attributes-values.html", true) - collapsedHash = generateHash(["custom-attributes-collapsed"]) + collapsedHash = (type) -> + return generateHash(["custom-attributes-collapsed", type]) link = ($scope, $el, $attrs, $ctrls) -> $ctrl = $ctrls[0] @@ -83,8 +84,9 @@ CustomAttributesValuesDirective = ($templates, $storage) -> $ctrl.loadCustomAttributesValues() $el.on "click", ".custom-fields-header a", -> - collapsed = not($storage.get(collapsedHash) or false) - $storage.set(collapsedHash, collapsed) + hash = collapsedHash($attrs.type) + collapsed = not($storage.get(hash) or false) + $storage.set(hash, collapsed) if collapsed $el.find(".custom-fields-header a").removeClass("open") $el.find(".custom-fields-body").removeClass("open") @@ -96,7 +98,7 @@ CustomAttributesValuesDirective = ($templates, $storage) -> $el.off() templateFn = ($el, $attrs) -> - collapsed = $storage.get(collapsedHash) or false + collapsed = $storage.get(collapsedHash($attrs.type)) or false return template({ requiredEditionPerm: $attrs.requiredEditionPerm From 408c6f95ccb0f4f1e4b650aad7f9351f5174b8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Fri, 20 Feb 2015 16:25:12 +0100 Subject: [PATCH 14/19] Update, Delete and Order Custom Attributes --- app/coffee/app.coffee | 25 ++- .../modules/admin/project-values.coffee | 194 ++++++++++++++++++ app/coffee/modules/base.coffee | 2 + app/coffee/modules/resources.coffee | 10 +- ...coffee => custom-attributes-values.coffee} | 0 .../resources/custom-attributes.coffee | 48 +++++ .../admin-project-values-issue-extras.jade | 16 ++ .../admin-project-values-task-extras.jade | 16 ++ .../admin/admin-project-values-us-extras.jade | 12 +- .../modules/admin-submenu-project-values.jade | 10 + .../admin/admin-custom-attributes.jade | 46 +++++ .../modules/admin/admin-us-extras.jade | 29 --- ...elds.scss => admin-custom-attributes.scss} | 0 main-sass.js | 2 +- 14 files changed, 365 insertions(+), 45 deletions(-) rename app/coffee/modules/resources/{custom-field-values.coffee => custom-attributes-values.coffee} (100%) create mode 100644 app/coffee/modules/resources/custom-attributes.coffee create mode 100644 app/partials/admin/admin-project-values-issue-extras.jade create mode 100644 app/partials/admin/admin-project-values-task-extras.jade create mode 100644 app/partials/includes/modules/admin/admin-custom-attributes.jade delete mode 100644 app/partials/includes/modules/admin/admin-us-extras.jade rename app/styles/modules/admin/{admin-custom-fields.scss => admin-custom-attributes.scss} (100%) diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index aaeb7abd..76e82774 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -39,17 +39,23 @@ taiga.sessionId = taiga.generateUniqueSessionIdentifier() configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEventsProvider, tgLoaderProvider) -> $routeProvider.when("/", {templateUrl: "project/projects.html", resolve: {loader: tgLoaderProvider.add()}}) + $routeProvider.when("/project/:pslug/", {templateUrl: "project/project.html"}) - $routeProvider.when("/project/:pslug/backlog", - {templateUrl: "backlog/backlog.html", resolve: {loader: tgLoaderProvider.add()}}) - $routeProvider.when("/project/:pslug/taskboard/:sslug", - {templateUrl: "taskboard/taskboard.html", resolve: {loader: tgLoaderProvider.add()}}) + $routeProvider.when("/project/:pslug/search", {templateUrl: "search/search.html", reloadOnSearch: false}) + + $routeProvider.when("/project/:pslug/backlog", + {templateUrl: "backlog/backlog.html", resolve: {loader: tgLoaderProvider.add()}}) + $routeProvider.when("/project/:pslug/kanban", {templateUrl: "kanban/kanban.html", resolve: {loader: tgLoaderProvider.add()}}) + # Milestone + $routeProvider.when("/project/:pslug/taskboard/:sslug", + {templateUrl: "taskboard/taskboard.html", resolve: {loader: tgLoaderProvider.add()}}) + # User stories $routeProvider.when("/project/:pslug/us/:usref", {templateUrl: "us/us-detail.html", resolve: {loader: tgLoaderProvider.add()}}) @@ -74,7 +80,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/project/:pslug/issue/:issueref", {templateUrl: "issue/issues-detail.html", resolve: {loader: tgLoaderProvider.add()}}) - # Admin + # Admin - Project Profile $routeProvider.when("/project/:pslug/admin/project-profile/details", {templateUrl: "admin/admin-project-profile.html"}) $routeProvider.when("/project/:pslug/admin/project-profile/default-values", @@ -83,6 +89,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "admin/admin-project-modules.html"}) $routeProvider.when("/project/:pslug/admin/project-profile/export", {templateUrl: "admin/admin-project-export.html"}) + # Admin Project Values $routeProvider.when("/project/:pslug/admin/project-values/us-status", {templateUrl: "admin/admin-project-values-us-status.html"}) $routeProvider.when("/project/:pslug/admin/project-values/us-points", @@ -91,6 +98,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "admin/admin-project-values-us-extras.html"}) $routeProvider.when("/project/:pslug/admin/project-values/task-status", {templateUrl: "admin/admin-project-values-task-status.html"}) + $routeProvider.when("/project/:pslug/admin/project-values/task-extras", + {templateUrl: "admin/admin-project-values-task-extras.html"}) $routeProvider.when("/project/:pslug/admin/project-values/issue-status", {templateUrl: "admin/admin-project-values-issue-status.html"}) $routeProvider.when("/project/:pslug/admin/project-values/issue-types", @@ -99,10 +108,15 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "admin/admin-project-values-issue-priorities.html"}) $routeProvider.when("/project/:pslug/admin/project-values/issue-severities", {templateUrl: "admin/admin-project-values-issue-severities.html"}) + $routeProvider.when("/project/:pslug/admin/project-values/issue-extras", + {templateUrl: "admin/admin-project-values-issue-extras.html"}) + # Admin - Memberships $routeProvider.when("/project/:pslug/admin/memberships", {templateUrl: "admin/admin-memberships.html"}) + # Admin - Roles $routeProvider.when("/project/:pslug/admin/roles", {templateUrl: "admin/admin-roles.html"}) + # Admin - Third Parties $routeProvider.when("/project/:pslug/admin/third-parties/webhooks", {templateUrl: "admin/admin-third-parties-webhooks.html"}) $routeProvider.when("/project/:pslug/admin/third-parties/github", @@ -111,6 +125,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven {templateUrl: "admin/admin-third-parties-gitlab.html"}) $routeProvider.when("/project/:pslug/admin/third-parties/bitbucket", {templateUrl: "admin/admin-third-parties-bitbucket.html"}) + # Admin - Contrib Plugins $routeProvider.when("/project/:pslug/admin/contrib/:plugin", {templateUrl: "contrib/main.html"}) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index bee3d41c..6cd085af 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -320,3 +320,197 @@ ColorSelectionDirective = () -> } module.directive("tgColorSelection", ColorSelectionDirective) + + +############################################################################# +## Custom Attributes Controller +############################################################################# + +class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.PageMixin) + @.$inject = [ + "$scope", + "$rootScope", + "$tgRepo", + "$tgResources", + "$routeParams", + "$q", + "$tgLocation", + "$tgNavUrls", + "$appTitle", + ] + + constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appTitle) -> + @scope.project = {} + + promise = @.loadInitialData() + + promise.then () => + @appTitle.set("Project Custom Attributes - " + @scope.sectionName + " - " + @scope.project.name) + + promise.then null, @.onInitialDataError.bind(@) + + loadInitialData: => + promise = @repo.resolve({pslug: @params.pslug}).then (data) => + @scope.projectId = data.project + return data + + return promise.then( => @q.all([ + @.loadProject(), + @.loadCustomAttributes(), + ])) + + ######################### + # Project + ######################### + + loadProject: => + return @rs.projects.get(@scope.projectId).then (project) => + @scope.project = project + @scope.$emit('project:loaded', project) + return project + + + ######################### + # Custom Attribute + ######################### + + loadCustomAttributes: => + return @rs.customAttributes[@scope.type].list(@scope.projectId).then (customAttributes) => + @scope.customAttributes = customAttributes + @scope.maxOrder = _.max(customAttributes, "order").order + return customAttributes + + + moveCustomAttributes: (attrModel, newIndex) => + customAttributes = @scope.customAttributes + r = customAttributes.indexOf(attrModel) + customAttributes.splice(r, 1) + customAttributes.splice(newIndex, 0, attrModel) + + _.each customAttributes, (val, idx) -> + val.order = idx + + @repo.saveAll(customAttributes) + + deleteCustomAttribute: (attrModel) => + return @repo.remove(attrModel) + + saveCustomAttribute: (attrModel) => + return @repo.save(attrModel) + +module.controller("ProjectCustomAttributesController", ProjectCustomAttributesController) + + +############################################################################# +## Custom Attributes Directive +############################################################################# + +ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) -> + link = ($scope, $el, $attrs) -> + $ctrl = $el.controller() + + $scope.$on "$destroy", -> + $el.off() + + ################################## + # Drag & Drop + ################################## + sortableEl = $el.find(".js-sortable") + + sortableEl.sortable({ + handle: ".js-view-custom-field", + dropOnEmpty: true + revert: 400 + axis: "y" + }) + + sortableEl.on "sortstop", (event, ui) -> + itemEl = ui.item + itemAttr = itemEl.scope().attr + itemIndex = itemEl.index() + $ctrl.moveCustomAttributes(itemAttr, itemIndex) + + ################################## + # New custom attribute + ################################## + + ################################## + # Edit custom attribute + ################################## + + showEditForm = (formEl) -> + formEl.find(".js-view-custom-field").addClass("hidden") + formEl.find(".js-edit-custom-field").removeClass("hidden") + + hideEditForm = (formEl) -> + formEl.find(".js-edit-custom-field").addClass("hidden") + formEl.find(".js-view-custom-field").removeClass("hidden") + + revertChangesInCustomAttribute = (formEl) -> + $scope.$apply -> + formEl.scope().attr.revert() + + update = (formEl) -> + onSucces = -> + $ctrl.loadCustomAttributes() + hideEditForm(formEl) + $confirm.notify("success") + + onError = -> + $confirm.notify("error") + + attr = formEl.scope().attr + $ctrl.saveCustomAttribute(attr).then(onSucces, onError) + + $el.on "click", ".js-edit-custom-field-button", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + formEl = target.closest("form") + + showEditForm(formEl) + + $el.on "click", ".js-cancel-edit-custom-field-button", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + formEl = target.closest("form") + + hideEditForm(formEl) + revertChangesInCustomAttribute(formEl) + + $el.on "click", ".js-update-custom-field-button", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + formEl = target.closest("form") + + update(formEl) + + ################################## + # Delete custom attribute + ################################## + + deleteCustomAttribute = (formEl) -> + attr = formEl.scope().attr + + title = "Delete custom attribute" # i18n + message = attr.name + $confirm.askOnDelete(title, message).then (finish) -> + onSucces = -> + $ctrl.loadCustomAttributes().finally -> + finish() + + onError = -> + finish(false) + $confirm.notify("error", null, "We have not been able to delete '#{message}'.") + + $ctrl.deleteCustomAttribute(attr).then(onSucces, onError) + + $el.on "click", ".js-delete-custom-field-button", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + formEl = target.closest("form") + + deleteCustomAttribute(formEl) + + return {link: link} + +module.directive("tgProjectCustomAttributes", ["$log", "$tgConfirm", "animationFrame", ProjectCustomAttributesDirective]) diff --git a/app/coffee/modules/base.coffee b/app/coffee/modules/base.coffee index 9324f20c..2ae8983e 100644 --- a/app/coffee/modules/base.coffee +++ b/app/coffee/modules/base.coffee @@ -88,10 +88,12 @@ urls = { "project-admin-project-values-us-points": "/project/:project/admin/project-values/us-points" "project-admin-project-values-us-extras": "/project/:project/admin/project-values/us-extras" "project-admin-project-values-task-status": "/project/:project/admin/project-values/task-status" + "project-admin-project-values-task-extras": "/project/:project/admin/project-values/task-extras" "project-admin-project-values-issue-status": "/project/:project/admin/project-values/issue-status" "project-admin-project-values-issue-types": "/project/:project/admin/project-values/issue-types" "project-admin-project-values-issue-priorities": "/project/:project/admin/project-values/issue-priorities" "project-admin-project-values-issue-severities": "/project/:project/admin/project-values/issue-severities" + "project-admin-project-values-issue-extras": "/project/:project/admin/project-values/issue-extras" "project-admin-memberships": "/project/:project/admin/memberships" "project-admin-roles": "/project/:project/admin/roles" "project-admin-third-parties-webhooks": "/project/:project/admin/third-parties/webhooks" diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 3c84802c..a519d869 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -102,8 +102,13 @@ urls = { "attachments/task": "/tasks/attachments" "attachments/wiki_page": "/wiki/attachments" + # Custom Attributess + "custom-attributes/us": "/userstory-custom-attributes" + "custom-attributes/issue": "/issue-custom-attributes" + "custom-attributes/task": "/task-custom-attributes" + # Custom field values - "custom-attributes-values/userstory": "/userstories/custom-attributes-values" + "custom-attributes-values/us": "/userstories/custom-attributes-values" "custom-attributes-values/issue": "/issues/custom-attributes-values" "custom-attributes-values/task": "/tasks/custom-attributes-values" @@ -138,6 +143,8 @@ module.run([ "$log", "$tgResources", "$tgProjectsResourcesProvider", + "$tgCustomAttributesResourcesProvider", + "$tgCustomAttributesValuesResourcesProvider", "$tgMembershipsResourcesProvider", "$tgNotifyPoliciesResourcesProvider", "$tgInvitationsResourcesProvider", @@ -150,7 +157,6 @@ module.run([ "$tgWikiResourcesProvider", "$tgSearchResourcesProvider", "$tgAttachmentsResourcesProvider", - "$tgCustomAttributesValuesResourcesProvider", "$tgMdRenderResourcesProvider", "$tgHistoryResourcesProvider", "$tgKanbanResourcesProvider", diff --git a/app/coffee/modules/resources/custom-field-values.coffee b/app/coffee/modules/resources/custom-attributes-values.coffee similarity index 100% rename from app/coffee/modules/resources/custom-field-values.coffee rename to app/coffee/modules/resources/custom-attributes-values.coffee diff --git a/app/coffee/modules/resources/custom-attributes.coffee b/app/coffee/modules/resources/custom-attributes.coffee new file mode 100644 index 00000000..dde0e637 --- /dev/null +++ b/app/coffee/modules/resources/custom-attributes.coffee @@ -0,0 +1,48 @@ +### +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino Garcia +# Copyright (C) 2014 David Barragán Merino +# +# 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 . +# +# File: modules/resources/projects.coffee +### + + +taiga = @.taiga +sizeFormat = @.taiga.sizeFormat + + +resourceProvider = ($repo) -> + _list = (projectId, resource) -> + return $repo.queryMany(resource, {project: projectId}) + + service = { + us:{ + list: (projectId) -> _list(projectId, "custom-attributes/us") + } + task:{ + list: (projectId) -> _list(projectId, "custom-attributes/task") + } + issue: { + list: (projectId) -> _list(projectId, "custom-attributes/issue") + } + } + + return (instance) -> + instance.customAttributes = service + + +module = angular.module("taigaResources") +module.factory("$tgCustomAttributesResourcesProvider", ["$tgRepo", resourceProvider]) diff --git a/app/partials/admin/admin-project-values-issue-extras.jade b/app/partials/admin/admin-project-values-issue-extras.jade new file mode 100644 index 00000000..dc187831 --- /dev/null +++ b/app/partials/admin/admin-project-values-issue-extras.jade @@ -0,0 +1,16 @@ +div.wrapper(tg-project-custom-attributes, ng-controller="ProjectCustomAttributesController as ctrl", + ng-init="section='admin'; type='issue'; sectionName='Issue extra'") + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + include ../includes/modules/admin-menu + + sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-issue-extras") + include ../includes/modules/admin-submenu-project-values + + section.main.admin-common + include ../includes/components/mainTitle + p.admin-subtitle Specify here issue custom fields. The new field will appear on your issue detail. + + div.custom-field-options + a.button.button-green.js-add-custom-field(href="",title="Add a custom field in issues") Add custom field + + include ../includes/modules/admin/admin-custom-attributes diff --git a/app/partials/admin/admin-project-values-task-extras.jade b/app/partials/admin/admin-project-values-task-extras.jade new file mode 100644 index 00000000..3e64934a --- /dev/null +++ b/app/partials/admin/admin-project-values-task-extras.jade @@ -0,0 +1,16 @@ +div.wrapper(tg-project-custom-attributes, ng-controller="ProjectCustomAttributesController as ctrl", + ng-init="section='admin'; type='task'; sectionName='Task extra'") + sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") + include ../includes/modules/admin-menu + + sidebar.menu-tertiary.sidebar(tg-admin-navigation="values-task-extras") + include ../includes/modules/admin-submenu-project-values + + section.main.admin-common + include ../includes/components/mainTitle + p.admin-subtitle Specify here task custom fields. The new field will appear on your task detail. + + div.custom-field-options + a.button.button-green.js-add-custom-field(href="",title="Add a custom field in tasks") Add custom field + + include ../includes/modules/admin/admin-custom-attributes diff --git a/app/partials/admin/admin-project-values-us-extras.jade b/app/partials/admin/admin-project-values-us-extras.jade index d5674a0b..ae41d124 100644 --- a/app/partials/admin/admin-project-values-us-extras.jade +++ b/app/partials/admin/admin-project-values-us-extras.jade @@ -1,6 +1,5 @@ -div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl", - ng-init="section='admin'; resource='userstories'; type='points'; sectionName='Us points'", - type="points") +div.wrapper(tg-project-custom-attributes, ng-controller="ProjectCustomAttributesController as ctrl", + ng-init="section='admin'; type='us'; sectionName='US extra'") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu @@ -12,9 +11,6 @@ div.wrapper(tg-project-values, ng-controller="ProjectValuesController as ctrl", p.admin-subtitle Specify here user story custom fields. The new field will appear on your user story detail. div.custom-field-options - a.button.button-green.add-custom-field(href="",title="Add a custom field in user stories") Add custom field + a.button.button-green.js-add-custom-field(href="",title="Add a custom field in user stories") Add custom field - include ../includes/modules/admin/admin-us-extras - -div.lightbox.lightbox-generic-notion.notion-admin-project-values-us-points(id="notion-admin-project-values-us-points", tg-lb-notion) - include ../includes/modules/help-notions/lightbox-notion-admin-project-values-us-points + include ../includes/modules/admin/admin-custom-attributes diff --git a/app/partials/includes/modules/admin-submenu-project-values.jade b/app/partials/includes/modules/admin-submenu-project-values.jade index 681b6520..31d45ae9 100644 --- a/app/partials/includes/modules/admin-submenu-project-values.jade +++ b/app/partials/includes/modules/admin-submenu-project-values.jade @@ -24,6 +24,11 @@ section.admin-submenu span.title Task statuses span.icon.icon-arrow-right + li#adminmenu-values-task-extras + a(href="", tg-nav="project-admin-project-values-task-extras:project=project.slug") + span.title Task extras + span.icon.icon-arrow-right + li#adminmenu-values-issue-status a(href="", tg-nav="project-admin-project-values-issue-status:project=project.slug") span.title Issue statuses @@ -43,3 +48,8 @@ section.admin-submenu a(href="", tg-nav="project-admin-project-values-issue-severities:project=project.slug") span.title Issue Severities span.icon.icon-arrow-right + + li#adminmenu-values-issue-extras + a(href="", tg-nav="project-admin-project-values-issue-extras:project=project.slug") + span.title Issue extras + span.icon.icon-arrow-right diff --git a/app/partials/includes/modules/admin/admin-custom-attributes.jade b/app/partials/includes/modules/admin/admin-custom-attributes.jade new file mode 100644 index 00000000..f9f9123f --- /dev/null +++ b/app/partials/includes/modules/admin/admin-custom-attributes.jade @@ -0,0 +1,46 @@ +section.custom-fields-table.basic-table + div.table-header + div.row + div.custom-name + span Name + div.custom-description + span Description + div.custom-options + + div.table-body + div.js-sortable + form.js-form(ng-repeat="attr in customAttributes track by attr.id") + div.row.single-custom-field.js-view-custom-field + span.icon.icon-drag-v + div.custom-name + span {{ attr.name }} + div.custom-description + span {{ attr.description }} + div.custom-options + div.custom-options-wrapper + a.js-edit-custom-field-button.icon.icon-edit(href="", title="Edit Custom Field") + a.js-delete-custom-field-button.icon.icon-delete(href="", title="Delete Custom Field") + + div.row.single-custom-field.js-edit-custom-field.hidden + fieldset.custom-name + input(type="text", placeholder="Set your custom field name", ng-model="attr.name", + data-required="true" data-maxlength="64") + fieldset.custom-description + input(type="text", placeholder="Set your custom field description", ng-model="attr.description") + + fieldset.custom-options + div.custom-options-wrapper + a.js-update-custom-field-button.icon.icon-floppy(href="", title="Update Custom Field") + a.js-cancel-edit-custom-field-button.icon.icon-delete(href="", title="Cancel edition") + + form.row.single-custom-field.js-new-custom-field.hidden(tg-new-custom-attribute) + fieldset.custom-name + input(type="text", placeholder="Set your custom field name", ng-model="newAttr.name", + data-required="true", data-maxlength="64") + fieldset.custom-description + input(type="text", placeholder="Set your custom field description", ng-model="newAttr.description") + + fieldset.custom-options + div.custom-options-wrapper + a.js-save-custom-field-button.icon.icon-floppy(href="", title="Save Custom Field") + a.js-cancel-new-custom-field-button.icon.icon-delete(href="", title="Cancel creation") diff --git a/app/partials/includes/modules/admin/admin-us-extras.jade b/app/partials/includes/modules/admin/admin-us-extras.jade deleted file mode 100644 index 94b2fef8..00000000 --- a/app/partials/includes/modules/admin/admin-us-extras.jade +++ /dev/null @@ -1,29 +0,0 @@ -section.custom-fields-table.basic-table - div.table-header - div.row - div.custom-name - span Name - div.custom-description - span Description - div.custom-options - div.table-body - div.row.single-custom-field - span.icon.icon-drag-v - div.custom-name - span Custom field name - div.custom-description - span Custom field looong Description - div.custom-options - div.custom-options-wrapper - a.edit-webhook.icon.icon-edit(href="", title="Edit Custom Field") - a.delete-webhook.icon.icon-delete(href="", title="Delete Custom Field") - form.row.single-custom-field - fieldset.custom-name - input(type="text", placeholder="Set your custom field name") - fieldset.custom-description - input(type="text", placeholder="Set your custom field description") - fieldset.custom-options - div.custom-options-wrapper - a.edit-webhook.icon.icon-floppy(href="", title="Save Custom Field") - a.delete-webhook.icon.icon-delete(href="", title="Delete Custom Field") - diff --git a/app/styles/modules/admin/admin-custom-fields.scss b/app/styles/modules/admin/admin-custom-attributes.scss similarity index 100% rename from app/styles/modules/admin/admin-custom-fields.scss rename to app/styles/modules/admin/admin-custom-attributes.scss diff --git a/main-sass.js b/main-sass.js index f729d1e6..e8314453 100644 --- a/main-sass.js +++ b/main-sass.js @@ -126,7 +126,7 @@ exports.files = function () { 'modules/admin/admin-membership-table', 'modules/admin/admin-project-profile', 'modules/admin/default-values', - 'modules/admin/admin-custom-fields', + 'modules/admin/admin-custom-attributes', 'modules/admin/project-values', 'modules/admin/third-parties', 'modules/admin/admin-third-parties-webhooks', From c8ad480846ff2693f38599ea72476bfa6b5f983b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Tue, 24 Feb 2015 08:53:10 +0100 Subject: [PATCH 15/19] Create new custom attributes --- .../modules/admin/project-values.coffee | 137 +++++++++++++++--- .../admin-project-values-issue-extras.jade | 3 +- .../admin-project-values-task-extras.jade | 3 +- .../admin/admin-project-values-us-extras.jade | 3 +- .../admin/admin-custom-attributes.jade | 18 ++- 5 files changed, 135 insertions(+), 29 deletions(-) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index 6cd085af..3b4882cf 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -369,7 +369,6 @@ class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.Pa @scope.$emit('project:loaded', project) return project - ######################### # Custom Attribute ######################### @@ -380,6 +379,14 @@ class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.Pa @scope.maxOrder = _.max(customAttributes, "order").order return customAttributes + createCustomAttribute: (attrValues) => + return @repo.create("custom-attributes/#{@scope.type}", attrValues) + + saveCustomAttribute: (attrModel) => + return @repo.save(attrModel) + + deleteCustomAttribute: (attrModel) => + return @repo.remove(attrModel) moveCustomAttributes: (attrModel, newIndex) => customAttributes = @scope.customAttributes @@ -392,11 +399,6 @@ class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.Pa @repo.saveAll(customAttributes) - deleteCustomAttribute: (attrModel) => - return @repo.remove(attrModel) - - saveCustomAttribute: (attrModel) => - return @repo.save(attrModel) module.controller("ProjectCustomAttributesController", ProjectCustomAttributesController) @@ -434,6 +436,88 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) -> # New custom attribute ################################## + showCreateForm = -> + $el.find(".js-new-custom-field").removeClass("hidden") + + hideCreateForm = -> + $el.find(".js-new-custom-field").addClass("hidden") + + showAddButton = -> + $el.find(".js-add-custom-field-button").removeClass("hidden") + + hideAddButton = -> + $el.find(".js-add-custom-field-button").addClass("hidden") + + showCancelButton = -> + $el.find(".js-cancel-new-custom-field-button").removeClass("hidden") + + hideCancelButton = -> + $el.find(".js-cancel-new-custom-field-button").addClass("hidden") + + resetNewAttr = -> + $scope.newAttr = {} + + create = (formEl) -> + form = formEl.checksley() + return if not form.validate() + + onSucces = => + $ctrl.loadCustomAttributes() + hideCreateForm() + resetNewAttr() + $confirm.notify("success") + + onError = (data) => + form.setErrors(data) + $confirm.notify("error") + + attr = $scope.newAttr + attr.project = $scope.projectId + attr.order = if $scope.maxOrder then $scope.maxOrder + 1 else 1 + + $ctrl.createCustomAttribute(attr).then(onSucces, onError) + + cancelCreate = -> + hideCreateForm() + resetNewAttr() + + $scope.$watch "customAttributes", (customAttributes) -> + return if not customAttributes + + if customAttributes.length == 0 + hideCancelButton() + hideAddButton() + showCreateForm() + else + hideCreateForm() + showAddButton() + showCancelButton() + + $el.on "click", ".js-add-custom-field-button", (event) -> + event.preventDefault() + + showCreateForm() + + $el.on "click", ".js-create-custom-field-button", debounce 2000, (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + formEl = target.closest("form") + + create(formEl) + + $el.on "click", ".js-cancel-new-custom-field-button", (event) -> + event.preventDefault() + + cancelCreate() + + $el.on "keyup", ".js-new-custom-field input", (event) -> + if event.keyCode == 13 # Enter + target = angular.element(event.currentTarget) + formEl = target.closest("form") + create(formEl) + else if event.keyCode == 27 # Esc + cancelCreate() + ################################## # Edit custom attribute ################################## @@ -451,17 +535,25 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) -> formEl.scope().attr.revert() update = (formEl) -> - onSucces = -> + form = formEl.checksley() + return if not form.validate() + + onSucces = => $ctrl.loadCustomAttributes() hideEditForm(formEl) $confirm.notify("success") - onError = -> + onError = (data) => + form.setErrors(data) $confirm.notify("error") attr = formEl.scope().attr $ctrl.saveCustomAttribute(attr).then(onSucces, onError) + cancelUpdate = (formEl) -> + hideEditForm(formEl) + revertChangesInCustomAttribute(formEl) + $el.on "click", ".js-edit-custom-field-button", (event) -> event.preventDefault() target = angular.element(event.currentTarget) @@ -469,21 +561,30 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) -> showEditForm(formEl) - $el.on "click", ".js-cancel-edit-custom-field-button", (event) -> - event.preventDefault() - target = angular.element(event.currentTarget) - formEl = target.closest("form") - - hideEditForm(formEl) - revertChangesInCustomAttribute(formEl) - - $el.on "click", ".js-update-custom-field-button", (event) -> + $el.on "click", ".js-update-custom-field-button", debounce 2000, (event) -> event.preventDefault() target = angular.element(event.currentTarget) formEl = target.closest("form") update(formEl) + $el.on "click", ".js-cancel-edit-custom-field-button", (event) -> + event.preventDefault() + target = angular.element(event.currentTarget) + formEl = target.closest("form") + + cancelUpdate(formEl) + + $el.on "keyup", ".js-edit-custom-field input", (event) -> + if event.keyCode == 13 # Enter + target = angular.element(event.currentTarget) + formEl = target.closest("form") + update(formEl) + else if event.keyCode == 27 # Esc + target = angular.element(event.currentTarget) + formEl = target.closest("form") + cancelUpdate(formEl) + ################################## # Delete custom attribute ################################## @@ -504,7 +605,7 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) -> $ctrl.deleteCustomAttribute(attr).then(onSucces, onError) - $el.on "click", ".js-delete-custom-field-button", (event) -> + $el.on "click", ".js-delete-custom-field-button", debounce 2000, (event) -> event.preventDefault() target = angular.element(event.currentTarget) formEl = target.closest("form") diff --git a/app/partials/admin/admin-project-values-issue-extras.jade b/app/partials/admin/admin-project-values-issue-extras.jade index dc187831..9335bfa3 100644 --- a/app/partials/admin/admin-project-values-issue-extras.jade +++ b/app/partials/admin/admin-project-values-issue-extras.jade @@ -11,6 +11,7 @@ div.wrapper(tg-project-custom-attributes, ng-controller="ProjectCustomAttributes p.admin-subtitle Specify here issue custom fields. The new field will appear on your issue detail. div.custom-field-options - a.button.button-green.js-add-custom-field(href="",title="Add a custom field in issues") Add custom field + a.button.button-green.js-add-custom-field-button(href="",title="Add a custom field in issues") + | Add custom field include ../includes/modules/admin/admin-custom-attributes diff --git a/app/partials/admin/admin-project-values-task-extras.jade b/app/partials/admin/admin-project-values-task-extras.jade index 3e64934a..95b50a1d 100644 --- a/app/partials/admin/admin-project-values-task-extras.jade +++ b/app/partials/admin/admin-project-values-task-extras.jade @@ -11,6 +11,7 @@ div.wrapper(tg-project-custom-attributes, ng-controller="ProjectCustomAttributes p.admin-subtitle Specify here task custom fields. The new field will appear on your task detail. div.custom-field-options - a.button.button-green.js-add-custom-field(href="",title="Add a custom field in tasks") Add custom field + a.button.button-green.js-add-custom-field-button(href="",title="Add a custom field in tasks") + | Add custom field include ../includes/modules/admin/admin-custom-attributes diff --git a/app/partials/admin/admin-project-values-us-extras.jade b/app/partials/admin/admin-project-values-us-extras.jade index ae41d124..55523d97 100644 --- a/app/partials/admin/admin-project-values-us-extras.jade +++ b/app/partials/admin/admin-project-values-us-extras.jade @@ -11,6 +11,7 @@ div.wrapper(tg-project-custom-attributes, ng-controller="ProjectCustomAttributes p.admin-subtitle Specify here user story custom fields. The new field will appear on your user story detail. div.custom-field-options - a.button.button-green.js-add-custom-field(href="",title="Add a custom field in user stories") Add custom field + a.button.button-green.js-add-custom-field-button(href="",title="Add a custom field in user stories") + | Add custom field include ../includes/modules/admin/admin-custom-attributes diff --git a/app/partials/includes/modules/admin/admin-custom-attributes.jade b/app/partials/includes/modules/admin/admin-custom-attributes.jade index f9f9123f..318a60be 100644 --- a/app/partials/includes/modules/admin/admin-custom-attributes.jade +++ b/app/partials/includes/modules/admin/admin-custom-attributes.jade @@ -23,24 +23,26 @@ section.custom-fields-table.basic-table div.row.single-custom-field.js-edit-custom-field.hidden fieldset.custom-name - input(type="text", placeholder="Set your custom field name", ng-model="attr.name", - data-required="true" data-maxlength="64") + input(type="text", name="name", placeholder="Set your custom field name", + ng-model="attr.name", data-required="true" data-maxlength="64") fieldset.custom-description - input(type="text", placeholder="Set your custom field description", ng-model="attr.description") + input(type="text", name="description", placeholder="Set your custom field description", + ng-model="attr.description") fieldset.custom-options div.custom-options-wrapper a.js-update-custom-field-button.icon.icon-floppy(href="", title="Update Custom Field") a.js-cancel-edit-custom-field-button.icon.icon-delete(href="", title="Cancel edition") - form.row.single-custom-field.js-new-custom-field.hidden(tg-new-custom-attribute) + form.row.single-custom-field.js-new-custom-field.hidden fieldset.custom-name - input(type="text", placeholder="Set your custom field name", ng-model="newAttr.name", - data-required="true", data-maxlength="64") + input(type="text", name="name", placeholder="Set your custom field name", + ng-model="newAttr.name", data-required="true", data-maxlength="64") fieldset.custom-description - input(type="text", placeholder="Set your custom field description", ng-model="newAttr.description") + input(type="text", name="description", placeholder="Set your custom field description", + ng-model="newAttr.description") fieldset.custom-options div.custom-options-wrapper - a.js-save-custom-field-button.icon.icon-floppy(href="", title="Save Custom Field") + a.js-create-custom-field-button.icon.icon-floppy(href="", title="Save Custom Field") a.js-cancel-new-custom-field-button.icon.icon-delete(href="", title="Cancel creation") From 08f0a2262a9959f1ebb2ddd8713fe8336baa4f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 26 Feb 2015 12:34:48 +0100 Subject: [PATCH 16/19] Fix userstory custom attributes values --- .../modules/common/custom-field-values.coffee | 3 ++- app/coffee/modules/resources.coffee | 4 ++-- .../resources/custom-attributes-values.coffee | 21 +++++++++++++------ .../resources/custom-attributes.coffee | 4 ++-- .../admin/admin-project-values-us-extras.jade | 2 +- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee index a9aecefd..bb8c4042 100644 --- a/app/coffee/modules/common/custom-field-values.coffee +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -47,7 +47,7 @@ class CustomAttributesValuesController extends taiga.Controller loadCustomAttributesValues: -> return @.customAttributesValues if not @.objectId - return @rs.customAttributesValues.get(@.type, @.objectId).then (customAttributesValues) => + return @rs.customAttributesValues[@.type].get(@.objectId).then (customAttributesValues) => @.customAttributes = @.project["#{@.type}_custom_attributes"] @.customAttributesValues = customAttributesValues return customAttributesValues @@ -70,6 +70,7 @@ class CustomAttributesValuesController extends taiga.Controller @.customAttributesValues.id = @.objectId return @repo.save(@.customAttributesValues).then(onSuccess, onError) + CustomAttributesValuesDirective = ($templates, $storage) -> template = $templates.get("custom-attributes/custom-attributes-values.html", true) collapsedHash = (type) -> diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index a519d869..1761ae39 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -103,12 +103,12 @@ urls = { "attachments/wiki_page": "/wiki/attachments" # Custom Attributess - "custom-attributes/us": "/userstory-custom-attributes" + "custom-attributes/userstory": "/userstory-custom-attributes" "custom-attributes/issue": "/issue-custom-attributes" "custom-attributes/task": "/task-custom-attributes" # Custom field values - "custom-attributes-values/us": "/userstories/custom-attributes-values" + "custom-attributes-values/userstory": "/userstories/custom-attributes-values" "custom-attributes-values/issue": "/issues/custom-attributes-values" "custom-attributes-values/task": "/tasks/custom-attributes-values" diff --git a/app/coffee/modules/resources/custom-attributes-values.coffee b/app/coffee/modules/resources/custom-attributes-values.coffee index cb0415b3..5322c639 100644 --- a/app/coffee/modules/resources/custom-attributes-values.coffee +++ b/app/coffee/modules/resources/custom-attributes-values.coffee @@ -21,15 +21,24 @@ taiga = @.taiga -resourceProvider = ($rootScope, $config, $urls, $model, $repo, $auth, $q) -> - service = {} +resourceProvider = ($repo) -> + _get = (objectId, resource) -> + return $repo.queryOne(resource, objectId) - service.get = (type, objectId) -> - return $repo.queryOne("custom-attributes-values/#{type}", objectId) + service = { + userstory: { + get: (objectId) -> _get(objectId, "custom-attributes-values/userstory") + } + task: { + get: (objectId) -> _get(objectId, "custom-attributes-values/task") + } + issue: { + get: (objectId) -> _get(objectId, "custom-attributes-values/issue") + } + } return (instance) -> instance.customAttributesValues = service module = angular.module("taigaResources") -module.factory("$tgCustomAttributesValuesResourcesProvider", ["$rootScope", "$tgConfig", "$tgUrls", "$tgModel", "$tgRepo", - "$tgAuth", "$q", resourceProvider]) +module.factory("$tgCustomAttributesValuesResourcesProvider", ["$tgRepo", resourceProvider]) diff --git a/app/coffee/modules/resources/custom-attributes.coffee b/app/coffee/modules/resources/custom-attributes.coffee index dde0e637..cf14c398 100644 --- a/app/coffee/modules/resources/custom-attributes.coffee +++ b/app/coffee/modules/resources/custom-attributes.coffee @@ -29,8 +29,8 @@ resourceProvider = ($repo) -> return $repo.queryMany(resource, {project: projectId}) service = { - us:{ - list: (projectId) -> _list(projectId, "custom-attributes/us") + userstory:{ + list: (projectId) -> _list(projectId, "custom-attributes/userstory") } task:{ list: (projectId) -> _list(projectId, "custom-attributes/task") diff --git a/app/partials/admin/admin-project-values-us-extras.jade b/app/partials/admin/admin-project-values-us-extras.jade index 55523d97..40339494 100644 --- a/app/partials/admin/admin-project-values-us-extras.jade +++ b/app/partials/admin/admin-project-values-us-extras.jade @@ -1,5 +1,5 @@ div.wrapper(tg-project-custom-attributes, ng-controller="ProjectCustomAttributesController as ctrl", - ng-init="section='admin'; type='us'; sectionName='US extra'") + ng-init="section='admin'; type='userstory'; sectionName='US extra'") sidebar.menu-secondary.sidebar(tg-admin-navigation="project-values") include ../includes/modules/admin-menu From 0a6d77f23fc2defae8ea42d7c663630c9513e526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 26 Feb 2015 12:02:56 +0100 Subject: [PATCH 17/19] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf817c5..7fb54d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## 1.6.0 ??? (Unreleased) ### Features -- ... +- Added custom fields per project for user stories, tasks and issues. ### Misc - New contrib plugin for hipchat (by Δndrea Stagi) From 910dac75b7948e69d4d4468a9809a862aec7bf77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavier=20Juli=C3=A1n?= Date: Mon, 2 Mar 2015 12:56:30 +0100 Subject: [PATCH 18/19] Minor style fixes in custom fields --- app/coffee/modules/admin/project-values.coffee | 3 ++- app/styles/modules/common/custom-fields.scss | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/coffee/modules/admin/project-values.coffee b/app/coffee/modules/admin/project-values.coffee index 3b4882cf..48f52d63 100644 --- a/app/coffee/modules/admin/project-values.coffee +++ b/app/coffee/modules/admin/project-values.coffee @@ -593,8 +593,9 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame) -> attr = formEl.scope().attr title = "Delete custom attribute" # i18n + subtitle = "Remeber that all values in this custom field will be deleted.
Are you sure you want to continue?" message = attr.name - $confirm.askOnDelete(title, message).then (finish) -> + $confirm.ask(title, subtitle, message).then (finish) -> onSucces = -> $ctrl.loadCustomAttributes().finally -> finish() diff --git a/app/styles/modules/common/custom-fields.scss b/app/styles/modules/common/custom-fields.scss index 2856188a..9500491a 100644 --- a/app/styles/modules/common/custom-fields.scss +++ b/app/styles/modules/common/custom-fields.scss @@ -33,8 +33,7 @@ } &.editable { .custom-field-options { - align-items: center; - display: flex; + margin-top: .5rem; } } .custom-field-options { @@ -62,6 +61,9 @@ line-height: .9rem; } } + .custom-field-options { + margin: 0; + } .custom-field-value { flex: 1; padding: 0 1rem 0 2rem; From 82659f480914ba562c2c7f46e2c5cfba2f388014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 4 Mar 2015 12:20:21 +0100 Subject: [PATCH 19/19] US #55: Custom fields - Fix history of custom attributes values in detail pages --- .../modules/common/custom-field-values.coffee | 2 ++ app/coffee/modules/common/history.coffee | 30 +++++++++++++++++++ app/coffee/modules/issues/detail.coffee | 3 ++ app/coffee/modules/tasks/detail.coffee | 2 ++ app/coffee/modules/userstories/detail.coffee | 3 ++ 5 files changed, 40 insertions(+) diff --git a/app/coffee/modules/common/custom-field-values.coffee b/app/coffee/modules/common/custom-field-values.coffee index bb8c4042..dd3e661b 100644 --- a/app/coffee/modules/common/custom-field-values.coffee +++ b/app/coffee/modules/common/custom-field-values.coffee @@ -59,6 +59,8 @@ class CustomAttributesValuesController extends taiga.Controller updateAttributeValue: (attributeValue) -> onSuccess = => + @rootscope.$broadcast("custom-attributes-values:edit") + onError = (response) => @confirm.notify("error") return @q.reject() diff --git a/app/coffee/modules/common/history.coffee b/app/coffee/modules/common/history.coffee index 0f7651b8..fcd1d961 100644 --- a/app/coffee/modules/common/history.coffee +++ b/app/coffee/modules/common/history.coffee @@ -173,6 +173,34 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) -> return _.flatten(attachments).join("\n") + renderCustomAttributesEntry = (value) -> + customAttributes = _.map value, (changes, type) -> + if type == "new" + return _.map changes, (change) -> + return templateChangeGeneric({ + name: change.name, + from: formatChange(""), + to: formatChange(change.value) + }) + else if type == "deleted" + return _.map changes, (change) -> + # TODO: i18n + return templateChangeDiff({ + name: "deleted custom attribute", + diff: change.name + }) + else + return _.map changes, (change) -> + customAttrsChanges = _.map change.changes, (values) -> + return templateChangeGeneric({ + name: change.name + from: formatChange(values[0]) + to: formatChange(values[1]) + }) + return _.flatten(customAttrsChanges).join("\n") + + return _.flatten(customAttributes).join("\n") + renderChangeEntry = (field, value) -> if field == "description" return templateChangeDiff({name: getHumanizedFieldName("description"), diff: value[1]}) @@ -182,6 +210,8 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) -> return templateChangePoints({points: value}) else if field == "attachments" return renderAttachmentEntry(value) + else if field == "custom_attributes" + return renderCustomAttributesEntry(value) else if field in ["tags", "watchers"] name = getHumanizedFieldName(field) removed = _.difference(value[0], value[1]) diff --git a/app/coffee/modules/issues/detail.coffee b/app/coffee/modules/issues/detail.coffee index eea923dc..2be4c7d7 100644 --- a/app/coffee/modules/issues/detail.coffee +++ b/app/coffee/modules/issues/detail.coffee @@ -85,6 +85,9 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @rootscope.$broadcast("history:reload") @.loadIssue() + @scope.$on "custom-attributes-values:edit", => + @rootscope.$broadcast("history:reload") + initializeOnDeleteGoToUrl: -> ctx = {project: @scope.project.slug} if @scope.project.is_issues_activated diff --git a/app/coffee/modules/tasks/detail.coffee b/app/coffee/modules/tasks/detail.coffee index 1dd56d59..353fbaff 100644 --- a/app/coffee/modules/tasks/detail.coffee +++ b/app/coffee/modules/tasks/detail.coffee @@ -71,6 +71,8 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @rootscope.$broadcast("history:reload") @scope.$on "attachment:delete", => @rootscope.$broadcast("history:reload") + @scope.$on "custom-attributes-values:edit", => + @rootscope.$broadcast("history:reload") initializeOnDeleteGoToUrl: -> ctx = {project: @scope.project.slug} diff --git a/app/coffee/modules/userstories/detail.coffee b/app/coffee/modules/userstories/detail.coffee index 6db4ccac..7347f72d 100644 --- a/app/coffee/modules/userstories/detail.coffee +++ b/app/coffee/modules/userstories/detail.coffee @@ -80,6 +80,9 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin) @scope.$on "attachment:delete", => @rootscope.$broadcast("history:reload") + @scope.$on "custom-attributes-values:edit", => + @rootscope.$broadcast("history:reload") + initializeOnDeleteGoToUrl: -> ctx = {project: @scope.project.slug} @scope.onDeleteGoToUrl = @navUrls.resolve("project", ctx)