Selaa lähdekoodia

Initial implementation for subtasks

Nicu Tofan 7 vuotta sitten
vanhempi
sitoutus
d59583915c

+ 3 - 0
client/components/cards/cardDetails.jade

@@ -144,6 +144,9 @@ template(name="cardDetails")
     hr
     hr
     +checklists(cardId = _id)
     +checklists(cardId = _id)
 
 
+    hr
+    +subtasks(cardId = _id)
+
     hr
     hr
     h3
     h3
       i.fa.fa-paperclip
       i.fa.fa-paperclip

+ 34 - 22
client/components/cards/cardDetails.js

@@ -364,6 +364,20 @@ BlazeComponent.extendComponent({
   },
   },
 }).register('boardsAndLists');
 }).register('boardsAndLists');
 
 
+function cloneCheckList(_id, checklist) {
+  'use strict';
+  const checklistId = checklist._id;
+  checklist.cardId = _id;
+  checklist._id = null;
+  const newChecklistId = Checklists.insert(checklist);
+  ChecklistItems.find({checklistId}).forEach(function(item) {
+    item._id = null;
+    item.checklistId = newChecklistId;
+    item.cardId = _id;
+    ChecklistItems.insert(item);
+  });
+}
+
 Template.copyCardPopup.events({
 Template.copyCardPopup.events({
   'click .js-done'() {
   'click .js-done'() {
     const card = Cards.findOne(Session.get('currentCard'));
     const card = Cards.findOne(Session.get('currentCard'));
@@ -392,19 +406,18 @@ Template.copyCardPopup.events({
 
 
       // copy checklists
       // copy checklists
       let cursor = Checklists.find({cardId: oldId});
       let cursor = Checklists.find({cardId: oldId});
+      cursor.forEach(function() {
+        cloneCheckList(_id, arguments[0]);
+      });
+
+      // copy subtasks
+      cursor = Subtasks.find({cardId: oldId});
       cursor.forEach(function() {
       cursor.forEach(function() {
         'use strict';
         'use strict';
-        const checklist = arguments[0];
-        const checklistId = checklist._id;
-        checklist.cardId = _id;
-        checklist._id = null;
-        const newChecklistId = Checklists.insert(checklist);
-        ChecklistItems.find({checklistId}).forEach(function(item) {
-          item._id = null;
-          item.checklistId = newChecklistId;
-          item.cardId = _id;
-          ChecklistItems.insert(item);
-        });
+        const subtask = arguments[0];
+        subtask.cardId = _id;
+        subtask._id = null;
+        /* const newSubtaskId = */ Subtasks.insert(subtask);
       });
       });
 
 
       // copy card comments
       // copy card comments
@@ -453,19 +466,18 @@ Template.copyChecklistToManyCardsPopup.events({
 
 
         // copy checklists
         // copy checklists
         let cursor = Checklists.find({cardId: oldId});
         let cursor = Checklists.find({cardId: oldId});
+        cursor.forEach(function() {
+          cloneCheckList(_id, arguments[0]);
+        });
+
+        // copy subtasks
+        cursor = Subtasks.find({cardId: oldId});
         cursor.forEach(function() {
         cursor.forEach(function() {
           'use strict';
           'use strict';
-          const checklist = arguments[0];
-          const checklistId = checklist._id;
-          checklist.cardId = _id;
-          checklist._id = null;
-          const newChecklistId = Checklists.insert(checklist);
-          ChecklistItems.find({checklistId}).forEach(function(item) {
-            item._id = null;
-            item.checklistId = newChecklistId;
-            item.cardId = _id;
-            ChecklistItems.insert(item);
-          });
+          const subtask = arguments[0];
+          subtask.cardId = _id;
+          subtask._id = null;
+          /* const newSubtaskId = */ Subtasks.insert(subtask);
         });
         });
 
 
         // copy card comments
         // copy card comments

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

@@ -27,7 +27,6 @@ template(name="checklistDetail")
         if canModifyCard
         if canModifyCard
           a.js-delete-checklist.toggle-delete-checklist-dialog {{_ "delete"}}...
           a.js-delete-checklist.toggle-delete-checklist-dialog {{_ "delete"}}...
 
 
-        span.checklist-stat(class="{{#if checklist.isFinished}}is-finished{{/if}}") {{checklist.finishedCount}}/{{checklist.itemCount}}
         if canModifyCard
         if canModifyCard
           h2.title.js-open-inlined-form.is-editable
           h2.title.js-open-inlined-form.is-editable
             +viewer
             +viewer
@@ -75,7 +74,7 @@ template(name="checklistItems")
       +inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
       +inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
         +editChecklistItemForm(type = 'item' item = item checklist = checklist)
         +editChecklistItemForm(type = 'item' item = item checklist = checklist)
       else
       else
-        +itemDetail(item = item checklist = checklist)
+        +cjecklistItemDetail(item = item checklist = checklist)
     if canModifyCard
     if canModifyCard
       +inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
       +inlinedForm(autoclose=false classNames="js-add-checklist-item" checklist = checklist)
         +addChecklistItemForm
         +addChecklistItemForm
@@ -84,7 +83,7 @@ template(name="checklistItems")
           i.fa.fa-plus
           i.fa.fa-plus
           | {{_ 'add-checklist-item'}}...
           | {{_ 'add-checklist-item'}}...
 
 
-template(name='itemDetail')
+template(name='cjecklistItemDetail')
   .js-checklist-item.checklist-item
   .js-checklist-item.checklist-item
     if canModifyCard
     if canModifyCard
       .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
       .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")

+ 2 - 2
client/components/cards/checklists.js

@@ -204,7 +204,7 @@ Template.checklistDeleteDialog.onDestroyed(() => {
   $cardDetails.animate( { scrollTop: this.scrollState.position });
   $cardDetails.animate( { scrollTop: this.scrollState.position });
 });
 });
 
 
-Template.itemDetail.helpers({
+Template.cjecklistItemDetail.helpers({
   canModifyCard() {
   canModifyCard() {
     return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
     return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
   },
   },
@@ -223,4 +223,4 @@ BlazeComponent.extendComponent({
       'click .js-checklist-item .check-box': this.toggleItem,
       'click .js-checklist-item .check-box': this.toggleItem,
     }];
     }];
   },
   },
-}).register('itemDetail');
+}).register('cjecklistItemDetail');

+ 96 - 0
client/components/cards/subtasks.jade

@@ -0,0 +1,96 @@
+template(name="subtasks")
+  h3 {{_ 'subtasks'}}
+  if toggleDeleteDialog.get
+    .board-overlay#card-details-overlay
+    +subtaskDeleteDialog(subtasks = subtasksToDelete)
+
+
+  .card-subtasks-items
+    each subtasks in currentCard.subtasks
+      +subtasksDetail(subtasks = subtasks)
+
+  if canModifyCard
+    +inlinedForm(autoclose=false classNames="js-add-subtask" cardId = cardId)
+      +addSubtaskItemForm
+    else
+      a.js-open-inlined-form
+        i.fa.fa-plus
+        | {{_ 'add-subtask'}}...
+
+template(name="subtasksDetail")
+  .js-subtasks.subtasks
+    +inlinedForm(classNames="js-edit-subtasks-title" subtasks = subtasks)
+      +editsubtasksItemForm(subtasks = subtasks)
+    else
+      .subtasks-title
+        span
+        if canModifyCard
+          a.js-delete-subtasks.toggle-delete-subtasks-dialog {{_ "delete"}}...
+
+        if canModifyCard
+          h2.title.js-open-inlined-form.is-editable
+            +viewer
+              = subtasks.title
+        else
+          h2.title
+            +viewer
+                = subtasks.title
+
+template(name="subtaskDeleteDialog")
+  .js-confirm-subtasks-delete
+    p
+      i(class="fa fa-exclamation-triangle" aria-hidden="true")
+    p
+      | {{_ 'confirm-subtask-delete-dialog'}}
+      span {{subtasks.title}}
+      | ?
+    .js-subtasks-delete-buttons
+      button.confirm-subtasks-delete(type="button") {{_ 'delete'}}
+      button.toggle-delete-subtasks-dialog(type="button") {{_ 'cancel'}}
+
+template(name="addSubtaskItemForm")
+  textarea.js-add-subtask-item(rows='1' autofocus)
+  .edit-controls.clearfix
+    button.primary.confirm.js-submit-add-subtask-item-form(type="submit") {{_ 'save'}}
+    a.fa.fa-times-thin.js-close-inlined-form
+
+template(name="editsubtasksItemForm")
+  textarea.js-edit-subtasks-item(rows='1' autofocus)
+    if $eq type 'item'
+      = item.title
+    else
+      = subtasks.title
+  .edit-controls.clearfix
+    button.primary.confirm.js-submit-edit-subtasks-item-form(type="submit") {{_ 'save'}}
+    a.fa.fa-times-thin.js-close-inlined-form
+    span(title=createdAt) {{ moment createdAt }}
+    if canModifyCard
+      a.js-delete-subtasks-item {{_ "delete"}}...
+
+template(name="subtasksItems")
+  .subtasks-items.js-subtasks-items
+    each item in subtasks.items
+      +inlinedForm(classNames="js-edit-subtasks-item" item = item subtasks = subtasks)
+        +editsubtasksItemForm(type = 'item' item = item subtasks = subtasks)
+      else
+        +subtaskItemDetail(item = item subtasks = subtasks)
+    if canModifyCard
+      +inlinedForm(autoclose=false classNames="js-add-subtask-item" subtasks = subtasks)
+        +addSubtaskItemForm
+      else
+        a.add-subtask-item.js-open-inlined-form
+          i.fa.fa-plus
+          | {{_ 'add-subtask-item'}}...
+
+template(name='subtaskItemDetail')
+  .js-subtasks-item.subtasks-item
+    if canModifyCard
+      .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
+      .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+        +viewer
+          = item.title
+    else
+      .materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
+      .item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
+        +viewer
+          = item.title

+ 166 - 0
client/components/cards/subtasks.js

@@ -0,0 +1,166 @@
+const { calculateIndexData } = Utils;
+
+function initSorting(items) {
+  items.sortable({
+    tolerance: 'pointer',
+    helper: 'clone',
+    items: '.js-subtasks-item:not(.placeholder)',
+    connectWith: '.js-subtasks-items',
+    appendTo: '.board-canvas',
+    distance: 7,
+    placeholder: 'subtasks-item placeholder',
+    scroll: false,
+    start(evt, ui) {
+      ui.placeholder.height(ui.helper.height());
+      EscapeActions.executeUpTo('popup-close');
+    },
+    stop(evt, ui) {
+      const parent = ui.item.parents('.js-subtasks-items');
+      const subtasksId = Blaze.getData(parent.get(0)).subtasks._id;
+      let prevItem = ui.item.prev('.js-subtasks-item').get(0);
+      if (prevItem) {
+        prevItem = Blaze.getData(prevItem).item;
+      }
+      let nextItem = ui.item.next('.js-subtasks-item').get(0);
+      if (nextItem) {
+        nextItem = Blaze.getData(nextItem).item;
+      }
+      const nItems = 1;
+      const sortIndex = calculateIndexData(prevItem, nextItem, nItems);
+      const subtasksDomElement = ui.item.get(0);
+      const subtasksData = Blaze.getData(subtasksDomElement);
+      const subtasksItem = subtasksData.item;
+
+      items.sortable('cancel');
+
+      subtasksItem.move(subtasksId, sortIndex.base);
+    },
+  });
+}
+
+BlazeComponent.extendComponent({
+  canModifyCard() {
+    return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
+  },
+}).register('subtasksDetail');
+
+BlazeComponent.extendComponent({
+
+  addSubtask(event) {
+    event.preventDefault();
+    const textarea = this.find('textarea.js-add-subtask-item');
+    const title = textarea.value.trim();
+    const cardId = this.currentData().cardId;
+    const card = Cards.findOne(cardId);
+
+    if (title) {
+      Subtasks.insert({
+        cardId,
+        title,
+        sort: card.subtasks().count(),
+      });
+      setTimeout(() => {
+        this.$('.add-subtask-item').last().click();
+      }, 100);
+    }
+    textarea.value = '';
+    textarea.focus();
+  },
+
+  canModifyCard() {
+    return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
+  },
+
+  deleteSubtask() {
+    const subtasks = this.currentData().subtasks;
+    if (subtasks && subtasks._id) {
+      Subtasks.remove(subtasks._id);
+      this.toggleDeleteDialog.set(false);
+    }
+  },
+
+  editSubtask(event) {
+    event.preventDefault();
+    const textarea = this.find('textarea.js-edit-subtasks-item');
+    const title = textarea.value.trim();
+    const subtasks = this.currentData().subtasks;
+    subtasks.setTitle(title);
+  },
+
+  onCreated() {
+    this.toggleDeleteDialog = new ReactiveVar(false);
+    this.subtasksToDelete = null; //Store data context to pass to subtaskDeleteDialog template
+  },
+
+  pressKey(event) {
+    //If user press enter key inside a form, submit it
+    //Unless the user is also holding down the 'shift' key
+    if (event.keyCode === 13 && !event.shiftKey) {
+      event.preventDefault();
+      const $form = $(event.currentTarget).closest('form');
+      $form.find('button[type=submit]').click();
+    }
+  },
+
+  events() {
+    const events = {
+      'click .toggle-delete-subtasks-dialog'(event) {
+        if($(event.target).hasClass('js-delete-subtasks')){
+          this.subtasksToDelete = this.currentData().subtasks; //Store data context
+        }
+        this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
+      },
+    };
+
+    return [{
+      ...events,
+      'submit .js-add-subtask': this.addSubtask,
+      'submit .js-edit-subtasks-title': this.editSubtask,
+      'click .confirm-subtasks-delete': this.deleteSubtask,
+      keydown: this.pressKey,
+    }];
+  },
+}).register('subtasks');
+
+Template.subtaskDeleteDialog.onCreated(() => {
+  const $cardDetails = this.$('.card-details');
+  this.scrollState = { position: $cardDetails.scrollTop(), //save current scroll position
+    top: false, //required for smooth scroll animation
+  };
+  //Callback's purpose is to only prevent scrolling after animation is complete
+  $cardDetails.animate({ scrollTop: 0 }, 500, () => { this.scrollState.top = true; });
+
+  //Prevent scrolling while dialog is open
+  $cardDetails.on('scroll', () => {
+    if(this.scrollState.top) { //If it's already in position, keep it there. Otherwise let animation scroll
+      $cardDetails.scrollTop(0);
+    }
+  });
+});
+
+Template.subtaskDeleteDialog.onDestroyed(() => {
+  const $cardDetails = this.$('.card-details');
+  $cardDetails.off('scroll'); //Reactivate scrolling
+  $cardDetails.animate( { scrollTop: this.scrollState.position });
+});
+
+Template.subtaskItemDetail.helpers({
+  canModifyCard() {
+    return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
+  },
+});
+
+BlazeComponent.extendComponent({
+  toggleItem() {
+    const subtasks = this.currentData().subtasks;
+    const item = this.currentData().item;
+    if (subtasks && item && item._id) {
+      item.toggleItem();
+    }
+  },
+  events() {
+    return [{
+      'click .js-subtasks-item .check-box': this.toggleItem,
+    }];
+  },
+}).register('subtaskItemDetail');

+ 139 - 0
client/components/cards/subtasks.styl

@@ -0,0 +1,139 @@
+.js-add-subtask
+  color: #8c8c8c
+
+textarea.js-add-subtask-item, textarea.js-edit-subtasks-item
+  overflow: hidden
+  word-wrap: break-word
+  resize: none
+  height: 34px
+
+.delete-text
+  color: #8c8c8c
+  text-decoration: underline
+  word-wrap: break-word
+  float: right
+  padding-top: 6px
+  &:hover
+    color: inherit
+
+.subtasks-title
+  .checkbox
+    float: left
+    width: 30px
+    height 30px
+    font-size: 18px
+    line-height: 30px
+
+  .title
+    font-size: 18px
+    line-height: 25px
+
+  .subtasks-stat
+    margin: 0 0.5em
+    float: right
+    padding-top: 6px
+    &.is-finished
+      color: #3cb500
+
+  .js-delete-subtasks
+    @extends .delete-text
+
+
+.js-confirm-subtasks-delete
+  background-color: darken(white, 3%)
+  position: absolute
+  float: left;
+  width: 60%
+  margin-top: 0
+  margin-left: 13%
+  padding-bottom: 2%
+  padding-left: 3%
+  padding-right: 3%
+  z-index: 17
+  border-radius: 3px
+
+  p
+    position: relative
+    margin-top: 3%
+    width: 100%
+    text-align: center
+    span
+      font-weight: bold
+
+    i
+      font-size: 2em
+
+  .js-subtasks-delete-buttons
+    position: relative
+    padding: left 2% right 2%
+    .confirm-subtasks-delete
+      margin-left: 12%
+      float: left
+    .toggle-delete-subtasks-dialog
+      margin-right: 12%
+      float: right
+
+#card-details-overlay
+  top: 0
+  bottom: -600px
+  right: 0
+
+.subtasks
+  background: darken(white, 3%)
+
+  &.placeholder
+    background: darken(white, 20%)
+    border-radius: 2px
+
+  &.ui-sortable-helper
+    box-shadow: -2px 2px 8px rgba(0, 0, 0, .3),
+                0 0 1px rgba(0, 0, 0, .5)
+    transform: rotate(4deg)
+    cursor: grabbing
+
+
+.subtasks-item
+  margin: 0 0 0 0.1em
+  line-height: 18px
+  font-size: 1.1em
+  margin-top: 3px
+  display: flex
+  background: darken(white, 3%)
+
+  &.placeholder
+    background: darken(white, 20%)
+    border-radius: 2px
+
+  &.ui-sortable-helper
+    box-shadow: -2px 2px 8px rgba(0, 0, 0, .3),
+                0 0 1px rgba(0, 0, 0, .5)
+    transform: rotate(4deg)
+    cursor: grabbing
+
+  &:hover
+    background-color: darken(white, 8%)
+
+  .check-box
+    margin: 0.1em 0 0 0;
+    &.is-checked
+      border-bottom: 2px solid #3cb500
+      border-right: 2px solid #3cb500
+
+  .item-title
+    flex: 1
+    padding-left: 10px;
+    &.is-checked
+      color: #8c8c8c
+      font-style: italic
+    & .viewer
+      p
+        margin-bottom: 2px
+
+.js-delete-subtasks-item
+  margin: 0 0 0.5em 1.33em
+  @extends .delete-text
+  padding: 12px 0 0 0
+
+.add-subtask-item
+  margin: 0.2em 0 0.5em 1.33em
+  display: inline-block

+ 5 - 0
i18n/en.i18n.json

@@ -2,6 +2,7 @@
     "accept": "Accept",
     "accept": "Accept",
     "act-activity-notify": "[Wekan] Activity Notification",
     "act-activity-notify": "[Wekan] Activity Notification",
     "act-addAttachment": "attached __attachment__ to __card__",
     "act-addAttachment": "attached __attachment__ to __card__",
+    "act-addSubtask": "added subtask __checklist__ to __card__",
     "act-addChecklist": "added checklist __checklist__ to __card__",
     "act-addChecklist": "added checklist __checklist__ to __card__",
     "act-addChecklistItem": "added __checklistItem__ to checklist __checklist__ on __card__",
     "act-addChecklistItem": "added __checklistItem__ to checklist __checklist__ on __card__",
     "act-addComment": "commented on __card__: __comment__",
     "act-addComment": "commented on __card__: __comment__",
@@ -41,6 +42,7 @@
     "activity-removed": "removed %s from %s",
     "activity-removed": "removed %s from %s",
     "activity-sent": "sent %s to %s",
     "activity-sent": "sent %s to %s",
     "activity-unjoined": "unjoined %s",
     "activity-unjoined": "unjoined %s",
+    "activity-subtask-added": "added subtask to %s",
     "activity-checklist-added": "added checklist to %s",
     "activity-checklist-added": "added checklist to %s",
     "activity-checklist-item-added": "added checklist item to '%s' in %s",
     "activity-checklist-item-added": "added checklist item to '%s' in %s",
     "add": "Add",
     "add": "Add",
@@ -48,6 +50,7 @@
     "add-board": "Add Board",
     "add-board": "Add Board",
     "add-card": "Add Card",
     "add-card": "Add Card",
     "add-swimlane": "Add Swimlane",
     "add-swimlane": "Add Swimlane",
+    "add-subtask": "Add Subtask",
     "add-checklist": "Add Checklist",
     "add-checklist": "Add Checklist",
     "add-checklist-item": "Add an item to checklist",
     "add-checklist-item": "Add an item to checklist",
     "add-cover": "Add Cover",
     "add-cover": "Add Cover",
@@ -140,6 +143,7 @@
     "changePasswordPopup-title": "Change Password",
     "changePasswordPopup-title": "Change Password",
     "changePermissionsPopup-title": "Change Permissions",
     "changePermissionsPopup-title": "Change Permissions",
     "changeSettingsPopup-title": "Change Settings",
     "changeSettingsPopup-title": "Change Settings",
+    "subtasks": "Subtasks",
     "checklists": "Checklists",
     "checklists": "Checklists",
     "click-to-star": "Click to star this board.",
     "click-to-star": "Click to star this board.",
     "click-to-unstar": "Click to unstar this board.",
     "click-to-unstar": "Click to unstar this board.",
@@ -162,6 +166,7 @@
     "comment-only": "Comment only",
     "comment-only": "Comment only",
     "comment-only-desc": "Can comment on cards only.",
     "comment-only-desc": "Can comment on cards only.",
     "computer": "Computer",
     "computer": "Computer",
+    "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask",
     "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist",
     "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist",
     "copy-card-link-to-clipboard": "Copy card link to clipboard",
     "copy-card-link-to-clipboard": "Copy card link to clipboard",
     "copyCardPopup-title": "Copy Card",
     "copyCardPopup-title": "Copy Card",

+ 3 - 0
models/activities.js

@@ -44,6 +44,9 @@ Activities.helpers({
   checklistItem() {
   checklistItem() {
     return ChecklistItems.findOne(this.checklistItemId);
     return ChecklistItems.findOne(this.checklistItemId);
   },
   },
+  subtasks() {
+    return Subtasks.findOne(this.subtaskId);
+  },
   customField() {
   customField() {
     return CustomFields.findOne(this.customFieldId);
     return CustomFields.findOne(this.customFieldId);
   },
   },

+ 24 - 0
models/cards.js

@@ -215,6 +215,27 @@ Cards.helpers({
     return this.checklistItemCount() !== 0;
     return this.checklistItemCount() !== 0;
   },
   },
 
 
+  subtasks() {
+    return Subtasks.find({cardId: this._id}, {sort: { sort: 1 } });
+  },
+
+  subtasksCount() {
+    return Subtasks.find({cardId: this._id}).count();
+  },
+
+  subtasksFinishedCount() {
+    return Subtasks.find({cardId: this._id, isFinished: true}).count();
+  },
+
+  subtasksFinished() {
+    const finishCount = this.subtasksFinishedCount();
+    return finishCount > 0 && this.subtasksCount() === finishCount;
+  },
+
+  hasSubtasks() {
+    return this.subtasksCount() !== 0;
+  },
+
   customFieldIndex(customFieldId) {
   customFieldIndex(customFieldId) {
     return _.pluck(this.customFields, '_id').indexOf(customFieldId);
     return _.pluck(this.customFields, '_id').indexOf(customFieldId);
   },
   },
@@ -513,6 +534,9 @@ function cardRemover(userId, doc) {
   Checklists.remove({
   Checklists.remove({
     cardId: doc._id,
     cardId: doc._id,
   });
   });
+  Subtasks.remove({
+    cardId: doc._id,
+  });
   CardComments.remove({
   CardComments.remove({
     cardId: doc._id,
     cardId: doc._id,
   });
   });

+ 2 - 0
models/export.js

@@ -58,9 +58,11 @@ class Exporter {
     result.activities = Activities.find(byBoard, noBoardId).fetch();
     result.activities = Activities.find(byBoard, noBoardId).fetch();
     result.checklists = [];
     result.checklists = [];
     result.checklistItems = [];
     result.checklistItems = [];
+    result.subtaskItems = [];
     result.cards.forEach((card) => {
     result.cards.forEach((card) => {
       result.checklists.push(...Checklists.find({ cardId: card._id }).fetch());
       result.checklists.push(...Checklists.find({ cardId: card._id }).fetch());
       result.checklistItems.push(...ChecklistItems.find({ cardId: card._id }).fetch());
       result.checklistItems.push(...ChecklistItems.find({ cardId: card._id }).fetch());
+      result.subtaskItems.push(...Subtasks.find({ cardId: card._id }).fetch());
     });
     });
 
 
     // [Old] for attachments we only export IDs and absolute url to original doc
     // [Old] for attachments we only export IDs and absolute url to original doc

+ 1 - 7
models/subtasks.js

@@ -41,13 +41,7 @@ Subtasks.attachSchema(new SimpleSchema({
 }));
 }));
 
 
 Subtasks.helpers({
 Subtasks.helpers({
-  isFinished() {
-    return 0 !== this.itemCount() && this.itemCount() === this.finishedCount();
-  },
-  itemIndex(itemId) {
-    const items = self.findOne({_id : this._id}).items;
-    return _.pluck(items, '_id').indexOf(itemId);
-  },
+  // ...
 });
 });
 
 
 Subtasks.allow({
 Subtasks.allow({

+ 1 - 0
server/publications/boards.js

@@ -103,6 +103,7 @@ Meteor.publishRelations('board', function(boardId) {
       this.cursor(Attachments.find({ cardId }));
       this.cursor(Attachments.find({ cardId }));
       this.cursor(Checklists.find({ cardId }));
       this.cursor(Checklists.find({ cardId }));
       this.cursor(ChecklistItems.find({ cardId }));
       this.cursor(ChecklistItems.find({ cardId }));
+      this.cursor(Subtasks.find({ cardId }));
     });
     });
 
 
     if (board.members) {
     if (board.members) {