mirror of
https://github.com/wekan/wekan.git
synced 2025-04-22 21:17:18 -04:00
Merge branch 'devel'
This commit is contained in:
commit
578bdf9e67
13 changed files with 342 additions and 262 deletions
|
@ -131,6 +131,7 @@
|
|||
"AccountSettings": true,
|
||||
"Announcements": true,
|
||||
"Swimlanes": true,
|
||||
"ChecklistItems": true,
|
||||
"Npm": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
# Upcoming Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
||||
- [Checklist items sort fix, and checklist sort capability](https://github.com/wekan/wekan/pull/1543).
|
||||
|
||||
Thanks to GitHub user andresmanelli for contributions.
|
||||
|
||||
# v0.78 2018-03-17 Wekan release
|
||||
|
||||
This release adds the following new features:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const subManager = new SubsManager();
|
||||
const { calculateIndexData } = Utils;
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
mixins() {
|
||||
|
@ -66,6 +67,51 @@ BlazeComponent.extendComponent({
|
|||
|
||||
onRendered() {
|
||||
if (!Utils.isMiniScreen()) this.scrollParentContainer();
|
||||
const $checklistsDom = this.$('.card-checklist-items');
|
||||
|
||||
$checklistsDom.sortable({
|
||||
tolerance: 'pointer',
|
||||
helper: 'clone',
|
||||
handle: '.checklist-title',
|
||||
items: '.js-checklist',
|
||||
placeholder: 'checklist placeholder',
|
||||
distance: 7,
|
||||
start(evt, ui) {
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
EscapeActions.executeUpTo('popup-close');
|
||||
},
|
||||
stop(evt, ui) {
|
||||
let prevChecklist = ui.item.prev('.js-checklist').get(0);
|
||||
if (prevChecklist) {
|
||||
prevChecklist = Blaze.getData(prevChecklist).checklist;
|
||||
}
|
||||
let nextChecklist = ui.item.next('.js-checklist').get(0);
|
||||
if (nextChecklist) {
|
||||
nextChecklist = Blaze.getData(nextChecklist).checklist;
|
||||
}
|
||||
const sortIndex = calculateIndexData(prevChecklist, nextChecklist, 1);
|
||||
|
||||
$checklistsDom.sortable('cancel');
|
||||
const checklist = Blaze.getData(ui.item.get(0)).checklist;
|
||||
|
||||
Checklists.update(checklist._id, {
|
||||
$set: {
|
||||
sort: sortIndex.base,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function userIsMember() {
|
||||
return Meteor.user() && Meteor.user().isBoardMember();
|
||||
}
|
||||
|
||||
// Disable sorting if the current user is not a board member
|
||||
this.autorun(() => {
|
||||
if ($checklistsDom.data('sortable')) {
|
||||
$checklistsDom.sortable('option', 'disabled', !userIsMember());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onDestroyed() {
|
||||
|
|
|
@ -18,24 +18,25 @@ template(name="checklists")
|
|||
| {{_ 'add-checklist'}}...
|
||||
|
||||
template(name="checklistDetail")
|
||||
+inlinedForm(classNames="js-edit-checklist-title" checklist = checklist)
|
||||
+editChecklistItemForm(checklist = checklist)
|
||||
else
|
||||
.checklist-title
|
||||
.checkbox.fa.fa-check-square-o
|
||||
if canModifyCard
|
||||
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
|
||||
h2.title.js-open-inlined-form.is-editable
|
||||
+viewer
|
||||
= checklist.title
|
||||
else
|
||||
h2.title
|
||||
+viewer
|
||||
.js-checklist.checklist
|
||||
+inlinedForm(classNames="js-edit-checklist-title" checklist = checklist)
|
||||
+editChecklistItemForm(checklist = checklist)
|
||||
else
|
||||
.checklist-title
|
||||
.checkbox.fa.fa-check-square-o
|
||||
if canModifyCard
|
||||
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
|
||||
h2.title.js-open-inlined-form.is-editable
|
||||
+viewer
|
||||
= checklist.title
|
||||
+checklistItems(checklist = checklist)
|
||||
else
|
||||
h2.title
|
||||
+viewer
|
||||
= checklist.title
|
||||
+checklistItems(checklist = checklist)
|
||||
|
||||
template(name="checklistDeleteDialog")
|
||||
.js-confirm-checklist-delete
|
||||
|
@ -70,7 +71,7 @@ template(name="editChecklistItemForm")
|
|||
|
||||
template(name="checklistItems")
|
||||
.checklist-items.js-checklist-items
|
||||
each item in checklist.getItemsSorted
|
||||
each item in checklist.items
|
||||
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
|
||||
+editChecklistItemForm(type = 'item' item = item checklist = checklist)
|
||||
else
|
||||
|
@ -84,7 +85,7 @@ template(name="checklistItems")
|
|||
| {{_ 'add-checklist-item'}}...
|
||||
|
||||
template(name='itemDetail')
|
||||
.item.js-checklist-item
|
||||
.js-checklist-item.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}}")
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
const { calculateIndexData } = Utils;
|
||||
|
||||
function initSorting(items) {
|
||||
items.sortable({
|
||||
tolerance: 'pointer',
|
||||
helper: 'clone',
|
||||
items: '.js-checklist-item:not(.placeholder)',
|
||||
axis: 'y',
|
||||
connectWith: '.js-checklist-items',
|
||||
appendTo: '.board-canvas',
|
||||
distance: 7,
|
||||
placeholder: 'placeholder',
|
||||
placeholder: 'checklist-item placeholder',
|
||||
scroll: false,
|
||||
start(evt, ui) {
|
||||
ui.placeholder.height(ui.helper.height());
|
||||
|
@ -13,57 +16,54 @@ function initSorting(items) {
|
|||
},
|
||||
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');
|
||||
const 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++) {
|
||||
const itemId = orderedItems[i];
|
||||
if (itemId !== currentItem._id) continue;
|
||||
const newItem = {
|
||||
_id: checklist.getNewItemId(),
|
||||
title: currentItem.title,
|
||||
sort: i,
|
||||
isFinished: currentItem.isFinished,
|
||||
};
|
||||
checklist.addFullItem(newItem);
|
||||
orderedItems[i] = currentItem._id;
|
||||
oldChecklist.removeItem(itemId);
|
||||
}
|
||||
} else {
|
||||
checklist.sortItems(orderedItems);
|
||||
const checklistId = Blaze.getData(parent.get(0)).checklist._id;
|
||||
let prevItem = ui.item.prev('.js-checklist-item').get(0);
|
||||
if (prevItem) {
|
||||
prevItem = Blaze.getData(prevItem).item;
|
||||
}
|
||||
let nextItem = ui.item.next('.js-checklist-item').get(0);
|
||||
if (nextItem) {
|
||||
nextItem = Blaze.getData(nextItem).item;
|
||||
}
|
||||
const nItems = 1;
|
||||
const sortIndex = calculateIndexData(prevItem, nextItem, nItems);
|
||||
const checklistDomElement = ui.item.get(0);
|
||||
const checklistData = Blaze.getData(checklistDomElement);
|
||||
const checklistItem = checklistData.item;
|
||||
|
||||
items.sortable('cancel');
|
||||
|
||||
checklistItem.move(checklistId, sortIndex.base);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
BlazeComponent.extendComponent({
|
||||
onRendered() {
|
||||
const self = this;
|
||||
self.itemsDom = this.$('.js-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());
|
||||
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());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
canModifyCard() {
|
||||
return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
|
||||
},
|
||||
}).register('checklistDetail');
|
||||
|
||||
BlazeComponent.extendComponent({
|
||||
|
||||
|
@ -95,7 +95,12 @@ BlazeComponent.extendComponent({
|
|||
const checklist = this.currentData().checklist;
|
||||
|
||||
if (title) {
|
||||
checklist.addItem(title);
|
||||
ChecklistItems.insert({
|
||||
title,
|
||||
checklistId: checklist._id,
|
||||
cardId: checklist.cardId,
|
||||
sort: checklist.itemCount(),
|
||||
});
|
||||
}
|
||||
// We keep the form opened, empty it.
|
||||
textarea.value = '';
|
||||
|
@ -118,7 +123,7 @@ BlazeComponent.extendComponent({
|
|||
const checklist = this.currentData().checklist;
|
||||
const item = this.currentData().item;
|
||||
if (checklist && item && item._id) {
|
||||
checklist.removeItem(item._id);
|
||||
ChecklistItems.remove(item._id);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -135,9 +140,8 @@ BlazeComponent.extendComponent({
|
|||
|
||||
const textarea = this.find('textarea.js-edit-checklist-item');
|
||||
const title = textarea.value.trim();
|
||||
const itemId = this.currentData().item._id;
|
||||
const checklist = this.currentData().checklist;
|
||||
checklist.editItem(itemId, title);
|
||||
const item = this.currentData().item;
|
||||
item.setTitle(title);
|
||||
},
|
||||
|
||||
onCreated() {
|
||||
|
@ -211,12 +215,12 @@ BlazeComponent.extendComponent({
|
|||
const checklist = this.currentData().checklist;
|
||||
const item = this.currentData().item;
|
||||
if (checklist && item && item._id) {
|
||||
checklist.toggleItem(item._id);
|
||||
item.toggleItem();
|
||||
}
|
||||
},
|
||||
events() {
|
||||
return [{
|
||||
'click .item .check-box': this.toggleItem,
|
||||
'click .js-checklist-item .check-box': this.toggleItem,
|
||||
}];
|
||||
},
|
||||
}).register('itemDetail');
|
||||
|
|
|
@ -78,34 +78,60 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
|
|||
bottom: -600px
|
||||
right: 0
|
||||
|
||||
.checklist-items
|
||||
.checklist
|
||||
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
|
||||
|
||||
|
||||
.checklist-item
|
||||
margin: 0 0 0.5em 1.33em
|
||||
line-height: 25px
|
||||
font-size: 1.1em
|
||||
margin-top: 3px
|
||||
display: flex
|
||||
background: darken(white, 3%)
|
||||
|
||||
.item
|
||||
line-height: 25px
|
||||
font-size: 1.1em
|
||||
margin-top: 3px
|
||||
display: flex
|
||||
&:hover
|
||||
background-color: darken(white, 8%)
|
||||
&.placeholder
|
||||
background: darken(white, 20%)
|
||||
border-radius: 2px
|
||||
|
||||
.check-box
|
||||
margin-top: 5px
|
||||
&.is-checked
|
||||
border-bottom: 2px solid #3cb500
|
||||
border-right: 2px solid #3cb500
|
||||
&.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
|
||||
|
||||
.item-title
|
||||
flex: 1
|
||||
padding-left: 10px;
|
||||
&.is-checked
|
||||
color: #8c8c8c
|
||||
font-style: italic
|
||||
&:hover
|
||||
background-color: darken(white, 8%)
|
||||
|
||||
.js-delete-checklist-item
|
||||
@extends .delete-text
|
||||
padding: 12px 0 0 0
|
||||
.check-box
|
||||
margin-top: 5px
|
||||
&.is-checked
|
||||
border-bottom: 2px solid #3cb500
|
||||
border-right: 2px solid #3cb500
|
||||
|
||||
.add-checklist-item
|
||||
padding-top: 0.5em
|
||||
display: inline-block
|
||||
.item-title
|
||||
flex: 1
|
||||
padding-left: 10px;
|
||||
&.is-checked
|
||||
color: #8c8c8c
|
||||
font-style: italic
|
||||
|
||||
.js-delete-checklist-item
|
||||
margin: 0 0 0.5em 1.33em
|
||||
@extends .delete-text
|
||||
padding: 12px 0 0 0
|
||||
|
||||
.add-checklist-item
|
||||
margin: 0 0 0.5em 1.33em
|
||||
padding-top: 0.5em
|
||||
display: inline-block
|
||||
|
|
|
@ -33,6 +33,37 @@ Utils = {
|
|||
return $(window).width() <= 800;
|
||||
},
|
||||
|
||||
calculateIndexData(prevData, nextData, nItems = 1) {
|
||||
let base, increment;
|
||||
// If we drop the card to an empty column
|
||||
if (!prevData && !nextData) {
|
||||
base = 0;
|
||||
increment = 1;
|
||||
// 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
|
||||
} else if (!nextData) {
|
||||
base = prevData.sort + 1;
|
||||
increment = 1;
|
||||
}
|
||||
// In the general case take the average of the previous and next element
|
||||
// sort indexes.
|
||||
else {
|
||||
const prevSortIndex = prevData.sort;
|
||||
const nextSortIndex = nextData.sort;
|
||||
increment = (nextSortIndex - prevSortIndex) / (nItems + 1);
|
||||
base = prevSortIndex + increment;
|
||||
}
|
||||
// XXX Return a generator that yield values instead of a base with a
|
||||
// increment number.
|
||||
return {
|
||||
base,
|
||||
increment,
|
||||
};
|
||||
},
|
||||
|
||||
// Determine the new sort index
|
||||
calculateIndex(prevCardDomElement, nextCardDomElement, nCards = 1) {
|
||||
let base, increment;
|
||||
|
|
|
@ -42,7 +42,7 @@ Activities.helpers({
|
|||
return Checklists.findOne(this.checklistId);
|
||||
},
|
||||
checklistItem() {
|
||||
return Checklists.findOne(this.checklistId).getItem(this.checklistItemId);
|
||||
return ChecklistItems.findOne(this.checklistItemId);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ Cards.helpers({
|
|||
},
|
||||
|
||||
checklists() {
|
||||
return Checklists.find({cardId: this._id}, {sort: {createdAt: 1}});
|
||||
return Checklists.find({cardId: this._id}, {sort: { sort: 1 } });
|
||||
},
|
||||
|
||||
checklistItemCount() {
|
||||
|
|
95
models/checklistItems.js
Normal file
95
models/checklistItems.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
ChecklistItems = new Mongo.Collection('checklistItems');
|
||||
|
||||
ChecklistItems.attachSchema(new SimpleSchema({
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
sort: {
|
||||
type: Number,
|
||||
decimal: true,
|
||||
},
|
||||
isFinished: {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
},
|
||||
checklistId: {
|
||||
type: String,
|
||||
},
|
||||
cardId: {
|
||||
type: String,
|
||||
},
|
||||
}));
|
||||
|
||||
ChecklistItems.allow({
|
||||
insert(userId, doc) {
|
||||
return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
|
||||
},
|
||||
update(userId, doc) {
|
||||
return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
|
||||
},
|
||||
remove(userId, doc) {
|
||||
return allowIsBoardMemberByCard(userId, Cards.findOne(doc.cardId));
|
||||
},
|
||||
fetch: ['userId', 'cardId'],
|
||||
});
|
||||
|
||||
ChecklistItems.before.insert((userId, doc) => {
|
||||
if (!doc.userId) {
|
||||
doc.userId = userId;
|
||||
}
|
||||
});
|
||||
|
||||
// Mutations
|
||||
ChecklistItems.mutations({
|
||||
setTitle(title) {
|
||||
return { $set: { title } };
|
||||
},
|
||||
toggleItem() {
|
||||
return { $set: { isFinished: !this.isFinished } };
|
||||
},
|
||||
move(checklistId, sortIndex) {
|
||||
const cardId = Checklists.findOne(checklistId).cardId;
|
||||
const mutatedFields = {
|
||||
cardId,
|
||||
checklistId,
|
||||
sort: sortIndex,
|
||||
};
|
||||
|
||||
return {$set: mutatedFields};
|
||||
},
|
||||
});
|
||||
|
||||
// Activities helper
|
||||
function itemCreation(userId, doc) {
|
||||
const card = Cards.findOne(doc.cardId);
|
||||
const boardId = card.boardId;
|
||||
Activities.insert({
|
||||
userId,
|
||||
activityType: 'addChecklistItem',
|
||||
cardId: doc.cardId,
|
||||
boardId,
|
||||
checklistId: doc.checklistId,
|
||||
checklistItemId: doc._id,
|
||||
});
|
||||
}
|
||||
|
||||
function itemRemover(userId, doc) {
|
||||
Activities.remove({
|
||||
checklistItemId: doc._id,
|
||||
});
|
||||
}
|
||||
|
||||
// Activities
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
ChecklistItems._collection._ensureIndex({ checklistId: 1 });
|
||||
});
|
||||
|
||||
ChecklistItems.after.insert((userId, doc) => {
|
||||
itemCreation(userId, doc);
|
||||
});
|
||||
|
||||
ChecklistItems.after.remove((userId, doc) => {
|
||||
itemRemover(userId, doc);
|
||||
});
|
||||
}
|
|
@ -7,24 +7,6 @@ Checklists.attachSchema(new SimpleSchema({
|
|||
title: {
|
||||
type: String,
|
||||
},
|
||||
items: {
|
||||
type: [Object],
|
||||
defaultValue: [],
|
||||
},
|
||||
'items.$._id': {
|
||||
type: String,
|
||||
},
|
||||
'items.$.title': {
|
||||
type: String,
|
||||
},
|
||||
'items.$.sort': {
|
||||
type: Number,
|
||||
decimal: true,
|
||||
},
|
||||
'items.$.isFinished': {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
},
|
||||
finishedAt: {
|
||||
type: Date,
|
||||
optional: true,
|
||||
|
@ -46,40 +28,28 @@ Checklists.attachSchema(new SimpleSchema({
|
|||
},
|
||||
}));
|
||||
|
||||
const self = Checklists;
|
||||
|
||||
Checklists.helpers({
|
||||
itemCount() {
|
||||
return this.items.length;
|
||||
return ChecklistItems.find({ checklistId: this._id }).count();
|
||||
},
|
||||
getItemsSorted() {
|
||||
return _.sortBy(this.items, 'sort');
|
||||
items() {
|
||||
return ChecklistItems.find(Filter.mongoSelector({
|
||||
checklistId: this._id,
|
||||
}), { sort: ['sort'] });
|
||||
},
|
||||
finishedCount() {
|
||||
return this.items.filter((item) => {
|
||||
return item.isFinished;
|
||||
}).length;
|
||||
return ChecklistItems.find({
|
||||
checklistId: this._id,
|
||||
isFinished: true,
|
||||
}).count();
|
||||
},
|
||||
isFinished() {
|
||||
return 0 !== this.itemCount() && this.itemCount() === this.finishedCount();
|
||||
},
|
||||
getItem(_id) {
|
||||
return _.findWhere(this.items, { _id });
|
||||
},
|
||||
itemIndex(itemId) {
|
||||
const items = self.findOne({_id : this._id}).items;
|
||||
return _.pluck(items, '_id').indexOf(itemId);
|
||||
},
|
||||
getNewItemId() {
|
||||
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;
|
||||
}
|
||||
return `${this._id}${idx}`;
|
||||
},
|
||||
});
|
||||
|
||||
Checklists.allow({
|
||||
|
@ -103,108 +73,9 @@ Checklists.before.insert((userId, doc) => {
|
|||
});
|
||||
|
||||
Checklists.mutations({
|
||||
//for checklist itself
|
||||
setTitle(title) {
|
||||
return { $set: { title } };
|
||||
},
|
||||
//for items in checklist
|
||||
addItem(title) {
|
||||
const _id = this.getNewItemId();
|
||||
return {
|
||||
$addToSet: {
|
||||
items: {
|
||||
_id, title,
|
||||
isFinished: false,
|
||||
sort: this.itemCount(),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
addFullItem(item) {
|
||||
const itemsUpdate = {};
|
||||
this.items.forEach(function(iterItem, index) {
|
||||
if (iterItem.sort >= item.sort) {
|
||||
itemsUpdate[`items.${index}.sort`] = iterItem.sort + 1;
|
||||
}
|
||||
});
|
||||
if (!_.isEmpty(itemsUpdate)) {
|
||||
self.direct.update({ _id: this._id }, { $set: itemsUpdate });
|
||||
}
|
||||
return { $addToSet: { items: item } };
|
||||
},
|
||||
removeItem(itemId) {
|
||||
const item = this.getItem(itemId);
|
||||
const itemsUpdate = {};
|
||||
this.items.forEach(function(iterItem, index) {
|
||||
if (iterItem.sort > item.sort) {
|
||||
itemsUpdate[`items.${index}.sort`] = iterItem.sort - 1;
|
||||
}
|
||||
});
|
||||
if (!_.isEmpty(itemsUpdate)) {
|
||||
self.direct.update({ _id: this._id }, { $set: itemsUpdate });
|
||||
}
|
||||
return { $pull: { items: { _id: itemId } } };
|
||||
},
|
||||
editItem(itemId, title) {
|
||||
if (this.getItem(itemId)) {
|
||||
const itemIndex = this.itemIndex(itemId);
|
||||
return {
|
||||
$set: {
|
||||
[`items.${itemIndex}.title`]: title,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
finishItem(itemId) {
|
||||
if (this.getItem(itemId)) {
|
||||
const itemIndex = this.itemIndex(itemId);
|
||||
return {
|
||||
$set: {
|
||||
[`items.${itemIndex}.isFinished`]: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
resumeItem(itemId) {
|
||||
if (this.getItem(itemId)) {
|
||||
const itemIndex = this.itemIndex(itemId);
|
||||
return {
|
||||
$set: {
|
||||
[`items.${itemIndex}.isFinished`]: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
toggleItem(itemId) {
|
||||
const item = this.getItem(itemId);
|
||||
if (item) {
|
||||
const itemIndex = this.itemIndex(itemId);
|
||||
return {
|
||||
$set: {
|
||||
[`items.${itemIndex}.isFinished`]: !item.isFinished,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
sortItems(itemIDs) {
|
||||
const validItems = [];
|
||||
itemIDs.forEach((itemID) => {
|
||||
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) {
|
||||
|
@ -222,30 +93,6 @@ if (Meteor.isServer) {
|
|||
});
|
||||
});
|
||||
|
||||
//TODO: so there will be no activity for adding item into checklist, maybe will be implemented in the future.
|
||||
// The future is now
|
||||
Checklists.after.update((userId, doc, fieldNames, modifier) => {
|
||||
if (fieldNames.includes('items')) {
|
||||
if (modifier.$addToSet) {
|
||||
Activities.insert({
|
||||
userId,
|
||||
activityType: 'addChecklistItem',
|
||||
cardId: doc.cardId,
|
||||
boardId: Cards.findOne(doc.cardId).boardId,
|
||||
checklistId: doc._id,
|
||||
checklistItemId: modifier.$addToSet.items._id,
|
||||
});
|
||||
} else if (modifier.$pull) {
|
||||
const activity = Activities.findOne({
|
||||
checklistItemId: modifier.$pull.items._id,
|
||||
});
|
||||
if (activity) {
|
||||
Activities.remove(activity._id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Checklists.before.remove((userId, doc) => {
|
||||
const activities = Activities.find({ checklistId: doc._id });
|
||||
if (activities) {
|
||||
|
@ -256,7 +103,6 @@ if (Meteor.isServer) {
|
|||
});
|
||||
}
|
||||
|
||||
//CARD COMMENT REST API
|
||||
if (Meteor.isServer) {
|
||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) {
|
||||
try {
|
||||
|
|
|
@ -187,3 +187,24 @@ Migrations.add('add-views', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
Migrations.add('add-checklist-items', () => {
|
||||
Checklists.find().forEach((checklist) => {
|
||||
// Create new items
|
||||
_.sortBy(checklist.items, 'sort').forEach((item, index) => {
|
||||
ChecklistItems.direct.insert({
|
||||
title: item.title,
|
||||
sort: index,
|
||||
isFinished: item.isFinished,
|
||||
checklistId: checklist._id,
|
||||
cardId: checklist.cardId,
|
||||
});
|
||||
});
|
||||
|
||||
// Delete old ones
|
||||
Checklists.direct.update({ _id: checklist._id },
|
||||
{ $unset: { items : 1 } },
|
||||
noValidate
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -101,6 +101,7 @@ Meteor.publishRelations('board', function(boardId) {
|
|||
this.cursor(CardComments.find({ cardId }));
|
||||
this.cursor(Attachments.find({ cardId }));
|
||||
this.cursor(Checklists.find({ cardId }));
|
||||
this.cursor(ChecklistItems.find({ cardId }));
|
||||
});
|
||||
|
||||
if (board.members) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue