Implement multi-selection

The UI and the internal APIs are still rough around the edges but the
feature is basically working. You can now select multiple cards and
move them together or (un|)assign them a label.
This commit is contained in:
Maxime Quandalle 2015-05-29 23:35:30 +02:00
parent 6457615e6a
commit 2c0030da62
45 changed files with 883 additions and 933 deletions

View file

@ -10,13 +10,12 @@ template(name="listBody")
+inlinedForm(autoclose=false position="bottom")
+addCardForm(listId=_id position="bottom")
else
if newCardFormIsVisible.get
a.open-card-composer.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-card'}}
a.open-minicard-composer.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-card'}}
template(name="addCardForm")
.minicard.js-composer
.minicard.minicard-composer.js-composer
.minicard-labels.js-minicard-composer-labels
.minicard-details.clearfix
textarea.minicard-composer-textarea.js-card-title(autofocus)

View file

@ -34,18 +34,17 @@ BlazeComponent.extendComponent({
}
if ($.trim(title)) {
Cards.insert({
var _id = Cards.insert({
title: title,
listId: this.data()._id,
boardId: this.data().board()._id,
sort: sortIndex
}, function(err, _id) {
// In case the filter is active we need to add the newly inserted card
// in the list of exceptions -- cards that are not filtered. Otherwise
// the card will disappear instantly.
// See https://github.com/libreboard/libreboard/issues/80
Filter.addException(_id);
});
// In case the filter is active we need to add the newly inserted card in
// the list of exceptions -- cards that are not filtered. Otherwise the
// card will disappear instantly.
// See https://github.com/libreboard/libreboard/issues/80
Filter.addException(_id);
// We keep the form opened, empty it, and scroll to it.
textarea.val('').focus();
@ -55,10 +54,6 @@ BlazeComponent.extendComponent({
}
},
showNewCardForm: function(value) {
this.newCardFormIsVisible.set(value);
},
scrollToBottom: function() {
var container = this.firstNode();
$(container).animate({
@ -66,14 +61,10 @@ BlazeComponent.extendComponent({
});
},
onCreated: function() {
this.newCardFormIsVisible = new ReactiveVar(true);
},
events: function() {
return [{
submit: this.addCard,
'click .open-card-composer': this.scrollToBottom
'click .open-minicard-composer': this.scrollToBottom
}];
}
}).register('listBody');

View file

@ -8,10 +8,6 @@ BlazeComponent.extendComponent({
this.componentChildren('listBody')[0].openForm(options);
},
showNewCardForm: function(value) {
this.componentChildren('listBody')[0].showNewCardForm(value);
},
onCreated: function() {
this.newCardFormIsVisible = new ReactiveVar(true);
},
@ -35,30 +31,59 @@ BlazeComponent.extendComponent({
connectWith: '.js-minicards',
tolerance: 'pointer',
appendTo: '.js-lists',
helper: 'clone',
items: itemsSelector,
placeholder: 'minicard placeholder',
start: function(event, ui) {
ui.placeholder.height(ui.helper.height());
Popup.close();
boardComponent.showNewCardForms(false);
helper: function(evt, item) {
var helper = item.clone();
if (MultiSelection.isActive()) {
var andNOthers = $cards.find('.js-minicard.is-checked').length - 1;
if (andNOthers > 0) {
helper.append($(Blaze.toHTML(HTML.DIV(
// XXX Super bad class name
{'class': 'and-n-other'},
// XXX Need to translate
'and ' + andNOthers + ' other cards.'
))));
}
}
return helper;
},
stop: function(event, ui) {
items: itemsSelector,
placeholder: 'minicard-wrapper placeholder',
start: function(evt, ui) {
ui.placeholder.height(ui.helper.height());
EscapeActions.executeLowerThan('popup');
boardComponent.setIsDragging(true);
},
stop: function(evt, ui) {
// To attribute the new index number, we need to get the dom element
// of the previous and the following card -- if any.
var cardDomElement = ui.item.get(0);
var prevCardDomElement = ui.item.prev('.js-minicard').get(0);
var nextCardDomElement = ui.item.next('.js-minicard').get(0);
var sort = Utils.getSortIndex(prevCardDomElement, nextCardDomElement);
var cardId = Blaze.getData(cardDomElement)._id;
var listId = Blaze.getData(ui.item.parents('.list').get(0))._id;
Cards.update(cardId, {
$set: {
listId: listId,
sort: sort
}
});
boardComponent.showNewCardForms(true);
if (MultiSelection.isActive()) {
Cards.find(MultiSelection.getMongoSelector()).forEach(function(c) {
Cards.update(c._id, {
$set: {
listId: listId,
sort: sort
}
});
});
} else {
var cardId = Blaze.getData(cardDomElement)._id;
Cards.update(cardId, {
$set: {
listId: listId,
// XXX Using the same sort index for multiple cards is
// unacceptable. Keep that only until we figure out if we want to
// refactor the whole sorting mecanism or do something more basic.
sort: sort
}
});
}
boardComponent.setIsDragging(false);
}
});

View file

@ -93,10 +93,13 @@
overflow-y: auto
padding: 5px 11px
.minicards form
margin-bottom: 9px
.ps-scrollbar-y-rail
transform: translateX(2px)
.open-card-composer
.open-minicard-composer
border-radius: 2px
color: #8c8c8c
display: block

View file

@ -5,6 +5,7 @@ template(name="listActionPopup")
if cards.count
hr
ul.pop-over-list
li: a.js-select-cards {{_ 'list-select-cards'}}
li: a.js-move-cards {{_ 'list-move-cards'}}
li: a.js-archive-cards {{_ 'list-archive-cards'}}
hr

View file

@ -6,6 +6,14 @@ Template.listActionPopup.events({
Popup.close();
},
'click .js-list-subscribe': function() {},
'click .js-select-cards': function() {
var cardIds = Cards.find(
{listId: this._id},
{fields: { _id: 1 }}
).map(function(card) { return card._id; });
MultiSelection.add(cardIds);
Popup.close();
},
'click .js-move-cards': Popup.open('listMoveCards'),
'click .js-archive-cards': Popup.afterConfirm('listArchiveCards', function() {
Cards.find({listId: this._id}).forEach(function(card) {