Jelajahi Sumber

Merge branch 'feature-rules' of https://github.com/Angtrim/wekan into Angtrim-feature-rules

Lauri Ojansivu 6 tahun lalu
induk
melakukan
6673b79738
60 mengubah file dengan 3074 tambahan dan 259 penghapusan
  1. TEMPAT SAMPAH
      .DS_Store
  2. 1 1
      .meteor/packages
  3. 51 0
      client/components/activities/activities.jade
  4. 16 0
      client/components/activities/activities.js
  5. 9 0
      client/components/boards/boardHeader.jade
  6. 3 0
      client/components/boards/boardHeader.js
  7. 1 0
      client/components/forms/forms.styl
  8. 2 0
      client/components/lists/listBody.js
  9. 12 5
      client/components/main/layouts.jade
  10. 17 0
      client/components/main/layouts.styl
  11. TEMPAT SAMPAH
      client/components/rules/.DS_Store
  12. 46 0
      client/components/rules/actions/boardActions.jade
  13. 121 0
      client/components/rules/actions/boardActions.js
  14. 43 0
      client/components/rules/actions/cardActions.jade
  15. 119 0
      client/components/rules/actions/cardActions.js
  16. 51 0
      client/components/rules/actions/checklistActions.jade
  17. 128 0
      client/components/rules/actions/checklistActions.js
  18. 11 0
      client/components/rules/actions/mailActions.jade
  19. 35 0
      client/components/rules/actions/mailActions.js
  20. 18 0
      client/components/rules/ruleDetails.jade
  21. 36 0
      client/components/rules/ruleDetails.js
  22. 156 0
      client/components/rules/rules.styl
  23. 25 0
      client/components/rules/rulesActions.jade
  24. 58 0
      client/components/rules/rulesActions.js
  25. 27 0
      client/components/rules/rulesList.jade
  26. 15 0
      client/components/rules/rulesList.js
  27. 9 0
      client/components/rules/rulesMain.jade
  28. 58 0
      client/components/rules/rulesMain.js
  29. 21 0
      client/components/rules/rulesTriggers.jade
  30. 53 0
      client/components/rules/rulesTriggers.js
  31. 61 0
      client/components/rules/triggers/boardTriggers.jade
  32. 103 0
      client/components/rules/triggers/boardTriggers.js
  33. 79 0
      client/components/rules/triggers/cardTriggers.jade
  34. 130 0
      client/components/rules/triggers/cardTriggers.js
  35. 83 0
      client/components/rules/triggers/checklistTriggers.jade
  36. 146 0
      client/components/rules/triggers/checklistTriggers.js
  37. 13 1
      client/lib/modal.js
  38. 1 0
      client/lib/popup.js
  39. 25 4
      client/lib/utils.js
  40. 113 1
      i18n/en.i18n.json
  41. TEMPAT SAMPAH
      models/.DS_Store
  42. 19 0
      models/actions.js
  43. 11 0
      models/activities.js
  44. 7 0
      models/attachments.js
  45. 4 0
      models/boards.js
  46. 358 52
      models/cards.js
  47. 87 0
      models/checklistItems.js
  48. 23 0
      models/checklists.js
  49. 59 15
      models/export.js
  50. 1 1
      models/lists.js
  51. 49 0
      models/rules.js
  52. 58 0
      models/triggers.js
  53. 288 175
      models/wekanCreator.js
  54. TEMPAT SAMPAH
      server/.DS_Store
  55. TEMPAT SAMPAH
      server/lib/.DS_Store
  56. 4 4
      server/lib/utils.js
  57. 2 0
      server/notifications/email.js
  58. 18 0
      server/publications/rules.js
  59. 131 0
      server/rulesHelper.js
  60. 59 0
      server/triggersDef.js

TEMPAT SAMPAH
.DS_Store


+ 1 - 1
.meteor/packages

@@ -6,7 +6,7 @@
 meteor-base@1.2.0
 
 # Build system
-ecmascript@0.9.0
+ecmascript
 stylus@2.513.13
 standard-minifier-css@1.3.5
 standard-minifier-js@2.2.0

+ 51 - 0
client/components/activities/activities.jade

@@ -14,6 +14,9 @@ template(name="boardActivities")
       p.activity-desc
         +memberName(user=user)
 
+        if($eq activityType 'deleteAttachment')
+          | {{{_ 'activity-delete-attach' cardLink}}}.
+
         if($eq activityType 'addAttachment')
           | {{{_ 'activity-attached' attachmentLink cardLink}}}.
 
@@ -31,12 +34,28 @@ template(name="boardActivities")
           .activity-checklist(href="{{ card.absoluteUrl }}")
             +viewer
               = checklist.title
+        if($eq activityType 'removeChecklist')
+          | {{{_ 'activity-checklist-removed' cardLink}}}.
+
+        if($eq activityType 'checkedItem')
+          | {{{_ 'activity-checked-item' checkItem checklist.title cardLink}}}.
+
+        if($eq activityType 'uncheckedItem')
+          | {{{_ 'activity-unchecked-item' checkItem checklist.title cardLink}}}.
+
+        if($eq activityType 'checklistCompleted')
+          | {{{_ 'activity-checklist-completed' checklist.title cardLink}}}.
+
+        if($eq activityType 'checklistUncompleted')
+          | {{{_ 'activity-checklist-uncompleted' checklist.title cardLink}}}.
 
         if($eq activityType 'addChecklistItem')
           | {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
           .activity-checklist(href="{{ card.absoluteUrl }}")
             +viewer
               = checklistItem.title
+        if($eq activityType 'removedChecklistItem')
+          | {{{_ 'activity-checklist-item-removed' checklist.title cardLink}}}.
 
         if($eq activityType 'archivedCard')
           | {{{_ 'activity-archived' cardLink}}}.
@@ -89,6 +108,12 @@ template(name="boardActivities")
         if($eq activityType 'restoredCard')
           | {{{_ 'activity-sent' cardLink boardLabel}}}.
 
+        if($eq activityType 'addedLabel')
+          | {{{_ 'activity-added-label' lastLabel cardLink}}}.
+
+        if($eq activityType 'removedLabel')
+          | {{{_ 'activity-removed-label' lastLabel cardLink}}}.
+
         if($eq activityType 'unjoinMember')
           if($eq user._id member._id)
             | {{{_ 'activity-unjoined' cardLink}}}.
@@ -119,6 +144,28 @@ template(name="cardActivities")
             | {{{_ 'activity-removed' cardLabel memberLink}}}.
         if($eq activityType 'archivedCard')
           | {{_ 'activity-archived' cardLabel}}.
+
+        if($eq activityType 'addedLabel')
+          | {{{_ 'activity-added-label-card' lastLabel }}}.
+
+        if($eq activityType 'removedLabel')
+          | {{{_ 'activity-removed-label-card' lastLabel }}}.
+
+        if($eq activityType 'removeChecklist')
+          | {{{_ 'activity-checklist-removed' cardLabel}}}.
+
+        if($eq activityType 'checkedItem')
+          | {{{_ 'activity-checked-item-card' checkItem checklist.title }}}.
+
+        if($eq activityType 'uncheckedItem')
+          | {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}.
+
+        if($eq activityType 'checklistCompleted')
+          | {{{_ 'activity-checklist-completed-card' checklist.title }}}.
+
+        if($eq activityType 'checklistUncompleted')
+          | {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}.
+
         if($eq activityType 'restoredCard')
           | {{_ 'activity-sent' cardLabel boardLabel}}.
         if($eq activityType 'moveCard')
@@ -127,6 +174,10 @@ template(name="cardActivities")
           | {{{_ 'activity-attached' attachmentLink cardLabel}}}.
           if attachment.isImage
             img.attachment-image-preview(src=attachment.url)
+        if($eq activityType 'deleteAttachment')
+          | {{{_ 'activity-delete-attach'  cardLabel}}}.
+        if($eq activityType 'removedChecklist')
+          | {{{_ 'activity-checklist-removed' cardLabel}}}.
         if($eq activityType 'addChecklist')
           | {{{_ 'activity-checklist-added' cardLabel}}}.
           .activity-checklist

+ 16 - 0
client/components/activities/activities.js

@@ -49,6 +49,12 @@ BlazeComponent.extendComponent({
       this.loadNextPageLocked = true;
     }
   },
+  
+  checkItem(){
+    const checkItemId = this.currentData().checklistItemId;
+    const checkItem = ChecklistItems.findOne({_id:checkItemId});
+    return checkItem.title;
+  },
 
   boardLabel() {
     return TAPi18n.__('this-board');
@@ -66,6 +72,16 @@ BlazeComponent.extendComponent({
     }, card.title));
   },
 
+  lastLabel(){
+    const lastLabelId = this.currentData().labelId;
+    const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(lastLabelId);
+    if(lastLabel.name == undefined || lastLabel.name == ""){
+      return lastLabel.color;
+    }else{
+      return lastLabel.name;
+    }
+  },
+
   listLabel() {
     return this.currentData().list().title;
   },

+ 9 - 0
client/components/boards/boardHeader.jade

@@ -88,6 +88,10 @@ template(name="boardHeaderBar")
           a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
             i.fa.fa-times-thin
 
+      a.board-header-btn.js-open-rules-view(title="{{_ 'rules'}}")
+        i.fa.fa-magic
+        span {{_ 'rules'}}
+
       a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
         i.fa.fa-search
         span {{_ 'search'}}
@@ -290,6 +294,11 @@ template(name="boardChangeTitlePopup")
       textarea.js-board-desc= description
     input.primary.wide(type="submit" value="{{_ 'rename'}}")
 
+template(name="boardCreateRulePopup")
+  p {{_ 'close-board-pop'}}
+  button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
+
+
 template(name="archiveBoardPopup")
   p {{_ 'close-board-pop'}}
   button.js-confirm.negate.full(type="submit") {{_ 'archive'}}

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

@@ -108,6 +108,9 @@ BlazeComponent.extendComponent({
       'click .js-open-search-view'() {
         Sidebar.setView('search');
       },
+      'click .js-open-rules-view'() {
+        Modal.openWide('rulesMain');
+      },
       'click .js-multiselection-activate'() {
         const currentCard = Session.get('currentCard');
         MultiSelection.activate();

+ 1 - 0
client/components/forms/forms.styl

@@ -1,5 +1,6 @@
 @import 'nib'
 
+select,
 textarea,
 input:not([type=file]),
 button

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

@@ -98,6 +98,8 @@ BlazeComponent.extendComponent({
       evt.preventDefault();
       Utils.goBoardId(Session.get('currentBoard'));
     }
+    console.log(evt)
+
   },
 
   cardIsSelected() {

+ 12 - 5
client/components/main/layouts.jade

@@ -36,11 +36,18 @@ template(name="defaultLayout")
   if (Modal.isOpen)
     #modal
       .overlay
-      .modal-content
-        a.modal-close-btn.js-close-modal
-          i.fa.fa-times-thin
-        +Template.dynamic(template=Modal.getHeaderName)
-        +Template.dynamic(template=Modal.getTemplateName)
+      if (Modal.isWide)
+        .modal-content-wide.modal-container
+          a.modal-close-btn.js-close-modal
+            i.fa.fa-times-thin
+          +Template.dynamic(template=Modal.getHeaderName)
+          +Template.dynamic(template=Modal.getTemplateName)
+      else
+        .modal-content.modal-container
+          a.modal-close-btn.js-close-modal
+            i.fa.fa-times-thin
+          +Template.dynamic(template=Modal.getHeaderName)
+          +Template.dynamic(template=Modal.getTemplateName)
 
 template(name="notFound")
   +message(label='page-not-found')

+ 17 - 0
client/components/main/layouts.styl

@@ -61,6 +61,23 @@ body
       display: block
       float: right
       font-size: 24px
+  
+  .modal-content-wide
+    width: 800px
+    min-height: 0px
+    margin: 42px auto
+    padding: 12px
+    border-radius: 4px
+    background: darken(white, 13%)
+    z-index: 110
+
+    h2
+      margin-bottom: 25px
+
+    .modal-close-btn
+      display: block
+      float: right
+      font-size: 24px
 
 h1
   font-size: 22px

TEMPAT SAMPAH
client/components/rules/.DS_Store


+ 46 - 0
client/components/rules/actions/boardActions.jade

@@ -0,0 +1,46 @@
+template(name="boardActions")
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-move-card-to'}}}
+      div.trigger-dropdown
+        select(id="move-gen-action")
+          option(value="top") {{{_'r-top-of'}}}
+          option(value="bottom") {{{_'r-bottom-of'}}}
+      div.trigger-text 
+        | {{{_'r-its-list'}}}
+    div.trigger-button.js-add-gen-move-action.js-goto-rules
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-move-card-to'}}}
+      div.trigger-dropdown
+        select(id="move-spec-action")
+          option(value="top") {{{_'r-top-of'}}}
+          option(value="bottom") {{{_'r-bottom-of'}}}
+      div.trigger-text 
+        | {{{_'r-list'}}}
+      div.trigger-dropdown
+        input(id="listName",type=text,placeholder="{{{_'r-name'}}}")  
+    div.trigger-button.js-add-spec-move-action.js-goto-rules
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-dropdown
+        select(id="arch-action")
+          option(value="archive") {{{_'r-archive'}}}
+          option(value="unarchive") {{{_'r-unarchive'}}}
+      div.trigger-text 
+        | {{{_'r-card'}}}
+    div.trigger-button.js-add-arch-action.js-goto-rules
+      i.fa.fa-plus  
+
+
+   
+  
+
+
+

+ 121 - 0
client/components/rules/actions/boardActions.js

@@ -0,0 +1,121 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+
+  },
+
+  events() {
+    return [{
+      'click .js-add-spec-move-action' (event) {
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const actionSelected = this.find('#move-spec-action').value;
+        const listTitle = this.find('#listName').value;
+        const boardId = Session.get('currentBoard');
+        const desc = Utils.getTriggerActionDesc(event, this);
+        if (actionSelected == "top") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "moveCardToTop",
+            "listTitle": listTitle,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+        if (actionSelected == "bottom") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "moveCardToBottom",
+            "listTitle": listTitle,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+      },
+      'click .js-add-gen-move-action' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        const boardId = Session.get('currentBoard');
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const actionSelected = this.find('#move-gen-action').value;
+        if (actionSelected == "top") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "moveCardToTop",
+            "listTitle": "*",
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+        if (actionSelected == "bottom") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "moveCardToBottom",
+            "listTitle": "*",
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+      },
+      'click .js-add-arch-action' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        const boardId = Session.get('currentBoard');
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const actionSelected = this.find('#arch-action').value;
+        if (actionSelected == "archive") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "archive",
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+        if (actionSelected == "unarchive") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "unarchive",
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+      },
+    }];
+  },
+
+}).register('boardActions');

+ 43 - 0
client/components/rules/actions/cardActions.jade

@@ -0,0 +1,43 @@
+template(name="cardActions")
+  div.trigger-item
+    div.trigger-content
+      div.trigger-dropdown
+        select(id="label-action")
+          option(value="add") {{{_'r-add'}}}
+          option(value="remove") {{{_'r-remove'}}}
+      div.trigger-text 
+        | {{{_'r-label'}}}
+      div.trigger-dropdown
+        select(id="label-id")
+          each labels
+            option(value="#{_id}")
+              = name
+    div.trigger-button.js-add-label-action.js-goto-rules
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-dropdown
+        select(id="member-action")
+          option(value="add") {{{_'r-add'}}}
+          option(value="remove") {{{_'r-remove'}}}
+      div.trigger-text 
+        | {{{_'r-member'}}}
+      div.trigger-dropdown
+        input(id="member-name",type=text,placeholder="{{{_'r-name'}}}")  
+    div.trigger-button.js-add-member-action.js-goto-rules
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-remove-all'}}}
+    div.trigger-button.js-add-removeall-action.js-goto-rules
+      i.fa.fa-plus
+
+
+   
+  
+
+
+

+ 119 - 0
client/components/rules/actions/cardActions.js

@@ -0,0 +1,119 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.subscribe('allRules');
+  },
+
+  labels() {
+    const labels = Boards.findOne(Session.get('currentBoard')).labels;
+    for (let i = 0; i < labels.length; i++) {
+      if (labels[i].name == "" || labels[i].name == undefined) {
+        labels[i].name = labels[i].color.toUpperCase();
+      }
+    }
+    console.log(labels);
+    return labels;
+  },
+
+  events() {
+    return [{
+      'click .js-add-label-action' (event) {
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const actionSelected = this.find('#label-action').value;
+        const labelId = this.find('#label-id').value;
+        const boardId = Session.get('currentBoard');
+        const desc = Utils.getTriggerActionDesc(event, this);
+        if (actionSelected == "add") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "addLabel",
+            "labelId": labelId,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+        if (actionSelected == "remove") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "removeLabel",
+            "labelId": labelId,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+
+      },
+      'click .js-add-member-action' (event) {
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const actionSelected = this.find('#member-action').value;
+        const memberName = this.find('#member-name').value;
+        const boardId = Session.get('currentBoard');
+        const desc = Utils.getTriggerActionDesc(event, this);
+        if (actionSelected == "add") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "addMember",
+            "memberName": memberName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "remove") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "removeMember",
+            "memberName": memberName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+      },
+      'click .js-add-removeall-action' (event) {
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const triggerId = Triggers.insert(trigger);
+        const desc = Utils.getTriggerActionDesc(event, this);
+        const boardId = Session.get('currentBoard');
+        const actionId = Actions.insert({
+          actionType: "removeMember",
+          "memberName": "*",
+          "boardId": boardId,
+          "desc": desc
+        });
+        Rules.insert({
+          title: ruleName,
+          triggerId: triggerId,
+          actionId: actionId,
+          "boardId": boardId
+        });
+      },
+    }];
+  },
+
+}).register('cardActions');

+ 51 - 0
client/components/rules/actions/checklistActions.jade

@@ -0,0 +1,51 @@
+template(name="checklistActions")
+  div.trigger-item
+    div.trigger-content
+      div.trigger-dropdown
+        select(id="check-action")
+          option(value="add") {{{_'r-add'}}}
+          option(value="remove") {{{_'r-remove'}}}
+      div.trigger-text 
+        | {{{_'r-checklist'}}}
+      div.trigger-dropdown
+        input(id="checklist-name",type=text,placeholder="{{{_'r-name'}}}")  
+    div.trigger-button.js-add-checklist-action.js-goto-rules
+      i.fa.fa-plus  
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-dropdown
+        select(id="checkall-action")
+          option(value="check") {{{_'r-check-all'}}}
+          option(value="uncheck") {{{_'r-uncheck-all'}}}
+      div.trigger-text 
+        | {{{_'r-items-check'}}}
+      div.trigger-dropdown
+        input(id="checklist-name2",type=text,placeholder="{{{_'r-name'}}}")  
+    div.trigger-button.js-add-checkall-action.js-goto-rules
+      i.fa.fa-plus
+
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-dropdown
+        select(id="check-item-action")
+          option(value="check") {{{_'r-check'}}}
+          option(value="uncheck") {{{_'r-uncheck'}}}
+      div.trigger-text 
+        | {{{_'r-item'}}}
+      div.trigger-dropdown
+        input(id="checkitem-name",type=text,placeholder="{{{_'r-name'}}}")
+      div.trigger-text 
+        | {{{_'r-of-checklist'}}}
+      div.trigger-dropdown
+        input(id="checklist-name3",type=text,placeholder="{{{_'r-name'}}}")  
+    div.trigger-button.js-add-check-item-action.js-goto-rules
+      i.fa.fa-plus
+
+
+   
+  
+
+
+

+ 128 - 0
client/components/rules/actions/checklistActions.js

@@ -0,0 +1,128 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.subscribe('allRules');
+  },
+  events() {
+    return [{
+      'click .js-add-checklist-action' (event) {
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const actionSelected = this.find('#check-action').value;
+        const checklistName = this.find('#checklist-name').value;
+        const boardId = Session.get('currentBoard');
+        const desc = Utils.getTriggerActionDesc(event, this);
+        if (actionSelected == "add") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "addChecklist",
+            "checklistName": checklistName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+        if (actionSelected == "remove") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "removeChecklist",
+            "checklistName": checklistName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+
+      },
+      'click .js-add-checkall-action' (event) {
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const actionSelected = this.find('#checkall-action').value;
+        const checklistName = this.find('#checklist-name2').value;
+        const boardId = Session.get('currentBoard');
+        const desc = Utils.getTriggerActionDesc(event, this);
+        if (actionSelected == "check") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "checkAll",
+            "checklistName": checklistName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+        if (actionSelected == "uncheck") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "uncheckAll",
+            "checklistName": checklistName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+      },
+      'click .js-add-check-item-action' (event) {
+        const ruleName = this.data().ruleName.get();
+        const trigger = this.data().triggerVar.get();
+        const checkItemName = this.find("#checkitem-name");
+        const checklistName = this.find("#checklist-name3");
+        const actionSelected = this.find('#check-item-action').value;
+        const boardId = Session.get('currentBoard');
+        const desc = Utils.getTriggerActionDesc(event, this);
+        if (actionSelected == "check") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "checkItem",
+            "checklistName": checklistName,
+            "checkItemName": checkItemName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+        if (actionSelected == "uncheck") {
+          const triggerId = Triggers.insert(trigger);
+          const actionId = Actions.insert({
+            actionType: "uncheckItem",
+            "checklistName": checklistName,
+            "checkItemName": checkItemName,
+            "boardId": boardId,
+            "desc": desc
+          });
+          Rules.insert({
+            title: ruleName,
+            triggerId: triggerId,
+            actionId: actionId,
+            "boardId": boardId
+          });
+        }
+      },
+    }];
+  },
+
+}).register('checklistActions');

+ 11 - 0
client/components/rules/actions/mailActions.jade

@@ -0,0 +1,11 @@
+template(name="mailActions")
+  div.trigger-item.trigger-item-mail
+    div.trigger-content.trigger-content-mail
+      div.trigger-text.trigger-text-email
+        | {{{_'r-send-email'}}}
+      div.trigger-dropdown-mail
+        input(id="email-to",type=text,placeholder="{{{_'r-to'}}}")
+      input(id="email-subject",type=text,placeholder="{{{_'r-subject'}}}")
+      textarea(id="email-msg")  
+    div.trigger-button.trigger-button-email.js-mail-action.js-goto-rules
+      i.fa.fa-plus  

+ 35 - 0
client/components/rules/actions/mailActions.js

@@ -0,0 +1,35 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+
+  },
+
+  events() {
+    return [{
+      'click .js-mail-action' (event) {
+        const emailTo = this.find('#email-to').value;
+        const emailSubject = this.find('#email-subject').value;
+        const emailMsg = this.find('#email-msg').value;
+        const trigger = this.data().triggerVar.get();
+        const ruleName = this.data().ruleName.get();
+        const triggerId = Triggers.insert(trigger);
+        const boardId = Session.get('currentBoard');
+        const desc = Utils.getTriggerActionDesc(event, this);
+        const actionId = Actions.insert({
+          actionType: "sendEmail",
+          "emailTo": emailTo,
+          "emailSubject": emailSubject,
+          "emailMsg": emailMsg,
+          "boardId": boardId,
+          "desc": desc
+        });
+        Rules.insert({
+          title: ruleName,
+          triggerId: triggerId,
+          actionId: actionId,
+          "boardId": boardId
+        });
+      },
+    }];
+  },
+
+}).register('mailActions');

+ 18 - 0
client/components/rules/ruleDetails.jade

@@ -0,0 +1,18 @@
+template(name="ruleDetails")
+  .rules
+    h2
+      i.fa.fa-magic
+      | {{{_ 'r-rule-details' }}}
+    .triggers-content
+        .triggers-body
+            .triggers-main-body
+                div.trigger-item
+                    div.trigger-content
+                        div.trigger-text 
+                        = trigger 
+                div.trigger-item
+                    div.trigger-content
+                        div.trigger-text 
+                        = action 
+    
+    

+ 36 - 0
client/components/rules/ruleDetails.js

@@ -0,0 +1,36 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.subscribe('allRules');
+    this.subscribe('allTriggers');
+    this.subscribe('allActions');
+
+  },
+
+  trigger() {
+    const ruleId = this.data().ruleId;
+    const rule = Rules.findOne({
+      _id: ruleId.get()
+    });
+    const trigger = Triggers.findOne({
+      _id: rule.triggerId
+    });
+    console.log(trigger);
+    return trigger.description();
+  },
+  action() {
+    const ruleId = this.data().ruleId;
+    const rule = Rules.findOne({
+      _id: ruleId.get()
+    });
+    const action = Actions.findOne({
+      _id: rule.actionId
+    });
+    console.log(action);
+    return action.description();
+  },
+
+  events() {
+    return [{}];
+  },
+
+}).register('ruleDetails');

+ 156 - 0
client/components/rules/rules.styl

@@ -0,0 +1,156 @@
+.rules-list
+  overflow:hidden
+  overflow-y:scroll
+  max-height: 400px
+.rules-lists-item
+  display: block
+  position: relative
+  overflow: auto
+  p
+  	display: inline-block
+  	float: left
+  	margin: revert
+
+.rules-btns-group
+  position: absolute
+  right: 0
+  top: 50%
+  transform: translateY(-50%)
+  button
+  	margin: auto
+.rules-add
+  display: block
+  overflow: auto
+  margin-top: 15px
+  margin-bottom: 5px
+  input
+  	display: inline-block
+  	float: right
+  	margin: auto
+  	margin-right: 10px
+  button
+  	display: inline-block
+  	float: right
+  	margin: auto
+.flex
+  display: -webkit-box
+  display: -moz-box
+  display: -webkit-flex
+  display: -moz-flex
+  display: -ms-flexbox
+  display: flex
+
+
+
+.triggers-content
+  color: #727479
+  background: #dedede
+  .triggers-body
+    display flex
+    padding-top 15px
+    height 100%
+
+    .triggers-side-menu
+      background-color: #f7f7f7
+      border: 1px solid #f0f0f0
+      border-radius: 4px
+      height: intrinsic
+      box-shadow: inset -1px -1px 3px rgba(0,0,0,.05)
+
+      ul
+
+        li
+          margin: 0.1rem 0.2rem;
+          width:50px
+          height:50px
+          text-align:center
+          font-size: 25px
+          position: relative
+          
+          i
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            box-shadow: none
+            transform: translate(-50%,-50%);
+
+
+          &.active
+            background #fff
+            box-shadow 0 1px 2px rgba(0,0,0,0.15);
+
+          &:hover
+            background #fff
+            box-shadow 0 1px 2px rgba(0,0,0,0.15);
+          a
+            @extends .flex
+            padding: 1rem 0 1rem 1rem
+            width: 100% - 5rem
+
+
+            span
+              font-size: 13px
+    .triggers-main-body
+      padding: 0.1em 1em
+      width:100%
+      .trigger-item
+        overflow:auto
+        padding:10px
+        height:40px
+        margin-bottom:5px
+        border-radius: 3px
+        position: relative
+        background-color: white
+        .trigger-content
+            position:absolute
+            top:50%
+            transform: translateY(-50%)
+            left:10px
+            .trigger-text
+              font-size: 16px
+              display:inline-block
+            .trigger-text.trigger-text-email
+              margin-left: 5px;
+              margin-top: 10px;
+              margin-bottom: 10px;
+            .trigger-dropdown
+              display:inline-block
+              select
+                width:100px
+                height:30px
+                margin:0px
+                margin-left:5px
+              input
+                display: inline-block
+                width: 80px;
+                margin: 0;
+        .trigger-content-mail
+          left:20px
+          right:100px
+        .trigger-button
+          position:absolute
+          top:50%
+          transform: translateY(-50%)
+          width:30px
+          height:30px
+          border: 1px solid #eee
+          border-radius: 4px
+          box-shadow: inset -1px -1px 3px rgba(0,0,0,.05)
+          text-align:center
+          font-size: 20px
+          right:10px
+          i
+            position: absolute
+            top: 50%
+            left: 50%
+            box-shadow: none
+            transform: translate(-50%,-50%)
+          &:hover, &.is-active
+            box-shadow: 0 0 0 2px darken(white, 60%) inset
+        .trigger-button.trigger-button-email
+          top:30px
+      .trigger-item.trigger-item-mail
+        height:300px
+  
+
+

+ 25 - 0
client/components/rules/rulesActions.jade

@@ -0,0 +1,25 @@
+template(name="rulesActions")
+  h2
+    i.fa.fa-magic
+    | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-action'}}}
+  .triggers-content
+    .triggers-body
+      .triggers-side-menu
+        ul
+          li.active.js-set-board-actions
+            i.fa.fa-columns
+          li.js-set-card-actions
+            i.fa.fa-sticky-note
+          li.js-set-checklist-actions
+            i.fa.fa-check
+          li.js-set-mail-actions
+            i.fa.fa-at
+      .triggers-main-body
+        if ($eq currentActions.get 'board')
+          +boardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
+        else if ($eq currentActions.get 'card')
+          +cardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
+        else if ($eq currentActions.get 'checklist')
+          +checklistActions(ruleName=data.ruleName triggerVar=data.triggerVar)
+        else if ($eq currentActions.get 'mail')
+          +mailActions(ruleName=data.ruleName triggerVar=data.triggerVar)  

+ 58 - 0
client/components/rules/rulesActions.js

@@ -0,0 +1,58 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.currentActions = new ReactiveVar("board");
+  },
+
+  setBoardActions() {
+    this.currentActions.set("board");
+    $('.js-set-card-actions').removeClass('active');
+    $('.js-set-board-actions').addClass('active');
+    $('.js-set-checklist-actions').removeClass('active');
+    $('.js-set-mail-actions').removeClass('active');
+  },
+  setCardActions() {
+    this.currentActions.set("card");
+    $('.js-set-card-actions').addClass('active');
+    $('.js-set-board-actions').removeClass('active');
+    $('.js-set-checklist-actions').removeClass('active');
+    $('.js-set-mail-actions').removeClass('active');
+  },
+  setChecklistActions() {
+    this.currentActions.set("checklist");
+    $('.js-set-card-actions').removeClass('active');
+    $('.js-set-board-actions').removeClass('active');
+    $('.js-set-checklist-actions').addClass('active');
+    $('.js-set-mail-actions').removeClass('active');
+  },
+  setMailActions() {
+    this.currentActions.set("mail");
+    $('.js-set-card-actions').removeClass('active');
+    $('.js-set-board-actions').removeClass('active');
+    $('.js-set-checklist-actions').removeClass('active');
+    $('.js-set-mail-actions').addClass('active');
+  },
+
+  rules() {
+    return Rules.find({});
+  },
+
+  name() {
+    console.log(this.data());
+  },
+  events() {
+    return [{
+      'click .js-set-board-actions' (event) {
+        this.setBoardActions();
+      },
+      'click .js-set-card-actions' (event) {
+        this.setCardActions();
+      },
+      'click .js-set-mail-actions' (event) {
+        this.setMailActions();
+      },
+      'click .js-set-checklist-actions' (event) {
+        this.setChecklistActions();
+      },
+    }];
+  },
+}).register('rulesActions');

+ 27 - 0
client/components/rules/rulesList.jade

@@ -0,0 +1,27 @@
+template(name="rulesList")
+  .rules
+    h2
+      i.fa.fa-magic
+      | {{{_ 'r-board-rules' }}}
+
+    ul.rules-list
+      each rules
+        li.rules-lists-item
+          p 
+            = title
+          div.rules-btns-group
+            button.js-goto-details
+              i.fa.fa-eye
+              | {{{_ 'r-view-rule'}}}
+            if currentUser.isAdmin
+              button.js-delete-rule
+                i.fa.fa-trash-o
+                | {{{_ 'r-delete-rule'}}}
+      else
+        li.no-items-message {{{_ 'r-no-rules' }}}
+    if currentUser.isAdmin
+      div.rules-add
+        button.js-goto-trigger
+          i.fa.fa-plus
+          | {{{_ 'r-add-rule'}}}
+        input(type=text,placeholder="{{{_ 'r-new-rule-name' }}}",id="ruleTitle")

+ 15 - 0
client/components/rules/rulesList.js

@@ -0,0 +1,15 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.subscribe('allRules');
+  },
+
+  rules() {
+    const boardId = Session.get('currentBoard');
+    return Rules.find({
+      "boardId": boardId
+    });
+  },
+  events() {
+    return [{}];
+  },
+}).register('rulesList');

+ 9 - 0
client/components/rules/rulesMain.jade

@@ -0,0 +1,9 @@
+template(name="rulesMain")
+  if($eq rulesCurrentTab.get 'rulesList')
+    +rulesList
+  if($eq rulesCurrentTab.get 'trigger')
+    +rulesTriggers(ruleName=ruleName triggerVar=triggerVar)
+  if($eq rulesCurrentTab.get 'action')
+    +rulesActions(ruleName=ruleName triggerVar=triggerVar)
+  if($eq rulesCurrentTab.get 'ruleDetails')
+    +ruleDetails(ruleId=ruleId)

+ 58 - 0
client/components/rules/rulesMain.js

@@ -0,0 +1,58 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.rulesCurrentTab = new ReactiveVar("rulesList")
+    this.ruleName = new ReactiveVar("");
+    this.triggerVar = new ReactiveVar();
+    this.ruleId = new ReactiveVar();
+  },
+
+  setTrigger() {
+    this.rulesCurrentTab.set("trigger")
+  },
+
+  setRulesList() {
+    this.rulesCurrentTab.set("rulesList")
+  },
+
+  setAction() {
+    this.rulesCurrentTab.set("action")
+  },
+  setRuleDetails() {
+    this.rulesCurrentTab.set("ruleDetails")
+  },
+
+  events() {
+    return [{
+      'click .js-delete-rule' (event) {
+        const rule = this.currentData();
+        Rules.remove(rule._id);
+        Actions.remove(rule.actionId);
+        Triggers.remove(rule.triggerId);
+
+      },
+      'click .js-goto-trigger' (event) {
+        event.preventDefault();
+        const ruleTitle = this.find('#ruleTitle').value;
+        this.find('#ruleTitle').value = "";
+        this.ruleName.set(ruleTitle)
+        this.setTrigger();
+      },
+      'click .js-goto-action' (event) {
+        event.preventDefault();
+        this.setAction();
+      },
+      'click .js-goto-rules' (event) {
+        event.preventDefault();
+        this.setRulesList();
+      },
+      'click .js-goto-details' (event) {
+        event.preventDefault();
+        const rule = this.currentData();
+        this.ruleId.set(rule._id)
+        this.setRuleDetails();
+      },
+
+    }];
+  },
+
+}).register('rulesMain');

+ 21 - 0
client/components/rules/rulesTriggers.jade

@@ -0,0 +1,21 @@
+template(name="rulesTriggers")
+  h2
+    i.fa.fa-magic
+    | {{{_ 'r-rule' }}} "#{data.ruleName.get}" - {{{_ 'r-add-trigger'}}}
+  .triggers-content
+    .triggers-body
+      .triggers-side-menu
+        ul
+          li.active.js-set-board-triggers
+            i.fa.fa-columns
+          li.js-set-card-triggers
+            i.fa.fa-sticky-note
+          li.js-set-checklist-triggers
+            i.fa.fa-check
+      .triggers-main-body
+        if showBoardTrigger.get
+          +boardTriggers
+        else if showCardTrigger.get
+          +cardTriggers
+        else if showChecklistTrigger.get
+          +checklistTriggers

+ 53 - 0
client/components/rules/rulesTriggers.js

@@ -0,0 +1,53 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.showBoardTrigger = new ReactiveVar(true);
+    this.showCardTrigger = new ReactiveVar(false);
+    this.showChecklistTrigger = new ReactiveVar(false);
+  },
+
+  setBoardTriggers() {
+    this.showBoardTrigger.set(true);
+    this.showCardTrigger.set(false);
+    this.showChecklistTrigger.set(false);
+    $('.js-set-card-triggers').removeClass('active');
+    $('.js-set-board-triggers').addClass('active');
+    $('.js-set-checklist-triggers').removeClass('active');
+  },
+  setCardTriggers() {
+    this.showBoardTrigger.set(false);
+    this.showCardTrigger.set(true);
+    this.showChecklistTrigger.set(false);
+    $('.js-set-card-triggers').addClass('active');
+    $('.js-set-board-triggers').removeClass('active');
+    $('.js-set-checklist-triggers').removeClass('active');
+  },
+  setChecklistTriggers() {
+    this.showBoardTrigger.set(false);
+    this.showCardTrigger.set(false);
+    this.showChecklistTrigger.set(true);
+    $('.js-set-card-triggers').removeClass('active');
+    $('.js-set-board-triggers').removeClass('active');
+    $('.js-set-checklist-triggers').addClass('active');
+  },
+
+  rules() {
+    return Rules.find({});
+  },
+
+  name() {
+    console.log(this.data());
+  },
+  events() {
+    return [{
+      'click .js-set-board-triggers' (event) {
+        this.setBoardTriggers();
+      },
+      'click .js-set-card-triggers' (event) {
+        this.setCardTriggers();
+      },
+      'click .js-set-checklist-triggers' (event) {
+        this.setChecklistTriggers();
+      },
+    }];
+  },
+}).register('rulesTriggers');

+ 61 - 0
client/components/rules/triggers/boardTriggers.jade

@@ -0,0 +1,61 @@
+template(name="boardTriggers")
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-card-is'}}}
+      div.trigger-dropdown
+        select(id="gen-action")
+          option(value="created") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-the-board'}}}
+    div.trigger-button.js-add-gen-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-card-is'}}}
+      div.trigger-dropdown
+        select(id="create-action")
+          option(value="created") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-list'}}}
+      div.trigger-dropdown
+        input(id="create-list-name",type=text,placeholder="{{{_'r-list-name'}}}")  
+    div.trigger-button.js-add-create-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-card-is'}}}
+      div.trigger-dropdown
+        select(id="move-action")
+          option(value="moved-to") {{{_'r-moved-to'}}}
+          option(value="moved-from") {{{_'r-moved-from'}}}
+      div.trigger-text 
+        | {{{_'r-list'}}}
+      div.trigger-dropdown
+        input(id="move-list-name",type=text,placeholder="{{{_'r-list-name'}}}")  
+    div.trigger-button.js-add-moved-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-card-is'}}}
+      div.trigger-dropdown
+        select(id="arch-action")
+          option(value="archived") {{{_'r-archived'}}}
+          option(value="unarchived") {{{_'r-unarchived'}}}
+    div.trigger-button.js-add-arch-trigger.js-goto-action
+      i.fa.fa-plus
+
+
+   
+  
+
+
+

+ 103 - 0
client/components/rules/triggers/boardTriggers.js

@@ -0,0 +1,103 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+
+  },
+
+  events() {
+    return [{
+      'click .js-add-gen-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#gen-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "created") {
+          datas.triggerVar.set({
+            activityType: "createCard",
+            "boardId": boardId,
+            "listName": "*",
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "removeCard",
+            "boardId": boardId,
+            "desc": desc
+          });
+        }
+
+      },
+      'click .js-add-create-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#create-action').value;
+        const listName = this.find('#create-list-name').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "created") {
+          datas.triggerVar.set({
+            activityType: "createCard",
+            "boardId": boardId,
+            "listName": listName,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "removeCard",
+            "boardId": boardId,
+            "listName": listName,
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-moved-trigger' (event) {
+        let datas = this.data();
+        const desc = Utils.getTriggerActionDesc(event, this);
+
+        const actionSelected = this.find('#move-action').value;
+        const listName = this.find('#move-list-name').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "moved-to") {
+          datas.triggerVar.set({
+            activityType: "moveCard",
+            "boardId": boardId,
+            "listName": listName,
+            "oldListName": "*",
+            "desc": desc
+          });
+        }
+        if (actionSelected == "moved-from") {
+          datas.triggerVar.set({
+            activityType: "moveCard",
+            "boardId": boardId,
+            "listName": "*",
+            "oldListName": listName,
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-arc-trigger' (event) {
+        let datas = this.data();
+        const desc = Utils.getTriggerActionDesc(event, this);
+        const actionSelected = this.find('#arch-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "archived") {
+          datas.triggerVar.set({
+            activityType: "archivedCard",
+            "boardId": boardId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "unarchived") {
+          datas.triggerVar.set({
+            activityType: "restoredCard",
+            "boardId": boardId,
+            "desc": desc
+          });
+        }
+      }
+
+    }];
+  },
+
+}).register('boardTriggers');

+ 79 - 0
client/components/rules/triggers/cardTriggers.jade

@@ -0,0 +1,79 @@
+template(name="cardTriggers")
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-label-is'}}}
+      div.trigger-dropdown
+        select(id="label-action")
+          option(value="added") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-a-card'}}}
+    div.trigger-button.js-add-gen-label-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-the-label-is'}}}
+      div.trigger-dropdown
+        select(id="spec-label")
+          each labels
+            option(value="#{_id}")
+              = name
+      div.trigger-text 
+        | {{{_'r-is'}}}
+      div.trigger-dropdown
+        select(id="spec-label-action")
+          option(value="added") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-a-card'}}}
+    div.trigger-button.js-add-spec-label-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-member'}}}
+      div.trigger-dropdown
+        select(id="gen-member-action")
+          option(value="added") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-a-card'}}}
+    div.trigger-button.js-add-gen-member-trigger.js-goto-action
+      i.fa.fa-plus
+
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-the-member'}}}
+      div.trigger-dropdown
+        input(id="spec-member",type=text,placeholder="{{{_'r-name'}}}") 
+      div.trigger-text 
+        | {{{_'r-is'}}}
+      div.trigger-dropdown
+        select(id="spec-member-action")
+          option(value="added") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-a-card'}}}
+    div.trigger-button.js-add-spec-member-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-attach'}}}
+      div.trigger-text 
+        | {{{_'r-is'}}}
+      div.trigger-dropdown
+        select(id="attach-action")
+          option(value="added") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-a-card'}}}
+    div.trigger-button.js-add-attachment-trigger.js-goto-action
+      i.fa.fa-plus

+ 130 - 0
client/components/rules/triggers/cardTriggers.js

@@ -0,0 +1,130 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.subscribe('allRules');
+  },
+  labels() {
+    const labels = Boards.findOne(Session.get('currentBoard')).labels;
+    console.log(labels);
+    for (let i = 0; i < labels.length; i++) {
+      if (labels[i].name == "" || labels[i].name == undefined) {
+        labels[i].name = labels[i].color.toUpperCase();
+      }
+    }
+    console.log(labels);
+    return labels;
+  },
+  events() {
+    return [{
+      'click .js-add-gen-label-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#label-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "added") {
+          datas.triggerVar.set({
+            activityType: "addedLabel",
+            "boardId": boardId,
+            "labelId": "*",
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "removedLabel",
+            "boardId": boardId,
+            "labelId": "*",
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-spec-label-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#spec-label-action').value;
+        const labelId = this.find('#spec-label').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "added") {
+          datas.triggerVar.set({
+            activityType: "addedLabel",
+            "boardId": boardId,
+            "labelId": labelId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "removedLabel",
+            "boardId": boardId,
+            "labelId": labelId,
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-gen-member-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#gen-member-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "added") {
+          datas.triggerVar.set({
+            activityType: "joinMember",
+            "boardId": boardId,
+            "memberId": "*",
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "unjoinMember",
+            "boardId": boardId,
+            "memberId": "*",
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-spec-member-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#spec-member-action').value;
+        const memberId = this.find('#spec-member').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "added") {
+          datas.triggerVar.set({
+            activityType: "joinMember",
+            "boardId": boardId,
+            "memberId": memberId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "unjoinMember",
+            "boardId": boardId,
+            "memberId": memberId,
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-attachment-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#attach-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "added") {
+          datas.triggerVar.set({
+            activityType: "addAttachment",
+            "boardId": boardId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "deleteAttachment",
+            "boardId": boardId,
+            "desc": desc
+          });
+        }
+      },
+    }];
+  },
+}).register('cardTriggers');

+ 83 - 0
client/components/rules/triggers/checklistTriggers.jade

@@ -0,0 +1,83 @@
+template(name="checklistTriggers")
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-checklist'}}}
+      div.trigger-dropdown
+        select(id="gen-check-action")
+          option(value="created") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-a-card'}}}
+    div.trigger-button.js-add-gen-check-trigger.js-goto-action
+      i.fa.fa-plus
+
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-the-checklist'}}}
+      div.trigger-dropdown
+        input(id="check-name",type=text,placeholder="{{{_'r-name'}}}") 
+      div.trigger-text 
+        | {{{_'r-is'}}}
+      div.trigger-dropdown
+        select(id="spec-check-action")
+          option(value="created") {{{_'r-added-to'}}}
+          option(value="removed") {{{_'r-removed-from'}}}
+      div.trigger-text 
+        | {{{_'r-a-card'}}}
+    div.trigger-button.js-add-spec-check-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-checklist'}}}
+      div.trigger-dropdown
+        select(id="gen-comp-check-action")
+          option(value="completed") {{{_'r-completed'}}}
+          option(value="uncompleted") {{{_'r-made-incomplete'}}}
+    div.trigger-button.js-add-gen-comp-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-the-checklist'}}}
+      div.trigger-dropdown
+        input(id="spec-comp-check-name",type=text,placeholder="{{{_'r-name'}}}") 
+      div.trigger-text 
+        | {{{_'r-is'}}}
+      div.trigger-dropdown
+        select(id="spec-comp-check-action")
+          option(value="completed") {{{_'r-completed'}}}
+          option(value="uncompleted") {{{_'r-made-incomplete'}}}
+    div.trigger-button.js-add-spec-comp-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-a-item'}}}
+      div.trigger-dropdown
+        select(id="check-item-gen-action")
+          option(value="checked") {{{_'r-checked'}}}
+          option(value="unchecked") {{{_'r-unchecked'}}}
+    div.trigger-button.js-add-gen-check-item-trigger.js-goto-action
+      i.fa.fa-plus
+
+  div.trigger-item
+    div.trigger-content
+      div.trigger-text 
+        | {{{_'r-when-the-item'}}}
+      div.trigger-dropdown
+        input(id="check-item-name",type=text,placeholder="{{{_'r-name'}}}") 
+      div.trigger-text 
+        | {{{_'r-is'}}}
+      div.trigger-dropdown
+        select(id="check-item-spec-action")
+          option(value="checked") {{{_'r-checked'}}}
+          option(value="unchecked") {{{_'r-unchecked'}}}
+    div.trigger-button.js-add-spec-check-item-trigger.js-goto-action
+      i.fa.fa-plus

+ 146 - 0
client/components/rules/triggers/checklistTriggers.js

@@ -0,0 +1,146 @@
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.subscribe('allRules');
+  },
+  events() {
+    return [{
+      'click .js-add-gen-check-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#gen-check-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "created") {
+          datas.triggerVar.set({
+            activityType: "addChecklist",
+            "boardId": boardId,
+            "checklistName": "*",
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "removeChecklist",
+            "boardId": boardId,
+            "checklistName": "*",
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-spec-check-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#spec-check-action').value;
+        const checklistId = this.find('#check-name').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "created") {
+          datas.triggerVar.set({
+            activityType: "addChecklist",
+            "boardId": boardId,
+            "checklistName": checklistId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "removeChecklist",
+            "boardId": boardId,
+            "checklistName": checklistId,
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-gen-comp-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+
+        let datas = this.data();
+        const actionSelected = this.find('#gen-comp-check-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "completed") {
+          datas.triggerVar.set({
+            activityType: "completeChecklist",
+            "boardId": boardId,
+            "checklistName": "*",
+            "desc": desc
+          });
+        }
+        if (actionSelected == "uncompleted") {
+          datas.triggerVar.set({
+            activityType: "uncompleteChecklist",
+            "boardId": boardId,
+            "checklistName": "*",
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-spec-comp-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#spec-comp-check-action').value;
+        const checklistId = this.find('#spec-comp-check-name').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "added") {
+          datas.triggerVar.set({
+            activityType: "completeChecklist",
+            "boardId": boardId,
+            "checklistName": checklistId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "removed") {
+          datas.triggerVar.set({
+            activityType: "uncompleteChecklist",
+            "boardId": boardId,
+            "checklistName": checklistId,
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-gen-check-item-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#check-item-gen-action').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "checked") {
+          datas.triggerVar.set({
+            activityType: "checkedItem",
+            "boardId": boardId,
+            "checklistItemName": "*",
+            "desc": desc
+          });
+        }
+        if (actionSelected == "unchecked") {
+          datas.triggerVar.set({
+            activityType: "uncheckedItem",
+            "boardId": boardId,
+            "checklistItemName": "*",
+            "desc": desc
+          });
+        }
+      },
+      'click .js-add-spec-check-item-trigger' (event) {
+        const desc = Utils.getTriggerActionDesc(event, this);
+        let datas = this.data();
+        const actionSelected = this.find('#check-item-spec-action').value;
+        const checklistItemId = this.find('#check-item-name').value;
+        const boardId = Session.get('currentBoard')
+        if (actionSelected == "checked") {
+          datas.triggerVar.set({
+            activityType: "checkedItem",
+            "boardId": boardId,
+            "checklistItemName": checklistItemId,
+            "desc": desc
+          });
+        }
+        if (actionSelected == "unchecked") {
+          datas.triggerVar.set({
+            activityType: "uncheckedItem",
+            "boardId": boardId,
+            "checklistItemName": checklistItemId,
+            "desc": desc
+          });
+        }
+      },
+    }];
+  },
+
+}).register('checklistTriggers');

+ 13 - 1
client/lib/modal.js

@@ -4,6 +4,7 @@ window.Modal = new class {
   constructor() {
     this._currentModal = new ReactiveVar(closedValue);
     this._onCloseGoTo = '';
+    this._isWideModal = false;
   }
 
   getHeaderName() {
@@ -20,6 +21,10 @@ window.Modal = new class {
     return this.getTemplateName() !== closedValue;
   }
 
+  isWide(){
+    return this._isWideModal;
+  }
+
   close() {
     this._currentModal.set(closedValue);
     if (this._onCloseGoTo) {
@@ -27,9 +32,16 @@ window.Modal = new class {
     }
   }
 
+  openWide(modalName, { header = '', onCloseGoTo = ''} = {}) {
+    this._currentModal.set({ header, modalName });
+    this._onCloseGoTo = onCloseGoTo;
+    this._isWideModal = true;
+  }
+
   open(modalName, { header = '', onCloseGoTo = ''} = {}) {
     this._currentModal.set({ header, modalName });
     this._onCloseGoTo = onCloseGoTo;
+
   }
 }();
 
@@ -38,5 +50,5 @@ Blaze.registerHelper('Modal', Modal);
 EscapeActions.register('modalWindow',
   () => Modal.close(),
   () => Modal.isOpen(),
-  { noClickEscapeOn: '.modal-content' }
+  { noClickEscapeOn: '.modal-container' }
 );

+ 1 - 0
client/lib/popup.js

@@ -83,6 +83,7 @@ window.Popup = new class {
       // our internal dependency, and since we just changed the top element of
       // our internal stack, the popup will be updated with the new data.
       if (!self.isOpen()) {
+        console.log(self.template)
         self.current = Blaze.renderWithData(self.template, () => {
           self._dep.depend();
           return { ...self._getTopStack(), stack: self._stack };

+ 25 - 4
client/lib/utils.js

@@ -39,11 +39,11 @@ Utils = {
     if (!prevData && !nextData) {
       base = 0;
       increment = 1;
-    // If we drop the card in the first position
+      // If we drop the card in the first position
     } else if (!prevData) {
       base = nextData.sort - 1;
       increment = -1;
-    // If we drop the card in the last position
+      // If we drop the card in the last position
     } else if (!nextData) {
       base = prevData.sort + 1;
       increment = 1;
@@ -71,11 +71,11 @@ Utils = {
     if (!prevCardDomElement && !nextCardDomElement) {
       base = 0;
       increment = 1;
-    // If we drop the card in the first position
+      // If we drop the card in the first position
     } else if (!prevCardDomElement) {
       base = Blaze.getData(nextCardDomElement).sort - 1;
       increment = -1;
-    // If we drop the card in the last position
+      // If we drop the card in the last position
     } else if (!nextCardDomElement) {
       base = Blaze.getData(prevCardDomElement).sort + 1;
       increment = 1;
@@ -145,6 +145,7 @@ Utils = {
     });
   },
 
+<<<<<<< HEAD
   setMatomo(data){
     window._paq = window._paq || [];
     window._paq.push(['setDoNotTrack', data.doNotTrack]);
@@ -188,6 +189,26 @@ Utils = {
     } else if (matomo) {
       window._paq.push(['trackPageView']);
     }
+
+  getTriggerActionDesc(event, tempInstance) {
+    const jqueryEl = tempInstance.$(event.currentTarget.parentNode);
+    const triggerEls = jqueryEl.find(".trigger-content").children();
+    let finalString = "";
+    for (let i = 0; i < triggerEls.length; i++) {
+      const element = tempInstance.$(triggerEls[i]);
+      if (element.hasClass("trigger-text")) {
+        finalString += element.text().toLowerCase();
+      } else if (element.find("select").length > 0) {
+        finalString += element.find("select option:selected").text().toLowerCase();
+      } else if (element.find("input").length > 0) {
+        finalString += element.find("input").val();
+      }
+      // Add space
+      if (i != length - 1) {
+        finalString += " ";
+      }
+    }
+    return finalString;
   },
 };
 

+ 113 - 1
i18n/en.i18n.json

@@ -43,9 +43,19 @@
     "activity-sent": "sent %s to %s",
     "activity-unjoined": "unjoined %s",
     "activity-subtask-added": "added subtask to %s",
+    "activity-checked-item": "checked %s in checklist %s of %s",
+    "activity-unchecked-item": "unchecked %s in checklist %s of %s",
     "activity-checklist-added": "added checklist to %s",
+    "activity-checklist-removed": "removed a checklist from %s",
+    "activity-checklist-completed": "completed the checklist %s of %s",
+    "activity-checklist-uncompleted": "uncompleted the checklist %s of %s",
     "activity-checklist-item-added": "added checklist item to '%s' in %s",
+    "activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
     "add": "Add",
+    "activity-checked-item-card": "checked %s in checklist %s",
+    "activity-unchecked-item-card": "unchecked %s in checklist %s",
+    "activity-checklist-completed-card": "completed the checklist %s",
+    "activity-checklist-uncompleted-card": "uncompleted the checklist %s",
     "add-attachment": "Add Attachment",
     "add-board": "Add Board",
     "add-card": "Add Card",
@@ -371,6 +381,7 @@
     "restore": "Restore",
     "save": "Save",
     "search": "Search",
+    "rules": "Rules",
     "search-cards": "Search from card titles and descriptions on this board",
     "search-example": "Text to search for?",
     "select-color": "Select Color",
@@ -507,6 +518,107 @@
     "change-card-parent": "Change card's parent",
     "parent-card": "Parent card",
     "source-board": "Source board",
-    "no-parent": "Don't show parent"
+    "no-parent": "Don't show parent",
+    "activity-added-label": "added label '%s' to %s",
+    "activity-removed-label": "removed label '%s' from %s",
+    "activity-delete-attach": "deleted an attachment from %s",
+    "activity-added-label-card": "added label '%s'",
+    "activity-removed-label-card": "removed label '%s'",
+    "activity-delete-attach-card": "deleted an attachment",
+    "r-rule": "Rule",
+    "r-add-trigger": "Add trigger",
+    "r-add-action": "Add action",
+    "r-board-rules": "Board rules",
+    "r-add-rule": "Add rule",
+    "r-view-rule": "View rule",
+    "r-delete-rule": "Delete rule",
+    "r-new-rule-name": "Add new rule",
+    "r-no-rules": "No rules",
+    "r-when-a-card-is": "When a card is",
+    "r-added-to": "Added to",
+    "r-removed-from": "Removed from",
+    "r-the-board": "the board",
+    "r-list": "list",
+    "r-moved-to": "Moved to",
+    "r-moved-from": "Moved from",
+    "r-archived": "Archived",
+    "r-unarchived": "Unarchived",
+    "r-a-card": "a card",
+    "r-when-a-label-is": "When a label is",
+    "r-when-the-label-is": "When the label is",
+    "r-list-name": "List name",
+    "r-when-a-member": "When a member is",
+    "r-when-the-member": "When the member is",
+    "r-name": "name",
+    "r-is": "is",
+    "r-when-a-attach": "When an attachment",
+    "r-when-a-checklist": "When a checklist is",
+    "r-when-the-checklist": "When the checklist",
+    "r-completed": "Completed",
+    "r-made-incomplete": "Made incomplete",
+    "r-when-a-item": "When a checklist item is",
+    "r-when-the-item": "When the checklist item",
+    "r-checked": "Checked",
+    "r-unchecked": "Unchecked",
+    "r-move-card-to": "Move card to",
+    "r-top-of": "Top of",
+    "r-bottom-of": "Bottom of",
+    "r-its-list": "its list",
+    "r-list": "list",
+    "r-archive": "Archive",
+    "r-unarchive": "Unarchive",
+    "r-card": "card",
+    "r-add": "Add",
+    "r-remove": "Remove",
+    "r-label": "label",
+    "r-member": "member",
+    "r-remove-all": "Remove all members from the card",
+    "r-checklist": "checklist",
+    "r-check-all": "Check all",
+    "r-uncheck-all": "Uncheck all",
+    "r-item-check": "Items of checklist",
+    "r-check": "Check",
+    "r-uncheck": "Uncheck",
+    "r-item": "item",
+    "r-of-checklist": "of checlist",
+    "r-send-email": "Send an email",
+    "r-to": "to",
+    "r-subject": "subject",
+    "r-rule-details": "Rule details",
+    "r-d-move-to-top-gen": "Move card to top of its list",
+    "r-d-move-to-top-spec": "Move card to top of list",
+    "r-d-move-to-bottom-gen": "Move card to bottom of its list",
+    "r-d-move-to-bottom-spec": "Move card to bottom of list",
+    "r-d-send-email": "Send email",
+    "r-d-send-email-to": "to",
+    "r-d-send-email-subject": "subject",
+    "r-d-send-email-message": "message",
+    "r-d-archive": "Archive the card",
+    "r-d-unarchive": "Unarchive the card",
+    "r-d-add-label": "Add label",
+    "r-d-remove-label": "Remove label",
+    "r-d-add-member": "Add member",
+    "r-d-remove-member": "Remove member",
+    "r-d-remove-all-member": "Remove all member",
+    "r-d-check-all": "Check all item of list",
+    "r-d-uncheck-all": "Uncheck all item of list",
+    "r-d-check-one": "Check item",
+    "r-d-uncheck-one": "Uncheck item",
+    "r-d-check-of-list": "of checklist",
+    "r-d-add-checklist": "Add checklist",
+    "r-d-remove-checklist": "Remove checklist"
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 }

TEMPAT SAMPAH
models/.DS_Store


+ 19 - 0
models/actions.js

@@ -0,0 +1,19 @@
+Actions = new Mongo.Collection('actions');
+
+Actions.allow({
+	insert(userId, doc) {
+		return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+	},
+	update(userId, doc) {
+		return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+	},
+	remove(userId, doc) {
+		return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+	}
+});
+
+Actions.helpers({
+	description() {
+		return this.desc;
+	}
+});

+ 11 - 0
models/activities.js

@@ -56,6 +56,17 @@ Activities.before.insert((userId, doc) => {
   doc.createdAt = new Date();
 });
 
+
+
+Activities.after.insert((userId, doc) => {
+   const activity = Activities._transform(doc);
+   RulesHelper.executeRules(activity);
+
+});
+
+
+
+
 if (Meteor.isServer) {
   // For efficiency create indexes on the date of creation, and on the date of
   // creation in conjunction with the card or board id, as corresponding views

+ 7 - 0
models/attachments.js

@@ -86,5 +86,12 @@ if (Meteor.isServer) {
     Activities.remove({
       attachmentId: doc._id,
     });
+    Activities.insert({
+        userId,
+        type: 'card',
+        activityType: 'deleteAttachment',
+        boardId: doc.boardId,
+        cardId: doc.cardId,
+      });
   });
 }

+ 4 - 0
models/boards.js

@@ -280,6 +280,10 @@ Boards.helpers({
     return _.findWhere(this.labels, { name, color });
   },
 
+  getLabelById(labelId){
+    return _.findWhere(this.labels, { _id: labelId });
+  },
+
   labelIndex(labelId) {
     return _.pluck(this.labels, '_id').indexOf(labelId);
   },

+ 358 - 52
models/cards.js

@@ -276,14 +276,22 @@ Cards.helpers({
     return Cards.find({
       parentId: this._id,
       archived: false,
-    }, {sort: { sort: 1 } });
+    }, {
+      sort: {
+        sort: 1
+      }
+    });
   },
 
   allSubtasks() {
     return Cards.find({
       parentId: this._id,
       archived: false,
-    }, {sort: { sort: 1 } });
+    }, {
+      sort: {
+        sort: 1
+      }
+    });
   },
 
   subtasksCount() {
@@ -296,7 +304,8 @@ Cards.helpers({
   subtasksFinishedCount() {
     return Cards.find({
       parentId: this._id,
-      archived: true}).count();
+      archived: true
+    }).count();
   },
 
   subtasksFinished() {
@@ -328,12 +337,9 @@ Cards.helpers({
       });
       //search for "True Value" which is for DropDowns other then the Value (which is the id)
       let trueValue = customField.value;
-      if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0)
-      {
-        for (let i = 0; i < definition.settings.dropdownItems.length; i++)
-        {
-          if (definition.settings.dropdownItems[i]._id === customField.value)
-          {
+      if (definition.settings.dropdownItems && definition.settings.dropdownItems.length > 0) {
+        for (let i = 0; i < definition.settings.dropdownItems.length; i++) {
+          if (definition.settings.dropdownItems[i]._id === customField.value) {
             trueValue = definition.settings.dropdownItems[i].name;
           }
         }
@@ -358,8 +364,10 @@ Cards.helpers({
   },
 
   canBeRestored() {
-    const list = Lists.findOne({_id: this.listId});
-    if(!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()){
+    const list = Lists.findOne({
+      _id: this.listId
+    });
+    if (!list.getWipLimit('soft') && list.getWipLimit('enabled') && list.getWipLimit('value') === list.cards().count()) {
       return false;
     }
     return true;
@@ -424,7 +432,7 @@ Cards.helpers({
   },
 
   parentString(sep) {
-    return this.parentList().map(function(elem){
+    return this.parentList().map(function(elem) {
       return elem.title;
     }).join(sep);
   },
@@ -826,19 +834,65 @@ Cards.helpers({
 
 Cards.mutations({
   applyToChildren(funct) {
-    Cards.find({ parentId: this._id }).forEach((card) => {
+    Cards.find({
+      parentId: this._id
+    }).forEach((card) => {
       funct(card);
     });
   },
 
   archive() {
-    this.applyToChildren((card) => { return card.archive(); });
-    return {$set: {archived: true}};
+    this.applyToChildren((card) => {
+      return card.archive();
+    });
+    return {
+      $set: {
+        archived: true
+      }
+    };
   },
 
   restore() {
-    this.applyToChildren((card) => { return card.restore(); });
-    return {$set: {archived: false}};
+    this.applyToChildren((card) => {
+      return card.restore();
+    });
+    return {
+      $set: {
+        archived: false
+      }
+    };
+  },
+
+  setTitle(title) {
+    return {
+      $set: {
+        title
+      }
+    };
+  },
+
+  setDescription(description) {
+    return {
+      $set: {
+        description
+      }
+    };
+  },
+
+  setRequestedBy(requestedBy) {
+    return {
+      $set: {
+        requestedBy
+      }
+    };
+  },
+
+  setAssignedBy(assignedBy) {
+    return {
+      $set: {
+        assignedBy
+      }
+    };
   },
 
   move(swimlaneId, listId, sortIndex) {
@@ -850,15 +904,25 @@ Cards.mutations({
       sort: sortIndex,
     };
 
-    return {$set: mutatedFields};
+    return {
+      $set: mutatedFields
+    };
   },
 
   addLabel(labelId) {
-    return {$addToSet: {labelIds: labelId}};
+    return {
+      $addToSet: {
+        labelIds: labelId
+      }
+    };
   },
 
   removeLabel(labelId) {
-    return {$pull: {labelIds: labelId}};
+    return {
+      $pull: {
+        labelIds: labelId
+      }
+    };
   },
 
   toggleLabel(labelId) {
@@ -869,12 +933,52 @@ Cards.mutations({
     }
   },
 
+<<<<<<< HEAD
+=======
+  assignMember(memberId) {
+    return {
+      $addToSet: {
+        members: memberId
+      }
+    };
+  },
+
+  unassignMember(memberId) {
+    return {
+      $pull: {
+        members: memberId
+      }
+    };
+  },
+
+  toggleMember(memberId) {
+    if (this.members && this.members.indexOf(memberId) > -1) {
+      return this.unassignMember(memberId);
+    } else {
+      return this.assignMember(memberId);
+    }
+  },
+
+>>>>>>> 36c04edb9f7cf16fb450b76598c4957968d4674b
   assignCustomField(customFieldId) {
-    return {$addToSet: {customFields: {_id: customFieldId, value: null}}};
+    return {
+      $addToSet: {
+        customFields: {
+          _id: customFieldId,
+          value: null
+        }
+      }
+    };
   },
 
   unassignCustomField(customFieldId) {
-    return {$pull: {customFields: {_id: customFieldId}}};
+    return {
+      $pull: {
+        customFields: {
+          _id: customFieldId
+        }
+      }
+    };
   },
 
   toggleCustomField(customFieldId) {
@@ -889,7 +993,9 @@ Cards.mutations({
     // todo
     const index = this.customFieldIndex(customFieldId);
     if (index > -1) {
-      const update = {$set: {}};
+      const update = {
+        $set: {}
+      };
       update.$set[`customFields.${index}.value`] = value;
       return update;
     }
@@ -899,19 +1005,122 @@ Cards.mutations({
   },
 
   setCover(coverId) {
-    return {$set: {coverId}};
+    return {
+      $set: {
+        coverId
+      }
+    };
   },
 
   unsetCover() {
-    return {$unset: {coverId: ''}};
+    return {
+      $unset: {
+        coverId: ''
+      }
+    };
+  },
+
+<<<<<<< HEAD
+=======
+  setReceived(receivedAt) {
+    return {
+      $set: {
+        receivedAt
+      }
+    };
+  },
+
+  unsetReceived() {
+    return {
+      $unset: {
+        receivedAt: ''
+      }
+    };
+  },
+
+  setStart(startAt) {
+    return {
+      $set: {
+        startAt
+      }
+    };
+  },
+
+  unsetStart() {
+    return {
+      $unset: {
+        startAt: ''
+      }
+    };
+  },
+
+  setDue(dueAt) {
+    return {
+      $set: {
+        dueAt
+      }
+    };
   },
 
+  unsetDue() {
+    return {
+      $unset: {
+        dueAt: ''
+      }
+    };
+  },
+
+  setEnd(endAt) {
+    return {
+      $set: {
+        endAt
+      }
+    };
+  },
+
+  unsetEnd() {
+    return {
+      $unset: {
+        endAt: ''
+      }
+    };
+  },
+
+  setOvertime(isOvertime) {
+    return {
+      $set: {
+        isOvertime
+      }
+    };
+  },
+
+  setSpentTime(spentTime) {
+    return {
+      $set: {
+        spentTime
+      }
+    };
+  },
+
+  unsetSpentTime() {
+    return {
+      $unset: {
+        spentTime: '',
+        isOvertime: false
+      }
+    };
+  },
+
+>>>>>>> 36c04edb9f7cf16fb450b76598c4957968d4674b
   setParentId(parentId) {
-    return {$set: {parentId}};
+    return {
+      $set: {
+        parentId
+      }
+    };
   },
 });
 
-
 //FUNCTIONS FOR creation of Activities
 
 function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) {
@@ -921,6 +1130,7 @@ function cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId) {
       userId,
       oldListId,
       activityType: 'moveCard',
+      listName: Lists.findOne(doc.listId).title,
       listId: doc.listId,
       boardId: doc.boardId,
       cardId: doc._id,
@@ -936,6 +1146,7 @@ function cardState(userId, doc, fieldNames) {
       Activities.insert({
         userId,
         activityType: 'archivedCard',
+        listName: Lists.findOne(doc.listId).title,
         boardId: doc.boardId,
         listId: doc.listId,
         cardId: doc._id,
@@ -945,6 +1156,7 @@ function cardState(userId, doc, fieldNames) {
         userId,
         activityType: 'restoredCard',
         boardId: doc.boardId,
+        listName: Lists.findOne(doc.listId).title,
         listId: doc.listId,
         cardId: doc._id,
       });
@@ -986,11 +1198,47 @@ function cardMembers(userId, doc, fieldNames, modifier) {
   }
 }
 
+function cardLabels(userId, doc, fieldNames, modifier) {
+  if (!_.contains(fieldNames, 'labelIds'))
+    return;
+  let labelId;
+  // Say hello to the new label
+  if (modifier.$addToSet && modifier.$addToSet.labelIds) {
+    labelId = modifier.$addToSet.labelIds;
+    if (!_.contains(doc.labelIds, labelId)) {
+      const act = {
+        userId,
+        labelId,
+        activityType: 'addedLabel',
+        boardId: doc.boardId,
+        cardId: doc._id,
+      }
+      Activities.insert(act);
+    }
+  }
+
+  // Say goodbye to the label
+  if (modifier.$pull && modifier.$pull.labelIds) {
+    labelId = modifier.$pull.labelIds;
+    // Check that the former member is member of the card
+    if (_.contains(doc.labelIds, labelId)) {
+      Activities.insert({
+        userId,
+        labelId,
+        activityType: 'removedLabel',
+        boardId: doc.boardId,
+        cardId: doc._id,
+      });
+    }
+  }
+}
+
 function cardCreation(userId, doc) {
   Activities.insert({
     userId,
     activityType: 'createCard',
     boardId: doc.boardId,
+    listName: Lists.findOne(doc.listId).title,
     listId: doc.listId,
     cardId: doc._id,
     swimlaneId: doc.swimlaneId,
@@ -1015,7 +1263,6 @@ function cardRemover(userId, doc) {
   });
 }
 
-
 if (Meteor.isServer) {
   // Cards are often fetched within a board, so we create an index to make these
   // queries more efficient.
@@ -1039,7 +1286,7 @@ if (Meteor.isServer) {
   });
 
   //New activity for card moves
-  Cards.after.update(function (userId, doc, fieldNames) {
+  Cards.after.update(function(userId, doc, fieldNames) {
     const oldListId = this.previous.listId;
     const oldSwimlaneId = this.previous.swimlaneId;
     cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId);
@@ -1050,6 +1297,11 @@ if (Meteor.isServer) {
     cardMembers(userId, doc, fieldNames, modifier);
   });
 
+  // Add a new activity if we add or remove a label to the card
+  Cards.before.update((userId, doc, fieldNames, modifier) => {
+    cardLabels(userId, doc, fieldNames, modifier);
+  });
+
   // Remove all activities associated with a card if we remove the card
   // Remove also card_comments / checklists / attachments
   Cards.after.remove((userId, doc) => {
@@ -1058,13 +1310,17 @@ if (Meteor.isServer) {
 }
 //LISTS REST API
 if (Meteor.isServer) {
-  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function (req, res) {
+  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function(req, res) {
     const paramBoardId = req.params.boardId;
     const paramListId = req.params.listId;
     Authentication.checkBoardAccess(req.userId, paramBoardId);
     JsonRoutes.sendResult(res, {
       code: 200,
-      data: Cards.find({boardId: paramBoardId, listId: paramListId, archived: false}).map(function (doc) {
+      data: Cards.find({
+        boardId: paramBoardId,
+        listId: paramListId,
+        archived: false
+      }).map(function(doc) {
         return {
           _id: doc._id,
           title: doc.title,
@@ -1074,24 +1330,31 @@ if (Meteor.isServer) {
     });
   });
 
-  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) {
+  JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) {
     const paramBoardId = req.params.boardId;
     const paramListId = req.params.listId;
     const paramCardId = req.params.cardId;
     Authentication.checkBoardAccess(req.userId, paramBoardId);
     JsonRoutes.sendResult(res, {
       code: 200,
-      data: Cards.findOne({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false}),
+      data: Cards.findOne({
+        _id: paramCardId,
+        listId: paramListId,
+        boardId: paramBoardId,
+        archived: false
+      }),
     });
   });
 
-  JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function (req, res) {
+  JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function(req, res) {
     Authentication.checkUserId(req.userId);
     const paramBoardId = req.params.boardId;
     const paramListId = req.params.listId;
-    const check = Users.findOne({_id: req.body.authorId});
+    const check = Users.findOne({
+      _id: req.body.authorId
+    });
     const members = req.body.members || [req.body.authorId];
-    if (typeof  check !== 'undefined') {
+    if (typeof check !== 'undefined') {
       const id = Cards.direct.insert({
         title: req.body.title,
         boardId: paramBoardId,
@@ -1109,7 +1372,9 @@ if (Meteor.isServer) {
         },
       });
 
-      const card = Cards.findOne({_id:id});
+      const card = Cards.findOne({
+        _id: id
+      });
       cardCreation(req.body.authorId, card);
 
     } else {
@@ -1119,7 +1384,7 @@ if (Meteor.isServer) {
     }
   });
 
-  JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) {
+  JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) {
     Authentication.checkUserId(req.userId);
     const paramBoardId = req.params.boardId;
     const paramCardId = req.params.cardId;
@@ -1127,27 +1392,63 @@ if (Meteor.isServer) {
 
     if (req.body.hasOwnProperty('title')) {
       const newTitle = req.body.title;
-      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
-        {$set: {title: newTitle}});
+      Cards.direct.update({
+        _id: paramCardId,
+        listId: paramListId,
+        boardId: paramBoardId,
+        archived: false
+      }, {
+        $set: {
+          title: newTitle
+        }
+      });
     }
     if (req.body.hasOwnProperty('listId')) {
       const newParamListId = req.body.listId;
-      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
-        {$set: {listId: newParamListId}});
+      Cards.direct.update({
+        _id: paramCardId,
+        listId: paramListId,
+        boardId: paramBoardId,
+        archived: false
+      }, {
+        $set: {
+          listId: newParamListId
+        }
+      });
 
-      const card = Cards.findOne({_id: paramCardId} );
-      cardMove(req.body.authorId, card, {fieldName: 'listId'}, paramListId);
+      const card = Cards.findOne({
+        _id: paramCardId
+      });
+      cardMove(req.body.authorId, card, {
+        fieldName: 'listId'
+      }, paramListId);
 
     }
     if (req.body.hasOwnProperty('description')) {
       const newDescription = req.body.description;
-      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
-        {$set: {description: newDescription}});
+      Cards.direct.update({
+        _id: paramCardId,
+        listId: paramListId,
+        boardId: paramBoardId,
+        archived: false
+      }, {
+        $set: {
+          description: newDescription
+        }
+      });
     }
     if (req.body.hasOwnProperty('labelIds')) {
       const newlabelIds = req.body.labelIds;
-      Cards.direct.update({_id: paramCardId, listId: paramListId, boardId: paramBoardId, archived: false},
-        {$set: {labelIds: newlabelIds}});
+      Cards.direct.update({
+        _id: paramCardId,
+        listId: paramListId,
+        boardId: paramBoardId,
+        archived: false
+      }, {
+        $set: {
+          labelIds: newlabelIds
+        }
+      });
     }
     if (req.body.hasOwnProperty('requestedBy')) {
       const newrequestedBy = req.body.requestedBy;
@@ -1202,15 +1503,20 @@ if (Meteor.isServer) {
     });
   });
 
-
-  JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function (req, res) {
+  JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', function(req, res) {
     Authentication.checkUserId(req.userId);
     const paramBoardId = req.params.boardId;
     const paramListId = req.params.listId;
     const paramCardId = req.params.cardId;
 
-    Cards.direct.remove({_id: paramCardId, listId: paramListId, boardId: paramBoardId});
-    const card = Cards.find({_id: paramCardId} );
+    Cards.direct.remove({
+      _id: paramCardId,
+      listId: paramListId,
+      boardId: paramBoardId
+    });
+    const card = Cards.find({
+      _id: paramCardId
+    });
     cardRemover(req.body.authorId, card);
     JsonRoutes.sendResult(res, {
       code: 200,

+ 87 - 0
models/checklistItems.js

@@ -44,6 +44,12 @@ ChecklistItems.mutations({
   setTitle(title) {
     return { $set: { title } };
   },
+  check(){
+    return { $set: { isFinished: true } };
+  },
+  uncheck(){
+    return { $set: { isFinished: false } };
+  },
   toggleItem() {
     return { $set: { isFinished: !this.isFinished } };
   },
@@ -70,21 +76,102 @@ function itemCreation(userId, doc) {
     boardId,
     checklistId: doc.checklistId,
     checklistItemId: doc._id,
+    checklistItemName:doc.title
   });
 }
 
 function itemRemover(userId, doc) {
+  const card = Cards.findOne(doc.cardId);
+  const boardId = card.boardId;
+  Activities.insert({
+    userId,
+    activityType: 'removedChecklistItem',
+    cardId: doc.cardId,
+    boardId,
+    checklistId: doc.checklistId,
+    checklistItemId: doc._id,
+    checklistItemName:doc.title
+  });
   Activities.remove({
     checklistItemId: doc._id,
   });
 }
 
+function publishCheckActivity(userId,doc){
+  const card = Cards.findOne(doc.cardId);
+  const boardId = card.boardId;
+  let activityType;
+  if(doc.isFinished){
+    activityType = "checkedItem";
+  }else{
+    activityType = "uncheckedItem";
+  }
+  let act = {
+    userId,
+    activityType: activityType,
+    cardId: doc.cardId,
+    boardId,
+    checklistId: doc.checklistId,
+    checklistItemId: doc._id,
+    checklistItemName:doc.title
+  }
+  console.log(act);
+  Activities.insert(act);
+}
+
+function publishChekListCompleted(userId,doc,fieldNames,modifier){
+  const card = Cards.findOne(doc.cardId);
+  const boardId = card.boardId;
+  const checklistId = doc.checklistId;
+  const checkList = Checklists.findOne({_id:checklistId});
+  if(checkList.isFinished()){
+    let act = {
+      userId,
+      activityType: "checklistCompleted",
+      cardId: doc.cardId,
+      boardId,
+      checklistId: doc.checklistId,
+      checklistName:doc.title
+    }
+    Activities.insert(act);
+  }
+}
+
+function publishChekListUncompleted(userId,doc,fieldNames,modifier){
+  const card = Cards.findOne(doc.cardId);
+  const boardId = card.boardId;
+  const checklistId = doc.checklistId;
+  const checkList = Checklists.findOne({_id:checklistId});
+  if(checkList.isFinished()){
+    let act = {
+      userId,
+      activityType: "checklistUncompleted",
+      cardId: doc.cardId,
+      boardId,
+      checklistId: doc.checklistId,
+      checklistName:doc.title
+    }
+    Activities.insert(act);
+  }
+}
+
 // Activities
 if (Meteor.isServer) {
   Meteor.startup(() => {
     ChecklistItems._collection._ensureIndex({ checklistId: 1 });
   });
 
+  ChecklistItems.after.update((userId, doc, fieldNames, modifier) => {
+    publishCheckActivity(userId,doc);
+    publishChekListCompleted(userId,doc,fieldNames,modifier)
+  });
+
+  ChecklistItems.before.update((userId, doc, fieldNames, modifier) => {
+    publishChekListUncompleted(userId,doc,fieldNames,modifier)
+  });
+
+
+
   ChecklistItems.after.insert((userId, doc) => {
     itemCreation(userId, doc);
   });

+ 23 - 0
models/checklists.js

@@ -47,6 +47,18 @@ Checklists.helpers({
   isFinished() {
     return 0 !== this.itemCount() && this.itemCount() === this.finishedCount();
   },
+  checkAllItems(){
+    const checkItems = ChecklistItems.find({checklistId: this._id});
+    checkItems.forEach(function(item){
+      item.check();
+    });
+  },
+  uncheckAllItems(){
+    const checkItems = ChecklistItems.find({checklistId: this._id});
+    checkItems.forEach(function(item){
+      item.uncheck();
+    });
+  },
   itemIndex(itemId) {
     const items = self.findOne({_id : this._id}).items;
     return _.pluck(items, '_id').indexOf(itemId);
@@ -91,6 +103,7 @@ if (Meteor.isServer) {
       cardId: doc.cardId,
       boardId: Cards.findOne(doc.cardId).boardId,
       checklistId: doc._id,
+      checklistName:doc.title
     });
   });
 
@@ -101,6 +114,16 @@ if (Meteor.isServer) {
         Activities.remove(activity._id);
       });
     }
+    Activities.insert({
+      userId,
+      activityType: 'removeChecklist',
+      cardId: doc.cardId,
+      boardId: Cards.findOne(doc.cardId).boardId,
+      checklistId: doc._id,
+      checklistName:doc.title
+    });
+
+
   });
 }
 

+ 59 - 15
models/export.js

@@ -14,7 +14,7 @@ if (Meteor.isServer) {
    * See https://blog.kayla.com.au/server-side-route-authentication-in-meteor/
    * for detailed explanations
    */
-  JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) {
+  JsonRoutes.add('get', '/api/boards/:boardId/export', function(req, res) {
     const boardId = req.params.boardId;
     let user = null;
     // todo XXX for real API, first look for token in Authentication: header
@@ -28,8 +28,11 @@ if (Meteor.isServer) {
     }
 
     const exporter = new Exporter(boardId);
-    if(exporter.canExport(user)) {
-      JsonRoutes.sendResult(res, { code: 200, data: exporter.build() });
+    if (exporter.canExport(user)) {
+      JsonRoutes.sendResult(res, {
+        code: 200,
+        data: exporter.build()
+      });
     } else {
       // we could send an explicit error message, but on the other hand the only
       // way to get there is by hacking the UI so let's keep it raw.
@@ -47,24 +50,49 @@ class Exporter {
     const byBoard = { boardId: this._boardId };
     const byBoardNoLinked = { boardId: this._boardId, linkedId: '' };
     // we do not want to retrieve boardId in related elements
-    const noBoardId = { fields: { boardId: 0 } };
+    const noBoardId = {
+      fields: {
+        boardId: 0
+      }
+    };
     const result = {
       _format: 'wekan-board-1.0.0',
     };
-    _.extend(result, Boards.findOne(this._boardId, { fields: { stars: 0 } }));
+    _.extend(result, Boards.findOne(this._boardId, {
+      fields: {
+        stars: 0
+      }
+    }));
     result.lists = Lists.find(byBoard, noBoardId).fetch();
     result.cards = Cards.find(byBoardNoLinked, noBoardId).fetch();
     result.swimlanes = Swimlanes.find(byBoard, noBoardId).fetch();
     result.customFields = CustomFields.find(byBoard, noBoardId).fetch();
     result.comments = CardComments.find(byBoard, noBoardId).fetch();
     result.activities = Activities.find(byBoard, noBoardId).fetch();
+    result.rules = Rules.find(byBoard, noBoardId).fetch();
     result.checklists = [];
     result.checklistItems = [];
     result.subtaskItems = [];
+    result.triggers = [];
+    result.actions = [];
     result.cards.forEach((card) => {
-      result.checklists.push(...Checklists.find({ cardId: card._id }).fetch());
-      result.checklistItems.push(...ChecklistItems.find({ cardId: card._id }).fetch());
-      result.subtaskItems.push(...Cards.find({ parentid: card._id }).fetch());
+      result.checklists.push(...Checklists.find({
+        cardId: card._id
+      }).fetch());
+      result.checklistItems.push(...ChecklistItems.find({
+        cardId: card._id
+      }).fetch());
+      result.subtaskItems.push(...Cards.find({
+        parentid: card._id
+      }).fetch());
+    });
+    result.rules.forEach((rule) => {
+      result.triggers.push(...Triggers.find({
+        _id: rule.triggerId
+      }, noBoardId).fetch());
+      result.actions.push(...Actions.find({
+        _id: rule.actionId
+      }, noBoardId).fetch());
     });
 
     // [Old] for attachments we only export IDs and absolute url to original doc
@@ -101,18 +129,34 @@ class Exporter {
     // 1- only exports users that are linked somehow to that board
     // 2- do not export any sensitive information
     const users = {};
-    result.members.forEach((member) => { users[member.userId] = true; });
-    result.lists.forEach((list) => { users[list.userId] = true; });
+    result.members.forEach((member) => {
+      users[member.userId] = true;
+    });
+    result.lists.forEach((list) => {
+      users[list.userId] = true;
+    });
     result.cards.forEach((card) => {
       users[card.userId] = true;
       if (card.members) {
-        card.members.forEach((memberId) => { users[memberId] = true; });
+        card.members.forEach((memberId) => {
+          users[memberId] = true;
+        });
       }
     });
-    result.comments.forEach((comment) => { users[comment.userId] = true; });
-    result.activities.forEach((activity) => { users[activity.userId] = true; });
-    result.checklists.forEach((checklist) => { users[checklist.userId] = true; });
-    const byUserIds = { _id: { $in: Object.getOwnPropertyNames(users) } };
+    result.comments.forEach((comment) => {
+      users[comment.userId] = true;
+    });
+    result.activities.forEach((activity) => {
+      users[activity.userId] = true;
+    });
+    result.checklists.forEach((checklist) => {
+      users[checklist.userId] = true;
+    });
+    const byUserIds = {
+      _id: {
+        $in: Object.getOwnPropertyNames(users)
+      }
+    };
     // we use whitelist to be sure we do not expose inadvertently
     // some secret fields that gets added to User later.
     const userFields = {

+ 1 - 1
models/lists.js

@@ -82,7 +82,7 @@ Lists.helpers({
     };
     if (swimlaneId)
       selector.swimlaneId = swimlaneId;
-    return Cards.find(Filter.mongoSelector(selector),
+    return Cards.find(selector,
       { sort: ['sort'] });
   },
 

+ 49 - 0
models/rules.js

@@ -0,0 +1,49 @@
+Rules = new Mongo.Collection('rules');
+
+Rules.attachSchema(new SimpleSchema({
+  title: {
+    type: String,
+    optional: false,
+  },
+  triggerId: {
+    type: String,
+    optional: false,
+  },
+  actionId: {
+    type: String,
+    optional: false,
+  },
+  boardId: {
+    type: String,
+    optional: false,
+  },
+}));
+
+Rules.mutations({
+  rename(description) {
+    return { $set: { description } };
+  },
+});
+
+Rules.helpers({
+  getAction(){
+    return Actions.findOne({_id:this.actionId});
+  },
+  getTrigger(){
+    return Triggers.findOne({_id:this.triggerId});
+  }
+});
+
+
+
+Rules.allow({
+  insert(userId, doc) {
+    return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+  },
+  update(userId, doc) {
+    return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+  },
+  remove(userId, doc) {
+    return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+  }
+});

+ 58 - 0
models/triggers.js

@@ -0,0 +1,58 @@
+Triggers = new Mongo.Collection('triggers');
+
+Triggers.mutations({
+  rename(description) {
+    return {
+      $set: {
+        description
+      }
+    };
+  },
+});
+
+Triggers.allow({
+  insert(userId, doc) {
+    return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+  },
+  update(userId, doc) {
+    return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+  },
+  remove(userId, doc) {
+    return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
+  }
+});
+
+Triggers.helpers({
+
+  description() {
+    return this.desc;
+  },
+
+  getRule() {
+    return Rules.findOne({
+      triggerId: this._id
+    });
+  },
+
+  fromList() {
+    return Lists.findOne(this.fromId);
+  },
+
+  toList() {
+    return Lists.findOne(this.toId);
+  },
+
+  findList(title) {
+    return Lists.findOne({
+      title: title
+    });
+  },
+
+  labels() {
+    const boardLabels = this.board().labels;
+    const cardLabels = _.filter(boardLabels, (label) => {
+      return _.contains(this.labelIds, label._id);
+    });
+    return cardLabels;
+  }
+});

+ 288 - 175
models/wekanCreator.js

@@ -1,4 +1,4 @@
-const DateString = Match.Where(function (dateAsString) {
+const DateString = Match.Where(function(dateAsString) {
   check(dateAsString, String);
   return moment(dateAsString, moment.ISO_8601).isValid();
 });
@@ -42,6 +42,10 @@ export class WekanCreator {
     this.comments = {};
     // the members, indexed by Wekan member id => Wekan user ID
     this.members = data.membersMapping ? data.membersMapping : {};
+    // Map of triggers Wekan ID => Wekan ID
+    this.triggers = {};
+    // Map of actions Wekan ID => Wekan ID
+    this.actions = {};
 
     // maps a wekanCardId to an array of wekanAttachments
     this.attachments = {};
@@ -57,10 +61,10 @@ export class WekanCreator {
    * @param {String} dateString a properly formatted Date
    */
   _now(dateString) {
-    if(dateString) {
+    if (dateString) {
       return new Date(dateString);
     }
-    if(!this._nowDate) {
+    if (!this._nowDate) {
       this._nowDate = new Date();
     }
     return this._nowDate;
@@ -72,9 +76,9 @@ export class WekanCreator {
    * Otherwise return current logged user.
    * @param wekanUserId
    * @private
-     */
+   */
   _user(wekanUserId) {
-    if(wekanUserId && this.members[wekanUserId]) {
+    if (wekanUserId && this.members[wekanUserId]) {
       return this.members[wekanUserId];
     }
     return Meteor.userId();
@@ -96,7 +100,7 @@ export class WekanCreator {
       // allowed values (is it worth the maintenance?)
       color: String,
       permission: Match.Where((value) => {
-        return ['private', 'public'].indexOf(value)>= 0;
+        return ['private', 'public'].indexOf(value) >= 0;
       }),
     }));
   }
@@ -147,6 +151,30 @@ export class WekanCreator {
     })]);
   }
 
+  checkRules(wekanRules) {
+    check(wekanRules, [Match.ObjectIncluding({
+      triggerId: String,
+      actionId: String,
+      title: String,
+    })]);
+  }
+
+  checkTriggers(wekanTriggers) {
+    // XXX More check based on trigger type
+    check(wekanTriggers, [Match.ObjectIncluding({
+      activityType: String,
+      desc: String,
+    })]);
+  }
+
+  checkActions(wekanActions) {
+    // XXX More check based on action type
+    check(wekanActions, [Match.ObjectIncluding({
+      actionType: String,
+      desc: String,
+    })]);
+  }
+
   // You must call parseActions before calling this one.
   createBoardAndLabels(boardToImport) {
     const boardToCreate = {
@@ -172,12 +200,12 @@ export class WekanCreator {
       title: boardToImport.title,
     };
     // now add other members
-    if(boardToImport.members) {
+    if (boardToImport.members) {
       boardToImport.members.forEach((wekanMember) => {
         // do we already have it in our list?
-        if(!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId))
+        if (!boardToCreate.members.some((member) => member.wekanId === wekanMember.wekanId))
           boardToCreate.members.push({
-            ... wekanMember,
+            ...wekanMember,
             userId: wekanMember.wekanId,
           });
       });
@@ -194,7 +222,11 @@ export class WekanCreator {
       boardToCreate.labels.push(labelToCreate);
     });
     const boardId = Boards.direct.insert(boardToCreate);
-    Boards.direct.update(boardId, {$set: {modifiedAt: this._now()}});
+    Boards.direct.update(boardId, {
+      $set: {
+        modifiedAt: this._now()
+      }
+    });
     // log activity
     Activities.direct.insert({
       activityType: 'importBoard',
@@ -246,21 +278,21 @@ export class WekanCreator {
         });
       }
       // add members {
-      if(card.members) {
+      if (card.members) {
         const wekanMembers = [];
         // we can't just map, as some members may not have been mapped
         card.members.forEach((sourceMemberId) => {
-          if(this.members[sourceMemberId]) {
+          if (this.members[sourceMemberId]) {
             const wekanId = this.members[sourceMemberId];
             // we may map multiple Wekan members to the same wekan user
             // in which case we risk adding the same user multiple times
-            if(!wekanMembers.find((wId) => wId === wekanId)){
+            if (!wekanMembers.find((wId) => wId === wekanId)) {
               wekanMembers.push(wekanId);
             }
           }
           return true;
         });
-        if(wekanMembers.length>0) {
+        if (wekanMembers.length > 0) {
           cardToCreate.members = wekanMembers;
         }
       }
@@ -321,9 +353,9 @@ export class WekanCreator {
           // - the template then tries to display the url to the attachment which causes other errors
           // so we make it server only, and let UI catch up once it is done, forget about latency comp.
           const self = this;
-          if(Meteor.isServer) {
+          if (Meteor.isServer) {
             if (att.url) {
-              file.attachData(att.url, function (error) {
+              file.attachData(att.url, function(error) {
                 file.boardId = boardId;
                 file.cardId = cardId;
                 file.userId = self._user(att.userId);
@@ -331,20 +363,26 @@ export class WekanCreator {
                 // attachments' related activities automatically
                 file.source = 'import';
                 if (error) {
-                  throw(error);
+                  throw (error);
                 } else {
                   const wekanAtt = Attachments.insert(file, () => {
                     // we do nothing
                   });
                   self.attachmentIds[att._id] = wekanAtt._id;
                   //
-                  if(wekanCoverId === att._id) {
-                    Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}});
+                  if (wekanCoverId === att._id) {
+                    Cards.direct.update(cardId, {
+                      $set: {
+                        coverId: wekanAtt._id
+                      }
+                    });
                   }
                 }
               });
             } else if (att.file) {
-              file.attachData(new Buffer(att.file, 'base64'), {type: att.type}, (error) => {
+              file.attachData(new Buffer(att.file, 'base64'), {
+                type: att.type
+              }, (error) => {
                 file.name(att.name);
                 file.boardId = boardId;
                 file.cardId = cardId;
@@ -353,15 +391,19 @@ export class WekanCreator {
                 // attachments' related activities automatically
                 file.source = 'import';
                 if (error) {
-                  throw(error);
+                  throw (error);
                 } else {
                   const wekanAtt = Attachments.insert(file, () => {
                     // we do nothing
                   });
                   this.attachmentIds[att._id] = wekanAtt._id;
                   //
-                  if(wekanCoverId === att._id) {
-                    Cards.direct.update(cardId, { $set: {coverId: wekanAtt._id}});
+                  if (wekanCoverId === att._id) {
+                    Cards.direct.update(cardId, {
+                      $set: {
+                        coverId: wekanAtt._id
+                      }
+                    });
                   }
                 }
               });
@@ -404,7 +446,11 @@ export class WekanCreator {
         sort: list.sort ? list.sort : listIndex,
       };
       const listId = Lists.direct.insert(listToCreate);
-      Lists.direct.update(listId, {$set: {'updatedAt': this._now()}});
+      Lists.direct.update(listId, {
+        $set: {
+          'updatedAt': this._now()
+        }
+      });
       this.lists[list._id] = listId;
       // // log activity
       // Activities.direct.insert({
@@ -437,7 +483,11 @@ export class WekanCreator {
         sort: swimlane.sort ? swimlane.sort : swimlaneIndex,
       };
       const swimlaneId = Swimlanes.direct.insert(swimlaneToCreate);
-      Swimlanes.direct.update(swimlaneId, {$set: {'updatedAt': this._now()}});
+      Swimlanes.direct.update(swimlaneId, {
+        $set: {
+          'updatedAt': this._now()
+        }
+      });
       this.swimlanes[swimlane._id] = swimlaneId;
     });
   }
@@ -459,6 +509,47 @@ export class WekanCreator {
     return result;
   }
 
+  createTriggers(wekanTriggers, boardId) {
+    wekanTriggers.forEach((trigger, ruleIndex) => {
+      if (trigger.hasOwnProperty('labelId')) {
+        trigger['labelId'] = this.labels[trigger['labelId']]
+      }
+      if (trigger.hasOwnProperty('memberId')) {
+        trigger['memberId'] = this.members[trigger['memberId']]
+      }
+      trigger['boardId'] = boardId;
+      const oldId = trigger['_id'];
+      delete trigger._id;
+      this.triggers[oldId] = Triggers.direct.insert(trigger);
+    });
+  }
+
+  createActions(wekanActions, boardId) {
+    wekanActions.forEach((action, ruleIndex) => {
+      if (action.hasOwnProperty('labelId')) {
+        action['labelId'] = this.labels[action['labelId']]
+      }
+      if (action.hasOwnProperty('memberId')) {
+        action['memberId'] = this.members[action['memberId']]
+      }
+      action['boardId'] = boardId;
+      const oldId = action['_id'];
+      delete action._id;
+      this.actions[oldId] = Actions.direct.insert(action);
+    });
+  }
+
+  createRules(wekanRules, boardId) {
+    wekanRules.forEach((rule, ruleIndex) => {
+      // Create the rule
+      rule['boardId'] = boardId;
+      rule['triggerId'] = this.triggers[rule['triggerId']];
+      rule['actionId'] = this.actions[rule['actionId']];
+      delete rule._id;
+      Rules.direct.insert(rule);
+    });
+  }
+
   createChecklistItems(wekanChecklistItems) {
     wekanChecklistItems.forEach((checklistitem, checklistitemIndex) => {
       // Create the checklistItem
@@ -477,166 +568,182 @@ export class WekanCreator {
   parseActivities(wekanBoard) {
     wekanBoard.activities.forEach((activity) => {
       switch (activity.activityType) {
-      case 'addAttachment': {
-        // We have to be cautious, because the attachment could have been removed later.
-        // In that case Wekan still reports its addition, but removes its 'url' field.
-        // So we test for that
-        const wekanAttachment = wekanBoard.attachments.filter((attachment) => {
-          return attachment._id === activity.attachmentId;
-        })[0];
+        case 'addAttachment':
+          {
+            // We have to be cautious, because the attachment could have been removed later.
+            // In that case Wekan still reports its addition, but removes its 'url' field.
+            // So we test for that
+            const wekanAttachment = wekanBoard.attachments.filter((attachment) => {
+              return attachment._id === activity.attachmentId;
+            })[0];
 
-        if ( typeof wekanAttachment !== 'undefined' && wekanAttachment ) {
-          if(wekanAttachment.url || wekanAttachment.file) {
-          // we cannot actually create the Wekan attachment, because we don't yet
-          // have the cards to attach it to, so we store it in the instance variable.
-            const wekanCardId = activity.cardId;
-            if(!this.attachments[wekanCardId]) {
-              this.attachments[wekanCardId] = [];
+            if (typeof wekanAttachment !== 'undefined' && wekanAttachment) {
+              if (wekanAttachment.url || wekanAttachment.file) {
+                // we cannot actually create the Wekan attachment, because we don't yet
+                // have the cards to attach it to, so we store it in the instance variable.
+                const wekanCardId = activity.cardId;
+                if (!this.attachments[wekanCardId]) {
+                  this.attachments[wekanCardId] = [];
+                }
+                this.attachments[wekanCardId].push(wekanAttachment);
+              }
             }
-            this.attachments[wekanCardId].push(wekanAttachment);
+            break;
+          }
+        case 'addComment':
+          {
+            const wekanComment = wekanBoard.comments.filter((comment) => {
+              return comment._id === activity.commentId;
+            })[0];
+            const id = activity.cardId;
+            if (!this.comments[id]) {
+              this.comments[id] = [];
+            }
+            this.comments[id].push(wekanComment);
+            break;
+          }
+        case 'createBoard':
+          {
+            this.createdAt.board = activity.createdAt;
+            break;
+          }
+        case 'createCard':
+          {
+            const cardId = activity.cardId;
+            this.createdAt.cards[cardId] = activity.createdAt;
+            this.createdBy.cards[cardId] = activity.userId;
+            break;
+          }
+        case 'createList':
+          {
+            const listId = activity.listId;
+            this.createdAt.lists[listId] = activity.createdAt;
+            break;
+          }
+        case 'createSwimlane':
+          {
+            const swimlaneId = activity.swimlaneId;
+            this.createdAt.swimlanes[swimlaneId] = activity.createdAt;
+            break;
           }
-        }
-        break;
-      }
-      case 'addComment': {
-        const wekanComment = wekanBoard.comments.filter((comment) => {
-          return comment._id === activity.commentId;
-        })[0];
-        const id = activity.cardId;
-        if (!this.comments[id]) {
-          this.comments[id] = [];
-        }
-        this.comments[id].push(wekanComment);
-        break;
-      }
-      case 'createBoard': {
-        this.createdAt.board = activity.createdAt;
-        break;
-      }
-      case 'createCard': {
-        const cardId = activity.cardId;
-        this.createdAt.cards[cardId] = activity.createdAt;
-        this.createdBy.cards[cardId] = activity.userId;
-        break;
-      }
-      case 'createList': {
-        const listId = activity.listId;
-        this.createdAt.lists[listId] = activity.createdAt;
-        break;
       }
-      case 'createSwimlane': {
-        const swimlaneId = activity.swimlaneId;
-        this.createdAt.swimlanes[swimlaneId] = activity.createdAt;
-        break;
-      }}
     });
   }
 
   importActivities(activities, boardId) {
     activities.forEach((activity) => {
       switch (activity.activityType) {
-      // Board related activities
-      // TODO: addBoardMember, removeBoardMember
-      case 'createBoard': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          type: 'board',
-          activityTypeId: boardId,
-          activityType: activity.activityType,
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
-      }
-      // List related activities
-      // TODO: removeList, archivedList
-      case 'createList': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          type: 'list',
-          activityType: activity.activityType,
-          listId: this.lists[activity.listId],
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
-      }
-      // Card related activities
-      // TODO: archivedCard, restoredCard, joinMember, unjoinMember
-      case 'createCard': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          activityType: activity.activityType,
-          listId: this.lists[activity.listId],
-          cardId: this.cards[activity.cardId],
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
-      }
-      case 'moveCard': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          oldListId: this.lists[activity.oldListId],
-          activityType: activity.activityType,
-          listId: this.lists[activity.listId],
-          cardId: this.cards[activity.cardId],
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
-      }
-      // Comment related activities
-      case 'addComment': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          activityType: activity.activityType,
-          cardId: this.cards[activity.cardId],
-          commentId: this.commentIds[activity.commentId],
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
-      }
-      // Attachment related activities
-      case 'addAttachment': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          type: 'card',
-          activityType: activity.activityType,
-          attachmentId: this.attachmentIds[activity.attachmentId],
-          cardId: this.cards[activity.cardId],
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
-      }
-      // Checklist related activities
-      case 'addChecklist': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          activityType: activity.activityType,
-          cardId: this.cards[activity.cardId],
-          checklistId: this.checklists[activity.checklistId],
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
+        // Board related activities
+        // TODO: addBoardMember, removeBoardMember
+        case 'createBoard':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              type: 'board',
+              activityTypeId: boardId,
+              activityType: activity.activityType,
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
+          // List related activities
+          // TODO: removeList, archivedList
+        case 'createList':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              type: 'list',
+              activityType: activity.activityType,
+              listId: this.lists[activity.listId],
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
+          // Card related activities
+          // TODO: archivedCard, restoredCard, joinMember, unjoinMember
+        case 'createCard':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              activityType: activity.activityType,
+              listId: this.lists[activity.listId],
+              cardId: this.cards[activity.cardId],
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
+        case 'moveCard':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              oldListId: this.lists[activity.oldListId],
+              activityType: activity.activityType,
+              listId: this.lists[activity.listId],
+              cardId: this.cards[activity.cardId],
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
+          // Comment related activities
+        case 'addComment':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              activityType: activity.activityType,
+              cardId: this.cards[activity.cardId],
+              commentId: this.commentIds[activity.commentId],
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
+          // Attachment related activities
+        case 'addAttachment':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              type: 'card',
+              activityType: activity.activityType,
+              attachmentId: this.attachmentIds[activity.attachmentId],
+              cardId: this.cards[activity.cardId],
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
+          // Checklist related activities
+        case 'addChecklist':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              activityType: activity.activityType,
+              cardId: this.cards[activity.cardId],
+              checklistId: this.checklists[activity.checklistId],
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
+        case 'addChecklistItem':
+          {
+            Activities.direct.insert({
+              userId: this._user(activity.userId),
+              activityType: activity.activityType,
+              cardId: this.cards[activity.cardId],
+              checklistId: this.checklists[activity.checklistId],
+              checklistItemId: activity.checklistItemId.replace(
+                activity.checklistId,
+                this.checklists[activity.checklistId]),
+              boardId,
+              createdAt: this._now(activity.createdAt),
+            });
+            break;
+          }
       }
-      case 'addChecklistItem': {
-        Activities.direct.insert({
-          userId: this._user(activity.userId),
-          activityType: activity.activityType,
-          cardId: this.cards[activity.cardId],
-          checklistId: this.checklists[activity.checklistId],
-          checklistItemId: activity.checklistItemId.replace(
-            activity.checklistId,
-            this.checklists[activity.checklistId]),
-          boardId,
-          createdAt: this._now(activity.createdAt),
-        });
-        break;
-      }}
     });
   }
 
@@ -652,6 +759,9 @@ export class WekanCreator {
       this.checkSwimlanes(board.swimlanes);
       this.checkCards(board.cards);
       this.checkChecklists(board.checklists);
+      this.checkRules(board.rules);
+      this.checkActions(board.actions);
+      this.checkTriggers(board.triggers);
       this.checkChecklistItems(board.checklistItems);
     } catch (e) {
       throw new Meteor.Error('error-json-schema');
@@ -674,7 +784,10 @@ export class WekanCreator {
     this.createChecklists(board.checklists);
     this.createChecklistItems(board.checklistItems);
     this.importActivities(board.activities, boardId);
+    this.createTriggers(board.triggers, boardId);
+    this.createActions(board.actions, boardId);
+    this.createRules(board.rules, boardId);
     // XXX add members
     return boardId;
   }
-}
+}

TEMPAT SAMPAH
server/.DS_Store


TEMPAT SAMPAH
server/lib/.DS_Store


+ 4 - 4
server/lib/utils.js

@@ -1,9 +1,9 @@
 allowIsBoardAdmin = function(userId, board) {
-  return board && board.hasAdmin(userId);
+	return board && board.hasAdmin(userId);
 };
 
 allowIsBoardMember = function(userId, board) {
-  return board && board.hasMember(userId);
+	return board && board.hasMember(userId);
 };
 
 allowIsBoardMemberCommentOnly = function(userId, board) {
@@ -15,6 +15,6 @@ allowIsBoardMemberNoComments = function(userId, board) {
 };
 
 allowIsBoardMemberByCard = function(userId, card) {
-  const board = card.board();
-  return board && board.hasMember(userId);
+	const board = card.board();
+	return board && board.hasMember(userId);
 };

+ 2 - 0
server/notifications/email.js

@@ -39,3 +39,5 @@ Meteor.startup(() => {
     }, 30000);
   });
 });
+
+

+ 18 - 0
server/publications/rules.js

@@ -0,0 +1,18 @@
+Meteor.publish('rules', (ruleId) => {
+	check(ruleId, String);
+	return Rules.find({
+		_id: ruleId
+	});
+});
+
+Meteor.publish('allRules', () => {
+	return Rules.find({});
+});
+
+Meteor.publish('allTriggers', () => {
+	return Triggers.find({});
+});
+
+Meteor.publish('allActions', () => {
+	return Actions.find({});
+});

+ 131 - 0
server/rulesHelper.js

@@ -0,0 +1,131 @@
+RulesHelper = {
+	executeRules(activity){
+		const matchingRules = this.findMatchingRules(activity);
+		for(let i = 0;i< matchingRules.length;i++){
+			const action = matchingRules[i].getAction();
+			this.performAction(activity,action);
+		}
+	},
+	findMatchingRules(activity){
+		const activityType = activity.activityType;
+		if(TriggersDef[activityType] == undefined){
+			return [];
+		}
+		const matchingFields = TriggersDef[activityType].matchingFields;
+		const matchingMap = this.buildMatchingFieldsMap(activity,matchingFields);
+		let matchingTriggers = Triggers.find(matchingMap);
+		let matchingRules = [];
+		matchingTriggers.forEach(function(trigger){
+			matchingRules.push(trigger.getRule());
+		});
+		return matchingRules;
+	},
+	buildMatchingFieldsMap(activity, matchingFields){
+		let matchingMap = {"activityType":activity.activityType};
+		for(let i = 0;i< matchingFields.length;i++){
+			// Creating a matching map with the actual field of the activity
+			// and with the wildcard (for example: trigger when a card is added
+			// in any [*] board
+			matchingMap[matchingFields[i]] = { $in: [activity[matchingFields[i]],"*"]};
+		}
+		return matchingMap;
+	},
+	performAction(activity,action){
+		const card = Cards.findOne({_id:activity.cardId});
+		const boardId = activity.boardId;
+		if(action.actionType == "moveCardToTop"){
+			let listId;
+			let list;
+			if(activity.listTitle == "*"){
+				listId = card.swimlaneId;
+				list = card.list();
+			}else{
+				list = Lists.findOne({title: action.listTitle, boardId:boardId });;
+				listId = list._id;
+			}
+			const minOrder = _.min(list.cards(card.swimlaneId).map((c) => c.sort));
+			card.move(card.swimlaneId, listId, minOrder - 1);
+		}
+		if(action.actionType == "moveCardToBottom"){
+			let listId;
+			let list;
+			if(activity.listTitle == "*"){
+				listId = card.swimlaneId;
+				list = card.list();
+			}else{
+				list = Lists.findOne({title: action.listTitle, boardId:boardId});
+				listId = list._id;
+			}
+			const maxOrder = _.max(list.cards(card.swimlaneId).map((c) => c.sort));
+    		card.move(card.swimlaneId, listId, maxOrder + 1);
+		}
+		if(action.actionType == "sendEmail"){
+			const emailTo = action.emailTo;
+			const emailMsg = action.emailMsg;
+			const emailSubject = action.emailSubject;
+			try {
+				Email.send({
+					to: to,
+					from: Accounts.emailTemplates.from,
+					subject: subject,
+					text,
+				});
+			} catch (e) {
+				return;
+			}
+		}
+		if(action.actionType == "archive"){
+			card.archive();
+		}
+		if(action.actionType == "unarchive"){
+			card.restore();
+		}
+		if(action.actionType == "addLabel"){
+			card.addLabel(action.labelId);
+		}
+		if(action.actionType == "removeLabel"){
+			card.removeLabel(action.labelId);
+		}
+		if(action.actionType == "addMember"){
+			const memberId = Users.findOne({username:action.memberName})._id;
+			card.assignMember(memberId);
+		}
+		if(action.actionType == "removeMember"){
+			if(action.memberName == "*"){
+				const members = card.members;
+				for(let i = 0;i< members.length;i++){
+					card.unassignMember(members[i]);
+				}
+			}else{
+				const memberId = Users.findOne({username:action.memberName})._id;
+				card.unassignMember(memberId);
+			}
+		}
+		if(action.actionType == "checkAll"){
+			const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id});
+			checkList.checkAllItems();
+		}
+		if(action.actionType == "uncheckAll"){
+			const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id});
+			checkList.uncheckAllItems();
+		}
+		if(action.actionType == "checkItem"){
+			const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id});
+			const checkItem = ChecklistItems.findOne({"title":action.checkItemName,"checkListId":checkList._id})
+			checkItem.check();
+		}
+		if(action.actionType == "uncheckItem"){
+			const checkList = Checklists.findOne({"title":action.checklistName,"cardId":card._id});
+			const checkItem = ChecklistItems.findOne({"title":action.checkItemName,"checkListId":checkList._id})
+			checkItem.uncheck();
+		}
+		if(action.actionType == "addChecklist"){
+			Checklists.insert({"title":action.checklistName,"cardId":card._id,"sort":0});
+		}
+		if(action.actionType == "removeChecklist"){
+			Checklists.remove({"title":action.checklistName,"cardId":card._id,"sort":0});
+		}
+
+	},
+
+}

+ 59 - 0
server/triggersDef.js

@@ -0,0 +1,59 @@
+TriggersDef = {
+	createCard:{
+		matchingFields: ["boardId", "listName"]
+	},
+	moveCard:{
+		matchingFields: ["boardId", "listName", "oldListName"]
+	},
+	archivedCard:{
+		matchingFields: ["boardId"]
+	},
+	restoredCard:{
+		matchingFields: ["boardId"]
+	},
+	joinMember:{
+		matchingFields: ["boardId","memberId"]
+	},
+	unjoinMember:{
+		matchingFields: ["boardId","memberId"]
+	},
+	addChecklist:{
+		matchingFields: ["boardId","checklistName"]
+	},
+	removeChecklist:{
+		matchingFields: ["boardId","checklistName"]
+	},
+	completeChecklist:{
+		matchingFields: ["boardId","checklistName"]
+	},
+	uncompleteChecklist:{
+		matchingFields: ["boardId","checklistName"]
+	},
+	addedChecklistItem:{
+		matchingFields: ["boardId","checklistItemName"]
+	},
+	removedChecklistItem:{
+		matchingFields: ["boardId","checklistItemName"]
+	},
+	checkedItem:{
+		matchingFields: ["boardId","checklistItemName"]
+	},
+	uncheckedItem:{
+		matchingFields: ["boardId","checklistItemName"]
+	},
+	addAttachment:{
+		matchingFields: ["boardId"]
+	},
+	deleteAttachment:{
+		matchingFields: ["boardId"]
+	},
+	addedLabel:{
+		matchingFields: ["boardId","labelId"]
+	},
+	removedLabel:{
+		matchingFields: ["boardId","labelId"]
+	}
+}
+
+
+