Merge pull request #925 from rhelsing/comment-permissions

Comment permissions
This commit is contained in:
Lauri Ojansivu 2017-03-28 20:55:02 +03:00 committed by GitHub
commit 578619d409
22 changed files with 177 additions and 63 deletions

View file

@ -118,6 +118,7 @@
"allowIsBoardAdmin": true,
"allowIsBoardMember": true,
"allowIsBoardMemberByCard": true,
"allowIsBoardMemberNonComment": true,
"Emoji": true,
"Checklists": true,
"Settings": true,

View file

@ -25,7 +25,7 @@ template(name="boardBody")
+list(this)
if currentCardIsInThisList
+cardDetails(currentCard)
if currentUser.isBoardMember
if canSeeAddList
+addListForm
template(name="addListForm")

View file

@ -204,3 +204,9 @@ BlazeComponent.extendComponent({
}];
},
}).register('addListForm');
Template.boardBody.helpers({
canSeeAddList() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});

View file

@ -65,7 +65,7 @@ template(name="boardHeaderBar")
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
@ -81,7 +81,7 @@ template(name="boardHeaderBar")
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
i.fa.fa-times-thin
if currentUser.isBoardMember
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
class="{{#if MultiSelection.isActive}}emphasis{{/if}}")

View file

@ -97,6 +97,12 @@ BlazeComponent.extendComponent({
},
}).register('boardHeaderBar');
Template.boardHeaderBar.helpers({
canModifyBoard() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
BlazeComponent.extendComponent({
backgroundColors() {
return Boards.simpleSchema()._schema.color.allowedValues;

View file

@ -15,6 +15,12 @@ template(name="editCardDate")
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
template(name="dateBadge")
a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
else
a.card-date(title="{{showTitle}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}

View file

@ -86,6 +86,12 @@ const EditCardDate = BlazeComponent.extendComponent({
},
});
Template.dateBadge.helpers({
canModifyCard() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
// editCardStartDatePopup
(class extends EditCardDate {
onCreated() {

View file

@ -8,7 +8,7 @@ template(name="cardDetails")
if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
h2.card-details-title.js-card-title(
class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}")
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
= title
if isWatching
i.fa.fa-eye.card-details-watch
@ -22,16 +22,16 @@ template(name="cardDetails")
each members
+userAvatar(userId=this cardId=../_id)
| {{! XXX Hack to hide syntaxic coloration /// }}
if currentUser.isBoardMember
if canModifyCard
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
i.fa.fa-plus
.card-details-item.card-details-item-labels
h3.card-details-item-title {{_ 'labels'}}
a(class="{{#if currentUser.isBoardMember}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
each labels
span.card-label(class="card-label-{{color}}" title=name)= name
if currentUser.isBoardMember
if canModifyCard
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
i.fa.fa-plus
@ -47,7 +47,7 @@ template(name="cardDetails")
//- XXX We should use "editable" to avoid repetiting ourselves
if currentUser.isBoardMember
if canModifyCard
h3.card-details-item-title {{_ 'description'}}
+inlinedCardDescription(classNames="card-description js-card-description")
+editor(autofocus=true)
@ -101,23 +101,24 @@ template(name="editCardTitleForm")
template(name="cardDetailsActionsPopup")
ul.pop-over-list
li: a.js-toggle-watch-card {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
hr
ul.pop-over-list
li: a.js-members {{_ 'card-edit-members'}}
li: a.js-labels {{_ 'card-edit-labels'}}
li: a.js-attachments {{_ 'card-edit-attachments'}}
li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
hr
ul.pop-over-list
li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
li: a.js-move-card-to-bottom {{_ 'moveCardToBottom-title'}}
hr
ul.pop-over-list
li: a.js-move-card {{_ 'moveCardPopup-title'}}
unless archived
li: a.js-archive {{_ 'archive-card'}}
li: a.js-more {{_ 'cardMorePopup-title'}}
if canModifyCard
hr
ul.pop-over-list
li: a.js-members {{_ 'card-edit-members'}}
li: a.js-labels {{_ 'card-edit-labels'}}
li: a.js-attachments {{_ 'card-edit-attachments'}}
li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
hr
ul.pop-over-list
li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
li: a.js-move-card-to-bottom {{_ 'moveCardToBottom-title'}}
hr
ul.pop-over-list
li: a.js-move-card {{_ 'moveCardPopup-title'}}
unless archived
li: a.js-archive {{_ 'archive-card'}}
li: a.js-more {{_ 'cardMorePopup-title'}}
template(name="moveCardPopup")
+boardLists

View file

@ -28,6 +28,10 @@ BlazeComponent.extendComponent({
return card.findWatcher(Meteor.userId());
},
canModifyCard() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
scrollParentContainer() {
const cardPanelWidth = 510;
const bodyBoardComponent = this.parentComponent();
@ -140,6 +144,10 @@ Template.cardDetailsActionsPopup.helpers({
isWatching() {
return this.findWatcher(Meteor.userId());
},
canModifyCard() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
Template.cardDetailsActionsPopup.events({

View file

@ -3,12 +3,13 @@ template(name="checklists")
.card-checklist-items
each checklist in currentCard.checklists
+checklistDetail(checklist = checklist)
+inlinedForm(classNames="js-add-checklist" cardId = cardId)
+addChecklistItemForm
else
a.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-checklist'}}...
if canModifyCard
+inlinedForm(classNames="js-add-checklist" cardId = cardId)
+addChecklistItemForm
else
a.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-checklist'}}...
template(name="checklistDetail")
+inlinedForm(classNames="js-edit-checklist-title" checklist = checklist)
@ -16,9 +17,13 @@ template(name="checklistDetail")
else
.checklist-title
.checkbox.fa.fa-check-square-o
a.js-delete-checklist {{_ "delete"}}...
if canModifyCard
a.js-delete-checklist {{_ "delete"}}...
span.checklist-stat(class="{{#if checklist.isFinished}}is-finished{{/if}}") {{checklist.finishedCount}}/{{checklist.itemCount}}
h2.title.js-open-inlined-form.is-editable {{checklist.title}}
if canModifyCard
h2.title.js-open-inlined-form.is-editable {{checklist.title}}
else
h2.title {{checklist.title}}
+checklistItems(checklist = checklist)
template(name="addChecklistItemForm")
@ -37,7 +42,7 @@ template(name="editChecklistItemForm")
button.primary.confirm.js-submit-edit-checklist-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
span(title=createdAt) {{ moment createdAt }}
if currentUser.isBoardMember
if canModifyCard
a.js-delete-checklist-item {{_ "delete"}}...
template(name="checklistItems")
@ -47,7 +52,7 @@ template(name="checklistItems")
+editChecklistItemForm(type = 'item' item = item checklist = checklist)
else
+itemDetail(item = item checklist = checklist)
if currentUser.isBoardMember
if canModifyCard
+inlinedForm(classNames="js-add-checklist-item" checklist = checklist)
+addChecklistItemForm
else
@ -57,5 +62,10 @@ template(name="checklistItems")
template(name='itemDetail')
.item
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}") {{item.title}}
if canModifyCard
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}") {{item.title}}
else
.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}") {{item.title}}

View file

@ -26,6 +26,10 @@ BlazeComponent.extendComponent({
checklist.setTitle(title);
},
canModifyCard() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
editChecklistItem(event) {
event.preventDefault();
@ -73,6 +77,12 @@ BlazeComponent.extendComponent({
},
}).register('checklists');
Template.itemDetail.helpers({
canModifyCard() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});
BlazeComponent.extendComponent({
toggleItem() {
const checklist = this.currentData().checklist;

View file

@ -79,10 +79,10 @@ BlazeComponent.extendComponent({
});
function userIsMember() {
return Meteor.user() && Meteor.user().isBoardMember();
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
}
// Disable drag-dropping if the current user is not a board member
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
$cards.sortable('option', 'disabled', !userIsMember());
});

View file

@ -12,7 +12,7 @@ template(name="listBody")
.materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection(
class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}")
+minicard(this)
if currentUser.isBoardMember
if canSeeAddCard
+inlinedForm(autoclose=false position="bottom")
+addCardForm(listId=_id position="bottom")
else

View file

@ -239,3 +239,10 @@ BlazeComponent.extendComponent({
});
},
}).register('addCardForm');
Template.listBody.helpers({
canSeeAddCard() {
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
},
});

View file

@ -25,17 +25,18 @@ template(name="editListTitleForm")
template(name="listActionPopup")
ul.pop-over-list
li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
hr
ul.pop-over-list
li: a.js-add-card {{_ 'add-card'}}
if cards.count
li: a.js-select-cards {{_ 'list-select-cards'}}
hr
ul.pop-over-list
li: a.js-close-list {{_ 'archive-list'}}
hr
ul.pop-over-list
li: a.js-remove-list {{_ 'remove-list'}}
unless currentUser.isCommentOnly
hr
ul.pop-over-list
li: a.js-add-card {{_ 'add-card'}}
if cards.count
li: a.js-select-cards {{_ 'list-select-cards'}}
hr
ul.pop-over-list
li: a.js-close-list {{_ 'archive-list'}}
hr
ul.pop-over-list
li: a.js-remove-list {{_ 'remove-list'}}
template(name="boardLists")
ul.pop-over-list

View file

@ -60,7 +60,7 @@ template(name="labelsWidget")
.board-widget-content
each currentBoard.labels
a.card-label(class="card-label-{{color}}"
class="{{#if currentUser.isBoardMember}}js-label{{/if}}")
class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}")
span.card-label-name= name
if currentUser.isBoardAdmin
a.card-label.add-label.js-add-label
@ -138,9 +138,15 @@ template(name="changePermissionsPopup")
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-normal{{/if}}")
| {{_ 'normal'}}
unless isAdmin
if isNormal
i.fa.fa-check
span.sub-name {{_ 'normal-desc'}}
li
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-only{{/if}}")
| {{_ 'comment-only'}}
if isCommentOnly
i.fa.fa-check
span.sub-name {{_ 'comment-only-desc'}}
if isLastAdmin
hr
p.quiet.bottom {{_ 'last-admin-desc'}}

View file

@ -121,7 +121,17 @@ Template.memberPopup.helpers({
},
memberType() {
const type = Users.findOne(this.userId).isBoardAdmin() ? 'admin' : 'normal';
return TAPi18n.__(type).toLowerCase();
if(type === 'normal'){
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const commentOnly = currentBoard.hasCommentOnly(this.userId);
if(commentOnly){
return TAPi18n.__('comment-only').toLowerCase();
} else {
return TAPi18n.__(type).toLowerCase();
}
} else {
return TAPi18n.__(type).toLowerCase();
}
},
isInvited() {
return Users.findOne(this.userId).isInvitedTo(Session.get('currentBoard'));
@ -308,11 +318,12 @@ BlazeComponent.extendComponent({
}).register('addMemberPopup');
Template.changePermissionsPopup.events({
'click .js-set-admin, click .js-set-normal'(event) {
'click .js-set-admin, click .js-set-normal, click .js-set-comment-only'(event) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const memberId = this.userId;
const isAdmin = $(event.currentTarget).hasClass('js-set-admin');
currentBoard.setMemberPermission(memberId, isAdmin);
const isCommentOnly = $(event.currentTarget).hasClass('js-set-comment-only');
currentBoard.setMemberPermission(memberId, isAdmin, isCommentOnly);
Popup.back(1);
},
});
@ -323,6 +334,16 @@ Template.changePermissionsPopup.helpers({
return currentBoard.hasAdmin(this.userId);
},
isNormal() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return !currentBoard.hasAdmin(this.userId) && !currentBoard.hasCommentOnly(this.userId);
},
isCommentOnly() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return !currentBoard.hasAdmin(this.userId) && currentBoard.hasCommentOnly(this.userId);
},
isLastAdmin() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return currentBoard.hasAdmin(this.userId) && (currentBoard.activeAdmins() === 1);

View file

@ -137,6 +137,8 @@
"color-yellow": "yellow",
"comment": "Comment",
"comment-placeholder": "Write a comment",
"comment-only": "Limited",
"comment-only-desc": "Can comment on cards only.",
"computer": "Computer",
"create": "Create",
"createBoardPopup-title": "Create Board",

View file

@ -107,6 +107,7 @@ Boards.attachSchema(new SimpleSchema({
userId: this.userId,
isAdmin: true,
isActive: true,
isCommentOnly: false,
}];
}
},
@ -120,6 +121,9 @@ Boards.attachSchema(new SimpleSchema({
'members.$.isActive': {
type: Boolean,
},
'members.$.isCommentOnly': {
type: Boolean,
},
permission: {
type: String,
allowedValues: ['public', 'private'],
@ -219,6 +223,10 @@ Boards.helpers({
return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: true});
},
hasCommentOnly(memberId) {
return !!_.findWhere(this.members, {userId: memberId, isActive: true, isAdmin: false, isCommentOnly: true});
},
absoluteUrl() {
return FlowRouter.url('board', { id: this._id, slug: this.slug });
},
@ -332,7 +340,7 @@ Boards.mutations({
};
},
setMemberPermission(memberId, isAdmin) {
setMemberPermission(memberId, isAdmin, isCommentOnly) {
const memberIndex = this.memberIndex(memberId);
// do not allow change permission of self
@ -343,6 +351,7 @@ Boards.mutations({
return {
$set: {
[`members.${memberIndex}.isAdmin`]: isAdmin,
[`members.${memberIndex}.isCommentOnly`]: isCommentOnly,
},
};
},

View file

@ -46,13 +46,13 @@ Lists.attachSchema(new SimpleSchema({
Lists.allow({
insert(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
return allowIsBoardMemberNonComment(userId, Boards.findOne(doc.boardId));
},
fetch: ['boardId'],
});

View file

@ -121,6 +121,16 @@ if (Meteor.isClient) {
return board && board.hasMember(this._id);
},
isNotCommentOnly() {
const board = Boards.findOne(Session.get('currentBoard'));
return board && board.hasMember(this._id) && !board.hasCommentOnly(this._id);
},
isCommentOnly() {
const board = Boards.findOne(Session.get('currentBoard'));
return board && board.hasCommentOnly(this._id);
},
isBoardAdmin() {
const board = Boards.findOne(Session.get('currentBoard'));
return board && board.hasAdmin(this._id);

View file

@ -6,6 +6,10 @@ allowIsBoardMember = function(userId, board) {
return board && board.hasMember(userId);
};
allowIsBoardMemberNonComment = function(userId, board) {
return board && board.hasMember(userId) && !board.hasCommentOnly(userId);
};
allowIsBoardMemberByCard = function(userId, card) {
const board = card.board();
return board && board.hasMember(userId);