summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2018-03-20 22:13:18 +0200
committerLauri Ojansivu <x@xet7.org>2018-03-20 22:13:18 +0200
commitec1630f8dd3719a15a17328687e5416aed82e874 (patch)
tree0752c6fe03c7776e9f8b9ed2fc437885b5b36325 /client
parent90f3f7ba163ef5d015bc473946b02c23e263a011 (diff)
parent5e5a5ed9be33fe020b76dfb1ab4b32aec0753a20 (diff)
downloadwekan-ec1630f8dd3719a15a17328687e5416aed82e874.tar.gz
wekan-ec1630f8dd3719a15a17328687e5416aed82e874.tar.bz2
wekan-ec1630f8dd3719a15a17328687e5416aed82e874.zip
Merge branch 'checklistItems' of https://github.com/andresmanelli/wekan into andresmanelli-checklistItems
Diffstat (limited to 'client')
-rw-r--r--client/components/cards/cardDetails.js46
-rw-r--r--client/components/cards/checklists.jade39
-rw-r--r--client/components/cards/checklists.js110
-rw-r--r--client/components/cards/checklists.styl82
-rw-r--r--client/lib/utils.js31
5 files changed, 208 insertions, 100 deletions
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index ab8a6288..77593a74 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -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() {
diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade
index 42fe3bd4..c79eb5aa 100644
--- a/client/components/cards/checklists.jade
+++ b/client/components/cards/checklists.jade
@@ -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}}")
diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js
index 5c0e3d2e..1f05aded 100644
--- a/client/components/cards/checklists.js
+++ b/client/components/cards/checklists.js
@@ -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();
+ }
- 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());
+ }
+ });
+ },
- // 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');
diff --git a/client/components/cards/checklists.styl b/client/components/cards/checklists.styl
index d4776397..7b35488f 100644
--- a/client/components/cards/checklists.styl
+++ b/client/components/cards/checklists.styl
@@ -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%)
+
+ &.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
- .item
- line-height: 25px
- font-size: 1.1em
- margin-top: 3px
- display: flex
- &:hover
- background-color: darken(white, 8%)
-
- .check-box
- margin-top: 5px
- &.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
-
- .js-delete-checklist-item
- @extends .delete-text
- padding: 12px 0 0 0
+ &:hover
+ background-color: darken(white, 8%)
+
+ .check-box
+ margin-top: 5px
+ &.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
+
+.js-delete-checklist-item
+ margin: 0 0 0.5em 1.33em
+ @extends .delete-text
+ padding: 12px 0 0 0
- .add-checklist-item
- padding-top: 0.5em
- display: inline-block
+.add-checklist-item
+ margin: 0 0 0.5em 1.33em
+ padding-top: 0.5em
+ display: inline-block
diff --git a/client/lib/utils.js b/client/lib/utils.js
index 9a9ff654..1f44c60d 100644
--- a/client/lib/utils.js
+++ b/client/lib/utils.js
@@ -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;