Browse Source

Merge branch 'frmwrk123-issue-876' into devel

Reorder checklists. Move checklist item to another checklist.
Thanks to frmwrk123 ! Related #876
Lauri Ojansivu 8 years ago
parent
commit
68897a77dc
4 changed files with 126 additions and 13 deletions
  1. 8 0
      CHANGELOG.md
  2. 3 4
      client/components/cards/checklists.jade
  3. 67 0
      client/components/cards/checklists.js
  4. 48 9
      models/checklists.js

+ 8 - 0
CHANGELOG.md

@@ -1,3 +1,11 @@
+# Upcoming Wekan release
+
+This release adds the following new features:
+
+* [Reorder checklists. Move checklist item to another checklist.](https://github.com/wekan/wekan/pull/1215)
+
+Thanks to GitHub user frmwrk123 for contributions.
+
 # v0.37 2017-09-09 Wekan release
 
 This release adds the following new features:

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

@@ -46,8 +46,8 @@ template(name="editChecklistItemForm")
       a.js-delete-checklist-item {{_ "delete"}}...
 
 template(name="checklistItems")
-  .checklist-items
-    each item in checklist.items
+  .checklist-items.js-checklist-items
+    each item in checklist.getItems
       +inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
         +editChecklistItemForm(type = 'item' item = item checklist = checklist)
       else
@@ -61,11 +61,10 @@ template(name="checklistItems")
           | {{_ 'add-checklist-item'}}...
 
 template(name='itemDetail')
-  .item
+  .item.js-checklist-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}}") {{item.title}}
     else
       .materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
       .item-title(class="{{#if item.isFinished }}is-checked{{/if}}") {{item.title}}
-

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

@@ -1,14 +1,81 @@
+function initSorting(items) {
+  items.sortable({
+    tolerance: 'pointer',
+    helper: 'clone',
+    items: '.js-checklist-item:not(.placeholder)',
+    axis: 'y',
+    distance: 7,
+    placeholder: '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-checklist-items');
+      const orderedItems = [];
+      parent.find('.js-checklist-item').each(function(i, item) {
+        const checklistItem = Blaze.getData(item).item;
+        orderedItems.push(checklistItem._id);
+      });
+      items.sortable('cancel');
+      const formerParent = ui.item.parents('.js-checklist-items');
+      let checklist = Blaze.getData(parent.get(0)).checklist;
+      const oldChecklist = Blaze.getData(formerParent.get(0)).checklist;
+      if (oldChecklist._id !== checklist._id) {
+        const currentItem = Blaze.getData(ui.item.get(0)).item;
+        for (let i = 0; i < orderedItems.length; i++) {
+          let itemId = orderedItems[i];
+          if (itemId !== currentItem._id) continue;
+          checklist.addItem(currentItem.title);
+          checklist = Checklists.findOne({_id: checklist._id});
+          itemId = checklist._id + (checklist.newItemIndex - 1);
+          if (currentItem.finished) {
+            checklist.finishItem(itemId);
+          }
+          orderedItems[i] = itemId;
+          oldChecklist.removeItem(currentItem._id);
+        }
+      }
+      checklist.sortItems(orderedItems);
+    },
+  });
+}
+
+Template.checklists.onRendered(function () {
+  const self = BlazeComponent.getComponentForElement(this.firstNode);
+  self.itemsDom = this.$('.card-checklist-items');
+  initSorting(self.itemsDom);
+  self.itemsDom.mousedown(function(evt) {
+    evt.stopPropagation();
+  });
+
+  function userIsMember() {
+    return Meteor.user() && Meteor.user().isBoardMember();
+  }
+
+  // Disable sorting if the current user is not a board member
+  self.autorun(() => {
+    const $itemsDom = $(self.itemsDom);
+    if ($itemsDom.data('sortable')) {
+      $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
+    }
+  });
+});
+
 BlazeComponent.extendComponent({
   addChecklist(event) {
     event.preventDefault();
     const textarea = this.find('textarea.js-add-checklist-item');
     const title = textarea.value.trim();
     const cardId = this.currentData().cardId;
+    const card = Cards.findOne(cardId);
 
     if (title) {
       Checklists.insert({
         cardId,
         title,
+        sort: card.checklists().count(),
       });
       setTimeout(() => {
         this.$('.add-checklist-item').last().click();

+ 48 - 9
models/checklists.js

@@ -17,6 +17,10 @@ Checklists.attachSchema(new SimpleSchema({
   'items.$.title': {
     type: String,
   },
+  'items.$.sort': {
+    type: Number,
+    decimal: true,
+  },
   'items.$.isFinished': {
     type: Boolean,
     defaultValue: false,
@@ -36,12 +40,34 @@ Checklists.attachSchema(new SimpleSchema({
       }
     },
   },
+  sort: {
+    type: Number,
+    decimal: true,
+  },
+  newItemIndex: {
+    type: Number,
+    decimal: true,
+    defaultValue: 0,
+  },
 }));
 
+const self = Checklists;
+
 Checklists.helpers({
   itemCount() {
     return this.items.length;
   },
+  getItems() {
+    return this.items.sort(function (itemA, itemB) {
+      if (itemA.sort < itemB.sort) {
+        return -1;
+      }
+      if (itemA.sort > itemB.sort) {
+        return 1;
+      }
+      return 0;
+    });
+  },
   finishedCount() {
     return this.items.filter((item) => {
       return item.isFinished;
@@ -54,7 +80,8 @@ Checklists.helpers({
     return _.findWhere(this.items, { _id });
   },
   itemIndex(itemId) {
-    return _.pluck(this.items, '_id').indexOf(itemId);
+    const items = self.findOne({_id : this._id}).items;
+    return _.pluck(items, '_id').indexOf(itemId);
   },
 });
 
@@ -86,14 +113,11 @@ Checklists.mutations({
   //for items in checklist
   addItem(title) {
     const itemCount = this.itemCount();
-    let idx = 0;
-    if (itemCount > 0) {
-      const lastId = this.items[itemCount - 1]._id;
-      const lastIdSuffix = lastId.substr(this._id.length);
-      idx = parseInt(lastIdSuffix, 10) + 1;
-    }
-    const _id = `${this._id}${idx}`;
-    return { $addToSet: { items: { _id, title, isFinished: false } } };
+    const _id = `${this._id}${this.newItemIndex}`;
+    return {
+      $addToSet: { items: { _id, title, isFinished: false, sort: itemCount } },
+      $set: { newItemIndex: this.newItemIndex + 1},
+    };
   },
   removeItem(itemId) {
     return { $pull: { items: { _id: itemId } } };
@@ -143,6 +167,21 @@ Checklists.mutations({
     }
     return {};
   },
+  sortItems(itemIDs) {
+    const validItems = [];
+    for (const itemID of itemIDs) {
+      if (this.getItem(itemID)) {
+        validItems.push(this.itemIndex(itemID));
+      }
+    }
+    const modifiedValues = {};
+    for (let i = 0; i < validItems.length; i++) {
+      modifiedValues[`items.${validItems[i]}.sort`] = i;
+    }
+    return {
+      $set: modifiedValues,
+    };
+  },
 });
 
 if (Meteor.isServer) {