Merge branch 'master' into stable

stable
Alejandro Alonso 2015-11-02 09:44:31 +01:00
commit 0aa85b46e7
559 changed files with 29143 additions and 3880 deletions

2
.gitignore vendored
View File

@ -12,5 +12,5 @@ app/coffee/modules/locales/locale*.coffee
tags
tmp/
app/config/main.coffee
app/plugins/taiga-front-extras
scss-lint.log
e2e/screenshots/

View File

@ -1,3 +1,5 @@
sudo: false
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
@ -11,4 +13,4 @@ before_script:
- gulp deploy
language: node_js
node_js:
- "0.12"
- "4.1"

View File

@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = sr@latin:sr-latn, zh-Hans:zh-hans, zh-Hant:zh-hant
lang_map = sr@latin:sr-latn, zh-Hans:zh-hans, zh-Hant:zh-hant, pt_BR:pt-br
[taiga-front.locale-enjson]
file_filter = app/locales/locale-<lang>.json

View File

@ -27,3 +27,5 @@ answer newbie questions, and generally made Taiga that much better:
- Florian Bezagu
- Ryan Swanstrom
- Chris Wilson <chris.wilson@aridhia.com>
- Brett Profitt <brett.profitt@gmail.com>
- Vlad Topala <topalavlad@gmail.com>

View File

@ -1,6 +1,30 @@
# Changelog #
## 1.9.0 Abies Siberica (2015-11-XX)
### Features
- Ability to create single-line or multi-line custom fields. (thanks to [@artlepool](https://github.com/artlepool)).
- Ability to date custom fields. (thanks to [@artlepool](https://github.com/artlepool)).
- Add custom videoconference system.
- Make burndown chart collapsible at the backlog panel.
- Ability to choose a theme (thanks to [@astagi](https://github.com/astagi)).
- Inline viewing of image attachments (thanks to [@brettp](https://github.com/brettp)).
- Autocomplete for usernames, user stories, tasks, issues, and wiki pages in text areas (thanks to [@brettp](https://github.com/brettp)).
- Support authentication via Application Tokens.
- User onboarding: improve placeholders and add joyriders.
- i18n.
- Add italian (it) translation.
- Add polish (pl) translation.
- Add portuguese (Brazil) (pt_BR) translation.
- Add russian (ru) translation.
### Misc
- Improve performance: Show cropped images in timelines.
- Caps lock warning in login and register form.
- Lots of small and not so small bugfixes.
## 1.8.0 Saracenia Purpurea (2015-06-18)
### Features

152
README.md
View File

@ -3,13 +3,107 @@
![Kaleidos Project](http://kaleidos.net/static/img/badge.png "Kaleidos Project")
[![Managed with Taiga](https://taiga.io/media/support/attachments/article-22/banner-gh.png)](https://taiga.io "Managed with Taiga")
[![Build Status](https://travis-ci.org/taigaio/taiga-front.svg?branch=public-header-bar)](https://travis-ci.org/taigaio/taiga-front)
[![Dependency Status](https://www.versioneye.com/user/projects/561ba659a193340f280013f4/badge.svg?style=flat)](https://www.versioneye.com/user/projects/561ba659a193340f280013f4)
## Get the compiled version ##
You can get the compiled version of this code in the
[taiga-front-dist](http://github.com/taigaio/taiga-front-dist) repository
## Setup initial environment ##
## Contribute to Taiga ##
#### Where to start ####
There are many different ways to contribute to Taiga's development, just find the one that best fits with your skills. Examples of contributions we would love to receive include:
- **Code patches**
- **Documentation improvements**
- **Translations**
- **Bug reports**
- **Patch reviews**
- **UI enhancements**
Big features are also welcome but if you want to see your contributions included in Taiga codebase we strongly recommend you start by initiating a chat though our [mailing list](http://groups.google.co.uk/d/forum/taigaio)
#### License ####
Every code patch accepted in taiga codebase is licensed under [AGPL v3.0](http://www.gnu.org/licenses/agpl-3.0.html). You should must be careful to not include any code that can not be licensed under this license.
Please read carefully [our license](https://github.com/taigaio/taiga-front/blob/master/LICENSE) and ask us if you have any questions.
#### Bug reports, enhancements and support ####
If you **nedd help to setup Taiga**, you want to **talk about some cool enhancemnt** or you have **some questions** please write us to our [mailing list](http://groups.google.com/d/forum/taigaio).
If you **find a bug** in Taiga you can always report it:
- in our [mailing list](http://groups.google.com/d/forum/taigaio).
- in [github issues](https://github.com/taigaio/taiga-front/issues).
- send us a mail to support@taiga.io if is a bug related to tree.taiga.io.
- send a mail to security@taiga.io if is a **security bug**.
One of our fellow Taiga developers will search, find and hunt it as soon as possible.
Please, before reporting an bug write down how can we reproduce it, your operating system, your browser and version, and if it's possible, a screenshot. Sometimes it take less time to fix a bug if the developer know how to find it and we will solve your problem as fast as possible.
#### Documentation improvements ####
We are gathering lots of information from our users to build and enhance our documentation. If you are the documentation to install or develop with Taiga and find any mistakes, omissions or confused sequences, it is enormously helpful to report it. Or better still, if you believe you can author additions, please make a pull-request to taiga project.
Currently, we have authored three main documentation hubs:
- **[API Docs](https://github.com/taigaio/taiga-doc)**: Our API documentation and reference for developing from Taiga API.
- **[Installation Guide](https://github.com/taigaio/taiga-doc)**: If you need to install Taiga on your own server, this is the place to find some guides.
- **[Taiga Support](https://github.com/taigaio/taiga-doc)**: This page is intended to be the support reference page for the users. If you find any mistake, please report it.
#### Translation ####
We are ready now to accept your help translating Taiga. It's easy (and fun!) just access to our team of translators with the link below, set up an account in Transifex and start contributing. Join us to make sure your language is covered! **[Help Taiga to trasnlate content](https://www.transifex.com/signup/ "Help Taiga to trasnlatecontent")**
#### Code patches ####
Taiga will always be glad to receive code patches to update, fix or improve its code.
If you know how to improve our code base or you found a bug, a security vulnerabilities a performance issue and you think you can solve, we will be very happy to accept your pull-request. If your code requires considerable changes, we recommend you first talk to us directly. We will find the best way to help.
#### UI enhancements ####
Taiga is made for developers and designers. We care enormously about UI because usability and design are both critical aspects of the Taiga experience.
There are two possible ways to contribute to our UI:
- **Bugs**: If you find a bug regarding front-end, please report it as previously indicated in the Bug reports section or send a pull-request as indicated in the Code Patches section.
- **Enhancements**: If its a design or UX bug or enhancement we will love to receive your feedback. Please send us your enhancement, with the reason and, if it's possible, an example. Our design and UX team will review your enhancement and fix it as soon as possible. We recommend you to use our [mailing list](http://groups.google.co.uk/d/forum/taigaio){target="_blank"} so we can have a lot of different opinions and debate.
- **Language Localization**: We are eager to offer localized versions of Taiga. Some members of the community have already volunteered to work to provide a variety of languages. We are working to implement some changes to allow for this and expect to accept these requests in the near future.
## Community ##
[Taiga has a mailing list](http://groups.google.com/d/forum/taigaio). Feel free to join it and ask any questions you may have.
To subscribe for announcements of releases, important changes and so on, please follow [@taigaio](https://twitter.com/taigaio) on Twitter.
## Donations ##
We are grateful for your emails volunteering donations to Taiga. We feel comfortable accepting them under these conditions: The first that we will only do so while we are in the current beta / pre-revenue stage and that whatever money is donated will go towards a bounty fund. Starting Q2 2015 we will be engaging much more actively with our community to help further the development of Taiga, and we will use these donations to reward people working alongside us.
If you wish to make a donation to this Taiga fund, you can do so via http://www.paypal.com using the email: eposner@taiga.io
## Setup ##
All the information about the different installation methods (production, development, vagrant, docker...) can be found here http://taigaio.github.io/taiga-doc/dist/#_installation_guide.
#### Initial dev env ####
Install requirements:
@ -19,36 +113,62 @@ You can install Ruby through the apt package manager, rbenv, or rvm.
Install Sass through your **Terminal or Command Prompt**.
```
$ gem install sass scss-lint
$ export PATH="~/.gem/ruby/2.1.0/bin:$PATH"
$ sass -v // should return Sass 3.3.8 (Maptastic Maple)
gem install sass scss-lint
export PATH="~/.gem/ruby/2.1.0/bin:$PATH"
sass -v # should return Sass 3.3.8 (Maptastic Maple)
```
Complete process for all OS at: http://sass-lang.com/install
**Node + Bower + Gulp**
We recommend using [nvm](https://github.com/creationix/nvmv) to manage diferent node versions
```
$ sudo npm install -g gulp
$ sudo npm install -g bower
$ npm install
$ bower install
$ gulp
npm install -g gulp
npm install -g bower
npm install
bower install
gulp
```
And go in your browser to: http://localhost:9001/
All the information about the different installation methods (production, development, vagrant, docker...) can be found here http://taigaio.github.io/taiga-doc/dist/#_installation_guide.
#### E2E test ####
## Community ##
If you want to run e2e tests
[Taiga has a mailing list](http://groups.google.com/d/forum/taigaio). Feel free to join it and ask any questions you may have.
```
npm install -g protractor
npm install -g mocha
npm install -g babel
To subscribe for announcements of releases, important changes and so on, please follow [@taigaio](https://twitter.com/taigaio) on Twitter.
webdriver-manager update
```
## Donations ##
## Tests ##
We are grateful for your emails volunteering donations to Taiga. We feel comfortable accepting them under these conditions: The first that we will only do so while we are in the current beta / pre-revenue stage and that whatever money is donated will go towards a bounty fund. Starting Q2 2015 we will be engaging much more actively with our community to help further the development of Taiga, and we will use these donations to reward people working alongside us.
#### Unit tests ####
If you wish to make a donation to this Taiga fund, you can do so via http://www.paypal.com using the email: eposner@taiga.io
- To run **unit tests**
```
gulp
```
```
npm test
```
#### E2E tests ####
- To run **e2e tests** you need [taiga-back](https://github.com/taigaio/taiga-back) running and
```
gulp
```
```
webdriver-manager start
```
```
protractor conf.e2e.js --suite=auth # To tests authentication
protractor conf.e2e.js --suite=full # To test all the platform authenticated
```

View File

@ -4,6 +4,8 @@ window.taigaConfig = {
"eventsUrl": null,
"debug": true,
"defaultLanguage": "en",
"themes": ["taiga", "material-design", "high-contrast"],
"defaultTheme": "taiga",
"publicRegisterEnabled": true,
"feedbackEnabled": true,
"privacyPolicyUrl": null,

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -66,9 +66,11 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
access: {
requiresLogin: true
},
loader: true,
title: "HOME.PAGE_TITLE",
loader: true,
description: "HOME.PAGE_DESCRIPTION",
loader: true
joyride: "dashboard"
}
)
@ -100,7 +102,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
{
templateUrl: "search/search.html",
reloadOnSearch: false,
section: "search"
section: "search",
loader: true
}
)
@ -108,7 +111,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
{
templateUrl: "backlog/backlog.html",
loader: true,
section: "backlog"
section: "backlog",
joyride: "backlog"
}
)
@ -116,7 +120,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
{
templateUrl: "kanban/kanban.html",
loader: true,
section: "kanban"
section: "kanban",
joyride: "kanban"
}
)
@ -333,29 +338,25 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
$routeProvider.when("/login",
{
templateUrl: "auth/login.html",
title: "LOGIN.PAGE_TITLE"
description: "LOGIN.PAGE_DESCRIPTION"
title: "LOGIN.PAGE_TITLE",
description: "LOGIN.PAGE_DESCRIPTION",
disableHeader: true
}
)
$routeProvider.when("/register",
{
templateUrl: "auth/register.html",
title: "REGISTER.PAGE_TITLE",
description: "REGISTER.PAGE_DESCRIPTION"
description: "REGISTER.PAGE_DESCRIPTION",
disableHeader: true
}
)
$routeProvider.when("/forgot-password",
{
templateUrl: "auth/forgot-password.html",
title: "FORGOT_PASSWORD.PAGE_TITLE",
description: "FORGOT_PASSWORD.PAGE_DESCRIPTION"
}
)
$routeProvider.when("/change-password",
{
templateUrl: "auth/change-password-from-recovery.html",
title: "CHANGE_PASSWORD.PAGE_TITLE",
description: "CHANGE_PASSWORD.PAGE_TITLE",
description: "FORGOT_PASSWORD.PAGE_DESCRIPTION",
disableHeader: true
}
)
$routeProvider.when("/change-password/:token",
@ -363,13 +364,26 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
templateUrl: "auth/change-password-from-recovery.html",
title: "CHANGE_PASSWORD.PAGE_TITLE",
description: "CHANGE_PASSWORD.PAGE_TITLE",
disableHeader: true
}
)
$routeProvider.when("/invitation/:token",
{
templateUrl: "auth/invitation.html",
title: "INVITATION.PAGE_TITLE",
description: "INVITATION.PAGE_DESCRIPTION"
description: "INVITATION.PAGE_DESCRIPTION",
disableHeader: true
}
)
$routeProvider.when("/external-apps",
{
templateUrl: "external-apps/external-app.html",
title: "EXTERNAL_APP.PAGE_TITLE",
description: "EXTERNAL_APP.PAGE_DESCRIPTION",
controller: "ExternalApp",
controllerAs: "vm",
disableHeader: true,
mobileViewport: true
}
)
@ -405,13 +419,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
# Add next param when user try to access to a secction need auth permissions.
authHttpIntercept = ($q, $location, $navUrls, $lightboxService) ->
httpResponseError = (response) ->
if response.status == 0
if response.status == 0 || response.status == -1
$lightboxService.closeAll()
$location.path($navUrls.resolve("error"))
$location.replace()
else if response.status == 401
nextPath = $location.path()
$location.url($navUrls.resolve("login")).search("next=#{nextPath}")
else if response.status == 401 and $location.url().indexOf('/login') == -1
nextUrl = encodeURIComponent($location.url())
$location.url($navUrls.resolve("login")).search("next=#{nextUrl}")
return $q.reject(response)
@ -499,9 +513,13 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
.addInterpolation('$translateMessageFormatInterpolation')
.preferredLanguage(preferedLangCode)
if not window.taigaConfig.debugInfo
$translateProvider.fallbackLanguage(preferedLangCode)
$translateProvider.fallbackLanguage(preferedLangCode)
# decoratos
decorators = _.where(@.taigaContribPlugins, {"type": "decorator"})
_.each decorators, (decorator) ->
$provide.decorator decorator.provider, decorator.decorator
# decoratos
decorators = _.where(@.taigaContribPlugins, {"type": "decorator"})
@ -544,7 +562,7 @@ i18nInit = (lang, $translate) ->
checksley.updateMessages('default', messages)
init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService) ->
init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService, navigationBarService) ->
$log.debug("Initialize application")
# Taiga Plugins
@ -588,7 +606,7 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na
projectService.setSection(next.section)
if next.params.pslug
projectService.setProject(next.params.pslug)
projectService.setProjectBySlug(next.params.pslug)
else
projectService.cleanProject()
@ -597,6 +615,17 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na
description = $translate.instant(next.description or "")
appMetaService.setAll(title, description)
if next.mobileViewport
appMetaService.addMobileViewport()
else
appMetaService.removeMobileViewport()
if next.disableHeader
navigationBarService.disableHeader()
else
navigationBarService.enableHeader()
pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module)
pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module)
@ -634,6 +663,7 @@ modules = [
"taigaProfile",
"taigaHome",
"taigaUserTimeline",
"taigaExternalApps",
# template cache
"templates",
@ -641,6 +671,7 @@ modules = [
# Vendor modules
"ngRoute",
"ngAnimate",
"ngAria",
"pascalprecht.translate",
"infinite-scroll",
"tgRepeat"
@ -673,5 +704,7 @@ module.run([
"tgAppMetaService",
"tgProjectService",
"tgLoader",
"tgNavigationBarService",
"$route",
init
])

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -41,7 +41,7 @@ CreateMembersDirective = ($rs, $rootScope, $confirm, $loading, lightboxService,
template = _.template("""
<div class="add-member-wrapper">
<fieldset>
<input type="email" placeholder="{{'LIGHTBOX.CREATE_MEMBER.PLACEHOLDER_TYPE_EMAIL' | translate}}"
<input tg-capslock type="email" placeholder="{{'LIGHTBOX.CREATE_MEMBER.PLACEHOLDER_TYPE_EMAIL' | translate}}"
<% if(required) { %> data-required="true" <% } %> data-type="email" />
</fieldset>
<fieldset>

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -210,15 +210,17 @@ module.directive("tgMemberships", ["$tgTemplate", "$compile", MembershipsDirecti
## Member Avatar Directive
#############################################################################
MembershipsRowAvatarDirective = ($log, $template) ->
MembershipsRowAvatarDirective = ($log, $template, $translate) ->
template = $template.get("admin/memberships-row-avatar.html", true)
link = ($scope, $el, $attrs) ->
pending = $translate.instant("ADMIN.MEMBERSHIP.STATUS_PENDING")
render = (member) ->
ctx = {
full_name: if member.full_name then member.full_name else ""
email: if member.user_email then member.user_email else member.email
imgurl: if member.photo then member.photo else "/images/unnamed.png"
pending: if !member.is_user_active then pending else ""
}
html = template(ctx)
@ -236,7 +238,7 @@ MembershipsRowAvatarDirective = ($log, $template) ->
return {link: link}
module.directive("tgMembershipsRowAvatar", ["$log", "$tgTemplate", MembershipsRowAvatarDirective])
module.directive("tgMembershipsRowAvatar", ["$log", "$tgTemplate", '$translate', MembershipsRowAvatarDirective])
#############################################################################
@ -357,9 +359,8 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm, $compile, $transla
"""
pendingTemplate = """
<a class="pending" href="">
{{'ADMIN.MEMBERSHIP.STATUS_PENDING' | translate}}
<span class="icon icon-reload"></span>
<a class="resend" href="">
{{'ADMIN.MEMBERSHIP.RESEND' | translate}}
</a>
<a class="delete" href="">
<span class="icon icon-delete"></span>
@ -402,9 +403,9 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm, $compile, $transla
defaultMsg = $translate.instant("ADMIN.MEMBERSHIP.DEFAULT_DELETE_MESSAGE", {email: member.email})
message = if member.user then member.full_name else defaultMsg
$confirm.askOnDelete(title, message).then (finish) ->
onSuccess = ->
finish()
$confirm.askOnDelete(title, message).then (askResponse) ->
onSuccess = =>
askResponse.finish()
if $scope.page > 1 && ($scope.count - 1) <= $scope.paginatedBy
$ctrl.selectFilter("page", $scope.page - 1)
@ -414,8 +415,8 @@ MembershipsRowActionsDirective = ($log, $repo, $rs, $confirm, $compile, $transla
text = $translate.instant("ADMIN.MEMBERSHIP.SUCCESS_DELETE")
$confirm.notify("success", null, text)
onError = ->
finish(false)
onError = =>
askResponse.finish(false)
text = $translate.instant("ADMIN.MEMBERSHIP.ERROR_DELETE", {message: message})
$confirm.notify("error", null, text)

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -104,7 +104,7 @@ module.controller("ProjectProfileController", ProjectProfileController)
## Project Profile Directive
#############################################################################
ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, projectService) ->
ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, projectService, currentUserService) ->
link = ($scope, $el, $attrs) ->
$ctrl = $el.controller()
@ -130,6 +130,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, proje
$ctrl.loadInitialData()
projectService.fetchProject()
currentUserService.loadProjects()
promise.then null, (data) ->
currentLoading.finish()
@ -144,7 +145,7 @@ ProjectProfileDirective = ($repo, $confirm, $loading, $navurls, $location, proje
return {link:link}
module.directive("tgProjectProfile", ["$tgRepo", "$tgConfirm", "$tgLoading", "$tgNavUrls", "$tgLocation",
"tgProjectService", ProjectProfileDirective])
"tgProjectService", "tgCurrentUserService", ProjectProfileDirective])
#############################################################################
@ -192,9 +193,10 @@ module.directive("tgProjectDefaultValues", ["$tgRepo", "$tgConfirm", "$tgLoading
ProjectModulesDirective = ($repo, $confirm, $loading, projectService) ->
link = ($scope, $el, $attrs) ->
form = $el.find("form").checksley()
submit = =>
form = $el.find("form").checksley()
return if not form.validate()
target = angular.element(".admin-functionalities .submit-button")
currentLoading = $loading()
.target(target)
@ -226,7 +228,7 @@ ProjectModulesDirective = ($repo, $confirm, $loading, projectService) ->
else
$el.find(".videoconference-attributes").addClass("hidden")
$scope.project.videoconferences = null
$scope.project.videoconferences_salt = ""
$scope.project.videoconferences_extra_data = ""
$scope.$watch "project", (project) ->
if project.videoconferences?
@ -357,7 +359,7 @@ class CsvExporterController extends taiga.Controller
setCsvUuid: =>
@scope.csvUuid = @scope.project["#{@.type}_csv_uuid"]
_generateUuid: (finish) =>
_generateUuid: (response=null) =>
promise = @rs.projects["regenerate_#{@.type}_csv_uuid"](@scope.projectId)
promise.then (data) =>
@ -367,7 +369,7 @@ class CsvExporterController extends taiga.Controller
@confirm.notify("error")
promise.finally ->
finish()
response.finish() if response
return promise
regenerateUuid: ->
@ -377,7 +379,7 @@ class CsvExporterController extends taiga.Controller
@confirm.ask(title, subtitle).then @._generateUuid
else
@._generateUuid(_.identity)
@._generateUuid()
class CsvExporterUserstoriesController extends CsvExporterController

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -374,6 +374,27 @@ module.directive("tgColorSelection", ColorSelectionDirective)
## Custom Attributes Controller
#############################################################################
# Custom attributes types (see taiga-back/taiga/projects/custom_attributes/choices.py)
TEXT_TYPE = "text"
MULTILINE_TYPE = "multiline"
DATE_TYPE = "date"
TYPE_CHOICES = [
{
key: TEXT_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_TEXT"
},
{
key: MULTILINE_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_MULTI"
},
{
key: DATE_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_DATE"
}
]
class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.PageMixin)
@.$inject = [
"$scope",
@ -390,6 +411,8 @@ class ProjectCustomAttributesController extends mixOf(taiga.Controller, taiga.Pa
constructor: (@scope, @rootscope, @repo, @rs, @params, @q, @location, @navUrls, @appMetaService,
@translate) ->
@scope.TYPE_CHOICES = TYPE_CHOICES
@scope.project = {}
@rootscope.$on "project:loaded", =>
@ -630,13 +653,11 @@ ProjectCustomAttributesDirective = ($log, $confirm, animationFrame, $translate)
title = $translate.instant("COMMON.CUSTOM_ATTRIBUTES.DELETE")
text = $translate.instant("COMMON.CUSTOM_ATTRIBUTES.CONFIRM_DELETE")
$confirm.ask(title, text, message).then (finish) ->
$confirm.ask(title, text, message).then (response) ->
onSucces = ->
$ctrl.loadCustomAttributes().finally ->
finish()
$ctrl.loadCustomAttributes().finally -> response.finish()
onError = ->
finish(false)
$confirm.notify("error", null, "We have not been able to delete '#{message}'.")
$ctrl.deleteCustomAttribute(attr).then(onSucces, onError)

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -129,7 +129,7 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
return @repo.remove(@scope.role, {moveTo: response.selected}).then onSuccess, onError
setComputable: debounce 2000, ->
_enableComputable: =>
onSuccess = =>
@confirm.notify("success")
@.loadProject()
@ -140,9 +140,37 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
@repo.save(@scope.role).then onSuccess, onError
_disableComputable: =>
askOnSuccess = (response) =>
onSuccess = =>
response.finish()
@confirm.notify("success")
@.loadProject()
onError = =>
response.finish()
@confirm.notify("error")
@scope.role.revert()
@repo.save(@scope.role).then onSuccess, onError
askOnError = (response) =>
@scope.role.revert()
title = @translate.instant("ADMIN.ROLES.DISABLE_COMPUTABLE_ALERT_TITLE")
subtitle = @translate.instant("ADMIN.ROLES.DISABLE_COMPUTABLE_ALERT_SUBTITLE", {
roleName: @scope.role.name
})
message = @translate.instant("ADMIN.ROLES.DISABLE_COMPUTABLE_ALERT_MESSAGE")
return @confirm.ask(title, subtitle, message).then askOnSuccess, askOnError
toggleComputable: debounce 2000, ->
if not @scope.role.computable
@._disableComputable()
else
@._enableComputable()
module.controller("RolesController", RolesController)
EditRoleDirective = ($repo, $confirm) ->
link = ($scope, $el, $attrs) ->
toggleView = ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -177,13 +177,13 @@ WebhookDirective = ($rs, $repo, $confirm, $loading, $translate) ->
title = $translate.instant("ADMIN.WEBHOOKS.DELETE")
message = $translate.instant("ADMIN.WEBHOOKS.WEBHOOK_NAME", {name: webhook.name})
$confirm.askOnDelete(title, message).then (finish) =>
$confirm.askOnDelete(title, message).then (askResponse) =>
onSucces = ->
finish()
askResponse.finish()
$scope.$emit("webhooks:reload")
onError = ->
finish(false)
askResponse.finish(false)
$confirm.notify("error")
$repo.remove(webhook).then(onSucces, onError)

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -37,10 +37,15 @@ class AuthService extends taiga.Service
"$tgUrls",
"$tgConfig",
"$translate",
"tgCurrentUserService"]
"tgCurrentUserService",
"tgThemeService"]
constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate, @currentUserService) ->
constructor: (@rootscope, @storage, @model, @rs, @http, @urls, @config, @translate, @currentUserService,
@themeService) ->
super()
@._currentTheme = @config.get("defaultTheme") || "taiga" # load on index.jade
userModel = @.getUser()
@.setUserdata(userModel)
@ -51,9 +56,18 @@ class AuthService extends taiga.Service
else
@.userData = null
_getUserTheme: ->
return @rootscope.user?.theme || @config.get("defaultTheme") || "taiga"
_setTheme: ->
newTheme = @._getUserTheme()
if @._currentTheme != newTheme
@._currentTheme = newTheme
@themeService.use(@._currentTheme)
_setLocales: ->
lang = @rootscope.user.lang || @config.get("defaultLanguage") || "en"
lang = @rootscope.user?.lang || @config.get("defaultLanguage") || "en"
@translate.preferredLanguage(lang) # Needed for calls to the api in the correct language
@translate.use(lang) # Needed for change the interface in runtime
@ -66,6 +80,9 @@ class AuthService extends taiga.Service
user = @model.make_model("users", userData)
@rootscope.user = user
@._setLocales()
@._setTheme()
return user
return null
@ -78,6 +95,7 @@ class AuthService extends taiga.Service
@.setUserdata(user)
@._setLocales()
@._setTheme()
clear: ->
@rootscope.auth = null
@ -117,9 +135,12 @@ class AuthService extends taiga.Service
logout: ->
@.removeToken()
@.clear()
@currentUserService.removeUser()
@._setTheme()
@._setLocales()
register: (data, type, existing) ->
url = @urls.resolve("auth-register")
@ -200,12 +221,12 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $
link = ($scope, $el, $attrs) ->
onSuccess = (response) ->
if $routeParams['next'] and $routeParams['next'] != $navUrls.resolve("login")
nextUrl = $routeParams['next']
nextUrl = decodeURIComponent($routeParams['next'])
else
nextUrl = $navUrls.resolve("home")
$events.setupConnection()
$location.path(nextUrl)
$location.url(nextUrl)
onError = (response) ->
$confirm.notify("light-error", $translate.instant("LOGIN_FORM.ERROR_AUTH_INCORRECT"))
@ -343,7 +364,10 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
$scope.tokenInParams = true
$scope.data.token = $params.token
else
$scope.tokenInParams = false
$location.path($navUrls.resolve("login"))
text = $translate.instant("CHANGE_PASSWORD_RECOVERY_FORM.ERROR")
$confirm.notify("light-error",text)
form = $el.find("form").checksley()
@ -354,7 +378,7 @@ ChangePasswordFromRecoveryDirective = ($auth, $confirm, $location, $params, $nav
$confirm.success(text)
onErrorSubmit = (response) ->
text = $translate.instant("COMMON.GENERIC_ERROR", {error: response.data._error_message})
text = $translate.instant("CHANGE_PASSWORD_RECOVERY_FORM.ERROR")
$confirm.notify("light-error", text)
submit = debounce 2000, (event) =>
@ -394,7 +418,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics
$location.path($navUrls.resolve("login"))
text = $translate.instant("INVITATION_LOGIN_FORM.NOT_FOUND")
$confirm.success(text)
$confirm.notify("light-error", text)
# Login form
$scope.dataLogin = {token: token}

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -35,11 +35,13 @@ module = angular.module("taigaBacklog")
## Issues Filters Directive
#############################################################################
BacklogFiltersDirective = ($log, $location, $templates) ->
BacklogFiltersDirective = ($q, $log, $location, $templates) ->
template = $templates.get("backlog/filters.html", true)
templateSelected = $templates.get("backlog/filter-selected.html", true)
link = ($scope, $el, $attrs) ->
currentFiltersType = ''
$ctrl = $el.closest(".wrapper").controller()
selectedFilters = []
@ -50,16 +52,18 @@ BacklogFiltersDirective = ($log, $location, $templates) ->
$el.find("h2 a.subfilter span.title").html(title)
$el.find("h2 a.subfilter span.title").prop("data-type", type)
currentFiltersType = getFiltersType()
showCategories = ->
$el.find(".filters-cats").show()
$el.find(".filter-list").addClass("hidden")
$el.find("h2.breadcrumb").addClass("hidden")
initializeSelectedFilters = (filters) ->
initializeSelectedFilters = () ->
showCategories()
selectedFilters = []
for name, values of filters
for name, values of $scope.filters
for val in values
selectedFilters.push(val) if val.selected
@ -81,43 +85,62 @@ BacklogFiltersDirective = ($log, $location, $templates) ->
html = template({filters:filters})
$el.find(".filter-list").html(html)
getFiltersType = () ->
return $el.find("h2 a.subfilter span.title").prop('data-type')
reloadUserstories = () ->
currentFiltersType = getFiltersType()
$q.all([$ctrl.loadUserstories(), $ctrl.generateFilters()]).then () ->
currentFilters = $scope.filters[currentFiltersType]
renderFilters(_.reject(currentFilters, "selected"))
toggleFilterSelection = (type, id) ->
currentFiltersType = getFiltersType()
filters = $scope.filters[type]
filter = _.find(filters, {id: taiga.toString(id)})
filter = _.find(filters, {id: id})
filter.selected = (not filter.selected)
if filter.selected
selectedFilters.push(filter)
$scope.$apply ->
$ctrl.selectFilter(type, id)
else
selectedFilters = _.reject(selectedFilters, filter)
$scope.$apply ->
$ctrl.unselectFilter(type, id)
selectedFilters = _.reject selectedFilters, (selected) ->
return filter.type == selected.type && filter.id == selected.id
$ctrl.unselectFilter(type, id)
renderSelectedFilters(selectedFilters)
currentFiltersType = $el.find("h2 a.subfilter span.title").prop('data-type')
if type == currentFiltersType
renderFilters(_.reject(filters, "selected"))
$ctrl.loadUserstories()
reloadUserstories()
selectQFilter = debounceLeading 100, (value) ->
return if value is undefined
if value.length == 0
$ctrl.replaceFilter("q", null)
else
$ctrl.replaceFilter("q", value)
$ctrl.loadUserstories()
reloadUserstories()
$scope.$watch("filtersQ", selectQFilter)
## Angular Watchers
$scope.$on "filters:loaded", (ctx, filters) ->
initializeSelectedFilters(filters)
$scope.$on "backlog:loaded", (ctx) ->
initializeSelectedFilters()
$scope.$on "filters:update", (ctx, filters) ->
renderFilters(filters)
$scope.$on "filters:update", (ctx) ->
$ctrl.generateFilters().then () ->
filters = $scope.filters[currentFiltersType]
if currentFiltersType
renderFilters(_.reject(filters, "selected"))
## Dom Event Handlers
$el.on "click", ".filters-cats > ul > li > a", (event) ->
@ -126,7 +149,7 @@ BacklogFiltersDirective = ($log, $location, $templates) ->
tags = $scope.filters[target.data("type")]
renderFilters(_.reject(tags, "selected"))
showFilters(target.attr("title"), target.data("type"))
showFilters(target.attr("title"), target.data('type'))
$el.on "click", ".filters-inner > .filters-step-cat > .breadcrumb > .back", (event) ->
event.preventDefault()
@ -153,4 +176,4 @@ BacklogFiltersDirective = ($log, $location, $templates) ->
return {link:link}
module.directive("tgBacklogFilters", ["$log", "$tgLocation", "$tgTemplate", BacklogFiltersDirective])
module.directive("tgBacklogFilters", ["$q", "$log", "$tgLocation", "$tgTemplate", BacklogFiltersDirective])

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -34,12 +34,13 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
hasErrors = false
createSprint = true
$scope.sprint = {
project: null
name: null
estimated_start: null
estimated_finish: null
}
resetSprint = () ->
$scope.sprint = {
project: null
name: null
estimated_start: null
estimated_finish: null
}
submit = debounce 2000, (event) =>
event.preventDefault()
@ -95,19 +96,30 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
title = $translate.instant("LIGHTBOX.DELETE_SPRINT.TITLE")
message = $scope.sprint.name
$confirm.askOnDelete(title, message).then (finish) =>
$confirm.askOnDelete(title, message).then (askResponse) =>
onSuccess = ->
finish()
askResponse.finish()
$scope.milestonesCounter -= 1
lightboxService.close($el)
$rootscope.$broadcast("sprintform:remove:success")
$rootscope.$broadcast("sprintform:remove:success", $scope.sprint)
onError = ->
finish(false)
askResponse.finish(false)
$confirm.notify("error")
$repo.remove($scope.sprint).then(onSuccess, onError)
getLastSprint = ->
openSprints = _.filter $scope.sprints, (sprint) ->
return !sprint.closed
sortedSprints = _.sortBy openSprints, (sprint) ->
return moment(sprint.estimated_finish, 'YYYY-MM-DD').format('X')
return sortedSprints[sortedSprints.length - 1]
$scope.$on "sprintform:create", (event, projectId) ->
resetSprint()
form = $el.find("form").checksley()
form.reset()
@ -117,20 +129,24 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
$scope.sprint.name = null
$scope.sprint.slug = null
lastSprint = $scope.sprints[0]
lastSprint = getLastSprint()
estimatedStart = moment()
if $scope.sprint.estimated_start
estimatedStart = moment($scope.sprint.estimated_start)
else if lastSprint?
if lastSprint
estimatedStart = moment(lastSprint.estimated_finish)
else if $scope.sprint.estimated_start
estimatedStart = moment($scope.sprint.estimated_start)
$scope.sprint.estimated_start = estimatedStart.format(prettyDate)
estimatedFinish = moment().add(2, "weeks")
if $scope.sprint.estimated_finish
estimatedFinish = moment($scope.sprint.estimated_finish)
else if lastSprint?
if lastSprint
estimatedFinish = moment(lastSprint.estimated_finish).add(2, "weeks")
else if $scope.sprint.estimated_finish
estimatedFinish = moment($scope.sprint.estimated_finish)
$scope.sprint.estimated_finish = estimatedFinish.format(prettyDate)
lastSprintNameDom = $el.find(".last-sprint-name")
@ -152,6 +168,8 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
$el.find(".last-sprint-name").removeClass("disappear")
$scope.$on "sprintform:edit", (ctx, sprint) ->
resetSprint()
createSprint = false
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
@ -187,6 +205,8 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading,
$scope.$on "$destroy", ->
$el.off()
resetSprint()
return {link: link}

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -28,6 +28,7 @@ bindOnce = @.taiga.bindOnce
groupBy = @.taiga.groupBy
timeout = @.taiga.timeout
bindMethods = @.taiga.bindMethods
generateHash = @.taiga.generateHash
module = angular.module("taigaBacklog")
@ -60,6 +61,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@scope.sectionName = @translate.instant("BACKLOG.SECTION_NAME")
@showTags = false
@activeFilters = false
@scope.showGraphPlaceholder = null
@.initializeEventHandlers()
@ -96,18 +98,26 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@scope.$on "usform:new:success", =>
@.loadUserstories()
@.loadProjectStats()
@rootscope.$broadcast("filters:update")
@analytics.trackEvent("userstory", "create", "create userstory on backlog", 1)
@scope.$on "sprintform:edit:success", =>
@.loadProjectStats()
@scope.$on "sprintform:remove:success", =>
@scope.$on "sprintform:remove:success", (event, sprint) =>
@.loadSprints()
@.loadProjectStats()
@.loadUserstories()
if sprint.closed
@.loadClosedSprints()
@rootscope.$broadcast("filters:update")
@scope.$on "usform:edit:success", =>
@.loadUserstories()
@rootscope.$broadcast("filters:update")
@scope.$on("sprint:us:move", @.moveUs)
@scope.$on("sprint:us:moved", @.loadSprints)
@ -137,18 +147,16 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
loadProjectStats: ->
return @rs.projects.stats(@scope.projectId).then (stats) =>
@scope.stats = stats
totalPoints = if stats.total_points then stats.total_points else stats.defined_points
if stats.total_points
@scope.stats.completedPercentage = Math.round(100 * stats.closed_points / stats.total_points)
if totalPoints
@scope.stats.completedPercentage = Math.round(100 * stats.closed_points / totalPoints)
else
@scope.stats.completedPercentage = 0
@scope.showGraphPlaceholder = !(stats.total_points? && stats.total_milestones?)
return stats
refreshTagsColors: ->
return @rs.projects.tagsColors(@scope.projectId).then (tags_colors) =>
@scope.project.tags_colors = tags_colors
unloadClosedSprints: ->
@scope.$apply =>
@scope.closedSprints = []
@ -156,17 +164,29 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
loadClosedSprints: ->
params = {closed: true}
return @rs.sprints.list(@scope.projectId, params).then (sprints) =>
return @rs.sprints.list(@scope.projectId, params).then (result) =>
sprints = result.milestones
@scope.totalClosedMilestones = result.closed
# NOTE: Fix order of USs because the filter orderBy does not work propertly in partials files
for sprint in sprints
sprint.user_stories = _.sortBy(sprint.user_stories, "sprint_order")
@scope.closedSprints = sprints
@scope.closedSprintsById = groupBy(sprints, (x) -> x.id)
@rootscope.$broadcast("closed-sprints:reloaded", sprints)
return sprints
loadSprints: ->
params = {closed: false}
return @rs.sprints.list(@scope.projectId, params).then (sprints) =>
return @rs.sprints.list(@scope.projectId, params).then (result) =>
sprints = result.milestones
@scope.totalMilestones = sprints
@scope.totalClosedMilestones = result.closed
@scope.totalOpenMilestones = result.open
@scope.totalMilestones = @scope.totalOpenMilestones + @scope.totalClosedMilestones
# NOTE: Fix order of USs because the filter orderBy does not work propertly in partials files
for sprint in sprints
sprint.user_stories = _.sortBy(sprint.user_stories, "sprint_order")
@ -182,7 +202,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
resetFilters: ->
selectedTags = _.filter(@scope.filters.tags, "selected")
selectedStatuses = _.filter(@scope.filters.statuses, "selected")
selectedStatuses = _.filter(@scope.filters.status, "selected")
@scope.filtersQ = ""
@ -195,23 +215,20 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@.unselectFilter(item.type, item.id)
@.loadUserstories()
@rootscope.$broadcast("filters:update")
loadUserstories: ->
@scope.httpParams = @.getUrlFilters()
@rs.userstories.storeQueryParams(@scope.projectId, @scope.httpParams)
promise = @q.all([@.refreshTagsColors(), @rs.userstories.listUnassigned(@scope.projectId, @scope.httpParams)])
promise = @rs.userstories.listUnassigned(@scope.projectId, @scope.httpParams)
return promise.then (data) =>
userstories = data[1]
return promise.then (userstories) =>
# NOTE: Fix order of USs because the filter orderBy does not work propertly in the partials files
@scope.userstories = _.sortBy(userstories, "backlog_order")
@.setSearchDataFilters()
@.filterVisibleUserstories()
@.generateFilters()
@rootscope.$broadcast("filters:loaded", @scope.filters)
# The broadcast must be executed when the DOM has been fully reloaded.
# We can't assure when this exactly happens so we need a defer
scopeDefer @scope, =>
@ -233,7 +250,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@scope.projectId = project.id
@scope.project = project
@scope.totalClosedMilestones = project.total_closed_milestones
@scope.closedMilestones = !!project.total_closed_milestones
@scope.$emit('project:loaded', project)
@scope.points = _.sortBy(project.points, "order")
@scope.pointsById = groupBy(project.points, (x) -> x.id)
@ -244,25 +261,13 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
loadInitialData: ->
promise = @.loadProject()
promise.then (project) =>
@.fillUsersAndRoles(project.users, project.roles)
@.fillUsersAndRoles(project.members, project.roles)
@.initializeSubscription()
return promise.then(=> @.loadBacklog())
filterVisibleUserstories: ->
@scope.visibleUserstories = []
# Filter by tags
@scope.visibleUserstories = _.reject @scope.userstories, (us) =>
return _.some us.tags, (tag) =>
return @isFilterSelected("tag", tag)
# Filter by status
@scope.visibleUserstories = _.filter @scope.visibleUserstories, (us) =>
if @searchdata["statuses"] && Object.keys(@searchdata["statuses"]).length
return @isFilterSelected("statuses", taiga.toString(us.status))
return true
return promise
.then(=> @.loadBacklog())
.then(=> @.generateFilters())
.then(=> @scope.$emit("backlog:loaded"))
prepareBulkUpdateData: (uses, field="backlog_order") ->
return _.map(uses, (x) -> {"us_id": x.id, "order": x[field]})
@ -281,6 +286,23 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
oldSprintId = usList[0].milestone
project = usList[0].project
movedFromClosedSprint = false
movedToClosedSprint = false
sprint = @scope.sprintsById[oldSprintId]
# Move from closed sprint
if !sprint && @scope.closedSprintsById
sprint = @scope.closedSprintsById[oldSprintId]
movedFromClosedSprint = true if sprint
newSprint = @scope.sprintsById[newSprintId]
# Move to closed sprint
if !newSprint && newSprintId
newSprint = @scope.closedSprintsById[newSprintId]
movedToClosedSprint = true if newSprint
# In the same sprint or in the backlog
if newSprintId == oldSprintId
items = null
@ -289,7 +311,7 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
if newSprintId == null
userstories = @scope.userstories
else
userstories = @scope.sprintsById[newSprintId].user_stories
userstories = newSprint.user_stories
@scope.$apply ->
for us, key in usList
@ -333,16 +355,9 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@scope.$apply =>
# Add new us to backlog userstories list
# @scope.userstories.splice(newUsIndex, 0, us)
# @scope.visibleUserstories.splice(newUsIndex, 0, us)
args = [newUsIndex, 0].concat(usList)
Array.prototype.splice.apply(@scope.userstories, args)
Array.prototype.splice.apply(@scope.visibleUserstories, args)
# Execute the prefiltering of user stories
@.filterVisibleUserstories()
# Remove the us from the sprint list.
sprint = @scope.sprintsById[oldSprintId]
for us, key in usList
r = sprint.user_stories.indexOf(us)
sprint.user_stories.splice(r, 1)
@ -358,13 +373,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
return @rs.userstories.bulkUpdateBacklogOrder(us.project, data).then =>
@rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId)
if movedFromClosedSprint
@rootscope.$broadcast("backlog:load-closed-sprints")
promise.then null, ->
console.log "FAIL" # TODO
return promise
# From backlog to sprint
newSprint = @scope.sprintsById[newSprintId]
if oldSprintId == null
us.milestone = newSprintId for us in usList
@ -376,9 +393,6 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
# Remove moving us from backlog userstories lists.
for us, key in usList
r = @scope.visibleUserstories.indexOf(us)
@scope.visibleUserstories.splice(r, 1)
r = @scope.userstories.indexOf(us)
@scope.userstories.splice(r, 1)
@ -394,9 +408,8 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
# Remove the us from the sprint list.
for us in usList
oldSprint = @scope.sprintsById[oldSprintId]
r = oldSprint.user_stories.indexOf(us)
oldSprint.user_stories.splice(r, 1)
r = sprint.user_stories.indexOf(us)
sprint.user_stories.splice(r, 1)
# Persist the milestone change of userstory
promises = _.map usList, (us) => @repo.save(us)
@ -407,13 +420,16 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
items = @.resortUserStories(newSprint.user_stories, "sprint_order")
data = @.prepareBulkUpdateData(items, "sprint_order")
@rs.userstories.bulkUpdateSprintOrder(project, data).then =>
@rs.userstories.bulkUpdateSprintOrder(project, data).then (result) =>
@rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId)
@rs.userstories.bulkUpdateBacklogOrder(project, data).then =>
for us in usList
@rootscope.$broadcast("sprint:us:moved", us, oldSprintId, newSprintId)
if movedToClosedSprint || movedFromClosedSprint
@scope.$broadcast("backlog:load-closed-sprints")
promise.then null, ->
console.log "FAIL" # TODO
@ -439,74 +455,77 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
@searchdata[name][val] = true
getUrlFilters: ->
return _.pick(@location.search(), "statuses", "tags", "q")
return _.pick(@location.search(), "status", "tags", "q")
generateFilters: ->
urlfilters = @.getUrlFilters()
@scope.filters = {}
@scope.filters = {}
#tags
plainTags = _.flatten(_.filter(_.map(@scope.visibleUserstories, "tags")))
plainTags.sort()
loadFilters = {}
loadFilters.project = @scope.projectId
loadFilters.tags = urlfilters.tags
loadFilters.status = urlfilters.status
loadFilters.q = urlfilters.q
loadFilters.milestone = 'null'
if plainTags.length == 0 and urlfilters["tags"]
plainTags.push(urlfilters["tags"])
return @rs.userstories.filtersData(loadFilters).then (data) =>
choicesFiltersFormat = (choices, type, byIdObject) =>
_.map choices, (t) ->
t.type = type
return t
@scope.filters.tags = _.map _.countBy(plainTags), (v, k) =>
obj = {
id: k,
type: "tags",
name: k,
color: @scope.project.tags_colors[k],
count: v
}
obj.selected = true if @isFilterSelected("tags", obj.id)
return obj
tagsFilterFormat = (tags) =>
return _.map tags, (t) ->
t.id = t.name
t.type = 'tags'
return t
selectedTags = _.filter(@scope.filters.tags, "selected")
selectedTags = _.map(selectedTags, "name")
# Build filters data structure
@scope.filters.status = choicesFiltersFormat(data.statuses, "status", @scope.usStatusById)
@scope.filters.tags = tagsFilterFormat(data.tags)
#status
plainStatuses = _.map(@scope.visibleUserstories, "status")
selectedTags = _.filter(@scope.filters.tags, "selected")
selectedTags = _.map(selectedTags, "id")
plainStatuses = _.filter plainStatuses, (status) =>
if status
return status
selectedStatuses = _.filter(@scope.filters.status, "selected")
selectedStatuses = _.map(selectedStatuses, "id")
if plainStatuses.length == 0 and urlfilters["statuses"]
plainStatuses.push(urlfilters["statuses"])
@.markSelectedFilters(@scope.filters, urlfilters)
@scope.filters.statuses = _.map _.countBy(plainStatuses), (v, k) =>
obj = {
id: k,
type: "statuses",
name: @scope.usStatusById[k].name,
color: @scope.usStatusById[k].color,
count:v
}
obj.selected = true if @isFilterSelected("statuses", obj.id)
#store query params
@rs.userstories.storeQueryParams(@scope.projectId, {
"status": selectedStatuses,
"tags": selectedTags,
"project": @scope.projectId
"milestone": null
})
return obj
markSelectedFilters: (filters, urlfilters) ->
# Build selected filters (from url) fast lookup data structure
searchdata = {}
for name, value of _.omit(urlfilters, "page", "orderBy")
if not searchdata[name]?
searchdata[name] = {}
selectedStatuses = _.filter(@scope.filters.statuses, "selected")
selectedStatuses = _.map(selectedStatuses, "id")
for val in "#{value}".split(",")
searchdata[name][val] = true
#store query params
@rs.userstories.storeQueryParams(@scope.projectId, {
"status": selectedStatuses,
"tags": selectedTags,
"project": @scope.projectId
"milestone": null
})
isSelected = (type, id) ->
if searchdata[type]? and searchdata[type][id]
return true
return false
for key, value of filters
for obj in value
obj.selected = if isSelected(obj.type, obj.id) then true else undefined
## Template actions
updateUserStoryStatus: () ->
@.setSearchDataFilters()
@.filterVisibleUserstories()
@.generateFilters()
@rootscope.$broadcast("filters:update", @scope.filters['statuses'])
@.loadProjectStats()
@.generateFilters().then () =>
@rootscope.$broadcast("filters:update")
@.loadProjectStats()
editUserStory: (projectId, ref, $event) ->
target = $($event.target)
@ -527,16 +546,15 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
message = us.subject
@confirm.askOnDelete(title, message).then (finish) =>
@confirm.askOnDelete(title, message).then (askResponse) =>
# We modify the userstories in scope so the user doesn't see the removed US for a while
@scope.userstories = _.without(@scope.userstories, us)
@filterVisibleUserstories()
promise = @.repo.remove(us)
promise.then =>
finish()
askResponse.finish()
@.loadBacklog()
promise.then null, =>
finish(false)
askResponse.finish(false)
@confirm.notify("error")
addNewUs: (type) ->
@ -563,7 +581,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
linkDoomLine = ($scope, $el, $attrs, $ctrl) ->
reloadDoomLine = ->
if $scope.stats?
if $scope.stats? and $scope.stats.total_points? and $scope.stats.total_points != 0
removeDoomlineDom()
stats = $scope.stats
@ -571,9 +589,9 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
total_points = stats.total_points
current_sum = stats.assigned_points
return if not $scope.visibleUserstories
return if not $scope.userstories
for us, i in $scope.visibleUserstories
for us, i in $scope.userstories
current_sum += us.total_points
if current_sum > total_points
@ -614,7 +632,6 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
# Update the total of points
$scope.sprints[0].total_points += totalExtraPoints
$ctrl.filterVisibleUserstories()
$repo.saveAll(selectedUss).then ->
$ctrl.loadSprints()
$ctrl.loadProjectStats()
@ -626,7 +643,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
checkSelected = (target) ->
lastChecked = target.closest(".us-item-row")
moveToCurrentSprintDom = $el.find("#move-to-current-sprint")
selectedUsDom = $el.find(".backlog-table-body .user-stories input:checkbox:checked")
selectedUsDom = $el.find(".backlog-table-body input:checkbox:checked")
if selectedUsDom.length > 0 and $scope.sprints.length > 0
moveToCurrentSprintDom.show()
@ -641,7 +658,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
return true
# Enable move to current sprint only when there are selected us's
$el.on "change", ".backlog-table-body .user-stories input:checkbox", (event) ->
$el.on "change", ".backlog-table-body input:checkbox", (event) ->
# check elements between the last two if shift is pressed
if lastChecked && shiftPressed
elements = []
@ -656,15 +673,16 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
_.map elements, (elm) ->
input = $(elm).find("input:checkbox")
input.prop('checked', true);
input.prop('checked', true)
checkSelected(input)
target = angular.element(event.currentTarget)
target.closest(".us-item-row").toggleClass('is-checked')
checkSelected(target)
$el.on "click", "#move-to-current-sprint", (event) =>
# Calculating the us's to be modified
ussDom = $el.find(".backlog-table-body .user-stories input:checkbox:checked")
ussDom = $el.find(".backlog-table-body input:checkbox:checked")
ussToMove = _.map ussDom, (item) ->
item = $(item).closest('.tg-scope')
@ -688,12 +706,12 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
elm.addClass("active")
text = $translate.instant("BACKLOG.TAGS.HIDE")
elm.find(".text").text(text)
elm.text(text)
else
elm.removeClass("active")
text = $translate.instant("BACKLOG.TAGS.SHOW")
elm.find(".text").text(text)
elm.text(text)
showHideFilter = ($scope, $el, $ctrl) ->
sidebar = $el.find("sidebar.filters-bar")
@ -736,8 +754,7 @@ BacklogDirective = ($repo, $rootscope, $translate) ->
$el.find(".backlog-table-body").disableSelection()
filters = $ctrl.getUrlFilters()
if filters.statuses ||
if filters.status ||
filters.tags ||
filters.q
showHideFilter($scope, $el, $ctrl)
@ -915,6 +932,56 @@ UsPointsDirective = ($tgEstimationsService, $repo, $tgTemplate) ->
module.directive("tgBacklogUsPoints", ["$tgEstimationsService", "$tgRepo", "$tgTemplate", UsPointsDirective])
#############################################################################
## Burndown graph directive
#############################################################################
ToggleBurndownVisibility = ($storage) ->
hide = () ->
$(".js-burndown-graph").removeClass("shown")
$(".js-toggle-burndown-visibility-button").removeClass("active")
$(".js-burndown-graph").removeClass("open")
show = (firstLoad) ->
$(".js-toggle-burndown-visibility-button").addClass("active")
if firstLoad
$(".js-burndown-graph").addClass("shown")
else
$(".js-burndown-graph").addClass("open")
link = ($scope, $el, $attrs) ->
firstLoad = true
hash = generateHash(["is-burndown-grpahs-collapsed"])
$scope.isBurndownGraphCollapsed = $storage.get(hash) or false
toggleGraph = ->
if $scope.isBurndownGraphCollapsed
hide(firstLoad)
else
show(firstLoad)
firstLoad = false
$scope.$watch "showGraphPlaceholder", () ->
if $scope.showGraphPlaceholder?
$scope.isBurndownGraphCollapsed = $scope.isBurndownGraphCollapsed || $scope.showGraphPlaceholder
toggleGraph()
$el.on "click", ".js-toggle-burndown-visibility-button", ->
$scope.isBurndownGraphCollapsed = !$scope.isBurndownGraphCollapsed
$storage.set(hash, $scope.isBurndownGraphCollapsed)
toggleGraph()
$scope.$on "$destroy", ->
$el.off()
return {
link: link
}
module.directive("tgToggleBurndownVisibility", ["$tgStorage", ToggleBurndownVisibility])
#############################################################################
## Burndown graph directive
#############################################################################
@ -1007,16 +1074,16 @@ BurndownBacklogGraphDirective = ($translate) ->
tooltipOpts: {
content: (label, xval, yval, flotItem) ->
if flotItem.seriesIndex == 1
ctx = {xval: xval, yval: yval}
ctx = {sprintName: dataToDraw.milestones[xval].name, value: Math.abs(yval)}
return $translate.instant("BACKLOG.CHART.OPTIMAL", ctx)
else if flotItem.seriesIndex == 2
ctx = {xval: xval, yval: yval}
ctx = {sprintName: dataToDraw.milestones[xval].name, value: Math.abs(yval)}
return $translate.instant("BACKLOG.CHART.REAL", ctx)
else if flotItem.seriesIndex == 3
ctx = {xval: xval, yval: Math.abs(yval)}
ctx = {sprintName: dataToDraw.milestones[xval].name, value: Math.abs(yval)}
return $translate.instant("BACKLOG.CHART.INCREMENT_TEAM", ctx)
else
ctx = {xval: xval, yval: Math.abs(yval)}
ctx = {sprintName: dataToDraw.milestones[xval].name, value: Math.abs(yval)}
return $translate.instant("BACKLOG.CHART.INCREMENT_CLIENT", ctx)
}
}
@ -1046,14 +1113,16 @@ module.directive("tgBurndownBacklogGraph", ["$translate", BurndownBacklogGraphDi
## Backlog progress bar directive
#############################################################################
TgBacklogProgressBarDirective = ($template) ->
TgBacklogProgressBarDirective = ($template, $compile) ->
template = $template.get("backlog/progress-bar.html", true)
render = (el, projectPointsPercentaje, closedPointsPercentaje) ->
el.html(template({
render = (scope, el, projectPointsPercentaje, closedPointsPercentaje) ->
html = template({
projectPointsPercentaje: projectPointsPercentaje,
closedPointsPercentaje:closedPointsPercentaje
}))
})
html = $compile(html)(scope)
el.html(html)
adjustPercentaje = (percentage) ->
adjusted = _.max([0 , percentage])
@ -1065,7 +1134,7 @@ TgBacklogProgressBarDirective = ($template) ->
$scope.$watch $attrs.tgBacklogProgressBar, (stats) ->
if stats?
totalPoints = stats.total_points
totalPoints = if stats.total_points then stats.total_points else stats.defined_points
definedPoints = stats.defined_points
closedPoints = stats.closed_points
if definedPoints > totalPoints
@ -1077,11 +1146,11 @@ TgBacklogProgressBarDirective = ($template) ->
projectPointsPercentaje = adjustPercentaje(projectPointsPercentaje - 3)
closedPointsPercentaje = adjustPercentaje(closedPointsPercentaje - 3)
render($el, projectPointsPercentaje, closedPointsPercentaje)
render($scope, $el, projectPointsPercentaje, closedPointsPercentaje)
$scope.$on "$destroy", ->
$el.off()
return {link: link}
module.directive("tgBacklogProgressBar", ["$tgTemplate", TgBacklogProgressBarDirective])
module.directive("tgBacklogProgressBar", ["$tgTemplate", "$compile", TgBacklogProgressBarDirective])

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -144,6 +144,7 @@ BacklogEmptySortableDirective = ($repo, $rs, $rootscope) ->
# If the user has not enough permissions we don't enable the sortable
if project.my_permissions.indexOf("modify_us") > -1
$el.sortable({
items: ".us-item-row",
dropOnEmpty: true
})

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -35,12 +35,6 @@ BacklogSprintDirective = ($repo, $rootscope) ->
easing: 'linear'
}
refreshSprintTableHeight = (sprintTable) =>
if !sprintTable.find(".row").length
sprintTable.css("height", sprintTableMinHeight)
else
sprintTable.css("height", "auto")
toggleSprint = ($el) =>
sprintTable = $el.find(".sprint-table")
sprintArrow = $el.find(".icon-arrow-up")
@ -48,8 +42,6 @@ BacklogSprintDirective = ($repo, $rootscope) ->
sprintArrow.toggleClass('active')
sprintTable.toggleClass('open')
refreshSprintTableHeight(sprintTable)
link = ($scope, $el, $attrs) ->
$scope.$watch $attrs.tgBacklogSprint, (sprint) ->
sprint = $scope.$eval($attrs.tgBacklogSprint)

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -71,16 +71,39 @@ NavigationUrlsDirective = ($navurls, $auth, $q, $location) ->
parseNav = (data, $scope) ->
[name, params] = _.map(data.split(":"), trim)
if params
params = _.map(params.split(","), trim)
# split by 'xxx='
# example
# project=vm.timeline.getIn(['data', 'project', 'slug']), ref=vm.timeline.getIn(['obj', 'ref'])
# ["", "project", "vm.timeline.getIn(['data', 'project', 'slug']), ", "ref", "vm.timeline.getIn(['obj', 'ref'])"]
result = params.split(/(\w+)=/)
# remove empty string
result = _.filter result, (str) -> return str.length
# remove , at the end of the string
result = _.map result, (str) -> return trim(str.replace(/,$/g, ''))
params = []
index = 0
# ['param1', 'value'] => [{'param1': 'value'}]
while index < result.length
obj = {}
obj[result[index]] = result[index + 1]
params.push obj
index = index + 2
else
params = []
values = _.map(params, (x) -> trim(x.split("=")[1]))
values = _.map params, (param) -> _.values(param)[0]
promises = _.map(values, (x) -> bindOnceP($scope, x))
return $q.all(promises).then ->
options = {}
for item in params
[key, value] = _.map(item.split("="), trim)
for param in params
key = Object.keys(param)[0]
value = param[key]
options[key] = $scope.$eval(value)
return [name, options]

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -140,7 +140,7 @@ class RepositoryService extends taiga.Service
return defered.promise
queryMany: (name, params, options={}) ->
queryMany: (name, params, options={}, headers=false) ->
url = @urls.resolve(name)
httpOptions = {headers: {}}
@ -148,7 +148,12 @@ class RepositoryService extends taiga.Service
httpOptions.headers["x-disable-pagination"] = "1"
return @http.get(url, params, httpOptions).then (data) =>
return _.map(data.data, (x) => @model.make_model(name, x))
result = _.map(data.data, (x) => @model.make_model(name, x))
if headers
return [result, data.headers]
return result
queryOneAttribute: (name, id, attribute, params, options={}) ->
url = @urls.resolve(name, id)

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -23,6 +23,57 @@ taiga = @.taiga
module = angular.module("taigaCommon", [])
#############################################################################
## Default datepicker config
#############################################################################
DataPickerConfig = ($translate) ->
return {
get: () ->
return {
i18n: {
previousMonth: $translate.instant("COMMON.PICKERDATE.PREV_MONTH"),
nextMonth: $translate.instant("COMMON.PICKERDATE.NEXT_MONTH"),
months: [
$translate.instant("COMMON.PICKERDATE.MONTHS.JAN"),
$translate.instant("COMMON.PICKERDATE.MONTHS.FEB"),
$translate.instant("COMMON.PICKERDATE.MONTHS.MAR"),
$translate.instant("COMMON.PICKERDATE.MONTHS.APR"),
$translate.instant("COMMON.PICKERDATE.MONTHS.MAY"),
$translate.instant("COMMON.PICKERDATE.MONTHS.JUN"),
$translate.instant("COMMON.PICKERDATE.MONTHS.JUL"),
$translate.instant("COMMON.PICKERDATE.MONTHS.AUG"),
$translate.instant("COMMON.PICKERDATE.MONTHS.SEP"),
$translate.instant("COMMON.PICKERDATE.MONTHS.OCT"),
$translate.instant("COMMON.PICKERDATE.MONTHS.NOV"),
$translate.instant("COMMON.PICKERDATE.MONTHS.DEC")
],
weekdays: [
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SUN"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.MON"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.TUE"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.WED"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.THU"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.FRI"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SAT")
],
weekdaysShort: [
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SUN"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.MON"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.TUE"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.WED"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.THU"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.FRI"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SAT")
]
},
isRTL: $translate.instant("COMMON.PICKERDATE.IS_RTL") == "true",
firstDay: parseInt($translate.instant("COMMON.PICKERDATE.FIRST_DAY_OF_WEEK"), 10),
format: $translate.instant("COMMON.PICKERDATE.FORMAT")
}
}
module.factory("tgDatePickerConfigService", ["$translate", DataPickerConfig])
#############################################################################
## Get the selected text
#############################################################################
@ -229,3 +280,46 @@ Template = ($templateCache) ->
}
module.factory("$tgTemplate", ["$templateCache", Template])
#############################################################################
## Permission directive, hide elements when necessary
#############################################################################
Capslock = ($translate) ->
link = ($scope, $el, $attrs) ->
open = false
warningIcon = $('<div>')
.addClass('icon')
.addClass('icon-capslock')
.attr('title', $translate.instant('COMMON.CAPSLOCK_WARNING'))
hideIcon = () ->
warningIcon.fadeOut () ->
open = false
$(this).remove()
showIcon = (e) ->
return if open
element = e.currentTarget
$(element).parent().append(warningIcon)
$('.icon-capslock').fadeIn()
open = true
$el.on 'blur', (e) ->
hideIcon()
$el.on 'keyup.capslock, focus', (e) ->
if $el.val() == $el.val().toLowerCase()
hideIcon(e)
else
showIcon(e)
$scope.$on "$destroy", ->
$el.off('.capslock')
return {link:link}
module.directive("tgCapslock", ["$translate", Capslock])

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -136,16 +136,16 @@ class AttachmentsController extends taiga.Controller
title = @translate.instant("ATTACHMENT.TITLE_LIGHTBOX_DELETE_ATTACHMENT")
message = @translate.instant("ATTACHMENT.MSG_LIGHTBOX_DELETE_ATTACHMENT", {fileName: attachment.name})
return @confirm.askOnDelete(title, message).then (finish) =>
return @confirm.askOnDelete(title, message).then (askResponse) =>
onSuccess = =>
finish()
askResponse.finish()
index = @.attachments.indexOf(attachment)
@.attachments.splice(index, 1)
@.updateCounters()
@rootscope.$broadcast("attachment:delete")
onError = =>
finish(false)
askResponse.finish(false)
message = @translate.instant("ATTACHMENT.ERROR_DELETE_ATTACHMENT", {errorMessage: message})
@confirm.notify("error", null, message)
return @q.reject()
@ -197,6 +197,7 @@ AttachmentsDirective = ($config, $confirm, $templates, $translate) ->
$el.on "change", ".attachments-header input", (event) ->
files = _.toArray(event.target.files)
return if files.length < 1
$scope.$apply ->
@ -245,7 +246,7 @@ AttachmentsDirective = ($config, $confirm, $templates, $translate) ->
module.directive("tgAttachments", ["$tgConfig", "$tgConfirm", "$tgTemplate", "$translate", AttachmentsDirective])
AttachmentDirective = ($template, $compile, $translate) ->
AttachmentDirective = ($template, $compile, $translate, $rootScope) ->
template = $template.get("attachment/attachment.html", true)
templateEdit = $template.get("attachment/attachment-edit.html", true)
@ -283,6 +284,7 @@ AttachmentDirective = ($template, $compile, $translate) ->
saveAttachment = ->
attachment.description = $el.find("input[name='description']").val()
attachment.is_deprecated = $el.find("input[name='is-deprecated']").prop("checked")
attachment.isCreatedRightNow = false
$scope.$apply ->
$ctrl.updateAttachment(attachment).then ->
@ -297,7 +299,7 @@ AttachmentDirective = ($template, $compile, $translate) ->
if event.keyCode == 13
saveAttachment()
else if event.keyCode == 27
render(attachment, false)
$scope.$apply -> render(attachment, false)
$el.on "click", "a.editable-settings.icon-delete", (event) ->
event.preventDefault()
@ -314,6 +316,12 @@ AttachmentDirective = ($template, $compile, $translate) ->
$scope.$apply ->
$ctrl.removeAttachment(attachment)
$el.on "click", "div.attachment-name a", (event) ->
if null != attachment.name.match(/\.(jpe?g|png|gif|gifv|webm)/i)
event.preventDefault()
$scope.$apply ->
$rootScope.$broadcast("attachment:preview", attachment)
$scope.$on "$destroy", ->
$el.off()
@ -329,4 +337,4 @@ AttachmentDirective = ($template, $compile, $translate) ->
restrict: "AE"
}
module.directive("tgAttachment", ["$tgTemplate", "$compile", "$translate", AttachmentDirective])
module.directive("tgAttachment", ["$tgTemplate", "$compile", "$translate", "$rootScope", AttachmentDirective])

View File

@ -1,3 +1,22 @@
###
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: bind-scope.coffee
###
module = angular.module("taigaCommon")
BindScope = (config) ->

View File

@ -1,3 +1,22 @@
###
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: compile-html.directive.coffee
###
CompileHtmlDirective = ($compile) ->
link = (scope, element, attrs) ->
scope.$watch attrs.tgCompileHtml, (newValue, oldValue) ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -52,52 +52,23 @@ module.directive("tgDateRange", ["$translate", DateRangeDirective])
## Date Selector Directive (using pikaday)
#############################################################################
DateSelectorDirective = ($rootscope, $translate) ->
DateSelectorDirective = ($rootscope, datePickerConfigService) ->
link = ($scope, $el, $attrs, $model) ->
selectedDate = null
initialize = () ->
$el.picker = new Pikaday({
datePickerConfig = datePickerConfigService.get()
_.merge(datePickerConfig, {
field: $el[0]
onSelect: (date) =>
selectedDate = date
onOpen: =>
$el.picker.setDate(selectedDate) if selectedDate?
i18n: {
previousMonth: $translate.instant("COMMON.PICKERDATE.PREV_MONTH"),
nextMonth: $translate.instant("COMMON.PICKERDATE.NEXT_MONTH"),
months: [$translate.instant("COMMON.PICKERDATE.MONTHS.JAN"),
$translate.instant("COMMON.PICKERDATE.MONTHS.FEB"),
$translate.instant("COMMON.PICKERDATE.MONTHS.MAR"),
$translate.instant("COMMON.PICKERDATE.MONTHS.APR"),
$translate.instant("COMMON.PICKERDATE.MONTHS.MAY"),
$translate.instant("COMMON.PICKERDATE.MONTHS.JUN"),
$translate.instant("COMMON.PICKERDATE.MONTHS.JUL"),
$translate.instant("COMMON.PICKERDATE.MONTHS.AUG"),
$translate.instant("COMMON.PICKERDATE.MONTHS.SEP"),
$translate.instant("COMMON.PICKERDATE.MONTHS.OCT"),
$translate.instant("COMMON.PICKERDATE.MONTHS.NOV"),
$translate.instant("COMMON.PICKERDATE.MONTHS.DEC")],
weekdays: [$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SUN"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.MON"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.TUE"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.WED"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.THU"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.FRI"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS.SAT")],
weekdaysShort: [$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SUN"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.MON"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.TUE"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.WED"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.THU"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.FRI"),
$translate.instant("COMMON.PICKERDATE.WEEK_DAYS_SHORT.SAT")]
},
isRTL: $translate.instant("COMMON.PICKERDATE.IS_RTL") == "true",
firstDay: parseInt($translate.instant("COMMON.PICKERDATE.FIRST_DAY_OF_WEEK"), 10),
format: $translate.instant("COMMON.PICKERDATE.FORMAT")
})
$el.picker = new Pikaday(datePickerConfig)
unbind = $rootscope.$on "$translateChangeEnd", (ctx) => initialize()
$scope.$watch $attrs.ngModel, (val) ->
@ -113,7 +84,7 @@ DateSelectorDirective = ($rootscope, $translate) ->
require: "ngModel"
}
module.directive("tgDateSelector", ["$rootScope", "$translate", DateSelectorDirective])
module.directive("tgDateSelector", ["$rootScope", "tgDatePickerConfigService", DateSelectorDirective])
#############################################################################
@ -152,7 +123,7 @@ module.directive("tgSprintProgressbar", SprintProgressBarDirective)
## Created-by display directive
#############################################################################
CreatedByDisplayDirective = ($template, $compile, $translate)->
CreatedByDisplayDirective = ($template, $compile, $translate, $navUrls)->
# Display the owner information (full name and photo) and the date of
# creation of an object (like USs, tasks and issues).
#
@ -168,13 +139,14 @@ CreatedByDisplayDirective = ($template, $compile, $translate)->
link = ($scope, $el, $attrs) ->
render = (model) ->
owner = $scope.usersById?[model.owner] or {
owner = model.owner_extra_info or {
full_name_display: $translate.instant("COMMON.EXTERNAL_USER")
photo: "/images/unnamed.png"
photo: "/images/user-noimage.png"
}
html = template({
owner: owner
url: if owner?.is_active then $navUrls.resolve("user-profile", {username: owner.username}) else ""
date: moment(model.created_date).format($translate.instant("COMMON.DATETIME"))
})
@ -194,7 +166,8 @@ CreatedByDisplayDirective = ($template, $compile, $translate)->
require: "ngModel"
}
module.directive("tgCreatedByDisplay", ["$tgTemplate", "$compile", "$translate", CreatedByDisplayDirective])
module.directive("tgCreatedByDisplay", ["$tgTemplate", "$compile", "$translate", "$tgNavUrls",
CreatedByDisplayDirective])
#############################################################################
@ -250,11 +223,7 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile,
html = $compile(template(ctx))($scope)
$el.html(html)
if isEditable() and watchers.length == 0
$el.find(".title").text("Add watchers")
$el.find(".watchers-header").addClass("no-watchers")
$el.on "click", ".icon-delete", (event) ->
$el.on "click", ".js-delete-watcher", (event) ->
event.preventDefault()
return if not isEditable()
target = angular.element(event.currentTarget)
@ -263,15 +232,15 @@ WatchersDirective = ($rootscope, $confirm, $repo, $qqueue, $template, $compile,
title = $translate.instant("COMMON.WATCHERS.TITLE_LIGHTBOX_DELETE_WARTCHER")
message = $scope.usersById[watcherId].full_name_display
$confirm.askOnDelete(title, message).then (finish) =>
finish()
$confirm.askOnDelete(title, message).then (askResponse) =>
askResponse.finish()
watcherIds = _.clone($model.$modelValue.watchers, false)
watcherIds = _.pull(watcherIds, watcherId)
deleteWatcher(watcherIds)
$el.on "click", ".add-watcher", (event) ->
$el.on "click", ".js-add-watcher", (event) ->
event.preventDefault()
return if not isEditable()
$scope.$apply ->
@ -353,8 +322,8 @@ AssignedToDirective = ($rootscope, $confirm, $repo, $loading, $qqueue, $template
return if not isEditable()
title = $translate.instant("COMMON.ASSIGNED_TO.CONFIRM_UNASSIGNED")
$confirm.ask(title).then (finish) =>
finish()
$confirm.ask(title).then (response) =>
response.finish()
$model.$modelValue.assigned_to = null
save(null)
@ -443,18 +412,18 @@ DeleteButtonDirective = ($log, $repo, $confirm, $location, $template) ->
if not $attrs.onDeleteTitle
return $log.error "DeleteButtonDirective requires on-delete-title set in scope."
$el.on "click", ".button", (event) ->
$el.on "click", ".button-delete", (event) ->
title = $attrs.onDeleteTitle
subtitle = $model.$modelValue.subject
$confirm.askOnDelete(title, subtitle).then (finish) =>
$confirm.askOnDelete(title, subtitle).then (askResponse) =>
promise = $repo.remove($model.$modelValue)
promise.then =>
finish()
askResponse.finish()
url = $scope.$eval($attrs.onDeleteGoToUrl)
$location.path(url)
promise.then null, =>
finish(false)
askResponse.finish(false)
$confirm.notify("error")
$scope.$on "$destroy", ->
@ -554,7 +523,7 @@ module.directive("tgEditableSubject", ["$rootScope", "$tgRepo", "$tgConfirm", "$
#############################################################################
## Editable subject directive
## Editable description directive
#############################################################################
EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading, $selectedText, $qqueue, $template) ->
@ -603,6 +572,13 @@ EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading,
$el.find('.view-description').hide()
$el.find('textarea').focus()
$el.on "click", "a", (event) ->
target = angular.element(event.target)
href = target.attr('href')
if href.indexOf("#") == 0
event.preventDefault()
$('body').scrollTop($(href).offset().top)
$el.on "click", ".save", (e) ->
e.preventDefault()
@ -673,14 +649,14 @@ ListItemAssignedtoDirective = ($template) ->
template = $template.get("common/components/list-item-assigned-to-avatar.html", true)
link = ($scope, $el, $attrs) ->
bindOnce $scope, "membersById", (membersById) ->
bindOnce $scope, "usersById", (usersById) ->
item = $scope.$eval($attrs.tgListitemAssignedto)
ctx = {name: "Unassigned", imgurl: "/images/unnamed.png"}
member = membersById[item.assigned_to]
member = usersById[item.assigned_to]
if member
ctx.imgurl = member.photo
ctx.name = member.full_name
ctx.name = member.full_name_display
$el.html(template(ctx))

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -67,11 +67,12 @@ class ConfirmService extends taiga.Service
currentLoading = @loading()
.target(target)
.start()
defered.resolve (ok=true) =>
currentLoading.finish()
if ok
@.hide(el)
defered.resolve {
finish: (ok=true) =>
currentLoading.finish()
if ok
@.hide(el)
}
el.on "click.confirm-dialog", "a.button-red", (event) =>
event.preventDefault()
@ -118,9 +119,10 @@ class ConfirmService extends taiga.Service
.start()
defered.resolve {
selected: choicesField.val()
finish: =>
finish: (ok=true) =>
currentLoading.finish()
@.hide(el)
if ok
@.hide(el)
}
el.on "click.confirm-dialog", "a.button-red", (event) =>
@ -245,7 +247,7 @@ class ConfirmService extends taiga.Service
delete @.tsem
el.on "click", ".icon-delete", (event) =>
el.on "click", ".icon-delete, .close", (event) =>
body.find(selector)
.removeClass('active')
.addClass('inactive')

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -27,6 +27,28 @@ generateHash = taiga.generateHash
module = angular.module("taigaCommon")
# Custom attributes types (see taiga-back/taiga/projects/custom_attributes/choices.py)
TEXT_TYPE = "text"
MULTILINE_TYPE = "multiline"
DATE_TYPE = "date"
TYPE_CHOICES = [
{
key: TEXT_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_TEXT"
},
{
key: MULTILINE_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_MULTI"
},
{
key: DATE_TYPE,
name: "ADMIN.CUSTOM_FIELDS.FIELD_TYPE_DATE"
}
]
class CustomAttributesValuesController extends taiga.Controller
@.$inject = ["$scope", "$rootScope", "$tgRepo", "$tgResources", "$tgConfirm", "$q"]
@ -118,85 +140,110 @@ CustomAttributesValuesDirective = ($templates, $storage) ->
template: templateFn
}
module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", CustomAttributesValuesDirective])
module.directive("tgCustomAttributesValues", ["$tgTemplate", "$tgStorage", "$translate",
CustomAttributesValuesDirective])
CustomAttributeValueDirective = ($template, $selectedText, $compile) ->
CustomAttributeValueDirective = ($template, $selectedText, $compile, $translate, datePickerConfigService) ->
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) ->
prettyDate = $translate.instant("COMMON.PICKERDATE.FORMAT")
render = (attributeValue, edit=false) ->
value = attributeValue.value
if attributeValue.type is DATE_TYPE and attributeValue.value
value = moment(attributeValue.value, "YYYY-MM-DD").format(prettyDate)
else
value = attributeValue.value
editable = isEditable()
ctx = {
id: attributeValue.id
name: attributeValue.name
description: attributeValue.description
value: value
isEditable: editable
type: attributeValue.type
}
if editable and (edit or not value)
html = templateEdit(ctx)
html = $compile(html)($scope)
$el.html(html)
if attributeValue.type == DATE_TYPE
datePickerConfig = datePickerConfigService.get()
_.merge(datePickerConfig, {
field: $el.find("input[name=value]")[0]
onSelect: (date) =>
selectedDate = date
onOpen: =>
$el.picker.setDate(selectedDate) if selectedDate?
})
$el.picker = new Pikaday(datePickerConfig)
else
html = template(ctx)
html = $compile(html)($scope)
$el.html(html)
$el.html(html)
isEditable = ->
permissions = $scope.project.my_permissions
requiredEditionPerm = $attrs.requiredEditionPerm
return permissions.indexOf(requiredEditionPerm) > -1
saveAttributeValue = ->
attributeValue.value = $el.find("input").val()
submit = debounce 2000, (event) =>
event.preventDefault()
attributeValue.value = $el.find("input[name=value], textarea[name='value']").val()
if attributeValue.type is DATE_TYPE
if moment(attributeValue.value, prettyDate).isValid()
attributeValue.value = moment(attributeValue.value, prettyDate).format("YYYY-MM-DD")
else
attributeValue.value = ""
$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()
return if $selectedText.get().length
render(attributeValue, true)
$el.find("input[name='description']").focus().select()
$scope.$apply()
$el.on "click", "a.icon-edit", (event) ->
event.preventDefault()
render(attributeValue, true)
$el.find("input[name='description']").focus().select()
$scope.$apply()
## 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()
setFocusAndSelectOnInputField = ->
$el.find("input[name='value'], textarea[name='value']").focus().select()
# Bootstrap
attributeValue = $scope.$eval($attrs.tgCustomAttributeValue)
render(attributeValue)
## Actions (on view mode)
$el.on "click", ".js-value-view-mode", ->
return if not isEditable()
return if $selectedText.get().length
render(attributeValue, true)
setFocusAndSelectOnInputField()
$el.on "click", "a.icon-edit", (event) ->
event.preventDefault()
render(attributeValue, true)
setFocusAndSelectOnInputField()
## Actions (on edit mode)
$el.on "keyup", "input[name=value], textarea[name='value']", (event) ->
if event.keyCode is 13 and event.currentTarget.type isnt "textarea"
submit(event)
else if event.keyCode == 27
render(attributeValue, false)
$el.on "submit", "form", submit
$el.on "click", "a.icon-floppy", submit
$scope.$on "$destroy", ->
$el.off()
return {
link: link
require: "^tgCustomAttributesValues"
restrict: "AE"
}
module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", "$compile", CustomAttributeValueDirective])
module.directive("tgCustomAttributeValue", ["$tgTemplate", "$selectedText", "$compile", "$translate",
"tgDatePickerConfigService", CustomAttributeValueDirective])

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -226,6 +226,10 @@ EstimationsService = ($template, $qqueue, $repo, $confirm, $q) ->
@$el.find(".pop-points-open").show()
pop = @$el.find(".pop-points-open")
if pop.offset().top + pop.height() > document.body.clientHeight
pop.addClass('pop-bottom')
create = ($el, us, project) ->
$el.unbind("click")

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -26,6 +26,14 @@ debounce = @.taiga.debounce
module = angular.module("taigaCommon")
IGNORED_FIELDS = {
"userstories.userstory": [
"watchers", "kanban_order", "backlog_order", "sprint_order", "finish_date"
]
"tasks.task": [
"watchers", "us_order", "taskboard_order"
]
}
#############################################################################
## History Directive (Main)
@ -68,7 +76,7 @@ class HistoryController extends taiga.Controller
return @rs.history.undeleteComment(type, objectId, activityId).then => @.loadHistory(type, objectId)
HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $compile) ->
HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $compile, $navUrls, $rootScope) ->
templateChangeDiff = $template.get("common/history/history-change-diff.html", true)
templateChangePoints = $template.get("common/history/history-change-points.html", true)
templateChangeGeneric = $template.get("common/history/history-change-generic.html", true)
@ -136,15 +144,6 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
return humanizedFieldNames[field] or field
getUserFullName = (userId) ->
return $scope.usersById[userId]?.full_name_display
getUserAvatar = (userId) ->
if $scope.usersById[userId]?
return $scope.usersById[userId].photo
else
return "/images/unnamed.png"
countChanges = (comment) ->
return _.keys(comment.values_diff).length
@ -263,6 +262,10 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
return templateChangeGeneric({name:name, from:from, to: to})
renderChangeEntries = (change) ->
changeModel = change.key.split(":")[0]
if IGNORED_FIELDS[changeModel]?
change.values_diff = _.removeKeys(change.values_diff, IGNORED_FIELDS[changeModel])
return _.map(change.values_diff, (value, field) -> renderChangeEntry(field, value))
renderChangesHelperText = (change) ->
@ -286,8 +289,9 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
return html[0].outerHTML
html = templateActivity({
avatar: getUserAvatar(comment.user.pk)
avatar: comment.user.photo
userFullName: comment.user.name
userProfileUrl: if comment.user.is_active then $navUrls.resolve("user-profile", {username: comment.user.username}) else ""
creationDate: moment(comment.created_at).format(getPrettyDateFormat())
comment: comment.comment_html
changesText: renderChangesHelperText(comment)
@ -305,8 +309,9 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
renderChange = (change) ->
return templateActivity({
avatar: getUserAvatar(change.user.pk)
avatar: change.user.photo
userFullName: change.user.name
userProfileUrl: if change.user.is_active then $navUrls.resolve("user-profile", {username: change.user.username}) else ""
creationDate: moment(change.created_at).format(getPrettyDateFormat())
comment: change.comment_html
changes: renderChangeEntries(change)
@ -359,6 +364,8 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
.start()
onSuccess = ->
$rootScope.$broadcast("comment:new")
$ctrl.loadHistory(type, objectId).finally ->
currentLoading.finish()
@ -385,6 +392,13 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
target = angular.element(event.currentTarget)
save(target)
$el.on "click", "a", (event) ->
target = angular.element(event.target)
href = target.attr('href')
if href && href.indexOf("#") == 0
event.preventDefault()
$('body').scrollTop($(href).offset().top)
$el.on "click", ".show-more", (event) ->
event.preventDefault()
@ -419,8 +433,13 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
$(this).addClass('active')
$el.on "click", ".history-tabs li a", (event) ->
$el.find(".history-tabs li a").toggleClass("active")
$el.find(".history section").toggleClass("hidden")
target = angular.element(event.currentTarget)
$el.find(".history-tabs li a").removeClass("active")
target.addClass("active")
$el.find(".history section").addClass("hidden")
$el.find(".history section.#{target.data('section-class')}").removeClass("hidden")
$el.on "click", ".comment-delete", debounce 2000, (event) ->
event.preventDefault()
@ -454,4 +473,4 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm, $translate, $c
module.directive("tgHistory", ["$log", "$tgLoading", "$tgQqueue", "$tgTemplate", "$tgConfirm", "$translate",
"$compile", HistoryDirective])
"$compile", "$tgNavUrls", "$rootScope", HistoryDirective])

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -39,13 +39,14 @@ class LightboxService extends taiga.Service
lightboxContent = $el.children().not(".close")
lightboxContent.hide()
$el.css('display', 'flex')
@animationFrame.add ->
$el.css('display', 'flex')
@animationFrame.add =>
@animationFrame.add ->
$el.addClass("open")
@animationFrame.add ->
$el.find('input,textarea').first().focus()
@animationFrame.add ->
$el.find('input,textarea').first().focus()
@animationFrame.add =>
lightboxContent.show()
@ -475,10 +476,9 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
}
html = usersTemplate(ctx)
html = $compile(html)($scope)
$el.find("div.watchers").html(html)
$el.find(".assigned-to-list").html(html)
closeLightbox = () ->
lightboxKeyboardNavigationService.stop()
@ -499,7 +499,7 @@ AssignedToLightboxDirective = (lightboxService, lightboxKeyboardNavigationServic
render(selectedUser, searchingText)
$el.find('input').focus()
$el.on "click", ".watcher-single", (event) ->
$el.on "click", ".user-list-single", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
@ -543,7 +543,7 @@ module.directive("tgLbAssignedto", ["lightboxService", "lightboxKeyboardNavigati
## Watchers Lightbox directive
#############################################################################
WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template) ->
WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile) ->
link = ($scope, $el, $attrs) ->
selectedItem = null
usersTemplate = $template.get("common/lightbox/lightbox-assigned-to-users.html", true)
@ -572,7 +572,8 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS
}
html = usersTemplate(ctx)
$el.find("div.watchers").html(html)
html = $compile(html)($scope)
$el.find(".ticket-watchers").html(html)
closeLightbox = () ->
lightboxKeyboardNavigationService.stop()
@ -596,7 +597,7 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS
render(users)
$el.find("input").focus()
$el.on "click", ".watcher-single", debounce 2000, (event) ->
$el.on "click", ".user-list-single", debounce 2000, (event) ->
closeLightbox()
event.preventDefault()
@ -622,4 +623,37 @@ WatchersLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationS
link:link
}
module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", WatchersLightboxDirective])
module.directive("tgLbWatchers", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", WatchersLightboxDirective])
#############################################################################
## Attachment Preview Lighbox
#############################################################################
AttachmentPreviewLightboxDirective = ($repo, lightboxService, lightboxKeyboardNavigationService, $template, $compile) ->
link = ($scope, $el, attrs) ->
template = $template.get("common/lightbox/lightbox-attachment-preview.html", true)
$scope.$on "attachment:preview", (event, attachment) ->
lightboxService.open($el)
render(attachment)
$scope.$on "$destroy", ->
$el.off()
render = (attachment) ->
ctx = {
url: attachment.url,
title: attachment.description,
name: attachment.name
}
html = template(ctx)
html = $compile(html)($scope)
$el.html(html)
return {
link: link
}
module.directive("tgLbAttachmentPreview", ["$tgRepo", "lightboxService", "lightboxKeyboardNavigationService", "$tgTemplate", "$compile", AttachmentPreviewLightboxDirective])

View File

@ -1,9 +1,9 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
# Copyright (C) 2014 Alejandro Alonso <alejandro.alonso@kaleidos.net>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
# Copyright (C) 2014-2015 Alejandro Alonso <alejandro.alonso@kaleidos.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -77,19 +77,11 @@ Loader = ($rootscope) ->
lastResponseDate = 0
autoClose = () ->
maxAuto = 5000
timeoutAuto = setTimeout (() ->
pageLoaded()
clearInterval(intervalAuto)
), maxAuto
intervalAuto = setInterval (() ->
if lastResponseDate && requestCount == 0
pageLoaded()
clearInterval(intervalAuto)
clearTimeout(timeoutAuto)
), 50
start = () ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -21,19 +21,29 @@
module = angular.module("taigaCommon")
TgLoadingService = ->
TgLoadingService = ($compile) ->
spinner = "<img class='loading-spinner' src='/svg/spinner-circle.svg' alt='loading...' />"
return () ->
service = {
settings: {
target: null,
scope: null,
classes: []
timeout: 0
timeout: 0,
template: null
},
target: (target) ->
service.settings.target = target
return service
scope: (scope) ->
service.settings.scope = scope
return service
template: (template) ->
service.settings.template = template
return service
removeClasses: (classess...) ->
service.settings.classes = classess
@ -51,9 +61,11 @@ TgLoadingService = ->
# The loader is shown after that quantity of milliseconds
timeoutId = setTimeout (->
if not target.hasClass('loading')
service.settings.oldContent = target.html()
if !service.settings.template
service.settings.template = target.html()
target.addClass('loading')
target.html(spinner)
), service.settings.timeout
@ -71,27 +83,37 @@ TgLoadingService = ->
removeClasses = service.settings.classes
removeClasses.map (className) -> service.settings.target.addClass(className)
target.html(service.settings.oldContent)
target.html(service.settings.template)
target.removeClass('loading')
if service.settings.scope
$compile(target.contents())(service.settings.scope)
return service
}
return service
TgLoadingService.$inject = [
"$compile"
]
module.factory("$tgLoading", TgLoadingService)
LoadingDirective = ($loading) ->
link = ($scope, $el, attr) ->
currentLoading = null
template = $el.html()
$scope.$watch attr.tgLoading, (showLoading) =>
if showLoading
currentLoading = $loading()
.target($el)
.timeout(50)
.template(template)
.scope($scope)
.start()
else
else if currentLoading
currentLoading.finish()
return {

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -149,6 +149,8 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans
startIndex = result.index
break
return if !result
regex = />>>/gi
endIndex = 0
loop
@ -181,10 +183,19 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans
onShiftEnter: {keepDefault:false, openWith:"\n\n"}
onEnter:
keepDefault: false,
replaceWith: () -> "\n"
replaceWith: () ->
# Allow textcomplete to intercept the enter key if the options list is displayed
# @todo There doesn't seem to be a more graceful way to do this with the textcomplete API.
if not $('.textcomplete-dropdown').is(':visible')
"\n"
afterInsert: (data) ->
lines = data.textarea.value.split("\n")
cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
# Detect if we are in this situation +- aa at the beginning if the textarea
if data.caretPosition > 0
cursorLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
else
cursorLine = 1
newLineContent = data.textarea.value[data.caretPosition..].split("\n")[0]
lastLine = lines[cursorLine - 1]
@ -342,6 +353,104 @@ MarkitupDirective = ($rootscope, $rs, $selectedText, $template, $compile, $trans
element
.markItUpRemove()
.markItUp(markdownSettings)
.textcomplete([
# us, task, and issue autocomplete: #id or #<part of title>
{
cache: true
match: /(^|\s)#([a-z0-9]+)$/i,
search: (term, callback) ->
term = taiga.slugify(term)
searchTypes = ['issues', 'tasks', 'userstories']
searchProps = ['ref', 'subject']
filter = (item) =>
for prop in searchProps
if taiga.slugify(item[prop]).indexOf(term) >= 0
return true
return false
$rs.search.do($scope.projectId, term).then (res) =>
# ignore wikipages if they're the only results. can't exclude them in search
if res.count < 1 or res.count == res.wikipages.length
callback([])
else
for type in searchTypes
if res[type] and res[type].length > 0
callback(res[type].filter(filter), true)
# must signal end of lists
callback([])
replace: (res) ->
return "$1\##{res.ref} "
template: (res, term) ->
return "\##{res.ref} - #{res.subject}"
}
# username autocomplete: @username or @<part of name>
{
cache: true
match: /(^|\s)@([a-z0-9\-\._]{2,})$/i
search: (term, callback) ->
username = taiga.slugify(term)
searchProps = ['username', 'full_name', 'full_name_display']
if $scope.project.members.length < 1
callback([])
else
callback $scope.project.members.filter (user) =>
for prop in searchProps
if taiga.slugify(user[prop]).indexOf(username) >= 0
return true
return false
replace: (user) ->
return "$1@#{user.username} "
template: (user) ->
return "#{user.username} - #{user.full_name_display}"
}
# wiki pages autocomplete: [[slug or [[<part of slug>
# if the search function was called with the 3rd param the regex
# like the docs claim, we could combine this with the #123 search
{
cache: true
match: /(^|\s)\[\[([a-z0-9\-]+)$/i
search: (term, callback) ->
term = taiga.slugify(term)
$rs.search.do($scope.projectId, term).then (res) =>
if res.count < 1
callback([])
if res.count < 1 or not res.wikipages or res.wikipages.length <= 0
callback([])
else
callback res.wikipages.filter((page) =>
return taiga.slugify(page['slug']).indexOf(term) >= 0
), true
# must signal end of lists
callback([])
replace: (res) ->
return "$1[[#{res.slug}]]"
template: (res, term) ->
return res.slug
}
],
{
debounce: 200
}
)
renderMarkItUp()

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -41,9 +41,9 @@ class PageMixin
@scope.usersById = groupBy(@scope.users, (e) -> e.id)
@scope.roles = _.sortBy(roles, "order")
availableRoles = _(@scope.project.memberships).map("role").uniq().value()
computableRoles = _(@scope.project.members).map("role").uniq().value()
@scope.computableRoles = _(roles).filter("computable")
.filter((x) -> _.contains(availableRoles, x.id))
.filter((x) -> _.contains(computableRoles, x.id))
.value()
loadUsersAndRoles: ->
promise = @q.all([

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -26,6 +26,7 @@ toString = @.taiga.toString
joinStr = @.taiga.joinStr
groupBy = @.taiga.groupBy
bindOnce = @.taiga.bindOnce
bindMethods = @.taiga.bindMethods
module = angular.module("taigaIssues")
@ -52,6 +53,8 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appMetaService, @analytics, @navUrls, @translate) ->
bindMethods(@)
@scope.issueRef = @params.issueref
@scope.sectionName = @translate.instant("ISSUES.SECTION_NAME")
@.initializeEventHandlers()
@ -83,20 +86,16 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
initializeEventHandlers: ->
@scope.$on "attachment:create", =>
@rootscope.$broadcast("object:updated")
@analytics.trackEvent("attachment", "create", "create attachment on issue", 1)
@scope.$on "attachment:edit", =>
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:delete", =>
@rootscope.$broadcast("object:updated")
@scope.$on "promote-issue-to-us:success", =>
@analytics.trackEvent("issue", "promoteToUserstory", "promote issue to userstory", 1)
@rootscope.$broadcast("object:updated")
@.loadIssue()
@scope.$on "comment:new", =>
@.loadIssue()
@scope.$on "custom-attributes-values:edit", =>
@rootscope.$broadcast("object:updated")
@ -120,7 +119,6 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.severityById = groupBy(project.severities, (x) -> x.id)
@scope.priorityList = project.priorities
@scope.priorityById = groupBy(project.priorities, (x) -> x.id)
@scope.membersById = groupBy(project.memberships, (x) -> x.user)
return project
loadIssue: ->
@ -146,9 +144,52 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
loadInitialData: ->
promise = @.loadProject()
return promise.then (project) =>
@.fillUsersAndRoles(project.users, project.roles)
@.fillUsersAndRoles(project.members, project.roles)
@.loadIssue()
###
# Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
# See app/modules/components/vote-button for more info
###
onUpvote: ->
onSuccess = =>
@.loadIssue()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.issues.upvote(@scope.issueId).then(onSuccess, onError)
onDownvote: ->
onSuccess = =>
@.loadIssue()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.issues.downvote(@scope.issueId).then(onSuccess, onError)
###
# Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
# See app/modules/components/watch-button for more info
###
onWatch: ->
onSuccess = =>
@.loadIssue()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.issues.watch(@scope.issueId).then(onSuccess, onError)
onUnwatch: ->
onSuccess = =>
@.loadIssue()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.issues.unwatch(@scope.issueId).then(onSuccess, onError)
module.controller("IssueDetailController", IssueDetailController)
@ -558,7 +599,7 @@ module.directive("tgIssuePriorityButton", ["$rootScope", "$tgRepo", "$tgConfirm"
PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue, $translate) ->
link = ($scope, $el, $attrs, $model) ->
save = $qqueue.bindAdd (issue, finish) =>
save = $qqueue.bindAdd (issue, askResponse) =>
data = {
generated_from_issue: issue.id
project: issue.project,
@ -570,12 +611,12 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue, $transl
}
onSuccess = ->
finish()
askResponse.finish()
$confirm.notify("success")
$rootScope.$broadcast("promote-issue-to-us:success")
onError = ->
finish(false)
askResponse.finish()
$confirm.notify("error")
$repo.create("userstories", data).then(onSuccess, onError)
@ -589,9 +630,8 @@ PromoteIssueToUsButtonDirective = ($rootScope, $repo, $confirm, $qqueue, $transl
message = $translate.instant("ISSUES.CONFIRM_PROMOTE.MESSAGE")
subtitle = issue.subject
$confirm.ask(title, subtitle, message).then (finish) =>
save(issue, finish)
$confirm.ask(title, subtitle, message).then (response) =>
save(issue, response)
$scope.$on "$destroy", ->
$el.off()

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -83,8 +83,6 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.$on "issueform:new:success", =>
@analytics.trackEvent("issue", "create", "create issue on issues list", 1)
@.loadIssues()
@.loadFilters()
initializeSubscription: ->
routingKey = "changes.project.#{@scope.projectId}.issues"
@ -112,13 +110,13 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.issueTypes = _.sortBy(project.issue_types, "order")
@scope.issueTypeById = groupBy(project.issue_types, (x) -> x.id)
@scope.membersById = groupBy(project.memberships, (x) -> x.user)
return project
getUrlFilters: ->
filters = _.pick(@location.search(), "page", "tags", "statuses", "types",
filters = _.pick(@location.search(), "page", "tags", "status", "types",
"q", "severities", "priorities",
"assignedTo", "createdBy", "orderBy")
filters.page = 1 if not filters.page
return filters
@ -181,20 +179,30 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.filters.myFilters = myFilters
return myFilters
loadFilters = {}
loadFilters.project = @scope.projectId
loadFilters.tags = urlfilters.tags
loadFilters.status = urlfilters.status
loadFilters.q = urlfilters.q
loadFilters.types = urlfilters.types
loadFilters.severities = urlfilters.severities
loadFilters.priorities = urlfilters.priorities
loadFilters.assigned_to = urlfilters.assignedTo
loadFilters.owner = urlfilters.createdBy
# Load default filters data
promise = promise.then =>
return @rs.issues.filtersData(@scope.projectId)
return @rs.issues.filtersData(loadFilters)
# Format filters and set them on scope
return promise.then (data) =>
usersFiltersFormat = (users, type, unknownOption) =>
reformatedUsers = _.map users, (t) =>
return {
id: t[0],
count: t[1],
type: type
name: if t[0] then @scope.usersById[t[0]].full_name_display else unknownOption
}
t.type = type
t.name = if t.full_name then t.full_name else unknownOption
return t
unknownItem = _.remove(reformatedUsers, (u) -> not u.id)
reformatedUsers = _.sortBy(reformatedUsers, (u) -> u.name.toUpperCase())
if unknownItem.length > 0
@ -203,34 +211,27 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
choicesFiltersFormat = (choices, type, byIdObject) =>
_.map choices, (t) ->
return {
id: t[0],
name: byIdObject[t[0]].name,
color: byIdObject[t[0]].color,
count: t[1],
type: type}
t.type = type
return t
tagsFilterFormat = (tags) =>
return _.map tags, (t) =>
return {
id: t[0],
name: t[0],
color: @scope.project.tags_colors[t[0]],
count: t[1],
type: "tags"
}
return _.map tags, (t) ->
t.id = t.name
t.type = 'tags'
return t
# Build filters data structure
@scope.filters.statuses = choicesFiltersFormat(data.statuses, "statuses", @scope.issueStatusById)
@scope.filters.status = choicesFiltersFormat(data.statuses, "status", @scope.issueStatusById)
@scope.filters.severities = choicesFiltersFormat(data.severities, "severities", @scope.severityById)
@scope.filters.priorities = choicesFiltersFormat(data.priorities, "priorities", @scope.priorityById)
@scope.filters.assignedTo = usersFiltersFormat(data.assigned_to, "assignedTo", "Unassigned")
@scope.filters.createdBy = usersFiltersFormat(data.created_by, "createdBy", "Unknown")
@scope.filters.createdBy = usersFiltersFormat(data.owners, "createdBy", "Unknown")
@scope.filters.types = choicesFiltersFormat(data.types, "types", @scope.issueTypeById)
@scope.filters.tags = tagsFilterFormat(data.tags)
@.removeNotExistingFiltersFromUrl()
@.markSelectedFilters(@scope.filters, urlfilters)
@rootscope.$broadcast("filters:loaded", @scope.filters)
# We need to guarantee that the last petition done here is the finally used
@ -258,7 +259,7 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
name = "assigned_to"
else if name == "createdBy"
name = "owner"
else if name == "statuses"
else if name == "status"
name = "status"
else if name == "types"
name = "type"
@ -273,14 +274,19 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.page = data.current
@scope.count = data.count
@scope.paginatedBy = data.paginatedBy
return data
return promise
loadInitialData: ->
promise = @.loadProject()
return promise.then (project) =>
@.fillUsersAndRoles(project.users, project.roles)
@.fillUsersAndRoles(project.members, project.roles)
@.initializeSubscription()
return @q.all([@.loadFilters(), @.loadIssues()])
@.loadFilters()
return @.loadIssues()
saveCurrentFiltersTo: (newFilter) ->
deferred = @q.defer()
@ -401,7 +407,7 @@ IssuesDirective = ($log, $location, $template, $compile) ->
# Draw the arrow the first time
currentOrder = $ctrl.getUrlFilter("orderBy") or "created_date"
if currentOrder
icon = if startswith(currentOrder, "-") then "icon-caret-up" else "icon-caret-down"
icon = if startswith(currentOrder, "-") then "icon-arrow-up" else "icon-arrow-bottom"
colHeadElement = $el.find(".row.title > div[data-fieldname='#{trim(currentOrder, "-")}']")
colHeadElement.html("#{colHeadElement.html()}<span class='icon #{icon}'></span>")
@ -419,7 +425,7 @@ IssuesDirective = ($log, $location, $template, $compile) ->
$ctrl.loadIssues().then ->
# Update the arrow
$el.find(".row.title > div > span.icon").remove()
icon = if startswith(finalOrder, "-") then "icon-caret-up" else "icon-caret-down"
icon = if startswith(finalOrder, "-") then "icon-arrow-up" else "icon-arrow-bottom"
target.html("#{target.html()}<span class='icon #{icon}'></span>")
## Issues Link
@ -440,12 +446,13 @@ module.directive("tgIssues", ["$log", "$tgLocation", "$tgTemplate", "$compile",
## Issues Filters Directive
#############################################################################
IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $translate, $compile, $auth) ->
IssuesFiltersDirective = ($q, $log, $location, $rs, $confirm, $loading, $template, $translate, $compile, $auth) ->
template = $template.get("issue/issues-filters.html", true)
templateSelected = $template.get("issue/issues-filters-selected.html", true)
link = ($scope, $el, $attrs) ->
$ctrl = $el.closest(".wrapper").controller()
selectedFilters = []
showFilters = (title, type) ->
@ -491,6 +498,16 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
html = $compile(html)($scope)
$el.find(".filter-list").html(html)
getFiltersType = () ->
return $el.find("h2 a.subfilter span.title").prop('data-type')
reloadIssues = () ->
currentFiltersType = getFiltersType()
$q.all([$ctrl.loadIssues(), $ctrl.loadFilters()]).then () ->
filters = $scope.filters[currentFiltersType]
renderFilters(_.reject(filters, "selected"))
toggleFilterSelection = (type, id) ->
if type == "myFilters"
$rs.issues.getMyFilters($scope.projectId).then (data) ->
@ -507,7 +524,6 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
filters = $scope.filters[type]
filterId = if type == 'tags' then taiga.toString(id) else id
filter = _.find(filters, {id: filterId})
filter.selected = (not filter.selected)
# Convert id to null as string for properly
@ -516,22 +532,23 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
if filter.selected
selectedFilters.push(filter)
$scope.$apply ->
$ctrl.selectFilter(type, id)
$ctrl.selectFilter("page", 1)
$ctrl.storeFilters()
$ctrl.loadIssues()
$ctrl.selectFilter(type, id)
$ctrl.selectFilter("page", 1)
$ctrl.storeFilters()
else
selectedFilters = _.reject(selectedFilters, filter)
$scope.$apply ->
$ctrl.unselectFilter(type, id)
$ctrl.selectFilter("page", 1)
$ctrl.storeFilters()
$ctrl.loadIssues()
selectedFilters = _.reject selectedFilters, (f) ->
return f.id == filter.id && f.type == filter.type
$ctrl.unselectFilter(type, id)
$ctrl.selectFilter("page", 1)
$ctrl.storeFilters()
reloadIssues()
renderSelectedFilters(selectedFilters)
currentFiltersType = $el.find("h2 a.subfilter span.title").prop('data-type')
currentFiltersType = getFiltersType()
if type == currentFiltersType
renderFilters(_.reject(filters, "selected"))
@ -540,7 +557,7 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
initializeSelectedFilters(filters)
$scope.$on "filters:issueupdate", (ctx, filters) ->
html = template({filters:filters.statuses})
html = template({filters:filters.status})
html = $compile(html)($scope)
$el.find(".filter-list").html(html)
@ -555,7 +572,8 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
else
$ctrl.replaceFilter("q", value)
$ctrl.storeFilters()
$ctrl.loadIssues()
reloadIssues()
$scope.$watch("filtersQ", selectQFilter)
@ -602,18 +620,18 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
title = $translate.instant("ISSUES.FILTERS.CONFIRM_DELETE.TITLE")
message = $translate.instant("ISSUES.FILTERS.CONFIRM_DELETE.MESSAGE", {customFilterName: customFilterName})
$confirm.askOnDelete(title, message).then (finish) ->
$confirm.askOnDelete(title, message).then (askResponse) ->
promise = $ctrl.deleteMyFilter(customFilterName)
promise.then ->
promise = $ctrl.loadMyFilters()
promise.then (filters) ->
finish()
askResponse.finish()
$scope.filters.myFilters = filters
renderFilters($scope.filters.myFilters)
promise.then null, ->
finish()
askResponse.finish()
promise.then null, ->
finish(false)
askResponse.finish(false)
$confirm.notify("error")
@ -664,7 +682,7 @@ IssuesFiltersDirective = ($log, $location, $rs, $confirm, $loading, $template, $
return {link:link}
module.directive("tgIssuesFilters", ["$log", "$tgLocation", "$tgResources", "$tgConfirm", "$tgLoading",
module.directive("tgIssuesFilters", ["$q", "$log", "$tgLocation", "$tgResources", "$tgConfirm", "$tgLoading",
"$tgTemplate", "$translate", "$compile", "$tgAuth", IssuesFiltersDirective])
@ -711,7 +729,7 @@ IssueStatusInlineEditionDirective = ($repo, $template, $rootscope) ->
event.stopPropagation()
target = angular.element(event.currentTarget)
for filter in $scope.filters.statuses
for filter in $scope.filters.status
if filter.id == issue.status
filter.count--
@ -723,15 +741,10 @@ IssueStatusInlineEditionDirective = ($repo, $template, $rootscope) ->
$repo.save(issue).then ->
$ctrl.loadIssues()
for filter in $scope.filters.statuses
if filter.id == issue.status
filter.count++
$rootscope.$broadcast("filters:issueupdate", $scope.filters)
for filter in $scope.filters.statuses
for filter in $scope.filters.status
if filter.id == issue.status
filter.count++
$rootscope.$broadcast("filters:issueupdate", $scope.filters)
taiga.bindOnce $scope, "project", (project) ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -34,14 +34,10 @@ module = angular.module("taigaKanban")
# Vars
defaultViewMode = "maximized"
defaultViewModes = {
maximized: {
cardClass: "kanban-task-maximized"
}
minimized: {
cardClass: "kanban-task-minimized"
}
}
viewModes = [
"maximized",
"minimized"
]
#############################################################################
@ -157,6 +153,10 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
usByStatus[status.id] = _.sortBy(usByStatus[status.id], "kanban_order")
if userstories.length == 0
status = @scope.usStatusList[0]
usByStatus[status.id].push({isPlaceholder: true})
@scope.usByStatus = usByStatus
# The broadcast must be executed when the DOM has been fully reloaded.
@ -209,7 +209,7 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
loadInitialData: ->
promise = @.loadProject()
return promise.then (project) =>
@.fillUsersAndRoles(project.users, project.roles)
@.fillUsersAndRoles(project.members, project.roles)
@.initializeSubscription()
@.loadKanban().then( => @scope.$broadcast("redraw:wip"))
@ -221,8 +221,9 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.statusViewModes = {}
for status in @scope.usStatusList
mode = storedStatusViewModes[status.id]
@scope.statusViewModes[status.id] = if _.has(defaultViewModes, mode) then mode else defaultViewMode
mode = storedStatusViewModes[status.id] || defaultViewMode
@scope.statusViewModes[status.id] = mode
@.storeStatusViewModes()
@ -233,9 +234,13 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
@scope.statusViewModes[statusId] = newViewMode
@.storeStatusViewModes()
getCardClass: (statusId)->
isMaximized: (statusId) ->
mode = @scope.statusViewModes[statusId] or defaultViewMode
return defaultViewModes[mode].cardClass or defaultViewModes[defaultViewMode].cardClass
return mode == 'maximized'
isMinimized: (statusId) ->
mode = @scope.statusViewModes[statusId] or defaultViewMode
return mode == 'minimized'
# Utils methods
@ -317,7 +322,7 @@ KanbanArchivedStatusHeaderDirective = ($rootscope, $translate) ->
status = $scope.$eval($attrs.tgKanbanArchivedStatusHeader)
hidden = true
$scope.class = "icon icon-open-eye"
$scope.class = "icon-open-eye"
$scope.title = showArchivedText
$el.on "click", (event) ->
@ -325,12 +330,12 @@ KanbanArchivedStatusHeaderDirective = ($rootscope, $translate) ->
$scope.$apply ->
if hidden
$scope.class = "icon icon-open-eye"
$scope.class = "icon-open-eye"
$scope.title = showArchivedText
$rootscope.$broadcast("kanban:hide-userstories-for-status", status.id)
else
$scope.class = "icon icon-closed-eye"
$scope.class = "icon-closed-eye"
$scope.title = hideArchivedText
$rootscope.$broadcast("kanban:show-userstories-for-status", status.id)
@ -414,7 +419,7 @@ KanbanUserstoryDirective = ($rootscope, $loading, $rs) ->
else if not us.is_blocked and $el.hasClass("blocked")
$el.removeClass("blocked")
$el.find(".icon-edit").on "click", (event) ->
$el.on 'click', '.icon-edit', (event) ->
if $el.find(".icon-edit").hasClass("noclick")
return
@ -431,11 +436,17 @@ KanbanUserstoryDirective = ($rootscope, $loading, $rs) ->
$rootscope.$broadcast("usform:edit", editingUserStory)
currentLoading.finish()
$scope.getTemplateUrl = () ->
if $scope.us.isPlaceholder
return "common/components/kanban-placeholder.html"
else
return "kanban/kanban-task.html"
$scope.$on "$destroy", ->
$el.off()
return {
templateUrl: "kanban/kanban-task.html"
template: '<ng-include src="getTemplateUrl()"/>',
link: link
require: "ngModel"
}

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -47,7 +47,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
$location.url($projectUrl.get(response))
lightboxService.close($el)
currentUserService._loadProjects()
currentUserService.loadProjects()
onErrorSubmit = (response) ->
currentLoading.finish()
@ -74,10 +74,7 @@ CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $project
promise.then(onSuccessSubmit, onErrorSubmit)
openLightbox = ->
$scope.data = {
total_story_points: 100
total_milestones: 5
}
$scope.data = {}
if !$scope.templates.length
$rs.projects.templates().then (result) =>
@ -173,7 +170,7 @@ DeleteProjectDirective = ($repo, $rootscope, $auth, $location, $navUrls, $confir
$rootscope.$broadcast("projects:reload")
$location.path($navUrls.resolve("home"))
$confirm.notify("success")
currentUserService._loadProjects()
currentUserService.loadProjects()
# FIXME: error handling?
promise.then null, ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -85,14 +85,15 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $tem
task = $model.$modelValue
message = task.subject
$confirm.askOnDelete(title, message).then (finish) ->
$confirm.askOnDelete(title, message).then (askResponse) ->
promise = $repo.remove(task)
promise.then ->
finish()
askResponse.finish()
$confirm.notify("success")
$scope.$emit("related-tasks:delete")
promise.then null, ->
askResponse.finish(false)
$confirm.notify("error")
$scope.$watch $attrs.ngModel, (val) ->
@ -216,7 +217,7 @@ RelatedTasksDirective = ($repo, $rs, $rootscope) ->
link = ($scope, $el, $attrs) ->
loadTasks = ->
return $rs.tasks.list($scope.projectId, null, $scope.usId).then (tasks) =>
$scope.tasks = tasks
$scope.tasks = _.sortBy(tasks, 'ref')
return tasks
$scope.$on "related-tasks:add", ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -37,8 +37,11 @@ urls = {
"users-change-password": "/users/change_password"
"users-change-email": "/users/change_email"
"users-cancel-account": "/users/cancel"
"contacts": "/users/%s/contacts"
"stats": "/users/%s/stats"
"user-stats": "/users/%s/stats"
"user-liked": "/users/%s/liked"
"user-voted": "/users/%s/voted"
"user-watched": "/users/%s/watched"
"user-contacts": "/users/%s/contacts"
# User - Notification
"permissions": "/permissions"
@ -63,6 +66,10 @@ urls = {
"project-templates": "/project-templates"
"project-modules": "/projects/%s/modules"
"bulk-update-projects-order": "/projects/bulk_update_order"
"project-like": "/projects/%s/like"
"project-unlike": "/projects/%s/unlike"
"project-watch": "/projects/%s/watch"
"project-unwatch": "/projects/%s/unwatch"
# Project Values - Choises
"userstory-statuses": "/userstory-statuses"
@ -82,15 +89,29 @@ urls = {
"bulk-update-us-backlog-order": "/userstories/bulk_update_backlog_order"
"bulk-update-us-sprint-order": "/userstories/bulk_update_sprint_order"
"bulk-update-us-kanban-order": "/userstories/bulk_update_kanban_order"
"userstories-filters": "/userstories/filters_data"
"userstory-upvote": "/userstories/%s/upvote"
"userstory-downvote": "/userstories/%s/downvote"
"userstory-watch": "/userstories/%s/watch"
"userstory-unwatch": "/userstories/%s/unwatch"
# Tasks
"tasks": "/tasks"
"bulk-create-tasks": "/tasks/bulk_create"
"bulk-update-task-taskboard-order": "/tasks/bulk_update_taskboard_order"
"task-upvote": "/tasks/%s/upvote"
"task-downvote": "/tasks/%s/downvote"
"task-watch": "/tasks/%s/watch"
"task-unwatch": "/tasks/%s/unwatch"
# Issues
"issues": "/issues"
"bulk-create-issues": "/issues/bulk_create"
"issues-filters": "/issues/filters_data"
"issue-upvote": "/issues/%s/upvote"
"issue-downvote": "/issues/%s/downvote"
"issue-watch": "/issues/%s/watch"
"issue-unwatch": "/issues/%s/unwatch"
# Wiki pages
"wiki": "/wiki"
@ -147,6 +168,10 @@ urls = {
# locales
"locales": "/locales"
# Application tokens
"applications": "/applications"
"application-tokens": "/application-tokens"
}
# Initialize api urls service

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -55,11 +55,27 @@ resourceProvider = ($repo, $http, $urls, $storage, $q) ->
params = {project_id: projectId, bulk_issues: data}
return $http.post(url, params)
service.upvote = (issueId) ->
url = $urls.resolve("issue-upvote", issueId)
return $http.post(url)
service.downvote = (issueId) ->
url = $urls.resolve("issue-downvote", issueId)
return $http.post(url)
service.watch = (issueId) ->
url = $urls.resolve("issue-watch", issueId)
return $http.post(url)
service.unwatch = (issueId) ->
url = $urls.resolve("issue-unwatch", issueId)
return $http.post(url)
service.stats = (projectId) ->
return $repo.queryOneRaw("projects", "#{projectId}/issues_stats")
service.filtersData = (projectId) ->
return $repo.queryOneRaw("projects", "#{projectId}/issue_filters_data")
service.filtersData = (params) ->
return $repo.queryOneRaw("issues-filters", null, params)
service.listValues = (projectId, type) ->
params = {"project": projectId}

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,3 +1,22 @@
###
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: modules.coffee
###
resourceProvider = ($repo) ->
service = {}

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -39,12 +39,20 @@ resourceProvider = ($repo, $model, $storage) ->
service.list = (projectId, filters) ->
params = {"project": projectId}
params = _.extend({}, params, filters or {})
return $repo.queryMany("milestones", params).then (milestones) =>
return $repo.queryMany("milestones", params, {}, true).then (result) =>
milestones = result[0]
headers = result[1]
for m in milestones
uses = m.user_stories
uses = _.map(uses, (u) => $model.make_model("userstories", u))
m._attrs.user_stories = uses
return milestones
return {
milestones: milestones,
closed: parseInt(headers("Taiga-Info-Total-Closed-Milestones"), 10),
open: parseInt(headers("Taiga-Info-Total-Opened-Milestones"), 10)
}
return (instance) ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -57,6 +57,22 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
return $http.post(url, params).then (result) ->
return result.data
service.upvote = (taskId) ->
url = $urls.resolve("task-upvote", taskId)
return $http.post(url)
service.downvote = (taskId) ->
url = $urls.resolve("task-downvote", taskId)
return $http.post(url)
service.watch = (taskId) ->
url = $urls.resolve("task-watch", taskId)
return $http.post(url)
service.unwatch = (taskId) ->
url = $urls.resolve("task-unwatch", taskId)
return $http.post(url)
service.bulkUpdateTaskTaskboardOrder = (projectId, data) ->
url = $urls.resolve("bulk-update-task-taskboard-order")
params = {project_id: projectId, bulk_tasks: data}

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -28,7 +28,7 @@ resourceProvider = ($http, $urls) ->
service = {}
service.contacts = (userId, options={}) ->
url = $urls.resolve("contacts", userId)
url = $urls.resolve("user-contacts", userId)
httpOptions = {headers: {}}
if not options.enablePagination

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -41,6 +41,9 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
service.listInAllProjects = (filters) ->
return $repo.queryMany("userstories", filters)
service.filtersData = (params) ->
return $repo.queryOneRaw("userstories-filters", null, params)
service.listUnassigned = (projectId, filters) ->
params = {"project": projectId, "milestone": "null"}
params = _.extend({}, params, filters or {})
@ -64,6 +67,22 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
return $http.post(url, data)
service.upvote = (userStoryId) ->
url = $urls.resolve("userstory-upvote", userStoryId)
return $http.post(url)
service.downvote = (userStoryId) ->
url = $urls.resolve("userstory-downvote", userStoryId)
return $http.post(url)
service.watch = (userStoryId) ->
url = $urls.resolve("userstory-watch", userStoryId)
return $http.post(url)
service.unwatch = (userStoryId) ->
url = $urls.resolve("userstory-unwatch", userStoryId)
return $http.post(url)
service.bulkUpdateBacklogOrder = (projectId, data) ->
url = $urls.resolve("bulk-update-us-backlog-order")
params = {project_id: projectId, bulk_stories: data}

View File

@ -1,3 +1,22 @@
###
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: webhooklogs.coffee
###
resourceProvider = ($repo, $urls, $http) ->
service = {}

View File

@ -1,3 +1,22 @@
###
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# File: webhooks.coffee
###
resourceProvider = ($repo, $urls, $http) ->
service = {}

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -64,12 +64,16 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin)
promise.then null, @.onInitialDataError.bind(@)
# Search input watcher
@scope.searchTerm = ""
@scope.searchTerm = null
loadSearchData = debounceLeading(100, (t) => @.loadSearchData(t))
bindOnce @scope, "projectId", (projectId) =>
if !@scope.searchResults && @scope.searchTerm
@.loadSearchData()
@scope.$watch "searchTerm", (term) =>
if term
loadSearchData(term)
if term != undefined && @scope.projectId
@.loadSearchData(term)
loadFilters: ->
defered = @q.defer()
@ -84,21 +88,31 @@ class SearchController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.taskStatusById = groupBy(project.task_statuses, (x) -> x.id)
@scope.severityById = groupBy(project.severities, (x) -> x.id)
@scope.priorityById = groupBy(project.priorities, (x) -> x.id)
@scope.membersById = groupBy(project.memberships, (x) -> x.user)
@scope.usStatusById = groupBy(project.us_statuses, (x) -> x.id)
return project
loadSearchData: (term) ->
promise = @rs.search.do(@scope.projectId, term).then (data) =>
@scope.searchResults = data
return data
loadSearchData: (term = "") ->
@scope.loading = true
return promise
@._loadSearchData(term).then (data) =>
if data
@scope.searchResults = data
@scope.loading = false
_loadSearchData: (term = "") ->
@.deferredAbort.resolve() if @.deferredAbort
@.deferredAbort = @q.defer()
@rs.search.do(@scope.projectId, term).then (data) =>
@.deferredAbort.resolve(data)
return @.deferredAbort.promise
loadInitialData: ->
return @.loadProject().then (project) =>
@scope.projectId = project.id
@.fillUsersAndRoles(project.users, project.roles)
@.fillUsersAndRoles(project.members, project.roles)
module.controller("SearchController", SearchController)
@ -162,13 +176,22 @@ module.directive("tgSearchBox", SearchBoxDirective)
SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) ->
linkTable = ($scope, $el, $attrs, $ctrl) ->
applyAutoTab = true
activeSectionName = "userstories"
tabsDom = $el.find("section.search-filter")
lastSeatchResults = null
lastSearchResults = null
getActiveSection = (data) ->
maxVal = 0
selectedSectionName = null
selectedSectionData = null
selectedSection = {}
selectedSection.name = "userstories"
selectedSection.value = []
if !applyAutoTab
selectedSection.name = activeSectionName
selectedSection.value = data[activeSectionName]
return selectedSection
if data
for name in ["userstories", "issues", "tasks", "wikipages"]
@ -176,14 +199,14 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) ->
if value.length > maxVal
maxVal = value.length
selectedSectionName = name
selectedSectionData = value
selectedSection.name = name
selectedSection.value = value
break;
if maxVal == 0
return {name: "userstories", value: []}
return selectedSection
return {name:selectedSectionName, value: selectedSectionData}
return selectedSection
renderFilterTabs = (data) ->
for name, value of data
@ -195,6 +218,9 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) ->
tabsDom.find("a.active").removeClass("active")
tabsDom.find("li.#{section.name} a").addClass("active")
applyAutoTab = false
activeSectionName = section.name
templates = {
issues: $templatecache.get("search-issues")
tasks: $templatecache.get("search-tasks")
@ -218,21 +244,26 @@ SearchDirective = ($log, $compile, $templatecache, $routeparams, $location) ->
$el.find(".search-result-table").html(element)
$scope.$watch "searchResults", (data) ->
lastSeatchResults = data
lastSearchResults = data
return if !lastSearchResults
activeSection = getActiveSection(data)
renderFilterTabs(data)
renderTableContent(activeSection)
markSectionTabActive(activeSection)
$scope.$watch "searchTerm", (searchTerm) ->
$location.search("text", searchTerm) if searchTerm
$location.search("text", searchTerm) if searchTerm != undefined
$el.on "click", ".search-filter li > a", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
sectionName = target.parent().data("name")
sectionData = lastSeatchResults[sectionName]
sectionData = if !lastSearchResults then [] else lastSearchResults[sectionName]
section = {
name: sectionName,

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -135,7 +135,7 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$emit('project:loaded', project)
@.fillUsersAndRoles(project.users, project.roles)
@.fillUsersAndRoles(project.members, project.roles)
return project
@ -184,6 +184,15 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
if @scope.usTasks[task.user_story]? and @scope.usTasks[task.user_story][task.status]?
@scope.usTasks[task.user_story][task.status].push(task)
if tasks.length == 0
if @scope.userstories.length > 0
usId = @scope.userstories[0].id
else
usId = null
@scope.usTasks[usId][@scope.taskStatusList[0].id].push({isPlaceholder: true})
return tasks
loadTaskboard: ->

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as

View File

@ -1,7 +1,7 @@
###
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
# Copyright (C) 2014-2015 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014-2015 Jesús Espino Garcia <jespinog@gmail.com>
# Copyright (C) 2014-2015 David Barragán Merino <bameda@dbarragan.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -23,6 +23,7 @@ taiga = @.taiga
mixOf = @.taiga.mixOf
groupBy = @.taiga.groupBy
bindMethods = @.taiga.bindMethods
module = angular.module("taigaTasks")
@ -50,6 +51,8 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
@log, @appMetaService, @navUrls, @analytics, @translate) ->
bindMethods(@)
@scope.taskRef = @params.taskref
@scope.sectionName = @translate.instant("TASK.SECTION_NAME")
@.initializeEventHandlers()
@ -77,13 +80,10 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
initializeEventHandlers: ->
@scope.$on "attachment:create", =>
@analytics.trackEvent("attachment", "create", "create attachment on task", 1)
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:edit", =>
@rootscope.$broadcast("object:updated")
@scope.$on "attachment:delete", =>
@rootscope.$broadcast("object:updated")
@scope.$on "custom-attributes-values:edit", =>
@rootscope.$broadcast("object:updated")
@scope.$on "comment:new", =>
@.loadTask()
initializeOnDeleteGoToUrl: ->
ctx = {project: @scope.project.slug}
@ -107,7 +107,6 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
@scope.$emit('project:loaded', project)
@scope.statusList = project.task_statuses
@scope.statusById = groupBy(project.task_statuses, (x) -> x.id)
@scope.membersById = groupBy(project.memberships, (x) -> x.user)
return project
loadTask: ->
@ -146,9 +145,53 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
loadInitialData: ->
promise = @.loadProject()
return promise.then (project) =>
@.fillUsersAndRoles(project.users, project.roles)
@.fillUsersAndRoles(project.members, project.roles)
@.loadTask().then(=> @q.all([@.loadSprint(), @.loadUserStory()]))
###
# Note: This methods (onUpvote() and onDownvote()) are related to tg-vote-button.
# See app/modules/components/vote-button for more info
###
onUpvote: ->
onSuccess = =>
@.loadTask()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.tasks.upvote(@scope.taskId).then(onSuccess, onError)
onDownvote: ->
onSuccess = =>
@.loadTask()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.tasks.downvote(@scope.taskId).then(onSuccess, onError)
###
# Note: This methods (onWatch() and onUnwatch()) are related to tg-watch-button.
# See app/modules/components/watch-button for more info
###
onWatch: ->
onSuccess = =>
@.loadTask()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.tasks.watch(@scope.taskId).then(onSuccess, onError)
onUnwatch: ->
onSuccess = =>
@.loadTask()
@rootscope.$broadcast("object:updated")
onError = =>
@confirm.notify("error")
return @rs.tasks.unwatch(@scope.taskId).then(onSuccess, onError)
module.controller("TaskDetailController", TaskDetailController)
@ -199,7 +242,7 @@ module.directive("tgTaskStatusDisplay", ["$tgTemplate", "$compile", TaskStatusDi
## Task status button directive
#############################################################################
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate) ->
TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $compile, $translate, $template) ->
# Display the status of Task and you can edit it.
#
# Example:
@ -210,21 +253,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co
# - scope.statusById object
# - $scope.project.my_permissions
template = _.template("""
<div class="status-data <% if(editable){ %>clickable<% }%>">
<span class="level" style="background-color:<%- status.color %>"></span>
<span class="status-status"><%- status.name %></span>
<% if(editable){ %><span class="icon icon-arrow-bottom"></span><% }%>
<span class="level-name" translate="COMMON.FIELDS.STATUS"></span>
<ul class="popover pop-status">
<% _.each(statuses, function(st) { %>
<li><a href="" class="status" title="<%- st.name %>"
data-status-id="<%- st.id %>"><%- st.name %></a></li>
<% }); %>
</ul>
</div>
""")
template = $template.get("us/us-status-button.html", true)
link = ($scope, $el, $attrs, $model) ->
isEditable = ->
@ -292,7 +321,7 @@ TaskStatusButtonDirective = ($rootScope, $repo, $confirm, $loading, $qqueue, $co
}
module.directive("tgTaskStatusButton", ["$rootScope", "$tgRepo", "$tgConfirm", "$tgLoading", "$tgQqueue",
"$compile", "$translate", TaskStatusButtonDirective])
"$compile", "$translate", "$tgTemplate", TaskStatusButtonDirective])
TaskIsIocaineButtonDirective = ($rootscope, $tgrepo, $confirm, $loading, $qqueue, $compile) ->

Some files were not shown because too many files have changed in this diff Show More