Merge branch 'minicard-editor' into devel

This commit is contained in:
Maxime Quandalle 2015-11-08 23:27:37 -08:00
commit 642ade9dc1
8 changed files with 154 additions and 13 deletions

View file

@ -13,7 +13,7 @@ BlazeComponent.extendComponent({
}, },
reachNextPeak() { reachNextPeak() {
const activitiesComponent = this.childrenComponents('activities')[0]; const activitiesComponent = this.childComponents('activities')[0];
activitiesComponent.loadNextPage(); activitiesComponent.loadNextPage();
}, },

View file

@ -617,8 +617,15 @@ button
margin-right: 5px margin-right: 5px
vertical-align: middle vertical-align: middle
.minicard-label
width: 11px
height: @width
border-radius: 2px
margin: 2px 7px -2px -2px
display: inline-block
&.active &.active
background: #005377 background: #005377
a a, .quiet
color: white color: white

View file

@ -7,7 +7,7 @@ BlazeComponent.extendComponent({
// Proxy // Proxy
openForm(options) { openForm(options) {
this.childrenComponents('listBody')[0].openForm(options); this.childComponents('listBody')[0].openForm(options);
}, },
onCreated() { onCreated() {

View file

@ -22,9 +22,20 @@ template(name="listBody")
template(name="addCardForm") template(name="addCardForm")
.minicard.minicard-composer.js-composer .minicard.minicard-composer.js-composer
.minicard-detailss.clearfix if getLabels
textarea.minicard-composer-textarea.js-card-title(autofocus) .minicard-labels
each getLabels
.minicard-label(class="card-label-{{color}}" title="{{name}}")
textarea.minicard-composer-textarea.js-card-title(autofocus)
if members.get
.minicard-members.js-minicard-composer-members .minicard-members.js-minicard-composer-members
each members.get
+userAvatar(userId=this)
.add-controls.clearfix .add-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}} button.primary.confirm(type="submit") {{_ 'add'}}
a.fa.fa-times-thin.js-close-inlined-form a.fa.fa-times-thin.js-close-inlined-form
template(name="autocompleteLabelLine")
.minicard-label(class="card-label-{{colorName}}" title=labelName)
span(class="{{#if hasNoName}}quiet{{/if}}")= labelName

View file

@ -11,7 +11,7 @@ BlazeComponent.extendComponent({
options = options || {}; options = options || {};
options.position = options.position || 'top'; options.position = options.position || 'top';
const forms = this.childrenComponents('inlinedForm'); const forms = this.childComponents('inlinedForm');
let form = forms.find((component) => { let form = forms.find((component) => {
return component.data().position === options.position; return component.data().position === options.position;
}); });
@ -26,8 +26,10 @@ BlazeComponent.extendComponent({
const firstCardDom = this.find('.js-minicard:first'); const firstCardDom = this.find('.js-minicard:first');
const lastCardDom = this.find('.js-minicard:last'); const lastCardDom = this.find('.js-minicard:last');
const textarea = $(evt.currentTarget).find('textarea'); const textarea = $(evt.currentTarget).find('textarea');
const title = textarea.val().trim();
const position = this.currentData().position; const position = this.currentData().position;
const title = textarea.val().trim();
const formComponent = this.childComponents('addCardForm')[0];
let sortIndex; let sortIndex;
if (position === 'top') { if (position === 'top') {
sortIndex = Utils.calculateIndex(null, firstCardDom).base; sortIndex = Utils.calculateIndex(null, firstCardDom).base;
@ -35,9 +37,14 @@ BlazeComponent.extendComponent({
sortIndex = Utils.calculateIndex(lastCardDom, null).base; sortIndex = Utils.calculateIndex(lastCardDom, null).base;
} }
const members = formComponent.members.get();
const labelIds = formComponent.labels.get();
if (title) { if (title) {
const _id = Cards.insert({ const _id = Cards.insert({
title, title,
members,
labelIds,
listId: this.data()._id, listId: this.data()._id,
boardId: this.data().board()._id, boardId: this.data().board()._id,
sort: sortIndex, sort: sortIndex,
@ -53,6 +60,8 @@ BlazeComponent.extendComponent({
if (position === 'bottom') { if (position === 'bottom') {
this.scrollToBottom(); this.scrollToBottom();
} }
formComponent.reset();
} }
}, },
@ -100,11 +109,39 @@ BlazeComponent.extendComponent({
}, },
}).register('listBody'); }).register('listBody');
function toggleValueInReactiveArray(reactiveValue, value) {
const array = reactiveValue.get();
const valueIndex = array.indexOf(value);
if (valueIndex === -1) {
array.push(value);
} else {
array.splice(valueIndex, 1);
}
reactiveValue.set(array);
}
BlazeComponent.extendComponent({ BlazeComponent.extendComponent({
template() { template() {
return 'addCardForm'; return 'addCardForm';
}, },
onCreated() {
this.labels = new ReactiveVar([]);
this.members = new ReactiveVar([]);
},
reset() {
this.labels.set([]);
this.members.set([]);
},
getLabels() {
const currentBoardId = Session.get('currentBoard');
return Boards.findOne(currentBoardId).labels.filter((label) => {
return this.labels.get().indexOf(label._id) > -1;
});
},
pressKey(evt) { pressKey(evt) {
// Pressing Enter should submit the card // Pressing Enter should submit the card
if (evt.keyCode === 13) { if (evt.keyCode === 13) {
@ -140,4 +177,66 @@ BlazeComponent.extendComponent({
keydown: this.pressKey, keydown: this.pressKey,
}]; }];
}, },
onRendered() {
const editor = this;
this.$('textarea').escapeableTextComplete([
// User mentions
{
match: /\B@(\w*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback($.map(currentBoard.members, (member) => {
const user = Users.findOne(member.userId);
return user.username.indexOf(term) === 0 ? user : null;
}));
},
template(user) {
return user.username;
},
replace(user) {
toggleValueInReactiveArray(editor.members, user._id);
return '';
},
index: 1,
},
// Labels
{
match: /\B#(\w*)$/,
search(term, callback) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
callback($.map(currentBoard.labels, (label) => {
if (label.name.indexOf(term) > -1 ||
label.color.indexOf(term) > -1) {
return label;
}
}));
},
template(label) {
return Blaze.toHTMLWithData(Template.autocompleteLabelLine, {
hasNoName: !Boolean(label.name),
colorName: label.color,
labelName: label.name || label.color,
});
},
replace(label) {
toggleValueInReactiveArray(editor.labels, label._id);
return '';
},
index: 1,
},
], {
// When the autocomplete menu is shown we want both a press of both `Tab`
// or `Enter` to validation the auto-completion. We also need to stop the
// event propagation to prevent the card from submitting (on `Enter`) or
// going on the next column (on `Tab`).
onKeydown(evt, commands) {
if (evt.keyCode === 9 || evt.keyCode === 13) {
evt.stopPropagation();
return commands.KEY_ENTER;
}
},
});
},
}).register('addCardForm'); }).register('addCardForm');

View file

@ -5,7 +5,7 @@ BlazeComponent.extendComponent({
editTitle(evt) { editTitle(evt) {
evt.preventDefault(); evt.preventDefault();
const newTitle = this.childrenComponents('inlinedForm')[0].getValue().trim(); const newTitle = this.childComponents('inlinedForm')[0].getValue().trim();
const list = this.currentData(); const list = this.currentData();
if (newTitle) { if (newTitle) {
list.rename(newTitle.trim()); list.rename(newTitle.trim());

View file

@ -54,7 +54,7 @@ BlazeComponent.extendComponent({
}, },
reachNextPeak() { reachNextPeak() {
const activitiesComponent = this.childrenComponents('activities')[0]; const activitiesComponent = this.childComponents('activities')[0];
activitiesComponent.loadNextPage(); activitiesComponent.loadNextPage();
}, },

View file

@ -3,8 +3,23 @@
// of the vanilla `textcomplete`. // of the vanilla `textcomplete`.
let dropdownMenuIsOpened = false; let dropdownMenuIsOpened = false;
$.fn.escapeableTextComplete = function(...args) { $.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
this.textcomplete(...args); // When the autocomplete menu is shown we want both a press of both `Tab`
// or `Enter` to validation the auto-completion. We also need to stop the
// event propagation to prevent EscapeActions side effect, for instance the
// minicard submission (on `Enter`) or going on the next column (on `Tab`).
options = {
onKeydown(evt, commands) {
if (evt.keyCode === 9 || evt.keyCode === 13) {
evt.stopPropagation();
return commands.KEY_ENTER;
}
},
...options,
};
// Proxy to the vanilla jQuery component
this.textcomplete(strategies, options, ...otherArgs);
// Since commit d474017 jquery-textComplete automatically closes a potential // Since commit d474017 jquery-textComplete automatically closes a potential
// opened dropdown menu when the user press Escape. This behavior conflicts // opened dropdown menu when the user press Escape. This behavior conflicts
@ -18,7 +33,14 @@ $.fn.escapeableTextComplete = function(...args) {
}, },
'textComplete:hide'() { 'textComplete:hide'() {
Tracker.afterFlush(() => { Tracker.afterFlush(() => {
dropdownMenuIsOpened = false; // XXX Hack. We unfortunately need to set a setTimeout here to make the
// `noClickEscapeOn` work bellow, otherwise clicking on a autocomplete
// item will close both the autocomplete menu (as expected) but also the
// next item in the stack (for example the minicard editor) which we
// don't want.
setTimeout(() => {
dropdownMenuIsOpened = false;
}, 100);
}); });
}, },
}); });
@ -26,5 +48,7 @@ $.fn.escapeableTextComplete = function(...args) {
EscapeActions.register('textcomplete', EscapeActions.register('textcomplete',
() => {}, () => {},
() => dropdownMenuIsOpened () => dropdownMenuIsOpened, {
noClickEscapeOn: '.textcomplete-dropdown',
}
); );