wekan/client/components/lists/listBody.js
2015-10-08 13:10:46 -06:00

251 lines
7.7 KiB
JavaScript

BlazeComponent.extendComponent({
template() {
return 'listBody';
},
mixins() {
return [Mixins.PerfectScrollbar];
},
openForm(options) {
options = options || {};
options.position = options.position || 'top';
const forms = this.componentChildren('inlinedForm');
let form = _.find(forms, (component) => {
return component.data().position === options.position;
});
if (!form && forms.length > 0) {
form = forms[0];
}
form.open();
},
addCard(evt) {
evt.preventDefault();
const firstCardDom = this.find('.js-minicard:first');
const lastCardDom = this.find('.js-minicard:last');
const textarea = $(evt.currentTarget).find('textarea');
let title = textarea.val();
const position = Blaze.getData(evt.currentTarget).position;
let sortIndex;
if (position === 'top') {
sortIndex = Utils.calculateIndex(null, firstCardDom).base;
} else if (position === 'bottom') {
sortIndex = Utils.calculateIndex(lastCardDom, null).base;
}
// Parse for @user and #label mentions, stripping them from the title
// and applying the appropriate users and labels to the card instead.
const currentBoard = Boards.findOne(Session.get('currentBoard'));
// Find all @-mentioned usernames, collect a list of their IDs and strip
// their mention out of the title.
let foundUserIds = []; // eslint-disable-line prefer-const
currentBoard.members.forEach((member) => {
const username = Users.findOne(member.userId).username;
if (title.indexOf(`@${username}`) !== -1) {
foundUserIds.push(member.userId);
title = title.replace(`@${username}`, '');
}
});
// Find all #-mentioned labels (based on their colour or name), collect a
// list of their IDs, and strip their mention out of the title.
let foundLabelIds = []; // eslint-disable-line prefer-const
currentBoard.labels.forEach((label) => {
const labelName = (!label.name || label.name === '')
? label.color : label.name;
if (title.indexOf(`#${labelName}`) !== -1) {
foundLabelIds.push(label._id);
title = title.replace(`#${labelName}`, '');
}
});
if ($.trim(title)) {
const _id = Cards.insert({
title,
listId: this.data()._id,
boardId: this.data().board()._id,
labelIds: foundLabelIds,
members: foundUserIds,
sort: sortIndex,
});
// 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/wekan/wekan/issues/80
Filter.addException(_id);
// We keep the form opened, empty it, and scroll to it.
textarea.val('').focus();
if (position === 'bottom') {
this.scrollToBottom();
}
}
},
scrollToBottom() {
const container = this.firstNode();
$(container).animate({
scrollTop: container.scrollHeight,
});
},
clickOnMiniCard(evt) {
if (MultiSelection.isActive() || evt.shiftKey) {
evt.stopImmediatePropagation();
evt.preventDefault();
const methodName = evt.shiftKey ? 'toggleRange' : 'toggle';
MultiSelection[methodName](this.currentData()._id);
// If the card is already selected, we want to de-select it.
// XXX We should probably modify the minicard href attribute instead of
// overwriting the event in case the card is already selected.
} else if (Session.equals('currentCard', this.currentData()._id)) {
evt.stopImmediatePropagation();
evt.preventDefault();
Utils.goBoardId(Session.get('currentBoard'));
}
},
cardIsSelected() {
return Session.equals('currentCard', this.currentData()._id);
},
toggleMultiSelection(evt) {
evt.stopPropagation();
evt.preventDefault();
MultiSelection.toggle(this.currentData()._id);
},
events() {
return [{
'click .js-minicard': this.clickOnMiniCard,
'click .js-toggle-multi-selection': this.toggleMultiSelection,
'click .open-minicard-composer': this.scrollToBottom,
submit: this.addCard,
}];
},
}).register('listBody');
let dropdownMenuIsOpened = false;
BlazeComponent.extendComponent({
template() {
return 'addCardForm';
},
pressKey(evt) {
// Don't do anything if the drop down is showing
if (dropdownMenuIsOpened) {
return;
}
// Pressing Enter should submit the card
if (evt.keyCode === 13) {
evt.preventDefault();
const $form = $(evt.currentTarget).closest('form');
// XXX For some reason $form.submit() does not work (it's probably a bug
// of blaze-component related to the fact that the submit event is non-
// bubbling). This is why we click on the submit button instead -- which
// work.
$form.find('button[type=submit]').click();
// Pressing Tab should open the form of the next column, and Maj+Tab go
// in the reverse order
} else if (evt.keyCode === 9) {
evt.preventDefault();
const isReverse = evt.shiftKey;
const list = $(`#js-list-${this.data().listId}`);
const listSelector = '.js-list:not(.js-list-composer)';
let nextList = list[isReverse ? 'prev' : 'next'](listSelector).get(0);
// If there is no next list, loop back to the beginning.
if (!nextList) {
nextList = $(listSelector + (isReverse ? ':last' : ':first')).get(0);
}
BlazeComponent.getComponentForElement(nextList).openForm({
position:this.data().position,
});
}
},
events() {
return [{
keydown: this.pressKey,
}];
},
onCreated() {
dropdownMenuIsOpened = false;
},
onRendered() {
const $textarea = this.$('textarea');
const currentBoard = Boards.findOne(Session.get('currentBoard'));
$textarea.textcomplete([
// User mentions
{
match: /\B@(\w*)$/,
search(term, callback) {
callback($.map(currentBoard.members, (member) => {
const username = Users.findOne(member.userId).username;
return username.indexOf(term) === 0 ? username : null;
}));
},
template(value) {
return value;
},
replace(username) {
return `@${username} `;
},
index: 1,
},
// Labels
{
match: /\B#(\w*)$/,
search(term, callback) {
callback($.map(currentBoard.labels, (label) => {
const labelName = (!label.name || label.name === '')
? label.color
: label.name;
return labelName.indexOf(term) === 0 ? labelName : null;
}));
},
template(value) {
// add a "colour badge" in front of the label name
// but first, get the colour's name from its value
const colorName = currentBoard.labels.find((label) => {
return value === label.name || value === label.color;
}).color;
return (colorName && colorName !== '')
? `<div class="minicard-label card-label-${colorName}"
title="${value}"></div> ${value}`
: value;
},
replace(label) {
return `#${label} `;
},
index: 1,
},
]);
// customize hooks for dealing with the dropdowns
$textarea.on({
'textComplete:show'() {
dropdownMenuIsOpened = true;
},
'textComplete:hide'() {
Tracker.afterFlush(() => {
dropdownMenuIsOpened = false;
});
},
});
EscapeActions.register('textcomplete',
() => {},
() => dropdownMenuIsOpened
);
},
}).register('addCardForm');