浏览代码

Merge pull request #925 from rhelsing/comment-permissions

Comment permissions
Lauri Ojansivu 8 年之前
父节点
当前提交
578619d409

+ 1 - 0
.eslintrc.json

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

+ 1 - 1
client/components/boards/boardBody.jade

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

+ 6 - 0
client/components/boards/boardBody.js

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

+ 2 - 2
client/components/boards/boardHeader.jade

@@ -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}}")

+ 6 - 0
client/components/boards/boardHeader.js

@@ -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;

+ 9 - 3
client/components/cards/cardDate.jade

@@ -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}}
+

+ 6 - 0
client/components/cards/cardDate.js

@@ -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() {

+ 23 - 22
client/components/cards/cardDetails.jade

@@ -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

+ 8 - 0
client/components/cards/cardDetails.js

@@ -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({

+ 22 - 12
client/components/cards/checklists.jade

@@ -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}}
+

+ 10 - 0
client/components/cards/checklists.js

@@ -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;

+ 2 - 2
client/components/lists/list.js

@@ -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());
     });

+ 1 - 1
client/components/lists/listBody.jade

@@ -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

+ 7 - 0
client/components/lists/listBody.js

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

+ 12 - 11
client/components/lists/listHeader.jade

@@ -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

+ 8 - 2
client/components/sidebar/sidebar.jade

@@ -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'}}

+ 24 - 3
client/components/sidebar/sidebar.js

@@ -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);

+ 2 - 0
i18n/en.i18n.json

@@ -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",

+ 10 - 1
models/boards.js

@@ -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,
       },
     };
   },

+ 3 - 3
models/lists.js

@@ -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'],
 });

+ 10 - 0
models/users.js

@@ -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);

+ 4 - 0
server/lib/utils.js

@@ -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);