Show likes, votes and watches list in user profile
parent
f52935f8c9
commit
331b14f13f
|
@ -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
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
section.profile-favorites
|
||||
nav.profile-favorites-filters
|
||||
a.active(href="", title="No Filter") all
|
||||
a(href="", title="Only show your team") projects
|
||||
a(href="", title="Only show people you follow") US
|
||||
a(href="", title="Only show people follow you") tasks
|
|
@ -11,32 +11,30 @@ section.profile-contacts
|
|||
div(ng-if="vm.isCurrentUser")
|
||||
p(translate="USER.PROFILE.CURRENT_USER_CONTACTS_EMPTY")
|
||||
p(translate="USER.PROFILE.CURRENT_USER_CONTACTS_EMPTY_EXPLAIN")
|
||||
//-
|
||||
nav.profile-contact-filters
|
||||
a.active(href="", title="No Filter") all
|
||||
a(href="", title="Only show your team") team
|
||||
a(href="", title="Only show people you follow") following
|
||||
a(href="", title="Only show people follow you") followers
|
||||
|
||||
// nav.profile-contact-filters
|
||||
// a.active(href="", title="No Filter") all
|
||||
// a(href="", title="Only show your team") team
|
||||
// a(href="", title="Only show people you follow") following
|
||||
// a(href="", title="Only show people follow you") followers
|
||||
div.list-itemtype-user(tg-repeat="contact in ::vm.contacts")
|
||||
a.list-itemtype-avatar(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name')}}")
|
||||
img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}")
|
||||
|
||||
div.profile-contact-single(tg-repeat="contact in ::vm.contacts")
|
||||
div.profile-contact-picture
|
||||
a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('name') }}")
|
||||
img(ng-src="{{::contact.get('photo')}}", alt="{{::contact.get('full_name')}}")
|
||||
div.list-itemtype-user-data
|
||||
h2
|
||||
a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name_display') }}") {{::contact.get('full_name_display')}}
|
||||
|
||||
p {{::contact.get('roles').join(", ")}}
|
||||
p.extra-info(ng-if="contact.get('bio')") {{::contact.get('bio')}}
|
||||
|
||||
div.profile-contact-data
|
||||
h1
|
||||
a(tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name_display') }}")
|
||||
| {{::contact.get('full_name_display')}}
|
||||
|
||||
p(ng-if="contact.bio") {{::contact.get('bio')}}
|
||||
|
||||
div.extra-info
|
||||
span.position {{::contact.get('roles').join(", ")}}
|
||||
// span.location todo
|
||||
// div.profile-project-stats
|
||||
// div.stat-projects(title="2 projects")
|
||||
// span.icon.icon-project
|
||||
// span.stat-num 2
|
||||
// div.stat-viewer(title="2 followers")
|
||||
// span.icon.icon-open-eye
|
||||
// span.stat-num 4
|
||||
//-
|
||||
span.location todo
|
||||
div.profile-project-stats
|
||||
div.stat-projects(title="2 projects")
|
||||
span.icon.icon-project
|
||||
span.stat-num 2
|
||||
div.stat-viewer(title="2 followers")
|
||||
span.icon.icon-open-eye
|
||||
span.stat-num 4
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
FavItemDirective = ->
|
||||
link = (scope, el, attrs, ctrl) ->
|
||||
scope.vm = {item: scope.item}
|
||||
|
||||
templateUrl = (el, attrs) ->
|
||||
if attrs.itemType == "project"
|
||||
return "profile/profile-favs/items/project.html"
|
||||
else # if attr.itemType in ["userstory", "task", "issue"]
|
||||
return "profile/profile-favs/items/ticket.html"
|
||||
|
||||
return {
|
||||
scope: {
|
||||
"item": "=tgFavItem"
|
||||
}
|
||||
link: link
|
||||
templateUrl: templateUrl
|
||||
}
|
||||
|
||||
|
||||
angular.module("taigaProfile").directive("tgFavItem", FavItemDirective)
|
|
@ -0,0 +1,34 @@
|
|||
.list-itemtype-project
|
||||
.list-itemtype-project-data
|
||||
h2
|
||||
a(
|
||||
href="#"
|
||||
tg-nav="project:project=vm.item.get('slug')"
|
||||
title="{{ ::vm.item.get('name') }}"
|
||||
) {{ ::vm.item.get('name') }}
|
||||
span.private(ng-if="::project.get('is_private')", title="{{'PROJECT.PRIVATE' | translate}}")
|
||||
p {{ ::vm.item.get('description') }}
|
||||
|
||||
.list-itemtype-project-tags.tags-container(ng-if="::vm.item.get('tags_colors').size")
|
||||
span.tag(
|
||||
tg-repeat="tag in ::vm.item.get('tags_colors')"
|
||||
style='border-left: 5px solid {{ ::tag.get("color") }};'
|
||||
)
|
||||
span.tag-name {{ ::tag.get('name') }}
|
||||
|
||||
.list-itemtype-track
|
||||
span.list-itemtype-track-likers(
|
||||
ng-class="{'active': vm.item.get('is_fan')}"
|
||||
title="{{ 'PROJECT.LIKE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_fans\")||0}:'messageformat' }}"
|
||||
)
|
||||
span.icon
|
||||
include ../../../../svg/like.svg
|
||||
span {{ ::vm.item.get('total_fans') }}
|
||||
|
||||
span.list-itemtype-track-watchers(
|
||||
ng-class="{'active': vm.item.get('is_watcher')}"
|
||||
title="{{ 'PROJECT.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_watchers\")||0}:'messageformat' }}"
|
||||
)
|
||||
span.icon
|
||||
include ../../../../svg/watch.svg
|
||||
span {{ ::vm.item.get('total_watchers') }}
|
|
@ -0,0 +1,80 @@
|
|||
div.list-itemtype-ticket
|
||||
a.list-itemtype-avatar(
|
||||
href=""
|
||||
ng-if="::vm.item.get('assigned_to')"
|
||||
tg-nav="user-profile:username=vm.item.get('assigned_to_username')"
|
||||
title="{{ ::vm.item.get('assigned_to_full_name') }}"
|
||||
)
|
||||
img(
|
||||
ng-src="{{ ::vm.item.get('assigned_to_photo') }}",
|
||||
alt="{{ ::vm.item.get('assigned_to_full_name') }}"
|
||||
)
|
||||
|
||||
a.list-itemtype-avatar(
|
||||
href=""
|
||||
ng-if="::!vm.item.get('assigned_to')",
|
||||
title="{{ 'COMMON.ASSIGNED_TO.NOT_ASSIGNED'|translate }}"
|
||||
)
|
||||
img(
|
||||
src="/images/unnamed.png",
|
||||
alt="{{ 'COMMON.ASSIGNED_TO.NOT_ASSIGNED'|translate }}"
|
||||
)
|
||||
|
||||
div.list-itemtype-ticket-data
|
||||
p
|
||||
span.ticket-project
|
||||
| {{:: vm.item.get('project_name') }}
|
||||
span.ticket-type(
|
||||
ng-if="::vm.item.get('type') === 'userstory'"
|
||||
translate="COMMON.USER_STORY"
|
||||
)
|
||||
span.ticket-type(
|
||||
ng-if="::vm.item.get('type') === 'task'"
|
||||
translate="COMMON.TASK"
|
||||
)
|
||||
span.ticket-type(
|
||||
ng-if="::vm.item.get('type') === 'issue'"
|
||||
translate="COMMON.ISSUE"
|
||||
)
|
||||
span.ticket-status(ng-style="::{'color': vm.item.get('status_color')}")
|
||||
| {{:: vm.item.get('status') }}
|
||||
h2
|
||||
span.ticket-id(tg-bo-ref="vm.item.get('ref')")
|
||||
a.ticket-title(
|
||||
href="#"
|
||||
ng-if="::vm.item.get('type') === 'userstory'"
|
||||
tg-nav="project-userstories-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
|
||||
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
|
||||
)
|
||||
| {{ ::vm.item.get('subject') }}
|
||||
a.ticket-title(
|
||||
href="#"
|
||||
ng-if="::vm.item.get('type') === 'task'"
|
||||
tg-nav="project-tasks-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
|
||||
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
|
||||
)
|
||||
| {{ ::vm.item.get('subject') }}
|
||||
a.ticket-title(
|
||||
href="#"
|
||||
ng-if="::vm.item.get('type') === 'issue'"
|
||||
tg-nav="project-issues-detail:project=vm.item.get('project_slug'),ref=vm.item.get('ref')"
|
||||
title="#{{ ::vm.item.get('ref') }} {{ ::vm.item.get('subject') }}"
|
||||
)
|
||||
| {{ ::vm.item.get('subject') }}
|
||||
|
||||
div.list-itemtype-track
|
||||
span.list-itemtype-track-likers(
|
||||
ng-class="{'active': vm.item.get('is_voter')}",
|
||||
title="{{ 'COMMON.VOTE_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_voters\")||0}:'messageformat' }}"
|
||||
)
|
||||
span.icon
|
||||
include ../../../../svg/upvote.svg
|
||||
span {{ ::vm.item.get('total_voters') }}
|
||||
|
||||
span.list-itemtype-track-watchers(
|
||||
ng-class="{'active': vm.item.get('is_watcher')}"
|
||||
title="{{ 'COMMON.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:vm.item.get(\"total_watchers\")||0}:'messageformat' }}"
|
||||
)
|
||||
span.icon
|
||||
include ../../../../svg/watch.svg
|
||||
span {{ ::vm.item.get('total_watchers') }}
|
|
@ -0,0 +1,168 @@
|
|||
debounceLeading = @.taiga.debounceLeading
|
||||
|
||||
class FavsBaseController
|
||||
constructor: ->
|
||||
@._init()
|
||||
|
||||
#@._getItems = null # Define in inheritance classes
|
||||
#
|
||||
_init: ->
|
||||
@.enableFilterByAll = true
|
||||
@.enableFilterByProjects = true
|
||||
@.enableFilterByUserStories = true
|
||||
@.enableFilterByTasks = true
|
||||
@.enableFilterByIssues = true
|
||||
@.enableFilterByTextQuery = true
|
||||
|
||||
@._resetList()
|
||||
@.q = null
|
||||
@.type = null
|
||||
|
||||
_resetList: ->
|
||||
@.items = Immutable.List()
|
||||
@.scrollDisabled = false
|
||||
@._page = 1
|
||||
|
||||
_enableLoadingSpinner: ->
|
||||
@.isLoading = true
|
||||
|
||||
_disableLoadingSpinner: ->
|
||||
@.isLoading = false
|
||||
|
||||
_enableScroll : ->
|
||||
@.scrollDisabled = false
|
||||
|
||||
_disableScroll : ->
|
||||
@.scrollDisabled = true
|
||||
|
||||
_checkIfHasMorePages: (hasNext) ->
|
||||
if hasNext
|
||||
@._page += 1
|
||||
@._enableScroll()
|
||||
else
|
||||
@._disableScroll()
|
||||
|
||||
_checkIfHasNoResults: ->
|
||||
@.hasNoResults = @.items.size == 0
|
||||
|
||||
loadItems: ->
|
||||
@._enableLoadingSpinner()
|
||||
@._disableScroll()
|
||||
|
||||
@._getItems(@.user.get("id"), @._page, @.type, @.q)
|
||||
.then (response) =>
|
||||
@.items = @.items.concat(response.get("data"))
|
||||
|
||||
@._checkIfHasMorePages(response.get("next"))
|
||||
@._checkIfHasNoResults()
|
||||
@._disableLoadingSpinner()
|
||||
|
||||
return @.items
|
||||
.catch =>
|
||||
@._disableLoadingSpinner()
|
||||
|
||||
return @.items
|
||||
|
||||
################################################
|
||||
## Filtre actions
|
||||
################################################
|
||||
filterByTextQuery: debounceLeading 500, ->
|
||||
@._resetList()
|
||||
@.loadItems()
|
||||
|
||||
showAll: ->
|
||||
if @.type isnt null
|
||||
@.type = null
|
||||
@._resetList()
|
||||
@.loadItems()
|
||||
|
||||
showProjectsOnly: ->
|
||||
if @.type isnt "project"
|
||||
@.type = "project"
|
||||
@._resetList()
|
||||
@.loadItems()
|
||||
|
||||
showUserStoriesOnly: ->
|
||||
if @.type isnt "userstory"
|
||||
@.type = "userstory"
|
||||
@._resetList()
|
||||
@.loadItems()
|
||||
|
||||
showTasksOnly: ->
|
||||
if @.type isnt "task"
|
||||
@.type = "task"
|
||||
@._resetList()
|
||||
@.loadItems()
|
||||
|
||||
showIssuesOnly: ->
|
||||
if @.type isnt "issue"
|
||||
@.type = "issue"
|
||||
@._resetList()
|
||||
@.loadItems()
|
||||
|
||||
|
||||
####################################################
|
||||
## Liked
|
||||
####################################################
|
||||
|
||||
class ProfileLikedController extends FavsBaseController
|
||||
@.$inject = [
|
||||
"tgUserService",
|
||||
]
|
||||
|
||||
constructor: (@userService) ->
|
||||
super()
|
||||
@.enableFilterByAll = false
|
||||
@.enableFilterByProjects = false
|
||||
@.enableFilterByUserStories = false
|
||||
@.enableFilterByTasks = false
|
||||
@.enableFilterByIssues = false
|
||||
@.enableFilterByTextQuery = true
|
||||
@._getItems = @userService.getLiked
|
||||
|
||||
|
||||
angular.module("taigaProfile")
|
||||
.controller("ProfileLiked", ProfileLikedController)
|
||||
|
||||
####################################################
|
||||
## Voted
|
||||
####################################################
|
||||
|
||||
class ProfileVotedController extends FavsBaseController
|
||||
@.$inject = [
|
||||
"tgUserService",
|
||||
]
|
||||
|
||||
constructor: (@userService) ->
|
||||
super()
|
||||
@.enableFilterByAll = true
|
||||
@.enableFilterByProjects = false
|
||||
@.enableFilterByUserStories = true
|
||||
@.enableFilterByTasks = true
|
||||
@.enableFilterByIssues = true
|
||||
@.enableFilterByTextQuery = true
|
||||
@._getItems = @userService.getVoted
|
||||
|
||||
|
||||
angular.module("taigaProfile")
|
||||
.controller("ProfileVoted", ProfileVotedController)
|
||||
|
||||
|
||||
|
||||
####################################################
|
||||
## Watched
|
||||
####################################################
|
||||
|
||||
class ProfileWatchedController extends FavsBaseController
|
||||
@.$inject = [
|
||||
"tgUserService",
|
||||
]
|
||||
|
||||
constructor: (@userService) ->
|
||||
super()
|
||||
@._getItems = @userService.getWatched
|
||||
|
||||
|
||||
angular.module("taigaProfile")
|
||||
.controller("ProfileWatched", ProfileWatchedController)
|
||||
|
|
@ -0,0 +1,679 @@
|
|||
describe "ProfileLiked", ->
|
||||
$controller = null
|
||||
provide = null
|
||||
$rootScope = null
|
||||
mocks = {}
|
||||
|
||||
user = Immutable.fromJS({id: 2})
|
||||
|
||||
_mockUserService = () ->
|
||||
mocks.userServices = {
|
||||
getLiked: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "tgUserService", mocks.userServices
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockUserService()
|
||||
|
||||
return null
|
||||
|
||||
_inject = (callback) ->
|
||||
inject (_$controller_, _$rootScope_) ->
|
||||
$rootScope = _$rootScope_
|
||||
$controller = _$controller_
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProfile"
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
it "load paginated items", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileLiked", $scope, {user: user})
|
||||
|
||||
items1 = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
items2 = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 4},
|
||||
{id: 5},
|
||||
],
|
||||
next: false
|
||||
})
|
||||
|
||||
mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, null).promise().resolve(items1)
|
||||
mocks.userServices.getLiked.withArgs(user.get("id"), 2, null, null).promise().resolve(items2)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = items1.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = expectItems.concat(items2.get("data"))
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.true
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "filter items by text query", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileLiked", $scope, {user: user})
|
||||
|
||||
textQuery = "_test_"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, textQuery).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.q = textQuery
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.equal(textQuery)
|
||||
done()
|
||||
|
||||
it "shou loading spinner during the call to the api", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileLiked", $scope, {user: user})
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mockPromise = mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, null).promise()
|
||||
|
||||
expect(ctrl.isLoading).to.be.undefined
|
||||
|
||||
promise = ctrl.loadItems()
|
||||
|
||||
expect(ctrl.isLoading).to.be.true
|
||||
|
||||
mockPromise.resolve(items)
|
||||
|
||||
promise.then () =>
|
||||
expect(ctrl.isLoading).to.be.false
|
||||
done()
|
||||
|
||||
it "shou no results placeholder", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileLiked", $scope, {user: user})
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [],
|
||||
next: false
|
||||
})
|
||||
|
||||
mocks.userServices.getLiked.withArgs(user.get("id"), 1, null, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.hasNoResults).to.be.undefined
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expect(ctrl.hasNoResults).to.be.true
|
||||
done()
|
||||
|
||||
|
||||
describe "ProfileVoted", ->
|
||||
$controller = null
|
||||
provide = null
|
||||
$rootScope = null
|
||||
mocks = {}
|
||||
|
||||
user = Immutable.fromJS({id: 2})
|
||||
|
||||
_mockUserService = () ->
|
||||
mocks.userServices = {
|
||||
getVoted: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "tgUserService", mocks.userServices
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockUserService()
|
||||
|
||||
return null
|
||||
|
||||
_inject = (callback) ->
|
||||
inject (_$controller_, _$rootScope_) ->
|
||||
$rootScope = _$rootScope_
|
||||
$controller = _$controller_
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProfile"
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
it "load paginated items", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileVoted", $scope, {user: user})
|
||||
|
||||
items1 = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
items2 = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 4},
|
||||
{id: 5},
|
||||
],
|
||||
next: false
|
||||
})
|
||||
|
||||
mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, null).promise().resolve(items1)
|
||||
mocks.userServices.getVoted.withArgs(user.get("id"), 2, null, null).promise().resolve(items2)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = items1.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = expectItems.concat(items2.get("data"))
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.true
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "filter items by text query", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileVoted", $scope, {user: user})
|
||||
|
||||
textQuery = "_test_"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, textQuery).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.q = textQuery
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.equal(textQuery)
|
||||
done()
|
||||
|
||||
it "show only items of user stories", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileVoted", $scope, {user: user})
|
||||
|
||||
type = "userstory"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getVoted.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.showUserStoriesOnly().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.type
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "show only items of tasks", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileVoted", $scope, {user: user})
|
||||
|
||||
type = "task"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getVoted.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.showTasksOnly().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.type
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "show only items of issues", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileVoted", $scope, {user: user})
|
||||
|
||||
type = "issue"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getVoted.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.showIssuesOnly().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.type
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "shou loading spinner during the call to the api", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileVoted", $scope, {user: user})
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mockPromise = mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, null).promise()
|
||||
|
||||
expect(ctrl.isLoading).to.be.undefined
|
||||
|
||||
promise = ctrl.loadItems()
|
||||
|
||||
expect(ctrl.isLoading).to.be.true
|
||||
|
||||
mockPromise.resolve(items)
|
||||
|
||||
promise.then () =>
|
||||
expect(ctrl.isLoading).to.be.false
|
||||
done()
|
||||
|
||||
it "shou no results placeholder", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileVoted", $scope, {user: user})
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [],
|
||||
next: false
|
||||
})
|
||||
|
||||
mocks.userServices.getVoted.withArgs(user.get("id"), 1, null, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.hasNoResults).to.be.undefined
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expect(ctrl.hasNoResults).to.be.true
|
||||
done()
|
||||
|
||||
describe "ProfileWatched", ->
|
||||
$controller = null
|
||||
provide = null
|
||||
$rootScope = null
|
||||
mocks = {}
|
||||
|
||||
user = Immutable.fromJS({id: 2})
|
||||
|
||||
_mockUserService = () ->
|
||||
mocks.userServices = {
|
||||
getWatched: sinon.stub()
|
||||
}
|
||||
|
||||
provide.value "tgUserService", mocks.userServices
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockUserService()
|
||||
|
||||
return null
|
||||
|
||||
_inject = (callback) ->
|
||||
inject (_$controller_, _$rootScope_) ->
|
||||
$rootScope = _$rootScope_
|
||||
$controller = _$controller_
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProfile"
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
it "load paginated items", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
items1 = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
items2 = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 4},
|
||||
{id: 5},
|
||||
],
|
||||
next: false
|
||||
})
|
||||
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, null).promise().resolve(items1)
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 2, null, null).promise().resolve(items2)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = items1.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = expectItems.concat(items2.get("data"))
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.true
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "filter items by text query", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
textQuery = "_test_"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, textQuery).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.q = textQuery
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.equal(textQuery)
|
||||
done()
|
||||
|
||||
it "show only items of projects", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
type = "project"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.showProjectsOnly().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.type
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "show only items of user stories", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
type = "userstory"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.showUserStoriesOnly().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.type
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "show only items of tasks", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
type = "task"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.showTasksOnly().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.type
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "show only items of issues", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
type = "issue"
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 1, type, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.items.size).to.be.equal(0)
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.null
|
||||
expect(ctrl.q).to.be.null
|
||||
|
||||
ctrl.showIssuesOnly().then () =>
|
||||
expectItems = items.get("data")
|
||||
|
||||
expect(ctrl.items.equals(expectItems)).to.be.true
|
||||
expect(ctrl.scrollDisabled).to.be.false
|
||||
expect(ctrl.type).to.be.type
|
||||
expect(ctrl.q).to.be.null
|
||||
done()
|
||||
|
||||
it "shou loading spinner during the call to the api", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
],
|
||||
next: true
|
||||
})
|
||||
|
||||
mockPromise = mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, null).promise()
|
||||
|
||||
expect(ctrl.isLoading).to.be.undefined
|
||||
|
||||
promise = ctrl.loadItems()
|
||||
|
||||
expect(ctrl.isLoading).to.be.true
|
||||
|
||||
mockPromise.resolve(items)
|
||||
|
||||
promise.then () =>
|
||||
expect(ctrl.isLoading).to.be.false
|
||||
done()
|
||||
|
||||
it "shou no results placeholder", (done) ->
|
||||
$scope = $rootScope.$new()
|
||||
ctrl = $controller("ProfileWatched", $scope, {user: user})
|
||||
|
||||
items = Immutable.fromJS({
|
||||
data: [],
|
||||
next: false
|
||||
})
|
||||
|
||||
mocks.userServices.getWatched.withArgs(user.get("id"), 1, null, null).promise().resolve(items)
|
||||
|
||||
expect(ctrl.hasNoResults).to.be.undefined
|
||||
|
||||
ctrl.loadItems().then () =>
|
||||
expect(ctrl.hasNoResults).to.be.true
|
||||
done()
|
|
@ -0,0 +1,50 @@
|
|||
base = {
|
||||
scope: {},
|
||||
bindToController: {
|
||||
user: "="
|
||||
type: "@"
|
||||
q: "@"
|
||||
scrollDisabled: "@"
|
||||
isLoading: "@"
|
||||
hasNoResults: "@"
|
||||
}
|
||||
controller: null, # Define in directives
|
||||
controllerAs: "vm",
|
||||
templateUrl: "profile/profile-favs/profile-favs.html",
|
||||
}
|
||||
|
||||
|
||||
####################################################
|
||||
## Liked
|
||||
####################################################
|
||||
|
||||
ProfileLikedDirective = () ->
|
||||
return _.extend({}, base, {
|
||||
controller: "ProfileLiked"
|
||||
})
|
||||
|
||||
angular.module("taigaProfile").directive("tgProfileLiked", ProfileLikedDirective)
|
||||
|
||||
|
||||
####################################################
|
||||
## Voted
|
||||
####################################################
|
||||
|
||||
ProfileVotedDirective = () ->
|
||||
return _.extend({}, base, {
|
||||
controller: "ProfileVoted"
|
||||
})
|
||||
|
||||
angular.module("taigaProfile").directive("tgProfileVoted", ProfileVotedDirective)
|
||||
|
||||
|
||||
####################################################
|
||||
## Watched
|
||||
####################################################
|
||||
|
||||
ProfileWatchedDirective = () ->
|
||||
return _.extend({}, base, {
|
||||
controller: "ProfileWatched"
|
||||
})
|
||||
|
||||
angular.module("taigaProfile").directive("tgProfileWatched", ProfileWatchedDirective)
|
|
@ -0,0 +1,80 @@
|
|||
section.profile-favs
|
||||
div.profile-filter
|
||||
div.searchbox(ng-if="::vm.enableFilterByTextQuery")
|
||||
span.icon-search
|
||||
input(
|
||||
type="text"
|
||||
ng-model="vm.q"
|
||||
ng-change="vm.filterByTextQuery()"
|
||||
placeholder="{{ 'USER.PROFILE_FAVS.FILTER_INPUT_PLACEHOLDER'|translate }}"
|
||||
)
|
||||
|
||||
div.filters
|
||||
a(
|
||||
href=""
|
||||
ng-if="::vm.enableFilterByAll"
|
||||
ng-click="vm.showAll()"
|
||||
ng-class="{active: vm.type === null}"
|
||||
title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ALL_TITLE'|translate }}"
|
||||
translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ALL'|translate }}"
|
||||
)
|
||||
a(
|
||||
href=""
|
||||
ng-if="::vm.enableFilterByProjects"
|
||||
ng-click="vm.showProjectsOnly()"
|
||||
ng-class="{active: vm.type === 'project'}"
|
||||
title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS_TITLE'|translate }}"
|
||||
translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_PROJECTS'|translate }}"
|
||||
)
|
||||
a(
|
||||
href=""
|
||||
ng-if="::vm.enableFilterByUserStories"
|
||||
ng-click="vm.showUserStoriesOnly()"
|
||||
ng-class="{active: vm.type === 'userstory'}",
|
||||
title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_USER_STORIES_TITLE'|translate }}"
|
||||
translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_USER_STORIES'|translate }}"
|
||||
)
|
||||
a(
|
||||
href=""
|
||||
ng-if="::vm.enableFilterByTasks"
|
||||
ng-click="vm.showTasksOnly()"
|
||||
ng-class="{active: vm.type === 'task'}"
|
||||
title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_TASKS_TITLE'|translate }}"
|
||||
translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_TASKS'|translate }}"
|
||||
)
|
||||
a(
|
||||
href=""
|
||||
ng-if="::vm.enableFilterByIssues"
|
||||
ng-click="vm.showIssuesOnly()"
|
||||
ng-class="{active: vm.type === 'issue'}"
|
||||
title="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ISSUES_TITLE'|translate }}"
|
||||
translate="{{ 'USER.PROFILE_FAVS.FILTER_TYPE_ISSUES'|translate }}"
|
||||
)
|
||||
|
||||
div(
|
||||
infinite-scroll="vm.loadItems()"
|
||||
infinite-scroll-distance="2"
|
||||
infinite-scroll-disabled="vm.scrollDisabled"
|
||||
)
|
||||
div(
|
||||
tg-repeat="item in vm.items track by $index"
|
||||
ng-switch="item.get('type')"
|
||||
)
|
||||
div(ng-switch-when="project", tg-fav-item="item", item-type="project")
|
||||
div(ng-switch-when="userstory", tg-fav-item="item", item-type="userstory")
|
||||
div(ng-switch-when="task", tg-fav-item="item", item-type="task")
|
||||
div(ng-switch-when="issue", tg-fav-item="item", item-type="issue")
|
||||
|
||||
div(ng-if="vm.isLoading")
|
||||
div.spin
|
||||
img(
|
||||
src="/svg/spinner-circle.svg"
|
||||
alt="{{ 'COMMON.LOADING'|translate }}"
|
||||
)
|
||||
|
||||
.empty-search-results(ng-if="vm.hasNoResults && !vm.isLoading")
|
||||
img(
|
||||
src="../../images/search-empty.png"
|
||||
alt="{{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }}"
|
||||
)
|
||||
p.title {{ 'USER.PROFILE_FAVS.EMPTY_TITLE' | translate }}
|
|
@ -0,0 +1,43 @@
|
|||
.profile-favs {
|
||||
border-top: 1px solid $whitish;
|
||||
}
|
||||
|
||||
.profile-filter {
|
||||
align-items: center;
|
||||
background: $whitish;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 1rem 0;
|
||||
padding: .5rem 1rem;
|
||||
.searchbox {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
.icon-search {
|
||||
color: grayer;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
input {
|
||||
border: 0;
|
||||
border-bottom: 1px solid transparent;
|
||||
flex: 1;
|
||||
margin-right: 1rem;
|
||||
&:focus {
|
||||
border-bottom: 1px solid $gray-light;
|
||||
outline: none;
|
||||
transition: border-bottom .3s ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
.filters {
|
||||
a {
|
||||
color: $gray-light;
|
||||
display: inline-block;
|
||||
padding: 0 .5rem;
|
||||
&:hover,
|
||||
&.active {
|
||||
color: $blackish;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,37 +3,56 @@ section.profile-projects
|
|||
div.spin
|
||||
img(src="/svg/spinner-circle.svg", alt="Loading...")
|
||||
|
||||
div.empty-tab(ng-if="vm.projects && !vm.projects.size")
|
||||
.empty-tab(ng-if="vm.projects && !vm.projects.size")
|
||||
include ../../../svg/hide.svg
|
||||
|
||||
p(translate="USER.PROFILE.PROJECTS_EMPTY", translate-values="{username: vm.user.get('full_name_display')}")
|
||||
p(
|
||||
translate="USER.PROFILE.PROJECTS_EMPTY"
|
||||
translate-values="{username: vm.user.get('full_name_display')}"
|
||||
)
|
||||
|
||||
div.project-list-single(tg-repeat="project in vm.projects")
|
||||
div.project-list-single-left
|
||||
.list-itemtype-project(tg-repeat="project in vm.projects")
|
||||
.list-itemtype-project-left
|
||||
|
||||
div.project-list-single-title
|
||||
h1
|
||||
a(href="#", tg-nav="project:project=project.get('slug')", title="{{ ::project.get('name') }}") {{::project.get('name')}}
|
||||
.project-list-single-title
|
||||
h2
|
||||
a(
|
||||
href="#"
|
||||
tg-nav="project:project=project.get('slug')"
|
||||
title="{{ ::project.get('name') }}"
|
||||
) {{::project.get('name')}}
|
||||
p {{ ::project.get('description') | limitTo:300 }}
|
||||
|
||||
div.project-list-single-tags.tags-container(ng-if="::project.get('tags').size")
|
||||
span.tag(style='border-left: 5px solid {{::tag.get("color")}};', tg-repeat="tag in ::project.get('colorized_tags')")
|
||||
.list-itemtype-project-tags.tags-container(ng-if="::project.get('tags').size")
|
||||
span.tag(
|
||||
style='border-left: 5px solid {{::tag.get("color")}};'
|
||||
tg-repeat="tag in ::project.get('colorized_tags')"
|
||||
)
|
||||
span.tag-name {{::tag.get('name')}}
|
||||
|
||||
div.project-list-single-right
|
||||
.list-itemtype-project-right
|
||||
|
||||
div.project-list-single-members
|
||||
a(tg-repeat="contact in ::project.get('contacts')", tg-nav="user-profile:username=contact.get('username')", title="{{::contact.get('full_name')}}")
|
||||
.list-itemtype-track
|
||||
span.list-itemtype-track-likers(
|
||||
ng-class="{'active': project.get('is_fan')}"
|
||||
title="{{ 'PROJECT.LIKE_BUTTON.COUNTER_TITLE'|translate:{total:project.get(\"total_fans\")||0}:'messageformat' }}"
|
||||
)
|
||||
span.icon
|
||||
include ../../../svg/like.svg
|
||||
span {{ ::project.get('total_fans') }}
|
||||
|
||||
span.list-itemtype-track-watchers(
|
||||
ng-class="{'active': project.get('is_watcher')}"
|
||||
title="{{ 'PROJECT.WATCH_BUTTON.COUNTER_TITLE'|translate:{total:project.get(\"total_watchers\")||0}:'messageformat' }}"
|
||||
)
|
||||
span.icon
|
||||
include ../../../svg/watch.svg
|
||||
span {{ ::project.get('total_watchers') }}
|
||||
|
||||
.list-itemtype-project-members
|
||||
a(
|
||||
tg-repeat="contact in ::project.get('contacts')"
|
||||
tg-nav="user-profile:username=contact.get('username')"
|
||||
title="{{::contact.get('full_name')}}"
|
||||
)
|
||||
img(ng-src="{{::contact.get('photo')}}")
|
||||
|
||||
// div.project-list-single-right
|
||||
// div.project-list-single-stats
|
||||
// div.stat-comments(title="2 comments")
|
||||
// span.icon.icon-comment
|
||||
// span.stat-num 2
|
||||
// div.stat-favorite.active(title="2 favorites")
|
||||
// span.icon.icon-star-fill
|
||||
// span.stat-num 4
|
||||
// div.stat-viewer(title="2 followers")
|
||||
// span.icon.icon-open-eye
|
||||
// span.stat-num 4
|
||||
|
|
|
@ -2,10 +2,12 @@ ProfileTabDirective = () ->
|
|||
link = (scope, element, attrs, ctrl, transclude) ->
|
||||
scope.tab = {}
|
||||
|
||||
attrs.$observe "tgProfileTab", (name) ->
|
||||
scope.tab.name = name
|
||||
|
||||
attrs.$observe "tabTitle", (title) ->
|
||||
scope.tab.title = title
|
||||
|
||||
scope.tab.name = attrs.tgProfileTab
|
||||
scope.tab.icon = attrs.tabIcon
|
||||
scope.tab.active = !!attrs.tabActive
|
||||
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
div
|
||||
nav.profile-content-tabs
|
||||
a.tab(ng-repeat="tab in ::vm.tabs", href="", title="{{tab.title}}", ng-class="{active: tab.active}" ng-click="vm.toggleTab(tab)")
|
||||
a.tab(
|
||||
href=""
|
||||
ng-repeat="tab in ::vm.tabs"
|
||||
title="{{tab.title}}"
|
||||
ng-click="vm.toggleTab(tab)"
|
||||
ng-class="{active: tab.active}"
|
||||
)
|
||||
span.icon(ng-class="::tab.icon")
|
||||
span {{::tab.name}}
|
||||
|
||||
ng-transclude
|
||||
ng-transclude
|
||||
|
|
|
@ -2,16 +2,48 @@ div.profile.centered(ng-if="vm.user")
|
|||
div(tg-profile-bar, user="vm.user", isCurrentUser="vm.isCurrentUser")
|
||||
div.main
|
||||
div.timeline-wrapper(tg-profile-tabs)
|
||||
div(tg-profile-tab="activity", tab-title="{{'USER.PROFILE.ACTIVITY_TAB' | translate}}", tab-icon="icon-timeline", tab-active)
|
||||
div(tg-user-timeline, user="vm.user", current-user="vm.isCurrentUser")
|
||||
div(
|
||||
tg-profile-tab="{{'USER.PROFILE.TABS.ACTIVITY_TAB' | translate}}"
|
||||
tab-title="{{'USER.PROFILE.TABS.ACTIVITY_TAB_TITLE' | translate}}"
|
||||
tab-icon="icon-timeline"
|
||||
tab-active
|
||||
)
|
||||
div(tg-user-timeline, user="vm.user", current-user="vm.isCurrentUser")
|
||||
|
||||
div(tab-disabled="{{vm.isCurrentUser}}", tg-profile-tab="projects", tab-title="{{'USER.PROFILE.PROJECTS_TAB' | translate}}", tab-icon="icon-project")
|
||||
div(tg-profile-projects, user="vm.user")
|
||||
div(
|
||||
tg-profile-tab="{{'USER.PROFILE.TABS.PROJECTS_TAB' | translate}}"
|
||||
tab-title="{{'USER.PROFILE.TABS.PROJECTS_TAB_TITLE' | translate}}"
|
||||
tab-icon="icon-project"
|
||||
tab-disabled="{{vm.isCurrentUser}}"
|
||||
)
|
||||
div(tg-profile-projects, user="vm.user")
|
||||
|
||||
div(tg-profile-tab="contacts", tab-title="{{'USER.PROFILE.CONTACTS_TAB' | translate}}", tab-icon="icon-team")
|
||||
div(tg-profile-contacts, user="vm.user")
|
||||
div(
|
||||
tg-profile-tab="{{'USER.PROFILE.TABS.LIKES_TAB' | translate}}"
|
||||
tab-title="{{'USER.PROFILE.TABS.LIKES_TAB_TITLE' | translate}}"
|
||||
tab-icon="icon-heart"
|
||||
)
|
||||
div(tg-profile-liked, user="vm.user")
|
||||
|
||||
// div(tg-profile-tab="favorites", tab-title="{{'USER.PROFILE.FAVORITES_TAB' | translate}}", tab-icon="icon-star-fill")
|
||||
// include includes/profile-favorites
|
||||
div(
|
||||
tg-profile-tab="{{'USER.PROFILE.TABS.VOTES_TAB' | translate}}"
|
||||
tab-title="{{'USER.PROFILE.TABS.VOTES_TAB_TITLE' | translate}}"
|
||||
tab-icon="icon-caret-up"
|
||||
)
|
||||
div(tg-profile-voted, user="vm.user")
|
||||
|
||||
div(
|
||||
tg-profile-tab="{{'USER.PROFILE.TABS.WATCHED_TAB' | translate}}"
|
||||
tab-title="{{'USER.PROFILE.TABS.WATCHED_TAB_TITLE' | translate}}"
|
||||
tab-icon="icon-eye"
|
||||
)
|
||||
div(tg-profile-watched, user="vm.user")
|
||||
|
||||
div(
|
||||
tg-profile-tab="{{'USER.PROFILE.TABS.CONTACTS_TAB' | translate}}"
|
||||
tab-title="{{'USER.PROFILE.TABS.CONTACTS_TAB_TITLE' | translate}}"
|
||||
tab-icon="icon-team"
|
||||
)
|
||||
div(tg-profile-contacts, user="vm.user")
|
||||
|
||||
include includes/profile-sidebar
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
.profile-favorites {
|
||||
border-top: 1px solid $whitish;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.profile-favorites-filters {
|
||||
align-self: flex-start;
|
||||
display: flex;
|
||||
a {
|
||||
border-bottom: 2px solid $white;
|
||||
color: $gray-light;
|
||||
display: inline-block;
|
||||
padding: 1rem 1.5rem;
|
||||
transition: all .2s linear;
|
||||
&:hover,
|
||||
&.active {
|
||||
border-bottom: 2px solid $gray-light;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,6 +51,25 @@ Resource = (urlsService, http, paginateResponseService) ->
|
|||
result = Immutable.fromJS(result)
|
||||
return paginateResponseService(result)
|
||||
|
||||
service.likeProject = (projectId) ->
|
||||
url = urlsService.resolve("project-like", projectId)
|
||||
return http.post(url)
|
||||
|
||||
service.unlikeProject = (projectId) ->
|
||||
url = urlsService.resolve("project-unlike", projectId)
|
||||
return http.post(url)
|
||||
|
||||
service.watchProject = (projectId, notifyPolicy) ->
|
||||
data = {
|
||||
notify_policy: notifyPolicy
|
||||
}
|
||||
url = urlsService.resolve("project-watch", projectId)
|
||||
return http.post(url, data)
|
||||
|
||||
service.unwatchProject = (projectId) ->
|
||||
url = urlsService.resolve("project-unwatch", projectId)
|
||||
return http.post(url)
|
||||
|
||||
return () ->
|
||||
return {"projects": service}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ Resource = (urlsService, http, paginateResponseService) ->
|
|||
return Immutable.fromJS(result.data)
|
||||
|
||||
service.getStats = (userId) ->
|
||||
url = urlsService.resolve("stats", userId)
|
||||
url = urlsService.resolve("user-stats", userId)
|
||||
|
||||
httpOptions = {
|
||||
headers: {
|
||||
|
@ -32,7 +32,7 @@ Resource = (urlsService, http, paginateResponseService) ->
|
|||
return Immutable.fromJS(result.data)
|
||||
|
||||
service.getContacts = (userId) ->
|
||||
url = urlsService.resolve("contacts", userId)
|
||||
url = urlsService.resolve("user-contacts", userId)
|
||||
|
||||
httpOptions = {
|
||||
headers: {
|
||||
|
@ -44,6 +44,45 @@ Resource = (urlsService, http, paginateResponseService) ->
|
|||
.then (result) ->
|
||||
return Immutable.fromJS(result.data)
|
||||
|
||||
service.getLiked = (userId, page, type, q) ->
|
||||
url = urlsService.resolve("user-liked", userId)
|
||||
|
||||
params = {}
|
||||
params.page = page if page?
|
||||
params.type = type if type?
|
||||
params.q = q if q?
|
||||
|
||||
return http.get(url, params)
|
||||
.then (result) ->
|
||||
result = Immutable.fromJS(result)
|
||||
return paginateResponseService(result)
|
||||
|
||||
service.getVoted = (userId, page, type, q) ->
|
||||
url = urlsService.resolve("user-voted", userId)
|
||||
|
||||
params = {}
|
||||
params.page = page if page?
|
||||
params.type = type if type?
|
||||
params.q = q if q?
|
||||
|
||||
return http.get(url, params)
|
||||
.then (result) ->
|
||||
result = Immutable.fromJS(result)
|
||||
return paginateResponseService(result)
|
||||
|
||||
service.getWatched = (userId, page, type, q) ->
|
||||
url = urlsService.resolve("user-watched", userId)
|
||||
|
||||
params = {}
|
||||
params.page = page if page?
|
||||
params.type = type if type?
|
||||
params.q = q if q?
|
||||
|
||||
return http.get(url, params)
|
||||
.then (result) ->
|
||||
result = Immutable.fromJS(result)
|
||||
return paginateResponseService(result)
|
||||
|
||||
service.getProfileTimeline = (userId, page) ->
|
||||
params = {
|
||||
page: page
|
||||
|
|
|
@ -3,7 +3,13 @@ taiga = @.taiga
|
|||
truncate = taiga.truncate
|
||||
|
||||
|
||||
class AppMetaService extends taiga.Service = ->
|
||||
class AppMetaService
|
||||
@.$inject = [
|
||||
"$rootScope"
|
||||
]
|
||||
|
||||
constructor: (@rootScope) ->
|
||||
|
||||
_set: (key, value) ->
|
||||
return if not key
|
||||
|
||||
|
@ -60,12 +66,19 @@ class AppMetaService extends taiga.Service = ->
|
|||
@.setOpenGraphMetas(title, description)
|
||||
|
||||
addMobileViewport: () ->
|
||||
$('head').append(
|
||||
'<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">'
|
||||
$("head").append(
|
||||
"<meta name=\"viewport\"
|
||||
content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0\">"
|
||||
)
|
||||
|
||||
removeMobileViewport: () ->
|
||||
$('meta[name="viewport"]').remove()
|
||||
$("meta[name=\"viewport\"]").remove()
|
||||
|
||||
setfn: (fn) ->
|
||||
@._listener() if @.listener
|
||||
|
||||
@._listener = @rootScope.$watchCollection fn, (metas) =>
|
||||
@.setAll(metas.title, metas.description)
|
||||
|
||||
|
||||
angular.module("taigaCommon").service("tgAppMetaService", AppMetaService)
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
describe "AppMetaService", ->
|
||||
appMetaService = null
|
||||
$rootScope = null
|
||||
data = {
|
||||
title: "--title--",
|
||||
description: "--description--"
|
||||
}
|
||||
|
||||
_inject = () ->
|
||||
inject (_tgAppMetaService_) ->
|
||||
inject (_tgAppMetaService_, _$rootScope_) ->
|
||||
appMetaService = _tgAppMetaService_
|
||||
$rootScope = _$rootScope_
|
||||
|
||||
beforeEach ->
|
||||
module "taigaCommon"
|
||||
|
@ -53,3 +55,18 @@ describe "AppMetaService", ->
|
|||
expect($("meta[property='og:description']")).to.have.attr("content", data.description)
|
||||
expect($("meta[property='og:image']")).to.have.attr("content", "#{window.location.origin}/images/logo-color.png")
|
||||
expect($("meta[property='og:url']")).to.have.attr("content", window.location.href)
|
||||
|
||||
it "set function to set the metas", () ->
|
||||
fn = () ->
|
||||
return {
|
||||
title: 'test',
|
||||
description: 'test2'
|
||||
}
|
||||
|
||||
|
||||
appMetaService.setAll = sinon.stub()
|
||||
appMetaService.setfn(fn)
|
||||
|
||||
$rootScope.$digest()
|
||||
|
||||
expect(appMetaService.setAll).to.have.been.calledWith('test', 'test2')
|
||||
|
|
|
@ -49,13 +49,7 @@ class CurrentUserService
|
|||
|
||||
loadProjects: () ->
|
||||
return @projectsService.getProjectsByUserId(@._user.get("id"))
|
||||
.then (projects) =>
|
||||
@._projects = @._projects.set("all", projects)
|
||||
@._projects = @._projects.set("recents", projects.slice(0, 10))
|
||||
|
||||
@._projectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id))
|
||||
|
||||
return @.projects
|
||||
.then (projects) => @.setProjects(projects)
|
||||
|
||||
disableJoyRide: (section) ->
|
||||
if section
|
||||
|
@ -96,4 +90,12 @@ class CurrentUserService
|
|||
@.loadProjects()
|
||||
])
|
||||
|
||||
setProjects: (projects) ->
|
||||
@._projects = @._projects.set("all", projects)
|
||||
@._projects = @._projects.set("recents", projects.slice(0, 10))
|
||||
|
||||
@._projectsById = Immutable.fromJS(groupBy(projects.toJS(), (p) -> p.id))
|
||||
|
||||
return @.projects
|
||||
|
||||
angular.module("taigaCommon").service("tgCurrentUserService", CurrentUserService)
|
||||
|
|
|
@ -92,7 +92,7 @@ describe "tgCurrentUserService", ->
|
|||
it "bulkUpdateProjectsOrder and reload projects", (done) ->
|
||||
fakeData = [{id: 1, id: 2}]
|
||||
|
||||
currentUserService.loadProjects = sinon.spy()
|
||||
currentUserService.loadProjects = sinon.stub()
|
||||
|
||||
mocks.projectsService.bulkUpdateProjectsOrder.withArgs(fakeData).promise().resolve()
|
||||
|
||||
|
@ -101,6 +101,41 @@ describe "tgCurrentUserService", ->
|
|||
|
||||
done()
|
||||
|
||||
it "loadProject and set it", (done) ->
|
||||
user = Immutable.fromJS({id: 1, name: "fake1"})
|
||||
project = Immutable.fromJS({id: 2, name: "fake2"})
|
||||
|
||||
currentUserService._user = user
|
||||
currentUserService.setProjects = sinon.stub()
|
||||
|
||||
mocks.projectsService.getProjectsByUserId.withArgs(1).promise().resolve(project)
|
||||
|
||||
currentUserService.loadProjects().then () ->
|
||||
expect(currentUserService.setProjects).to.have.been.calledWith(project)
|
||||
|
||||
done()
|
||||
|
||||
it "setProject", () ->
|
||||
projectsRaw = [
|
||||
{id: 1, name: "fake1"},
|
||||
{id: 2, name: "fake2"},
|
||||
{id: 3, name: "fake3"},
|
||||
{id: 4, name: "fake4"}
|
||||
]
|
||||
projectsRawById = {
|
||||
1: {id: 1, name: "fake1"},
|
||||
2: {id: 2, name: "fake2"},
|
||||
3: {id: 3, name: "fake3"},
|
||||
4: {id: 4, name: "fake4"}
|
||||
}
|
||||
projects = Immutable.fromJS(projectsRaw)
|
||||
|
||||
currentUserService.setProjects(projects)
|
||||
|
||||
expect(currentUserService.projects.get('all').toJS()).to.be.eql(projectsRaw)
|
||||
expect(currentUserService.projects.get('recents').toJS()).to.be.eql(projectsRaw)
|
||||
expect(currentUserService.projectsById.toJS()).to.be.eql(projectsRawById)
|
||||
|
||||
it "is authenticated", () ->
|
||||
currentUserService.getUser = sinon.stub()
|
||||
currentUserService.getUser.returns({})
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
taiga = @.taiga
|
||||
bindMethods = taiga.bindMethods
|
||||
|
||||
|
||||
class UserService extends taiga.Service
|
||||
@.$inject = ["tgResources"]
|
||||
|
||||
constructor: (@rs) ->
|
||||
bindMethods(@)
|
||||
|
||||
getUserByUserName: (username) ->
|
||||
return @rs.users.getUserByUsername(username)
|
||||
|
@ -11,6 +14,15 @@ class UserService extends taiga.Service
|
|||
getContacts: (userId) ->
|
||||
return @rs.users.getContacts(userId)
|
||||
|
||||
getLiked: (userId, pageNumber, objectType, textQuery) ->
|
||||
return @rs.users.getLiked(userId, pageNumber, objectType, textQuery)
|
||||
|
||||
getVoted: (userId, pageNumber, objectType, textQuery) ->
|
||||
return @rs.users.getVoted(userId, pageNumber, objectType, textQuery)
|
||||
|
||||
getWatched: (userId, pageNumber, objectType, textQuery) ->
|
||||
return @rs.users.getWatched(userId, pageNumber, objectType, textQuery)
|
||||
|
||||
getStats: (userId) ->
|
||||
return @rs.users.getStats(userId)
|
||||
|
||||
|
|
|
@ -32,19 +32,6 @@ describe "UserService", ->
|
|||
_mocks()
|
||||
_inject()
|
||||
|
||||
it "get user contacts", () ->
|
||||
userId = 2
|
||||
|
||||
contacts = [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
]
|
||||
|
||||
mocks.resources.users.getContacts.withArgs(userId).returns(true)
|
||||
|
||||
expect(userService.getContacts(userId)).to.be.true
|
||||
|
||||
it "attach user contacts to projects", (done) ->
|
||||
userId = 2
|
||||
|
||||
|
@ -88,6 +75,75 @@ describe "UserService", ->
|
|||
|
||||
$rootScope.$apply()
|
||||
|
||||
it "get user liked", (done) ->
|
||||
userId = 2
|
||||
pageNumber = 1
|
||||
objectType = null
|
||||
textQuery = null
|
||||
|
||||
liked = [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
]
|
||||
|
||||
mocks.resources.users.getLiked = sinon.stub()
|
||||
mocks.resources.users.getLiked.withArgs(userId, pageNumber, objectType, textQuery)
|
||||
.promise()
|
||||
.resolve(liked)
|
||||
|
||||
userService.getLiked(userId, pageNumber, objectType, textQuery).then (_liked_) ->
|
||||
expect(_liked_).to.be.eql(liked)
|
||||
done()
|
||||
|
||||
$rootScope.$apply()
|
||||
|
||||
it "get user voted", (done) ->
|
||||
userId = 2
|
||||
pageNumber = 1
|
||||
objectType = null
|
||||
textQuery = null
|
||||
|
||||
voted = [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
]
|
||||
|
||||
mocks.resources.users.getVoted = sinon.stub()
|
||||
mocks.resources.users.getVoted.withArgs(userId, pageNumber, objectType, textQuery)
|
||||
.promise()
|
||||
.resolve(voted)
|
||||
|
||||
userService.getVoted(userId, pageNumber, objectType, textQuery).then (_voted_) ->
|
||||
expect(_voted_).to.be.eql(voted)
|
||||
done()
|
||||
|
||||
$rootScope.$apply()
|
||||
|
||||
it "get user watched", (done) ->
|
||||
userId = 2
|
||||
pageNumber = 1
|
||||
objectType = null
|
||||
textQuery = null
|
||||
|
||||
watched = [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
]
|
||||
|
||||
mocks.resources.users.getWatched = sinon.stub()
|
||||
mocks.resources.users.getWatched.withArgs(userId, pageNumber, objectType, textQuery)
|
||||
.promise()
|
||||
.resolve(watched)
|
||||
|
||||
userService.getWatched(userId, pageNumber, objectType, textQuery).then (_watched_) ->
|
||||
expect(_watched_).to.be.eql(watched)
|
||||
done()
|
||||
|
||||
$rootScope.$apply()
|
||||
|
||||
it "get user by username", (done) ->
|
||||
username = "username-1"
|
||||
|
||||
|
|
Loading…
Reference in New Issue