Browse Source

Add Worker role.
Add more Font Awesome icons.
Fix browser console errors when editing user profile name etc.

Thanks to xet7 !

Closes #2788

Lauri Ojansivu 5 years ago
parent
commit
2bf004120d
37 changed files with 683 additions and 320 deletions
  1. 1 0
      .eslintrc.json
  2. 14 12
      client/components/cards/attachments.jade
  3. 2 1
      client/components/cards/cardDate.js
  4. 187 96
      client/components/cards/cardDetails.jade
  5. 44 2
      client/components/cards/cardDetails.js
  6. 3 1
      client/components/cards/checklists.jade
  7. 6 3
      client/components/cards/checklists.js
  8. 6 10
      client/components/cards/minicard.js
  9. 3 1
      client/components/cards/subtasks.jade
  10. 6 3
      client/components/cards/subtasks.js
  11. 3 5
      client/components/lists/list.js
  12. 2 1
      client/components/lists/listBody.js
  13. 35 12
      client/components/lists/listHeader.jade
  14. 7 8
      client/components/lists/listHeader.js
  15. 6 2
      client/components/settings/informationBody.jade
  16. 10 3
      client/components/settings/peopleBody.jade
  17. 1 1
      client/components/settings/peopleBody.styl
  18. 19 6
      client/components/settings/settingBody.jade
  19. 4 1
      client/components/settings/settingBody.styl
  20. 78 27
      client/components/sidebar/sidebar.jade
  21. 29 12
      client/components/sidebar/sidebar.js
  22. 30 24
      client/components/sidebar/sidebarArchives.jade
  23. 9 0
      client/components/sidebar/sidebarArchives.js
  24. 8 7
      client/components/sidebar/sidebarFilters.jade
  25. 3 5
      client/components/swimlanes/swimlaneHeader.js
  26. 17 16
      client/components/swimlanes/swimlanes.jade
  27. 9 15
      client/components/swimlanes/swimlanes.js
  28. 1 0
      client/components/users/userAvatar.jade
  29. 47 18
      client/components/users/userHeader.jade
  30. 21 3
      client/components/users/userHeader.js
  31. 18 22
      client/lib/utils.js
  32. 12 0
      models/accountSettings.js
  33. 28 1
      models/boards.js
  34. 1 1
      models/cards.js
  35. 1 1
      models/checklists.js
  36. 10 0
      models/users.js
  37. 2 0
      sandstorm.js

+ 1 - 0
.eslintrc.json

@@ -145,6 +145,7 @@
     "allowIsBoardMemberByCard": true,
     "allowIsBoardMemberByCard": true,
     "allowIsBoardMemberCommentOnly": true,
     "allowIsBoardMemberCommentOnly": true,
     "allowIsBoardMemberNoComments": true,
     "allowIsBoardMemberNoComments": true,
+    "allowIsBoardMemberWorker": true,
     "Emoji": true,
     "Emoji": true,
     "Checklists": true,
     "Checklists": true,
     "Settings": true,
     "Settings": true,

+ 14 - 12
client/components/cards/attachments.jade

@@ -38,18 +38,20 @@ template(name="attachmentsGalery")
               | {{_ 'download'}}
               | {{_ 'download'}}
             if currentUser.isBoardMember
             if currentUser.isBoardMember
               unless currentUser.isCommentOnly
               unless currentUser.isCommentOnly
-                if isImage
-                  a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
-                    i.fa.fa-thumb-tack
-                    if($eq ../coverId _id)
-                      | {{_ 'remove-cover'}}
-                    else
-                      | {{_ 'add-cover'}}
-                a.js-confirm-delete
-                  i.fa.fa-close
-                  | {{_ 'delete'}}
+                unless currentUser.isWorker
+                  if isImage
+                    a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
+                      i.fa.fa-thumb-tack
+                      if($eq ../coverId _id)
+                        | {{_ 'remove-cover'}}
+                      else
+                        | {{_ 'add-cover'}}
+                  a.js-confirm-delete
+                    i.fa.fa-close
+                    | {{_ 'delete'}}
 
 
     if currentUser.isBoardMember
     if currentUser.isBoardMember
       unless currentUser.isCommentOnly
       unless currentUser.isCommentOnly
-        li.attachment-item.add-attachment
-          a.js-add-attachment {{_ 'add-attachment' }}
+        unless currentUser.isWorker
+          li.attachment-item.add-attachment
+            a.js-add-attachment {{_ 'add-attachment' }}

+ 2 - 1
client/components/cards/cardDate.js

@@ -97,7 +97,8 @@ Template.dateBadge.helpers({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 });
 });

+ 187 - 96
client/components/cards/cardDetails.jade

@@ -1,7 +1,7 @@
 template(name="cardDetails")
 template(name="cardDetails")
   section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas
   section.card-details.js-card-details.js-perfect-scrollbar: .card-details-canvas
     .card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
     .card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
-      +inlinedForm(classNames="js-card-details-title")
+      +inlinedForm(classNames="{{#if canModifyCardWorker}}js-card-details-title{{/if}}")
         +editCardTitleForm
         +editCardTitleForm
       else
       else
         unless isMiniScreen
         unless isMiniScreen
@@ -13,11 +13,11 @@ template(name="cardDetails")
           if currentUser.isBoardMember
           if currentUser.isBoardMember
             a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu
             a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu
         h2.card-details-title.js-card-title(
         h2.card-details-title.js-card-title(
-          class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
+          class="{{#if canModifyCardWorker}}js-open-inlined-form is-editable{{/if}}")
             +viewer
             +viewer
               = getTitle
               = getTitle
-              if isWatching
-                i.fa.fa-eye.card-details-watch
+            if isWatching
+              i.card-details-watch.fa.fa-eye
         .card-details-path
         .card-details-path
           each parentList
           each parentList
             |   >  
             |   >  
@@ -37,49 +37,66 @@ template(name="cardDetails")
 
 
     .card-details-items
     .card-details-items
       .card-details-item.card-details-item-received
       .card-details-item.card-details-item-received
-        h3.card-details-item-title {{_ 'card-received'}}
+        h3
+          i.fa.fa-sign-out
+          card-details-item-title {{_ 'card-received'}}
         if getReceived
         if getReceived
           +cardReceivedDate
           +cardReceivedDate
         else
         else
           if canModifyCard
           if canModifyCard
-            a.js-received-date {{_ 'add'}}
+            unless currentUser.isWorker
+              a.js-received-date {{_ 'add'}}
 
 
       .card-details-item.card-details-item-start
       .card-details-item.card-details-item-start
-        h3.card-details-item-title {{_ 'card-start'}}
+        h3
+          i.fa.fa-hourglass-start
+          card-details-item-title {{_ 'card-start'}}
         if getStart
         if getStart
           +cardStartDate
           +cardStartDate
         else
         else
           if canModifyCard
           if canModifyCard
-            a.js-start-date {{_ 'add'}}
+            unless currentUser.isWorker
+              a.js-start-date {{_ 'add'}}
 
 
       .card-details-item.card-details-item-due
       .card-details-item.card-details-item-due
-        h3.card-details-item-title {{_ 'card-due'}}
+        h3
+          i.fa.fa-sign-in
+          card-details-item-title {{_ 'card-due'}}
         if getDue
         if getDue
           +cardDueDate
           +cardDueDate
         else
         else
           if canModifyCard
           if canModifyCard
-            a.js-due-date {{_ 'add'}}
+            unless currentUser.isWorker
+              a.js-due-date {{_ 'add'}}
 
 
       .card-details-item.card-details-item-end
       .card-details-item.card-details-item-end
-        h3.card-details-item-title {{_ 'card-end'}}
+        h3
+          i.fa.fa-hourglass-end
+          card-details-item-title {{_ 'card-end'}}
         if getEnd
         if getEnd
           +cardEndDate
           +cardEndDate
         else
         else
           if canModifyCard
           if canModifyCard
-            a.js-end-date {{_ 'add'}}
+            unless currentUser.isWorker
+              a.js-end-date {{_ 'add'}}
 
 
     .card-details-items
     .card-details-items
       .card-details-item.card-details-item-members
       .card-details-item.card-details-item-members
-        h3.card-details-item-title {{_ 'members'}}
+        h3
+          i.fa.fa-users
+          card-details-item-title {{_ 'members'}}
         each getMembers
         each getMembers
           +userAvatar(userId=this cardId=../_id)
           +userAvatar(userId=this cardId=../_id)
           | {{! XXX Hack to hide syntaxic coloration /// }}
           | {{! XXX Hack to hide syntaxic coloration /// }}
         if canModifyCard
         if canModifyCard
-          a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
-            i.fa.fa-plus
+          unless currentUser.isWorker
+            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-assignees
       .card-details-item.card-details-item-assignees
-        h3.card-details-item-title {{_ 'assignee'}}
+        h3
+          i.fa.fa-user
+          card-details-item-title {{_ 'assignee'}}
         each getAssignees
         each getAssignees
           +userAvatarAssignee(userId=this cardId=../_id)
           +userAvatarAssignee(userId=this cardId=../_id)
           | {{! XXX Hack to hide syntaxic coloration /// }}
           | {{! XXX Hack to hide syntaxic coloration /// }}
@@ -89,15 +106,18 @@ template(name="cardDetails")
               i.fa.fa-plus
               i.fa.fa-plus
 
 
       .card-details-item.card-details-item-labels
       .card-details-item.card-details-item-labels
-        h3.card-details-item-title {{_ 'labels'}}
+        h3
+          i.fa.fa-tags
+          card-details-item-title {{_ 'labels'}}
         a(class="{{#if canModifyCard}}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
           each labels
             span.card-label(class="card-label-{{color}}" title=name)
             span.card-label(class="card-label-{{color}}" title=name)
               +viewer
               +viewer
                 = name
                 = name
         if canModifyCard
         if canModifyCard
-          a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
-            i.fa.fa-plus
+          unless currentUser.isWorker
+            a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
+              i.fa.fa-plus
 
 
     .card-details-items
     .card-details-items
       each customFieldsWD
       each customFieldsWD
@@ -118,26 +138,29 @@ template(name="cardDetails")
 
 
     //- XXX We should use "editable" to avoid repetiting ourselves
     //- XXX We should use "editable" to avoid repetiting ourselves
     if canModifyCard
     if canModifyCard
-      h3.card-details-item-title {{_ 'description'}}
-      +inlinedCardDescription(classNames="card-description js-card-description")
-        +editor(autofocus=true)
-          | {{getUnsavedValue 'cardDescription' _id getDescription}}
-        .edit-controls.clearfix
-          button.primary(type="submit") {{_ 'save'}}
-          a.fa.fa-times-thin.js-close-inlined-form
-      else
-        a.js-open-inlined-form
-          if getDescription
-            +viewer
-              = getDescription
-          else
-            | {{_ 'edit'}}
-        if (hasUnsavedValue 'cardDescription' _id)
-          p.quiet
-            | {{_ 'unsaved-description'}}
-            a.js-open-inlined-form {{_ 'view-it'}}
-            = ' - '
-            a.js-close-inlined-form {{_ 'discard'}}
+      unless currentUser.isWorker
+        h3
+          i.fa.fa-align-left
+          card-details-item-title {{_ 'description'}}
+        +inlinedCardDescription(classNames="card-description js-card-description")
+          +editor(autofocus=true)
+            | {{getUnsavedValue 'cardDescription' _id getDescription}}
+          .edit-controls.clearfix
+            button.primary(type="submit") {{_ 'save'}}
+            a.fa.fa-times-thin.js-close-inlined-form
+        else
+          a.js-open-inlined-form
+            if getDescription
+              +viewer
+                = getDescription
+            else
+              | {{_ 'edit'}}
+          if (hasUnsavedValue 'cardDescription' _id)
+            p.quiet
+              | {{_ 'unsaved-description'}}
+              a.js-open-inlined-form {{_ 'view-it'}}
+              = ' - '
+              a.js-close-inlined-form {{_ 'discard'}}
     else if getDescription
     else if getDescription
       h3.card-details-item-title {{_ 'description'}}
       h3.card-details-item-title {{_ 'description'}}
       +viewer
       +viewer
@@ -145,33 +168,39 @@ template(name="cardDetails")
 
 
     .card-details-items
     .card-details-items
       .card-details-item.card-details-item-name
       .card-details-item.card-details-item-name
-        h3.card-details-item-title {{_ 'requested-by'}}
+        h3
+          i.fa.fa-shopping-cart
+          card-details-item-title {{_ 'requested-by'}}
         if canModifyCard
         if canModifyCard
-          +inlinedForm(classNames="js-card-details-requester")
-            +editCardRequesterForm
-          else
-            a.js-open-inlined-form
-              if getRequestedBy
-                +viewer
-                  = getRequestedBy
-              else
-                | {{_ 'add'}}
+          unless currentUser.isWorker
+            +inlinedForm(classNames="js-card-details-requester")
+              +editCardRequesterForm
+            else
+              a.js-open-inlined-form
+                if getRequestedBy
+                  +viewer
+                    = getRequestedBy
+                else
+                  | {{_ 'add'}}
         else if getRequestedBy
         else if getRequestedBy
           +viewer
           +viewer
             = getRequestedBy
             = getRequestedBy
 
 
       .card-details-item.card-details-item-name
       .card-details-item.card-details-item-name
-        h3.card-details-item-title {{_ 'assigned-by'}}
+        h3
+          i.fa.fa-user-plus
+          card-details-item-title {{_ 'assigned-by'}}
         if canModifyCard
         if canModifyCard
-          +inlinedForm(classNames="js-card-details-assigner")
-            +editCardAssignerForm
-          else
-            a.js-open-inlined-form
-              if getAssignedBy
-                +viewer
-                  = getAssignedBy
-              else
-                | {{_ 'add'}}
+          unless currentUser.isWorker
+            +inlinedForm(classNames="js-card-details-assigner")
+              +editCardAssignerForm
+            else
+              a.js-open-inlined-form
+                if getAssignedBy
+                  +viewer
+                    = getAssignedBy
+                else
+                  | {{_ 'add'}}
         else if getRequestedBy
         else if getRequestedBy
           +viewer
           +viewer
             = getAssignedBy
             = getAssignedBy
@@ -193,7 +222,9 @@ template(name="cardDetails")
     hr
     hr
     unless currentUser.isNoComments
     unless currentUser.isNoComments
       .activity-title
       .activity-title
-        h3 {{ _ 'activity'}}
+        h3
+          i.fa.fa-history
+          | {{ _ 'activity'}}
         if currentUser.isBoardMember
         if currentUser.isBoardMember
           .material-toggle-switch
           .material-toggle-switch
             span.toggle-switch-title {{_ 'hide-system-messages'}}
             span.toggle-switch-title {{_ 'hide-system-messages'}}
@@ -235,32 +266,79 @@ template(name="editCardAssignerForm")
 
 
 template(name="cardDetailsActionsPopup")
 template(name="cardDetailsActionsPopup")
   ul.pop-over-list
   ul.pop-over-list
-    li: a.js-toggle-watch-card {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
+    li
+      a.js-toggle-watch-card
+        if isWatching
+          i.fa.fa-eye
+          |  {{_ 'unwatch'}}
+        else
+          i.fa.fa-eye-slash
+          |  {{_ 'watch'}}
   if canModifyCard
   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-custom-fields {{_ 'card-edit-custom-fields'}}
-      //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
-      //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
-      //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
-      //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
-      li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}}
-      li: a.js-set-card-color {{_ 'setCardColorPopup-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
+    unless currentUser.isWorker
+      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-custom-fields
+            i.fa.fa-list-alt
+            | {{_ 'card-edit-custom-fields'}}
+        //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
+        //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
+        //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
+        //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
+        li
+          a.js-spent-time
+            i.fa.fa-clock-o
+            | {{_ 'editCardSpentTimePopup-title'}}
+        li
+          a.js-set-card-color
+            i.fa.fa-paint-brush
+            | {{_ 'setCardColorPopup-title'}}
+      hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-move-card {{_ 'moveCardPopup-title'}}
-      li: a.js-copy-card {{_ 'copyCardPopup-title'}}
-      li: a.js-copy-checklist-cards {{_ 'copyChecklistToManyCardsPopup-title'}}
+      li
+        a.js-move-card-to-top
+          i.fa.fa-arrow-up
+          | {{_ 'moveCardToTop-title'}}
+      li
+        a.js-move-card-to-bottom
+          i.fa.fa-arrow-down
+          | {{_ 'moveCardToBottom-title'}}
+    unless currentUser.isWorker
+      hr
+      ul.pop-over-list
+        li
+          a.js-move-card
+            i.fa.fa-arrow-right
+            | {{_ 'moveCardPopup-title'}}
+        li
+          a.js-copy-card
+            i.fa.fa-copy
+            | {{_ 'copyCardPopup-title'}}
+      hr
+      ul.pop-over-list
+        li
+          a.js-copy-checklist-cards
+            i.fa.fa-list
+            i.fa.fa-copy
+            | {{_ 'copyChecklistToManyCardsPopup-title'}}
       unless archived
       unless archived
-        li: a.js-archive {{_ 'archive-card'}}
-      li: a.js-more {{_ 'cardMorePopup-title'}}
+        hr
+        ul.pop-over-list
+          li
+            a.js-archive
+              i.fa.fa-arrow-right
+              i.fa.fa-archive
+              | {{_ 'archive-card'}}
+      hr
+      ul.pop-over-list
+        li
+          a.js-more
+            i.fa.fa-link
+            | {{_ 'cardMorePopup-title'}}
 
 
 template(name="moveCardPopup")
 template(name="moveCardPopup")
   +boardsAndLists
   +boardsAndLists
@@ -312,16 +390,27 @@ template(name="cardMembersPopup")
             i.fa.fa-check
             i.fa.fa-check
 
 
 template(name="cardAssigneesPopup")
 template(name="cardAssigneesPopup")
-  ul.pop-over-list.js-card-assignee-list
-    each board.activeMembers
-      li.item(class="{{#if isCardAssignee}}active{{/if}}")
-        a.name.js-select-assignee(href="#")
-          +userAvatar(userId=user._id)
-          span.full-name
-            = user.profile.fullname
-            | (<span class="username">{{ user.username }}</span>)
-          if isCardAssignee
-            i.fa.fa-check
+  unless currentUser.isWorker
+    ul.pop-over-list.js-card-assignee-list
+      each board.activeMembers
+        li.item(class="{{#if isCardAssignee}}active{{/if}}")
+          a.name.js-select-assignee(href="#")
+            +userAvatar(userId=user._id)
+            span.full-name
+              = user.profile.fullname
+              | (<span class="username">{{ user.username }}</span>)
+            if isCardAssignee
+              i.fa.fa-check
+  if currentUser.isWorker
+    ul.pop-over-list.js-card-assignee-list
+        li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
+          a.name.js-select-assigneeWorker(href="#")
+            +userAvatar(userId=currentUser._id)
+            span.full-name
+              = currentUser.profile.fullname
+              | (<span class="username">{{ currentUser.username }}</span>)
+            if currentUser.isCardAssignee
+              i.fa.fa-check
 
 
 template(name="userAvatarAssignee")
 template(name="userAvatarAssignee")
   a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
   a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
@@ -349,11 +438,13 @@ template(name="cardAssigneePopup")
         p.quiet @{{ user.username }}
         p.quiet @{{ user.username }}
     ul.pop-over-list
     ul.pop-over-list
       if currentUser.isNotCommentOnly
       if currentUser.isNotCommentOnly
+        unless currentUser.isWorker
           li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
           li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
 
 
-      if $eq currentUser._id user._id
-        with currentUser
-          li: a.js-edit-profile {{_ 'edit-profile'}}
+      unless currentUser.isWorker
+        if $eq currentUser._id user._id
+          with currentUser
+            li: a.js-edit-profile {{_ 'edit-profile'}}
 
 
 template(name="userAvatarAssigneeInitials")
 template(name="userAvatarAssigneeInitials")
   svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")
   svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")

+ 44 - 2
client/components/cards/cardDetails.js

@@ -26,6 +26,7 @@ BlazeComponent.extendComponent({
 
 
   onCreated() {
   onCreated() {
     this.currentBoard = Boards.findOne(Session.get('currentBoard'));
     this.currentBoard = Boards.findOne(Session.get('currentBoard'));
+    this.currentUser = Meteor.user();
     this.isLoaded = new ReactiveVar(false);
     this.isLoaded = new ReactiveVar(false);
     const boardBody = this.parentComponent().parentComponent();
     const boardBody = this.parentComponent().parentComponent();
     //in Miniview parent is Board, not BoardBody.
     //in Miniview parent is Board, not BoardBody.
@@ -55,6 +56,15 @@ BlazeComponent.extendComponent({
     );
     );
   },
   },
 
 
+  canModifyCardWorker() {
+    return (
+      Meteor.user() &&
+      Meteor.user().isBoardMember() &&
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
+    );
+  },
+
   scrollParentContainer() {
   scrollParentContainer() {
     const cardPanelWidth = 510;
     const cardPanelWidth = 510;
     const bodyBoardComponent = this.parentComponent().parentComponent();
     const bodyBoardComponent = this.parentComponent().parentComponent();
@@ -322,7 +332,12 @@ BlazeComponent.extendComponent({
         'click .js-assignee': Popup.open('cardAssignee'),
         'click .js-assignee': Popup.open('cardAssignee'),
         'click .js-add-assignees': Popup.open('cardAssignees'),
         'click .js-add-assignees': Popup.open('cardAssignees'),
         'click .js-add-labels': Popup.open('cardLabels'),
         'click .js-add-labels': Popup.open('cardLabels'),
-        'click .js-received-date': Popup.open('editCardReceivedDate'),
+        'click .js-received-date'(event) {
+          event.preventDefault();
+          if (!Meteor.user().isWorker) {
+            Popup.open('editCardReceivedDate');
+          }
+        },
         'click .js-start-date': Popup.open('editCardStartDate'),
         'click .js-start-date': Popup.open('editCardStartDate'),
         'click .js-due-date': Popup.open('editCardDueDate'),
         'click .js-due-date': Popup.open('editCardDueDate'),
         'click .js-end-date': Popup.open('editCardEndDate'),
         'click .js-end-date': Popup.open('editCardEndDate'),
@@ -383,6 +398,13 @@ Template.cardDetails.helpers({
     return user && user.isBoardAdmin() ? 'admin' : 'normal';
     return user && user.isBoardAdmin() ? 'admin' : 'normal';
   },
   },
 
 
+  isWorker() {
+    const currentBoard = Boards.findOne(Session.get('currentBoard'));
+    return (
+      !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
+    );
+  },
+
   presenceStatusClassName() {
   presenceStatusClassName() {
     const user = Users.findOne(this.userId);
     const user = Users.findOne(this.userId);
     const userPresence = presences.findOne({ userId: this.userId });
     const userPresence = presences.findOne({ userId: this.userId });
@@ -459,6 +481,15 @@ Template.cardDetailsActionsPopup.helpers({
       !Meteor.user().isCommentOnly()
       !Meteor.user().isCommentOnly()
     );
     );
   },
   },
+
+  canModifyCardWorker() {
+    return (
+      Meteor.user() &&
+      Meteor.user().isBoardMember() &&
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
+    );
+  },
 });
 });
 
 
 Template.cardDetailsActionsPopup.events({
 Template.cardDetailsActionsPopup.events({
@@ -467,7 +498,12 @@ Template.cardDetailsActionsPopup.events({
   'click .js-labels': Popup.open('cardLabels'),
   'click .js-labels': Popup.open('cardLabels'),
   'click .js-attachments': Popup.open('cardAttachments'),
   'click .js-attachments': Popup.open('cardAttachments'),
   'click .js-custom-fields': Popup.open('cardCustomFields'),
   'click .js-custom-fields': Popup.open('cardCustomFields'),
-  'click .js-received-date': Popup.open('editCardReceivedDate'),
+  'click .js-received-date'(event) {
+    event.preventDefault();
+    if (!Meteor.user().isWorker) {
+      Popup.open('editCardReceivedDate');
+    }
+  },
   'click .js-start-date': Popup.open('editCardStartDate'),
   'click .js-start-date': Popup.open('editCardStartDate'),
   'click .js-due-date': Popup.open('editCardDueDate'),
   'click .js-due-date': Popup.open('editCardDueDate'),
   'click .js-end-date': Popup.open('editCardEndDate'),
   'click .js-end-date': Popup.open('editCardEndDate'),
@@ -879,6 +915,12 @@ Template.cardAssigneesPopup.events({
     card.toggleAssignee(assigneeId);
     card.toggleAssignee(assigneeId);
     event.preventDefault();
     event.preventDefault();
   },
   },
+  'click .js-select-assigneeWorker'(event) {
+    const card = Cards.findOne(Session.get('currentCard'));
+    const assigneeId = currentUser._id;
+    card.toggleAssignee(assigneeId);
+    event.preventDefault();
+  },
 });
 });
 
 
 Template.cardAssigneesPopup.helpers({
 Template.cardAssigneesPopup.helpers({

+ 3 - 1
client/components/cards/checklists.jade

@@ -1,5 +1,7 @@
 template(name="checklists")
 template(name="checklists")
-  h3 {{_ 'checklists'}}
+  h3
+    i.fa.fa-check
+    | {{_ 'checklists'}}
   if toggleDeleteDialog.get
   if toggleDeleteDialog.get
     .board-overlay#card-details-overlay
     .board-overlay#card-details-overlay
     +checklistDeleteDialog(checklist = checklistToDelete)
     +checklistDeleteDialog(checklist = checklistToDelete)

+ 6 - 3
client/components/cards/checklists.js

@@ -67,7 +67,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 }).register('checklistDetail');
 }).register('checklistDetail');
@@ -120,7 +121,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -228,7 +230,8 @@ Template.checklistItemDetail.helpers({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 });
 });

+ 6 - 10
client/components/cards/minicard.js

@@ -36,24 +36,20 @@ Template.minicard.helpers({
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).showDesktopDragHandles;
       return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
     } else {
     } else {
-      if (cookies.has('showDesktopDragHandles')) {
-        return true;
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
   hiddenMinicardLabelText() {
   hiddenMinicardLabelText() {
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).hiddenMinicardLabelText;
       return (currentUser.profile || {}).hiddenMinicardLabelText;
+    } else if (cookies.has('hiddenMinicardLabelText')) {
+      return true;
     } else {
     } else {
-      if (cookies.has('hiddenMinicardLabelText')) {
-        return true;
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
 });
 });

+ 3 - 1
client/components/cards/subtasks.jade

@@ -1,5 +1,7 @@
 template(name="subtasks")
 template(name="subtasks")
-  h3 {{_ 'subtasks'}}
+  h3
+    i.fa.fa-sitemap
+    | {{_ 'subtasks'}}
   if toggleDeleteDialog.get
   if toggleDeleteDialog.get
     .board-overlay#card-details-overlay
     .board-overlay#card-details-overlay
     +subtaskDeleteDialog(subtask = subtaskToDelete)
     +subtaskDeleteDialog(subtask = subtaskToDelete)

+ 6 - 3
client/components/cards/subtasks.js

@@ -3,7 +3,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 }).register('subtaskDetail');
 }).register('subtaskDetail');
@@ -55,7 +56,8 @@ BlazeComponent.extendComponent({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -154,7 +156,8 @@ Template.subtaskItemDetail.helpers({
     return (
     return (
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 });
 });

+ 3 - 5
client/components/lists/list.js

@@ -176,12 +176,10 @@ Template.list.helpers({
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).showDesktopDragHandles;
       return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
     } else {
     } else {
-      if (cookies.has('showDesktopDragHandles')) {
-        return true;
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
 });
 });

+ 2 - 1
client/components/lists/listBody.js

@@ -189,7 +189,8 @@ BlazeComponent.extendComponent({
       !this.reachedWipLimit() &&
       !this.reachedWipLimit() &&
       Meteor.user() &&
       Meteor.user() &&
       Meteor.user().isBoardMember() &&
       Meteor.user().isBoardMember() &&
-      !Meteor.user().isCommentOnly()
+      !Meteor.user().isCommentOnly() &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 

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

@@ -56,25 +56,47 @@ template(name="editListTitleForm")
 
 
 template(name="listActionPopup")
 template(name="listActionPopup")
   ul.pop-over-list
   ul.pop-over-list
-    li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
+    li
+      a.js-toggle-watch-list
+        if isWatching
+          i.fa.fa-eye
+          |  {{_ 'unwatch'}}
+        else
+          i.fa.fa-eye-slash
+          |  {{_ 'watch'}}
   unless currentUser.isCommentOnly
   unless currentUser.isCommentOnly
-    hr
-    ul.pop-over-list
-      li: a.js-set-color-list {{_ 'set-color-list'}}
-    hr
+    unless currentUser.isWorker
+      ul.pop-over-list
+        li
+          a.js-set-color-list
+            i.fa.fa-paint-brush
+            | {{_ 'set-color-list'}}
     ul.pop-over-list
     ul.pop-over-list
       if cards.count
       if cards.count
-        li: a.js-select-cards {{_ 'list-select-cards'}}
-        hr
+        li
+          a.js-select-cards
+            i.fa.fa-check-square
+            | {{_ 'list-select-cards'}}
     if currentUser.isBoardAdmin
     if currentUser.isBoardAdmin
       ul.pop-over-list
       ul.pop-over-list
-        li: a.js-set-wip-limit {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
+        li
+          a.js-set-wip-limit
+            i.fa.fa-ban
+            | {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
+    unless currentUser.isWorker
       hr
       hr
-    ul.pop-over-list
-      li: a.js-close-list {{_ 'archive-list'}}
+      ul.pop-over-list
+        li
+          a.js-close-list
+            i.fa.fa-arrow-right
+            i.fa.fa-archive
+            | {{_ 'archive-list'}}
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-more {{_ 'listMorePopup-title'}}
+      li
+        a.js-more
+          i.fa.fa-link
+          | {{_ 'listMorePopup-title'}}
 
 
 template(name="boardLists")
 template(name="boardLists")
   ul.pop-over-list
   ul.pop-over-list
@@ -94,7 +116,8 @@ template(name="listMorePopup")
       input.inline-input(type="text" readonly value="{{ rootUrl }}")
       input.inline-input(type="text" readonly value="{{ rootUrl }}")
     | {{_ 'added'}}
     | {{_ 'added'}}
     span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
     span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
-    a.js-delete {{_ 'delete'}}
+    unless currentUser.isWorker
+      a.js-delete {{_ 'delete'}}
 
 
 template(name="listDeletePopup")
 template(name="listDeletePopup")
   p {{_ "list-delete-pop"}}
   p {{_ "list-delete-pop"}}

+ 7 - 8
client/components/lists/listHeader.js

@@ -9,9 +9,10 @@ BlazeComponent.extendComponent({
   canSeeAddCard() {
   canSeeAddCard() {
     const list = Template.currentData();
     const list = Template.currentData();
     return (
     return (
-      !list.getWipLimit('enabled') ||
-      list.getWipLimit('soft') ||
-      !this.reachedWipLimit()
+      (!list.getWipLimit('enabled') ||
+        list.getWipLimit('soft') ||
+        !this.reachedWipLimit()) &&
+      !Meteor.user().isWorker()
     );
     );
   },
   },
 
 
@@ -109,12 +110,10 @@ Template.listHeader.helpers({
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).showDesktopDragHandles;
       return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
     } else {
     } else {
-      if (cookies.has('showDesktopDragHandles')) {
-        return true;
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
 });
 });

+ 6 - 2
client/components/settings/informationBody.jade

@@ -4,12 +4,16 @@ template(name='information')
       | {{_ 'error-notAuthorized'}}
       | {{_ 'error-notAuthorized'}}
     else
     else
       .content-title
       .content-title
-        span {{_ 'info'}}
+        span
+          i.fa.fa-info-circle
+          | {{_ 'info'}}
       .content-body
       .content-body
         .side-menu
         .side-menu
           ul
           ul
             li.active
             li.active
-              a.js-setting-menu(data-id="information-display") {{_ 'info'}}
+              a.js-setting-menu(data-id="information-display")
+                i.fa.fa-info-circle
+                | {{_ 'info'}}
         .main-body
         .main-body
           +statistics
           +statistics
 
 

+ 10 - 3
client/components/settings/peopleBody.jade

@@ -5,16 +5,22 @@ template(name="people")
     else
     else
       .content-title.ext-box
       .content-title.ext-box
         .ext-box-left
         .ext-box-left
-          span {{_ 'people'}}
+          span
+            i.fa.fa-users
+            | {{_ 'people'}}
           input#searchInput(placeholder="{{_ 'search'}}")
           input#searchInput(placeholder="{{_ 'search'}}")
-          button#searchButton {{_ 'search'}}
+          button#searchButton
+            i.fa.fa-search
+            | {{_ 'search'}}
         .ext-box-right
         .ext-box-right
           span {{_ 'people-number'}} #{peopleNumber}
           span {{_ 'people-number'}} #{peopleNumber}
       .content-body
       .content-body
         .side-menu
         .side-menu
           ul
           ul
             li.active
             li.active
-              a.js-setting-menu(data-id="people-setting") {{_ 'people'}}
+              a.js-setting-menu(data-id="people-setting")
+                i.fa.fa-users
+                | {{_ 'people'}}
         .main-body
         .main-body
           if loading.get
           if loading.get
             +spinner
             +spinner
@@ -90,6 +96,7 @@ template(name="peopleRow")
       td {{_ userData.authenticationMethod }}
       td {{_ userData.authenticationMethod }}
     td
     td
       a.edit-user
       a.edit-user
+        i.fa.fa-edit
         | {{_ 'edit'}}
         | {{_ 'edit'}}
 
 
 template(name="editUserPopup")
 template(name="editUserPopup")

+ 1 - 1
client/components/settings/peopleBody.styl

@@ -33,7 +33,7 @@ table
     padding: 0;
     padding: 0;
 
 
   button
   button
-    min-width: 60px;
+    min-width: 90px;
 
 
 .content-wrapper
 .content-wrapper
   margin-top: 10px
   margin-top: 10px

+ 19 - 6
client/components/settings/settingBody.jade

@@ -4,22 +4,35 @@ template(name="setting")
       | {{_ 'error-notAuthorized'}}
       | {{_ 'error-notAuthorized'}}
     else
     else
       .content-title
       .content-title
+        i.fa.fa-cog
         span {{_ 'settings'}}
         span {{_ 'settings'}}
       .content-body
       .content-body
         .side-menu
         .side-menu
           ul
           ul
             li.active
             li.active
-              a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}}
+              a.js-setting-menu(data-id="registration-setting")
+                i.fa.fa-sign-in
+                | {{_ 'registration'}}
             li
             li
-              a.js-setting-menu(data-id="email-setting") {{_ 'email'}}
+              a.js-setting-menu(data-id="email-setting")
+                i.fa.fa-envelope
+                | {{_ 'email'}}
             li
             li
-              a.js-setting-menu(data-id="account-setting") {{_ 'accounts'}}
+              a.js-setting-menu(data-id="account-setting")
+                i.fa.fa-users
+                | {{_ 'accounts'}}
             li
             li
-              a.js-setting-menu(data-id="announcement-setting") {{_ 'admin-announcement'}}
+              a.js-setting-menu(data-id="announcement-setting")
+                i.fa.fa-bullhorn
+                | {{_ 'admin-announcement'}}
             li
             li
-              a.js-setting-menu(data-id="layout-setting") {{_ 'layout'}}
+              a.js-setting-menu(data-id="layout-setting")
+                i.fa.fa-object-group
+                | {{_ 'layout'}}
             li
             li
-              a.js-setting-menu(data-id="webhook-setting") {{_ 'global-webhook'}}
+              a.js-setting-menu(data-id="webhook-setting")
+                i.fa.fa-globe
+                | {{_ 'global-webhook'}}
         .main-body
         .main-body
           if loading.get
           if loading.get
             +spinner
             +spinner

+ 4 - 1
client/components/settings/settingBody.styl

@@ -41,15 +41,18 @@
           &:hover
           &:hover
             background #fff
             background #fff
             box-shadow 0 1px 2px rgba(0,0,0,0.15);
             box-shadow 0 1px 2px rgba(0,0,0,0.15);
+
           a
           a
             @extends .flex
             @extends .flex
             padding: 1rem 0 1rem 1rem
             padding: 1rem 0 1rem 1rem
             width: 100% - 5rem
             width: 100% - 5rem
 
 
-
             span
             span
               font-size: 13px
               font-size: 13px
 
 
+            i
+              margin-right: 20px
+
     .main-body
     .main-body
       padding: 0.1em 1em
       padding: 0.1em 1em
       -webkit-user-select: text // Safari 3.1+
       -webkit-user-select: text // Safari 3.1+

+ 78 - 27
client/components/sidebar/sidebar.jade

@@ -37,11 +37,12 @@ template(name='homeSidebar')
 template(name="membersWidget")
 template(name="membersWidget")
   .board-widget.board-widget-members
   .board-widget.board-widget-members
     h3
     h3
-      i.fa.fa-user
+      i.fa.fa-users
       | {{_ 'members'}}
       | {{_ 'members'}}
       unless currentUser.isCommentOnly
       unless currentUser.isCommentOnly
-        a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
-          i.board-header-btn-icon.fa.fa-cog
+        unless currentUser.isWorker
+          a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
+            i.board-header-btn-icon.fa.fa-cog
 
 
     .board-widget-content
     .board-widget-content
       each currentBoard.activeMembers
       each currentBoard.activeMembers
@@ -130,7 +131,9 @@ template(name="chooseBoardSource")
 
 
 template(name="archiveBoardPopup")
 template(name="archiveBoardPopup")
   p {{_ 'close-board-pop'}}
   p {{_ 'close-board-pop'}}
-  button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
+  button.js-confirm.negate.full(type="submit")
+    i.fa.fa-archive
+    | {{_ 'archive'}}
 
 
 template(name="outgoingWebhooksPopup")
 template(name="outgoingWebhooksPopup")
   each integrations
   each integrations
@@ -162,38 +165,80 @@ template(name="outgoingWebhooksPopup")
 
 
 template(name="boardMenuPopup")
 template(name="boardMenuPopup")
   ul.pop-over-list
   ul.pop-over-list
-    li: a.js-custom-fields {{_ 'custom-fields'}}
-    li: a.js-open-archives {{_ 'archived-items'}}
+    if isNotWorker
+      li: a.js-custom-fields {{_ 'custom-fields'}}
+      li
+        a.js-open-archives
+          i.fa.fa-archive
+          | {{_ 'archived-items'}}
     if currentUser.isBoardAdmin
     if currentUser.isBoardAdmin
-      li: a.js-change-board-color {{_ 'board-change-color'}}
+      li
+        a.js-change-board-color
+          i.fa.fa-paint-brush
+          | {{_ 'board-change-color'}}
+
     //-
     //-
       XXX Language should be handled by sandstorm, but for now display a
       XXX Language should be handled by sandstorm, but for now display a
       language selection link in the board menu. This link is normally present
       language selection link in the board menu. This link is normally present
       in the header bar that is not displayed on sandstorm.
       in the header bar that is not displayed on sandstorm.
     if isSandstorm
     if isSandstorm
-      li: a.js-change-language {{_ 'language'}}
+      li
+        a.js-change-language
+          i.fa.fa-flag
+          | {{_ 'language'}}
   unless isSandstorm
   unless isSandstorm
     if currentUser.isBoardAdmin
     if currentUser.isBoardAdmin
       hr
       hr
       ul.pop-over-list
       ul.pop-over-list
-        li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
-        unless currentBoard.isTemplatesBoard
-          li: a.js-archive-board {{_ 'archive-board'}}
-        li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
-      hr
-      ul.pop-over-list
-        li: a.js-subtask-settings {{_ 'subtask-settings'}}
+        li
+          a(href="{{exportUrl}}", download="{{exportFilename}}")
+            i.fa.fa-share-alt
+            | {{_ 'export-board'}}
+        li
+          a.js-outgoing-webhooks
+            i.fa.fa-globe
+            | {{_ 'outgoing-webhooks'}}
+        li
+          a.js-subtask-settings
+            i.fa.fa-sitemap
+            | {{_ 'subtask-settings'}}
+      unless currentBoard.isTemplatesBoard
+        hr
+        ul.pop-over-list
+          li
+            a.js-archive-board
+              i.fa.fa-arrow-right
+              i.fa.fa-archive
+              | {{_ 'archive-board'}}
 
 
   if isSandstorm
   if isSandstorm
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
-      li: a.js-import-board {{_ 'import-board-c'}}
-      li: a.js-archive-board {{_ 'archive-board'}}
-      li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
+      li
+        a(href="{{exportUrl}}", download="{{exportFilename}}")
+          i.fa.fa-share-alt
+          i.fa.fa-sign-out
+          | {{_ 'export-board'}}
+      li
+        a.js-import-board
+          i.fa.fa-share-alt
+          i.fa.fa-sign-in
+          | {{_ 'import-board-c'}}
+      li
+        a.js-archive-board
+          i.fa.fa-arrow-right
+          i.fa.fa-archive
+          | {{_ 'archive-board'}}
+      li
+        a.js-outgoing-webhooks
+          i.fa.fa-globe
+          | {{_ 'outgoing-webhooks'}}
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-subtask-settings {{_ 'subtask-settings'}}
+      li
+        a.js-subtask-settings
+          i.fa.fa-sitemap
+          | {{_ 'subtask-settings'}}
 
 
 template(name="labelsWidget")
 template(name="labelsWidget")
   .board-widget.board-widget-labels
   .board-widget.board-widget-labels
@@ -203,7 +248,7 @@ template(name="labelsWidget")
     .board-widget-content
     .board-widget-content
       each currentBoard.labels
       each currentBoard.labels
           a.card-label(class="card-label-{{color}}"
           a.card-label(class="card-label-{{color}}"
-            class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}")
+            class="{{#if currentUser.isNotCommentOnly}}{{#if currentUser.isNotWorker}}js-label{{/if}}{{/if}}")
             span.card-label-name
             span.card-label-name
               +viewer
               +viewer
                 = name
                 = name
@@ -232,12 +277,12 @@ template(name="memberPopup")
           a.js-change-role
           a.js-change-role
             | {{_ 'change-permissions'}}
             | {{_ 'change-permissions'}}
             span.quiet (#{memberType})
             span.quiet (#{memberType})
-      li
-        if $eq currentUser._id userId
-          a.js-leave-member {{_ 'leave-board'}}
-        else if currentUser.isBoardAdmin
-          a.js-remove-member {{_ 'remove-from-board'}}
-
+      unless currentUser.isWorker
+        li
+          if $eq currentUser._id userId
+            a.js-leave-member {{_ 'leave-board'}}
+          else if currentUser.isBoardAdmin
+            a.js-remove-member {{_ 'remove-from-board'}}
 
 
 template(name="removeMemberPopup")
 template(name="removeMemberPopup")
   p {{_ 'remove-member-pop' name=user.profile.fullname username=user.username boardTitle=board.title}}
   p {{_ 'remove-member-pop' name=user.profile.fullname username=user.username boardTitle=board.title}}
@@ -301,6 +346,12 @@ template(name="changePermissionsPopup")
         if isCommentOnly
         if isCommentOnly
           i.fa.fa-check
           i.fa.fa-check
         span.sub-name {{_ 'comment-only-desc'}}
         span.sub-name {{_ 'comment-only-desc'}}
+    li
+      a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
+        | {{_ 'worker'}}
+        if isWorker
+          i.fa.fa-check
+        span.sub-name {{_ 'worker-desc'}}
   if isLastAdmin
   if isLastAdmin
     hr
     hr
     p.quiet.bottom {{_ 'last-admin-desc'}}
     p.quiet.bottom {{_ 'last-admin-desc'}}

+ 29 - 12
client/components/sidebar/sidebar.js

@@ -112,12 +112,10 @@ BlazeComponent.extendComponent({
           currentUser = Meteor.user();
           currentUser = Meteor.user();
           if (currentUser) {
           if (currentUser) {
             Meteor.call('toggleMinicardLabelText');
             Meteor.call('toggleMinicardLabelText');
+          } else if (cookies.has('hiddenMinicardLabelText')) {
+            cookies.remove('hiddenMinicardLabelText');
           } else {
           } else {
-            if (cookies.has('hiddenMinicardLabelText')) {
-              cookies.remove('hiddenMinicardLabelText');
-            } else {
-              cookies.set('hiddenMinicardLabelText', 'true');
-            }
+            cookies.set('hiddenMinicardLabelText', 'true');
           }
           }
         },
         },
         'click .js-shortcuts'() {
         'click .js-shortcuts'() {
@@ -135,12 +133,10 @@ Template.homeSidebar.helpers({
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).hiddenMinicardLabelText;
       return (currentUser.profile || {}).hiddenMinicardLabelText;
+    } else if (cookies.has('hiddenMinicardLabelText')) {
+      return true;
     } else {
     } else {
-      if (cookies.has('hiddenMinicardLabelText')) {
-        return true;
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
 });
 });
@@ -165,10 +161,13 @@ Template.memberPopup.helpers({
       const currentBoard = Boards.findOne(Session.get('currentBoard'));
       const currentBoard = Boards.findOne(Session.get('currentBoard'));
       const commentOnly = currentBoard.hasCommentOnly(this.userId);
       const commentOnly = currentBoard.hasCommentOnly(this.userId);
       const noComments = currentBoard.hasNoComments(this.userId);
       const noComments = currentBoard.hasNoComments(this.userId);
+      const worker = currentBoard.hasWorker(this.userId);
       if (commentOnly) {
       if (commentOnly) {
         return TAPi18n.__('comment-only').toLowerCase();
         return TAPi18n.__('comment-only').toLowerCase();
       } else if (noComments) {
       } else if (noComments) {
         return TAPi18n.__('no-comments').toLowerCase();
         return TAPi18n.__('no-comments').toLowerCase();
+      } else if (worker) {
+        return TAPi18n.__('worker').toLowerCase();
       } else {
       } else {
         return TAPi18n.__(type).toLowerCase();
         return TAPi18n.__(type).toLowerCase();
       }
       }
@@ -271,6 +270,14 @@ Template.membersWidget.helpers({
     const user = Meteor.user();
     const user = Meteor.user();
     return user && user.isInvitedTo(Session.get('currentBoard'));
     return user && user.isInvitedTo(Session.get('currentBoard'));
   },
   },
+  isWorker() {
+    const user = Meteor.user();
+    if (user) {
+      return Meteor.call(Boards.hasWorker(user.memberId));
+    } else {
+      return false;
+    }
+  },
 });
 });
 
 
 Template.membersWidget.events({
 Template.membersWidget.events({
@@ -648,7 +655,7 @@ BlazeComponent.extendComponent({
 }).register('addMemberPopup');
 }).register('addMemberPopup');
 
 
 Template.changePermissionsPopup.events({
 Template.changePermissionsPopup.events({
-  'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only'(
+  'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
     event,
     event,
   ) {
   ) {
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
@@ -658,11 +665,13 @@ Template.changePermissionsPopup.events({
       'js-set-comment-only',
       'js-set-comment-only',
     );
     );
     const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
     const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
+    const isWorker = $(event.currentTarget).hasClass('js-set-worker');
     currentBoard.setMemberPermission(
     currentBoard.setMemberPermission(
       memberId,
       memberId,
       isAdmin,
       isAdmin,
       isNoComments,
       isNoComments,
       isCommentOnly,
       isCommentOnly,
+      isWorker,
     );
     );
     Popup.back(1);
     Popup.back(1);
   },
   },
@@ -679,7 +688,8 @@ Template.changePermissionsPopup.helpers({
     return (
     return (
       !currentBoard.hasAdmin(this.userId) &&
       !currentBoard.hasAdmin(this.userId) &&
       !currentBoard.hasNoComments(this.userId) &&
       !currentBoard.hasNoComments(this.userId) &&
-      !currentBoard.hasCommentOnly(this.userId)
+      !currentBoard.hasCommentOnly(this.userId) &&
+      !currentBoard.hasWorker(this.userId)
     );
     );
   },
   },
 
 
@@ -699,6 +709,13 @@ Template.changePermissionsPopup.helpers({
     );
     );
   },
   },
 
 
+  isWorker() {
+    const currentBoard = Boards.findOne(Session.get('currentBoard'));
+    return (
+      !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
+    );
+  },
+
   isLastAdmin() {
   isLastAdmin() {
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     const currentBoard = Boards.findOne(Session.get('currentBoard'));
     return (
     return (

+ 30 - 24
client/components/sidebar/sidebarArchives.jade

@@ -2,54 +2,60 @@ template(name="archivesSidebar")
   if isArchiveReady.get
   if isArchiveReady.get
     +basicTabs(tabs=tabs)
     +basicTabs(tabs=tabs)
       +tabContent(slug="cards")
       +tabContent(slug="cards")
-        p.quiet
-          a.js-restore-all-cards {{_ 'restore-all'}}
-          | -
-          a.js-delete-all-cards {{_ 'delete-all'}}
+        unless isWorker
+          p.quiet
+            a.js-restore-all-cards {{_ 'restore-all'}}
+            | -
+            a.js-delete-all-cards {{_ 'delete-all'}}
         each archivedCards
         each archivedCards
           .minicard-wrapper.js-minicard
           .minicard-wrapper.js-minicard
             +minicard(this)
             +minicard(this)
           if currentUser.isBoardMember
           if currentUser.isBoardMember
-            p.quiet
-              a.js-restore-card {{_ 'restore'}}
-              | -
-              a.js-delete-card {{_ 'delete'}}
+            unless isWorker
+              p.quiet
+                a.js-restore-card {{_ 'restore'}}
+                | -
+                a.js-delete-card {{_ 'delete'}}
             if cardIsInArchivedList
             if cardIsInArchivedList
               p.quiet.small ({{_ 'warn-list-archived'}})
               p.quiet.small ({{_ 'warn-list-archived'}})
         else
         else
           p.no-items-message {{_ 'no-archived-cards'}}
           p.no-items-message {{_ 'no-archived-cards'}}
 
 
       +tabContent(slug="lists")
       +tabContent(slug="lists")
-        p.quiet
-          a.js-restore-all-lists {{_ 'restore-all'}}
-          | -
-          a.js-delete-all-lists {{_ 'delete-all'}}
+        unless isWorker
+          p.quiet
+            a.js-restore-all-lists {{_ 'restore-all'}}
+            | -
+            a.js-delete-all-lists {{_ 'delete-all'}}
         ul.archived-lists
         ul.archived-lists
           each archivedLists
           each archivedLists
             li.archived-lists-item
             li.archived-lists-item
               = title
               = title
               if currentUser.isBoardMember
               if currentUser.isBoardMember
-                p.quiet
-                  a.js-restore-list {{_ 'restore'}}
-                  | -
-                  a.js-delete-list {{_ 'delete'}}
+                unless isWorker
+                  p.quiet
+                    a.js-restore-list {{_ 'restore'}}
+                    | -
+                    a.js-delete-list {{_ 'delete'}}
           else
           else
             li.no-items-message {{_ 'no-archived-lists'}}
             li.no-items-message {{_ 'no-archived-lists'}}
 
 
       +tabContent(slug="swimlanes")
       +tabContent(slug="swimlanes")
-        p.quiet
-          a.js-restore-all-swimlanes {{_ 'restore-all'}}
-          | -
-          a.js-delete-all-swimlanes {{_ 'delete-all'}}
+        unless isWorker
+          p.quiet
+            a.js-restore-all-swimlanes {{_ 'restore-all'}}
+            | -
+            a.js-delete-all-swimlanes {{_ 'delete-all'}}
         ul.archived-lists
         ul.archived-lists
           each archivedSwimlanes
           each archivedSwimlanes
             li.archived-lists-item
             li.archived-lists-item
               = title
               = title
               if currentUser.isBoardMember
               if currentUser.isBoardMember
-                p.quiet
-                  a.js-restore-swimlane {{_ 'restore'}}
-                  | -
-                  a.js-delete-swimlane {{_ 'delete'}}
+                unless isWorker
+                  p.quiet
+                    a.js-restore-swimlane {{_ 'restore'}}
+                    | -
+                    a.js-delete-swimlane {{_ 'delete'}}
           else
           else
             li.no-items-message {{_ 'no-archived-swimlanes'}}
             li.no-items-message {{_ 'no-archived-swimlanes'}}
   else
   else

+ 9 - 0
client/components/sidebar/sidebarArchives.js

@@ -139,3 +139,12 @@ BlazeComponent.extendComponent({
     ];
     ];
   },
   },
 }).register('archivesSidebar');
 }).register('archivesSidebar');
+
+Template.archivesSidebar.helpers({
+  isWorker() {
+    const currentBoard = Boards.findOne(Session.get('currentBoard'));
+    return (
+      !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
+    );
+  },
+});

+ 8 - 7
client/components/sidebar/sidebarFilters.jade

@@ -117,13 +117,14 @@ template(name="multiselectionSidebar")
               i.fa.fa-check
               i.fa.fa-check
             else if someSelectedElementHave 'member' _id
             else if someSelectedElementHave 'member' _id
               i.fa.fa-ellipsis-h
               i.fa.fa-ellipsis-h
-  hr
-  a.sidebar-btn.js-move-selection
-    i.fa.fa-share
-    span {{_ 'move-selection'}}
-  a.sidebar-btn.js-archive-selection
-    i.fa.fa-archive
-    span {{_ 'archive-selection'}}
+  unless currentUser.isWorker
+    hr
+    a.sidebar-btn.js-move-selection
+      i.fa.fa-share
+      span {{_ 'move-selection'}}
+    a.sidebar-btn.js-archive-selection
+      i.fa.fa-archive
+      span {{_ 'archive-selection'}}
 
 
 template(name="disambiguateMultiLabelPopup")
 template(name="disambiguateMultiLabelPopup")
   p {{_ 'what-to-do'}}
   p {{_ 'what-to-do'}}

+ 3 - 5
client/components/swimlanes/swimlaneHeader.js

@@ -35,12 +35,10 @@ Template.swimlaneHeader.helpers({
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).showDesktopDragHandles;
       return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
     } else {
     } else {
-      if (cookies.has('showDesktopDragHandles')) {
-        return true;
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
 });
 });

+ 17 - 16
client/components/swimlanes/swimlanes.jade

@@ -43,19 +43,20 @@ template(name="listsGroup")
           +addListForm
           +addListForm
 
 
 template(name="addListForm")
 template(name="addListForm")
-  .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
-    .list-header-add
-      +inlinedForm(autoclose=false)
-        input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
-          autocomplete="off" autofocus)
-        .edit-controls.clearfix
-          button.primary.confirm(type="submit") {{_ 'save'}}
-          unless currentBoard.isTemplatesBoard
-            unless currentBoard.isTemplateBoard
-              span.quiet
-                | {{_ 'or'}}
-                a.js-list-template {{_ 'template'}}
-      else
-        a.open-list-composer.js-open-inlined-form
-          i.fa.fa-plus
-          | {{_ 'add-list'}}
+  unless currentUser.isWorker
+    .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
+      .list-header-add
+        +inlinedForm(autoclose=false)
+          input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
+            autocomplete="off" autofocus)
+          .edit-controls.clearfix
+            button.primary.confirm(type="submit") {{_ 'save'}}
+            unless currentBoard.isTemplatesBoard
+              unless currentBoard.isTemplateBoard
+                span.quiet
+                  | {{_ 'or'}}
+                  a.js-list-template {{_ 'template'}}
+        else
+          a.open-list-composer.js-open-inlined-form
+            i.fa.fa-plus
+            | {{_ 'add-list'}}

+ 9 - 15
client/components/swimlanes/swimlanes.js

@@ -104,12 +104,10 @@ function initSortable(boardComponent, $listsDom) {
     if (currentUser) {
     if (currentUser) {
       showDesktopDragHandles = (currentUser.profile || {})
       showDesktopDragHandles = (currentUser.profile || {})
         .showDesktopDragHandles;
         .showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      showDesktopDragHandles = true;
     } else {
     } else {
-      if (cookies.has('showDesktopDragHandles')) {
-        showDesktopDragHandles = true;
-      } else {
-        showDesktopDragHandles = false;
-      }
+      showDesktopDragHandles = false;
     }
     }
 
 
     if (!Utils.isMiniScreen() && showDesktopDragHandles) {
     if (!Utils.isMiniScreen() && showDesktopDragHandles) {
@@ -182,12 +180,10 @@ BlazeComponent.extendComponent({
           if (currentUser) {
           if (currentUser) {
             showDesktopDragHandles = (currentUser.profile || {})
             showDesktopDragHandles = (currentUser.profile || {})
               .showDesktopDragHandles;
               .showDesktopDragHandles;
+          } else if (cookies.has('showDesktopDragHandles')) {
+            showDesktopDragHandles = true;
           } else {
           } else {
-            if (cookies.has('showDesktopDragHandles')) {
-              showDesktopDragHandles = true;
-            } else {
-              showDesktopDragHandles = false;
-            }
+            showDesktopDragHandles = false;
           }
           }
 
 
           const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
           const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
@@ -276,12 +272,10 @@ Template.swimlane.helpers({
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).showDesktopDragHandles;
       return (currentUser.profile || {}).showDesktopDragHandles;
+    } else if (cookies.has('showDesktopDragHandles')) {
+      return true;
     } else {
     } else {
-      if (cookies.has('showDesktopDragHandles')) {
-        return true;
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
   canSeeAddList() {
   canSeeAddList() {

+ 1 - 0
client/components/users/userAvatar.jade

@@ -73,6 +73,7 @@ template(name="cardMemberPopup")
         p.quiet @{{ user.username }}
         p.quiet @{{ user.username }}
     ul.pop-over-list
     ul.pop-over-list
       if currentUser.isNotCommentOnly
       if currentUser.isNotCommentOnly
+        if currentUser.isNotWorker
           li: a.js-remove-member {{_ 'remove-member-from-card'}}
           li: a.js-remove-member {{_ 'remove-member-from-card'}}
 
 
       if $eq currentUser._id user._id
       if $eq currentUser._id user._id

+ 47 - 18
client/components/users/userHeader.jade

@@ -13,21 +13,46 @@ template(name="headerUserBar")
 template(name="memberMenuPopup")
 template(name="memberMenuPopup")
   ul.pop-over-list
   ul.pop-over-list
     with currentUser
     with currentUser
-      li: a.js-edit-profile {{_ 'edit-profile'}}
-      li: a.js-change-settings {{_ 'change-settings'}}
-      li: a.js-change-avatar {{_ 'edit-avatar'}}
+      li
+        a.js-edit-profile
+          i.fa.fa-user
+          | {{_ 'edit-profile'}}
+      li
+        a.js-change-settings
+          i.fa.fa-cog
+          | {{_ 'change-settings'}}
+      li
+        a.js-change-avatar
+          i.fa.fa-picture-o
+          | {{_ 'edit-avatar'}}
       unless isSandstorm
       unless isSandstorm
-        li: a.js-change-password {{_ 'changePasswordPopup-title'}}
-        li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
+        li
+          a.js-change-password
+            i.fa.fa-key
+            | {{_ 'changePasswordPopup-title'}}
+        li
+          a.js-change-language
+            i.fa.fa-flag
+            | {{_ 'changeLanguagePopup-title'}}
     if currentUser.isAdmin
     if currentUser.isAdmin
-      li: a.js-go-setting(href="{{pathFor 'setting'}}") {{_ 'admin-panel'}}
-  hr
-  ul.pop-over-list
-    li: a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") {{_ 'templates'}}
+      li
+        a.js-go-setting(href="{{pathFor 'setting'}}")
+          i.fa.fa-lock
+          | {{_ 'admin-panel'}}
+  unless currentUser.isWorker
+    hr
+    ul.pop-over-list
+      li
+        a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
+          i.fa.fa-clone
+          | {{_ 'templates'}}
   unless isSandstorm
   unless isSandstorm
     hr
     hr
     ul.pop-over-list
     ul.pop-over-list
-      li: a.js-logout {{_ 'log-out'}}
+      li
+        a.js-logout
+          i.fa.fa-sign-out
+          | {{_ 'log-out'}}
 
 
 template(name="editProfilePopup")
 template(name="editProfilePopup")
   form
   form
@@ -75,21 +100,25 @@ template(name="changeSettingsPopup")
   ul.pop-over-list
   ul.pop-over-list
     li
     li
       a.js-toggle-system-messages
       a.js-toggle-system-messages
+        i.fa.fa-comments-o
         | {{_ 'hide-system-messages'}}
         | {{_ 'hide-system-messages'}}
         if hiddenSystemMessages
         if hiddenSystemMessages
           i.fa.fa-check
           i.fa.fa-check
     li
     li
       a.js-toggle-desktop-drag-handles
       a.js-toggle-desktop-drag-handles
+        i.fa.fa-arrows
         | {{_ 'show-desktop-drag-handles'}}
         | {{_ 'show-desktop-drag-handles'}}
         if showDesktopDragHandles
         if showDesktopDragHandles
           i.fa.fa-check
           i.fa.fa-check
-    li
-      label.bold
-        | {{_ 'show-cards-minimum-count'}}
-      input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
-      input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
-
+    unless currentUser.isWorker
+      li
+        label.bold
+          i.fa.fa-sort-numeric-asc
+          | {{_ 'show-cards-minimum-count'}}
+        input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
+        input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
 
 
 template(name="userDeletePopup")
 template(name="userDeletePopup")
-  p {{_ 'delete-user-confirm-popup'}}
-  button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
+  unless currentUser.isWorker
+    p {{_ 'delete-user-confirm-popup'}}
+    button.js-confirm.negate.full(type="submit") {{_ 'delete'}}

+ 21 - 3
client/components/users/userHeader.js

@@ -45,13 +45,31 @@ Template.memberMenuPopup.events({
 
 
 Template.editProfilePopup.helpers({
 Template.editProfilePopup.helpers({
   allowEmailChange() {
   allowEmailChange() {
-    return AccountSettings.findOne('accounts-allowEmailChange').booleanValue;
+    Meteor.call('AccountSettings.allowEmailChange', (_, result) => {
+      if (result) {
+        return true;
+      } else {
+        return false;
+      }
+    });
   },
   },
   allowUserNameChange() {
   allowUserNameChange() {
-    return AccountSettings.findOne('accounts-allowUserNameChange').booleanValue;
+    Meteor.call('AccountSettings.allowUserNameChange', (_, result) => {
+      if (result) {
+        return true;
+      } else {
+        return false;
+      }
+    });
   },
   },
   allowUserDelete() {
   allowUserDelete() {
-    return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
+    Meteor.call('AccountSettings.allowUserDelete', (_, result) => {
+      if (result) {
+        return true;
+      } else {
+        return false;
+      }
+    });
   },
   },
 });
 });
 
 

+ 18 - 22
client/lib/utils.js

@@ -24,18 +24,14 @@ Utils = {
     currentUser = Meteor.user();
     currentUser = Meteor.user();
     if (currentUser) {
     if (currentUser) {
       return (currentUser.profile || {}).boardView;
       return (currentUser.profile || {}).boardView;
+    } else if (cookies.get('boardView') === 'board-view-lists') {
+      return 'board-view-lists';
+    } else if (cookies.get('boardView') === 'board-view-swimlanes') {
+      return 'board-view-swimlanes';
+    } else if (cookies.get('boardView') === 'board-view-cal') {
+      return 'board-view-cal';
     } else {
     } else {
-      if (cookies.get('boardView') === 'board-view-lists') {
-        return 'board-view-lists';
-      } else if (
-        cookies.get('boardView') === 'board-view-swimlanes'
-      ) {
-        return 'board-view-swimlanes';
-      } else if (cookies.get('boardView') === 'board-view-cal') {
-        return 'board-view-cal';
-      } else {
-        return false;
-      }
+      return false;
     }
     }
   },
   },
 
 
@@ -43,8 +39,8 @@ Utils = {
   goBoardId(_id) {
   goBoardId(_id) {
     const board = Boards.findOne(_id);
     const board = Boards.findOne(_id);
     return (
     return (
-      board
-      && FlowRouter.go('board', {
+      board &&
+      FlowRouter.go('board', {
         id: board._id,
         id: board._id,
         slug: board.slug,
         slug: board.slug,
       })
       })
@@ -55,8 +51,8 @@ Utils = {
     const card = Cards.findOne(_id);
     const card = Cards.findOne(_id);
     const board = Boards.findOne(card.boardId);
     const board = Boards.findOne(card.boardId);
     return (
     return (
-      board
-      && FlowRouter.go('card', {
+      board &&
+      FlowRouter.go('card', {
         cardId: card._id,
         cardId: card._id,
         boardId: board._id,
         boardId: board._id,
         slug: board.slug,
         slug: board.slug,
@@ -227,8 +223,8 @@ Utils = {
       };
       };
 
 
       if (
       if (
-        'ontouchstart' in window
-        || (window.DocumentTouch && document instanceof window.DocumentTouch)
+        'ontouchstart' in window ||
+        (window.DocumentTouch && document instanceof window.DocumentTouch)
       ) {
       ) {
         return true;
         return true;
       }
       }
@@ -249,8 +245,8 @@ Utils = {
 
 
   calculateTouchDistance(touchA, touchB) {
   calculateTouchDistance(touchA, touchB) {
     return Math.sqrt(
     return Math.sqrt(
-      Math.pow(touchA.screenX - touchB.screenX, 2)
-        + Math.pow(touchA.screenY - touchB.screenY, 2),
+      Math.pow(touchA.screenX - touchB.screenX, 2) +
+        Math.pow(touchA.screenY - touchB.screenY, 2),
     );
     );
   },
   },
 
 
@@ -267,9 +263,9 @@ Utils = {
     });
     });
     $(document).on('touchend', selector, function(e) {
     $(document).on('touchend', selector, function(e) {
       if (
       if (
-        touchStart
-        && lastTouch
-        && Utils.calculateTouchDistance(touchStart, lastTouch) <= 20
+        touchStart &&
+        lastTouch &&
+        Utils.calculateTouchDistance(touchStart, lastTouch) <= 20
       ) {
       ) {
         e.preventDefault();
         e.preventDefault();
         const clickEvent = document.createEvent('MouseEvents');
         const clickEvent = document.createEvent('MouseEvents');

+ 12 - 0
models/accountSettings.js

@@ -82,4 +82,16 @@ if (Meteor.isServer) {
   });
   });
 }
 }
 
 
+AccountSettings.helpers({
+  allowEmailChange() {
+    return AccountSettings.findOne('accounts-allowEmailChange').booleanValue;
+  },
+  allowUserNameChange() {
+    return AccountSettings.findOne('accounts-allowUserNameChange').booleanValue;
+  },
+  allowUserDelete() {
+    return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
+  },
+});
+
 export default AccountSettings;
 export default AccountSettings;

+ 28 - 1
models/boards.js

@@ -185,6 +185,7 @@ Boards.attachSchema(
               isActive: true,
               isActive: true,
               isNoComments: false,
               isNoComments: false,
               isCommentOnly: false,
               isCommentOnly: false,
+              isWorker: false,
             },
             },
           ];
           ];
         }
         }
@@ -222,6 +223,13 @@ Boards.attachSchema(
       type: Boolean,
       type: Boolean,
       optional: true,
       optional: true,
     },
     },
+    'members.$.isWorker': {
+      /**
+       * Is the member only allowed to move card, assign himself to card and comment
+       */
+      type: Boolean,
+      optional: true,
+    },
     permission: {
     permission: {
       /**
       /**
        * visibility of the board
        * visibility of the board
@@ -538,6 +546,7 @@ Boards.helpers({
       isActive: true,
       isActive: true,
       isAdmin: false,
       isAdmin: false,
       isNoComments: true,
       isNoComments: true,
+      isWorker: false,
     });
     });
   },
   },
 
 
@@ -547,6 +556,17 @@ Boards.helpers({
       isActive: true,
       isActive: true,
       isAdmin: false,
       isAdmin: false,
       isCommentOnly: true,
       isCommentOnly: true,
+      isWorker: false,
+    });
+  },
+
+  hasWorker(memberId) {
+    return !!_.findWhere(this.members, {
+      userId: memberId,
+      isActive: true,
+      isAdmin: false,
+      isCommentOnly: false,
+      isWorker: true,
     });
     });
   },
   },
 
 
@@ -849,6 +869,7 @@ Boards.mutations({
           isActive: true,
           isActive: true,
           isNoComments: false,
           isNoComments: false,
           isCommentOnly: false,
           isCommentOnly: false,
+          isWorker: false,
         },
         },
       },
       },
     };
     };
@@ -881,6 +902,7 @@ Boards.mutations({
     isAdmin,
     isAdmin,
     isNoComments,
     isNoComments,
     isCommentOnly,
     isCommentOnly,
+    isWorker,
     currentUserId = Meteor.userId(),
     currentUserId = Meteor.userId(),
   ) {
   ) {
     const memberIndex = this.memberIndex(memberId);
     const memberIndex = this.memberIndex(memberId);
@@ -894,6 +916,7 @@ Boards.mutations({
         [`members.${memberIndex}.isAdmin`]: isAdmin,
         [`members.${memberIndex}.isAdmin`]: isAdmin,
         [`members.${memberIndex}.isNoComments`]: isNoComments,
         [`members.${memberIndex}.isNoComments`]: isNoComments,
         [`members.${memberIndex}.isCommentOnly`]: isCommentOnly,
         [`members.${memberIndex}.isCommentOnly`]: isCommentOnly,
+        [`members.${memberIndex}.isWorker`]: isWorker,
       },
       },
     };
     };
   },
   },
@@ -1281,6 +1304,7 @@ if (Meteor.isServer) {
    * @param {boolean} [isActive] is the board active (default true)
    * @param {boolean} [isActive] is the board active (default true)
    * @param {boolean} [isNoComments] disable comments (default false)
    * @param {boolean} [isNoComments] disable comments (default false)
    * @param {boolean} [isCommentOnly] only enable comments (default false)
    * @param {boolean} [isCommentOnly] only enable comments (default false)
+   * @param {boolean} [isWorker] only move cards, assign himself to card and comment (default false)
    * @param {string} [permission] "private" board <== Set to "public" if you
    * @param {string} [permission] "private" board <== Set to "public" if you
    *                 want public Wekan board
    *                 want public Wekan board
    * @param {string} [color] the color of the board
    * @param {string} [color] the color of the board
@@ -1300,6 +1324,7 @@ if (Meteor.isServer) {
             isActive: req.body.isActive || true,
             isActive: req.body.isActive || true,
             isNoComments: req.body.isNoComments || false,
             isNoComments: req.body.isNoComments || false,
             isCommentOnly: req.body.isCommentOnly || false,
             isCommentOnly: req.body.isCommentOnly || false,
+            isWorker: req.body.isWorker || false,
           },
           },
         ],
         ],
         permission: req.body.permission || 'private',
         permission: req.body.permission || 'private',
@@ -1403,6 +1428,7 @@ if (Meteor.isServer) {
    * @param {boolean} isAdmin admin capability
    * @param {boolean} isAdmin admin capability
    * @param {boolean} isNoComments NoComments capability
    * @param {boolean} isNoComments NoComments capability
    * @param {boolean} isCommentOnly CommentsOnly capability
    * @param {boolean} isCommentOnly CommentsOnly capability
+   * @param {boolean} isWorker Worker capability
    */
    */
   JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function(
   JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function(
     req,
     req,
@@ -1411,7 +1437,7 @@ if (Meteor.isServer) {
     try {
     try {
       const boardId = req.params.boardId;
       const boardId = req.params.boardId;
       const memberId = req.params.memberId;
       const memberId = req.params.memberId;
-      const { isAdmin, isNoComments, isCommentOnly } = req.body;
+      const { isAdmin, isNoComments, isCommentOnly, isWorker } = req.body;
       Authentication.checkBoardAccess(req.userId, boardId);
       Authentication.checkBoardAccess(req.userId, boardId);
       const board = Boards.findOne({ _id: boardId });
       const board = Boards.findOne({ _id: boardId });
       function isTrue(data) {
       function isTrue(data) {
@@ -1426,6 +1452,7 @@ if (Meteor.isServer) {
         isTrue(isAdmin),
         isTrue(isAdmin),
         isTrue(isNoComments),
         isTrue(isNoComments),
         isTrue(isCommentOnly),
         isTrue(isCommentOnly),
+        isTrue(isWorker),
         req.userId,
         req.userId,
       );
       );
 
 

+ 1 - 1
models/cards.js

@@ -2008,7 +2008,7 @@ if (Meteor.isServer) {
     const paramBoardId = req.params.boardId;
     const paramBoardId = req.params.boardId;
     // Check user has permission to add card to the board
     // Check user has permission to add card to the board
     const board = Boards.findOne({
     const board = Boards.findOne({
-      _id: paramBoardId
+      _id: paramBoardId,
     });
     });
     const addPermission = allowIsBoardMemberCommentOnly(req.userId, board);
     const addPermission = allowIsBoardMemberCommentOnly(req.userId, board);
     Authentication.checkAdminOrCondition(req.userId, addPermission);
     Authentication.checkAdminOrCondition(req.userId, addPermission);

+ 1 - 1
models/checklists.js

@@ -288,7 +288,7 @@ if (Meteor.isServer) {
       const paramBoardId = req.params.boardId;
       const paramBoardId = req.params.boardId;
       // Check user has permission to add checklist to the card
       // Check user has permission to add checklist to the card
       const board = Boards.findOne({
       const board = Boards.findOne({
-        _id: paramBoardId
+        _id: paramBoardId,
       });
       });
       const addPermission = allowIsBoardMemberCommentOnly(req.userId, board);
       const addPermission = allowIsBoardMemberCommentOnly(req.userId, board);
       Authentication.checkAdminOrCondition(req.userId, addPermission);
       Authentication.checkAdminOrCondition(req.userId, addPermission);

+ 10 - 0
models/users.js

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

+ 2 - 0
sandstorm.js

@@ -236,12 +236,14 @@ if (isSandstorm && Meteor.isServer) {
     const isAdmin = permissions.indexOf('configure') > -1;
     const isAdmin = permissions.indexOf('configure') > -1;
     const isCommentOnly = false;
     const isCommentOnly = false;
     const isNoComments = false;
     const isNoComments = false;
+    const isWorker = false;
     const permissionDoc = {
     const permissionDoc = {
       userId,
       userId,
       isActive,
       isActive,
       isAdmin,
       isAdmin,
       isNoComments,
       isNoComments,
       isCommentOnly,
       isCommentOnly,
+      isWorker,
     };
     };
 
 
     const boardMembers = Boards.findOne(sandstormBoard._id).members;
     const boardMembers = Boards.findOne(sandstormBoard._id).members;