Merge pull request #371 from taigaio/public-projects

Public projects
stable
David Barragán Merino 2015-03-10 14:44:45 +01:00
commit 515df55db2
12 changed files with 152 additions and 81 deletions

View File

@ -64,16 +64,36 @@ class RolesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fil
loadProject: -> loadProject: ->
return @rs.projects.get(@scope.projectId).then (project) => return @rs.projects.get(@scope.projectId).then (project) =>
@scope.project = project @scope.project = project
@scope.$emit('project:loaded', project) @scope.$emit('project:loaded', project)
@scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable)) @scope.anyComputableRole = _.some(_.map(project.roles, (point) -> point.computable))
return project return project
loadExternalUserRole: (roles) ->
roles = roles.map (role) ->
role.external_user = false
return role
public_permission = {
"name": "External User",
"permissions": @scope.project.public_permissions,
"external_user": true
}
roles.push(public_permission)
return roles
loadRoles: -> loadRoles: ->
return @rs.roles.list(@scope.projectId).then (data) => return @rs.roles.list(@scope.projectId)
@scope.roles = data .then @loadExternalUserRole
@scope.role = @scope.roles[0] .then (roles) =>
return data @scope.roles = roles
@scope.role = @scope.roles[0]
return roles
loadInitialData: -> loadInitialData: ->
promise = @repo.resolve({pslug: @params.pslug}).then (data) => promise = @repo.resolve({pslug: @params.pslug}).then (data) =>
@ -256,7 +276,7 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
<div class="category-item" data-id="<%- permission.key %>"> <div class="category-item" data-id="<%- permission.key %>">
<span><%- permission.description %></span> <span><%- permission.description %></span>
<div class="check"> <div class="check">
<input type="checkbox" <% if(permission.active) { %>checked="checked"<% } %>/> <input type="checkbox" <% if(!permission.editable) { %>disabled="disabled"<% } %> <% if(permission.active) { %>checked="checked"<% } %>/>
<div></div> <div></div>
<span class="check-text check-yes">Yes</span> <span class="check-text check-yes">Yes</span>
<span class="check-text check-no">No</span> <span class="check-text check-no">No</span>
@ -279,10 +299,23 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
setActivePermissions = (permissions) -> setActivePermissions = (permissions) ->
return _.map(permissions, (x) -> _.extend({}, x, {active: x["key"] in role.permissions})) return _.map(permissions, (x) -> _.extend({}, x, {active: x["key"] in role.permissions}))
isPermissionEditable = (permission, role, project) ->
if role.external_user &&
!project.is_private &&
permission.key.indexOf("view_") == 0
return false
else
return true
setActivePermissionsPerCategory = (category) -> setActivePermissionsPerCategory = (category) ->
return _.map(category, (x) -> return _.map(category, (cat) ->
_.extend({}, x, { cat.permissions = cat.permissions.map (permission) ->
activePermissions: _.filter(x["permissions"], "active").length permission.editable = isPermissionEditable(permission, role, $scope.project)
return permission
_.extend({}, cat, {
activePermissions: _.filter(cat["permissions"], "active").length
}) })
) )
@ -366,10 +399,11 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
return activePermissions return activePermissions
target = angular.element(event.currentTarget) target = angular.element(event.currentTarget)
$scope.role.permissions = getActivePermissions() $scope.role.permissions = getActivePermissions()
onSuccess = (role) -> onSuccess = () ->
categories = generateCategoriesFromRole(role) categories = generateCategoriesFromRole($scope.role)
categoryId = target.parents(".category-config").data("id") categoryId = target.parents(".category-config").data("id")
renderResume(target.parents(".category-config"), categories[categoryId]) renderResume(target.parents(".category-config"), categories[categoryId])
$rootscope.$broadcast("projects:reload") $rootscope.$broadcast("projects:reload")
@ -381,7 +415,14 @@ RolePermissionsDirective = ($rootscope, $repo, $confirm) ->
target.prop "checked", !target.prop("checked") target.prop "checked", !target.prop("checked")
$scope.role.permissions = getActivePermissions() $scope.role.permissions = getActivePermissions()
$repo.save($scope.role).then onSuccess, onError if $scope.role.external_user
$scope.project.public_permissions = $scope.role.permissions
$scope.project.anon_permissions = $scope.role.permissions.filter (permission) ->
return permission.indexOf("view_") == 0
$repo.save($scope.project).then onSuccess, onError
else
$repo.save($scope.role).then onSuccess, onError
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()

View File

@ -258,7 +258,7 @@ HistoryDirective = ($log, $loading, $qqueue, $template, $confirm) ->
deleteCommentDate: moment(comment.delete_comment_date).format("DD MMM YYYY HH:mm") if comment.delete_comment_date deleteCommentDate: moment(comment.delete_comment_date).format("DD MMM YYYY HH:mm") if comment.delete_comment_date
deleteCommentUser: comment.delete_comment_user.name if comment.delete_comment_user?.name deleteCommentUser: comment.delete_comment_user.name if comment.delete_comment_user?.name
activityId: comment.id activityId: comment.id
canDeleteComment: comment.user.pk == $scope.user.id or $scope.project.my_permissions.indexOf("modify_project") > -1 canDeleteComment: comment.user.pk == $scope.user?.id or $scope.project.my_permissions.indexOf("modify_project") > -1
}) })
renderChange = (change) -> renderChange = (change) ->

View File

@ -36,47 +36,52 @@ module = angular.module("taigaBacklog")
TaskboardSortableDirective = ($repo, $rs, $rootscope) -> TaskboardSortableDirective = ($repo, $rs, $rootscope) ->
link = ($scope, $el, $attrs) -> link = ($scope, $el, $attrs) ->
oldParentScope = null bindOnce $scope, "project", (project) ->
newParentScope = null # If the user has not enough permissions we don't enable the sortable
itemEl = null if not (project.my_permissions.indexOf("modify_us") > -1)
tdom = $el return
deleteElement = (itemEl) -> oldParentScope = null
# Completelly remove item and its scope from dom newParentScope = null
itemEl.scope().$destroy() itemEl = null
itemEl.off() tdom = $el
itemEl.remove()
tdom.sortable({ deleteElement = (itemEl) ->
handle: ".taskboard-task-inner", # Completelly remove item and its scope from dom
dropOnEmpty: true itemEl.scope().$destroy()
connectWith: ".taskboard-tasks-box" itemEl.off()
revert: 400 itemEl.remove()
})
tdom.on "sortstop", (event, ui) -> tdom.sortable({
parentEl = ui.item.parent() handle: ".taskboard-task-inner",
itemEl = ui.item dropOnEmpty: true
itemTask = itemEl.scope().task connectWith: ".taskboard-tasks-box"
itemIndex = itemEl.index() revert: 400
newParentScope = parentEl.scope() })
oldUsId = if oldParentScope.us then oldParentScope.us.id else null tdom.on "sortstop", (event, ui) ->
oldStatusId = oldParentScope.st.id parentEl = ui.item.parent()
newUsId = if newParentScope.us then newParentScope.us.id else null itemEl = ui.item
newStatusId = newParentScope.st.id itemTask = itemEl.scope().task
itemIndex = itemEl.index()
newParentScope = parentEl.scope()
if newStatusId != oldStatusId or newUsId != oldUsId oldUsId = if oldParentScope.us then oldParentScope.us.id else null
deleteElement(itemEl) oldStatusId = oldParentScope.st.id
newUsId = if newParentScope.us then newParentScope.us.id else null
newStatusId = newParentScope.st.id
$scope.$apply -> if newStatusId != oldStatusId or newUsId != oldUsId
$rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex) deleteElement(itemEl)
ui.item.find('a').removeClass('noclick') $scope.$apply ->
$rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex)
tdom.on "sortstart", (event, ui) -> ui.item.find('a').removeClass('noclick')
oldParentScope = ui.item.parent().scope()
ui.item.find('a').addClass('noclick') tdom.on "sortstart", (event, ui) ->
oldParentScope = ui.item.parent().scope()
ui.item.find('a').addClass('noclick')
$scope.$on "$destroy", -> $scope.$on "$destroy", ->
$el.off() $el.off()

View File

@ -69,18 +69,18 @@ class TeamController extends mixOf(taiga.Controller, taiga.PageMixin)
loadMembers: -> loadMembers: ->
return @rs.memberships.list(@scope.projectId, {}, false).then (data) => return @rs.memberships.list(@scope.projectId, {}, false).then (data) =>
currentUser = @auth.getUser() currentUser = @auth.getUser()
if not currentUser.photo? if currentUser? and not currentUser.photo?
currentUser.photo = "/images/unnamed.png" currentUser.photo = "/images/unnamed.png"
@scope.currentUser = _.find data, (membership) => @scope.currentUser = _.find data, (membership) =>
return membership.user == currentUser.id return currentUser? and membership.user == currentUser.id
@scope.totals = {} @scope.totals = {}
_.forEach data, (membership) => _.forEach data, (membership) =>
@scope.totals[membership.user] = 0 @scope.totals[membership.user] = 0
@scope.memberships = _.filter data, (membership) => @scope.memberships = _.filter data, (membership) =>
if membership.user && membership.user != currentUser.id && membership.is_user_active if membership.user && (not currentUser? or membership.user != currentUser.id) && membership.is_user_active
return membership return membership
for membership in @scope.memberships for membership in @scope.memberships

View File

@ -238,6 +238,7 @@ EditableWikiContentDirective = ($window, $document, $repo, $confirm, $loading, $
$el.on "mouseup", ".view-wiki-content", (event) -> $el.on "mouseup", ".view-wiki-content", (event) ->
target = angular.element(event.target) target = angular.element(event.target)
return if getSelectedText() return if getSelectedText()
return if not isEditable()
return if target.is('a') return if target.is('a')
return if target.is('pre') return if target.is('pre')

View File

@ -40,16 +40,14 @@ div.wrapper(tg-project-profile, ng-controller="ProjectProfileController as ctrl"
tg-privacy-settings-inputs tg-privacy-settings-inputs
div.privacy-settings div.privacy-settings
div div
input.hidden(type="radio", disabled="disabled") input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="false")
label.trans-button(for="public-project") label.trans-button(for="public-project")
span Public Project span Public Project
div div
input.hidden(type="radio", checked="checked", disabled="disabled") input.privacy-project(type="radio", name="private-project", ng-model="project.is_private", ng-value="true")
label.trans-button(for="private-project") label.trans-button(for="private-project")
span Private Project span Private Project
p All projects are private during Taiga's beta period.
button.button-green.submit-button(type="submit", title="Save") Save button.button-green.submit-button(type="submit", title="Save") Save
a.delete-project(href="", title="Delete this project", ng-click="ctrl.openDeleteLightbox()") Delete this project a.delete-project(href="", title="Delete this project", ng-click="ctrl.openDeleteLightbox()") Delete this project

View File

@ -8,28 +8,33 @@ div.wrapper.roles(ng-controller="RolesController as ctrl",
section.main.admin-roles.admin-common section.main.admin-roles.admin-common
.header-with-actions .header-with-actions
include ../includes/components/mainTitle include ../includes/components/mainTitle
.action-buttons .action-buttons(ng-if="!role.external_user")
a.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()") a.button-red.delete-role(href="", title="Delete", ng-click="ctrl.delete()")
span Delete span Delete
div(tg-edit-role) div(ng-if="!role.external_user")
.edit-role div(tg-edit-role)
input(type="text", value="{{ role.name }}") .edit-role
a.save.icon.icon-floppy(href="", title="Save") input(type="text", value="{{ role.name }}")
a.save.icon.icon-floppy(href="", title="Save")
p.total
span.role-name(title="{{ role.members_count }} members with this role") {{ role.name }}
a.edit-value.icon.icon-edit
div.any-computable-role(ng-hide="anyComputableRole") Be careful, no role in your project will be able to estimate the point value for user stories
div.general-category
| When enabled, members assigned to this role will be able to estimate the point value for user stories
div.check
input(type="checkbox", ng-model="role.computable", ng-change="ctrl.setComputable()")
div
span.check-text.check-yes Yes
span.check-text.check-no No
div(ng-if="role.external_user")
p.total p.total
span.role-name(title="{{ role.members_count }} members with this role") {{ role.name }} span.role-name {{ role.name }}
a.edit-value.icon.icon-edit
div.any-computable-role(ng-hide="anyComputableRole") Be careful, no role in your project will be able to estimate the point value for user stories
div.general-category
| When enabled, members assigned to this role will be able to estimate the point value for user stories
div.check
input(type="checkbox", ng-model="role.computable", ng-change="ctrl.setComputable()")
div
span.check-text.check-yes Yes
span.check-text.check-no No
div(tg-role-permissions, ng-model="role") div(tg-role-permissions, ng-model="role")

View File

@ -27,7 +27,7 @@ section.table-team.basic-table
div.popover.attribute-explanation div.popover.attribute-explanation
span Total Points span Total Points
div.hero(tg-team-current-user, stats="stats", currentuser="currentUser", projectid="projectId", issuesEnabled="issuesEnabled", tasksenabled="tasksEnabled", wikienabled="wikiEnabled") div.hero(tg-team-current-user, stats="stats", currentuser="currentUser", projectid="projectId", issuesEnabled="issuesEnabled", tasksenabled="tasksEnabled", wikienabled="wikiEnabled", ng-if="::currentUser")
h2(ng-show="memberships.length") h2(ng-show="memberships.length")
span Team > span Team >

View File

@ -44,6 +44,7 @@ div(class="menu-container")
span(class="icon icon-settings") span(class="icon icon-settings")
span(class="item") Admin span(class="item") Admin
<% } %> <% } %>
<% if (user) { %>
div(class="user") div(class="user")
div(class="user-settings") div(class="user-settings")
ul(class="popover") ul(class="popover")
@ -61,3 +62,4 @@ div(class="menu-container")
a(href="" title="Logout" class="logout") Logout a(href="" title="Logout" class="logout") Logout
a(href="" title="User preferences" class="avatar" id="nav-user-settings") a(href="" title="User preferences" class="avatar" id="nav-user-settings")
img(src="{{ user.photo }}" alt="{{ user.full_name_display }}") img(src="{{ user.photo }}" alt="{{ user.full_name_display }}")
<% } %>

View File

@ -24,10 +24,6 @@
position: relative; position: relative;
.view-wiki-content { .view-wiki-content {
&:hover { &:hover {
.wysiwyg {
background: $whitish;
cursor: pointer;
}
.edit { .edit {
opacity: 1; opacity: 1;
top: -1.5rem; top: -1.5rem;
@ -67,4 +63,12 @@
top: .4rem; top: .4rem;
} }
} }
&.editable {
&:hover {
.wysiwyg {
background: $whitish;
cursor: pointer;
}
}
}
} }

View File

@ -21,14 +21,15 @@
.privacy-settings { .privacy-settings {
display: flex; display: flex;
margin-bottom: 2rem; margin-bottom: 2rem;
div { > div {
flex-basis: 0; flex-basis: 0;
flex-grow: 1; flex-grow: 1;
overflow: hidden;
position: relative;
&:first-child { &:first-child {
margin-right: .5rem; margin-right: .5rem;
} }
} }
// TODO: This should change when public projects available
label { label {
@extend %title; @extend %title;
border: 1px solid $gray-light; border: 1px solid $gray-light;
@ -39,14 +40,22 @@
span { span {
color: $gray-light; color: $gray-light;
} }
// &:hover {
// border: 1px solid $fresh-taiga;
// }
} }
input:checked+label { }
.privacy-project {
cursor: pointer;
height: 500px;
left: -10px;
opacity: 0;
position: absolute;
top: -10px;
width: 500px;
z-index: 999;
}
.privacy-project:checked {
+ label {
background: $fresh-taiga; background: $fresh-taiga;
border: 1px solid $fresh-taiga; border: 1px solid $fresh-taiga;
cursor: default;
span { span {
color: $white; color: $white;
} }

View File

@ -101,5 +101,11 @@
opacity: 0; opacity: 0;
} }
} }
input:disabled {
cursor: auto;
+ div {
background-color: #ccc;
}
}
} }
} }