diff options
author | Maxime Quandalle <maxime@quandalle.com> | 2015-10-31 09:26:55 -0700 |
---|---|---|
committer | Maxime Quandalle <maxime@quandalle.com> | 2015-11-08 23:17:24 -0800 |
commit | 5d77ad4f6ba70038486d734e97844c547e664e88 (patch) | |
tree | aa4a0dd855b98ff94ac10fb7b2add3f7e1627dfd /client/components/lists | |
parent | 2b134ff7a986eb97f8ec360321be284a7a8ea11e (diff) | |
download | wekan-5d77ad4f6ba70038486d734e97844c547e664e88.tar.gz wekan-5d77ad4f6ba70038486d734e97844c547e664e88.tar.bz2 wekan-5d77ad4f6ba70038486d734e97844c547e664e88.zip |
Finish the minicard editor auto-completion feature
This commit stands on the initial support implemented in #342. We now
avoid error-prone parsing step by adding the member or the label
directly to the card object.
We also added support for `Tab` to completion on our textComplete
component.
Closes #342
Diffstat (limited to 'client/components/lists')
-rw-r--r-- | client/components/lists/list.js | 2 | ||||
-rw-r--r-- | client/components/lists/listBody.jade | 15 | ||||
-rw-r--r-- | client/components/lists/listBody.js | 156 | ||||
-rw-r--r-- | client/components/lists/listHeader.js | 2 |
4 files changed, 86 insertions, 89 deletions
diff --git a/client/components/lists/list.js b/client/components/lists/list.js index 75e816b5..f5410ed0 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -7,7 +7,7 @@ BlazeComponent.extendComponent({ // Proxy openForm(options) { - this.childrenComponents('listBody')[0].openForm(options); + this.childComponents('listBody')[0].openForm(options); }, onCreated() { diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index b0a374ea..e659b179 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -22,9 +22,20 @@ template(name="listBody") template(name="addCardForm") .minicard.minicard-composer.js-composer - .minicard-detailss.clearfix - textarea.minicard-composer-textarea.js-card-title(autofocus) + if getLabels + .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 + each members.get + +userAvatar(userId=this) + .add-controls.clearfix button.primary.confirm(type="submit") {{_ 'add'}} 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 diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 2ed5d38a..36b60d06 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -11,7 +11,7 @@ BlazeComponent.extendComponent({ options = options || {}; options.position = options.position || 'top'; - const forms = this.childrenComponents('inlinedForm'); + const forms = this.childComponents('inlinedForm'); let form = forms.find((component) => { return component.data().position === options.position; }); @@ -27,7 +27,9 @@ BlazeComponent.extendComponent({ const lastCardDom = this.find('.js-minicard:last'); const textarea = $(evt.currentTarget).find('textarea'); const position = this.currentData().position; - let title = textarea.val().trim(); + const title = textarea.val().trim(); + + const formComponent = this.childComponents('addCardForm')[0]; let sortIndex; if (position === 'top') { sortIndex = Utils.calculateIndex(null, firstCardDom).base; @@ -35,40 +37,16 @@ BlazeComponent.extendComponent({ 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}`, ''); - } - }); + const members = formComponent.members.get(); + const labelIds = formComponent.labels.get(); if (title) { const _id = Cards.insert({ title, + members, + labelIds, 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 @@ -82,6 +60,8 @@ BlazeComponent.extendComponent({ if (position === 'bottom') { this.scrollToBottom(); } + + formComponent.reset(); } }, @@ -129,18 +109,40 @@ BlazeComponent.extendComponent({ }, }).register('listBody'); -let dropdownMenuIsOpened = false; +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({ template() { return 'addCardForm'; }, - pressKey(evt) { - // Don't do anything if the drop down is showing - if (dropdownMenuIsOpened) { - return; - } + 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) { // Pressing Enter should submit the card if (evt.keyCode === 13) { evt.preventDefault(); @@ -176,28 +178,25 @@ BlazeComponent.extendComponent({ }]; }, - onCreated() { - dropdownMenuIsOpened = false; - }, - onRendered() { - const $textarea = this.$('textarea'); - const currentBoard = Boards.findOne(Session.get('currentBoard')); - $textarea.textcomplete([ + 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 username = Users.findOne(member.userId).username; - return username.indexOf(term) === 0 ? username : null; + const user = Users.findOne(member.userId); + return user.username.indexOf(term) === 0 ? user : null; })); }, - template(value) { - return value; + template(user) { + return user.username; }, - replace(username) { - return `@${username} `; + replace(user) { + toggleValueInReactiveArray(editor.members, user._id); + return ''; }, index: 1, }, @@ -206,51 +205,38 @@ BlazeComponent.extendComponent({ { match: /\B#(\w*)$/, search(term, callback) { + const currentBoard = Boards.findOne(Session.get('currentBoard')); callback($.map(currentBoard.labels, (label) => { - const labelName = (!label.name || label.name === '') - ? label.color - : label.name; - return labelName.indexOf(term) === 0 ? labelName : null; + if (label.name.indexOf(term) > -1 || + label.color.indexOf(term) > -1) { + return label; + } })); }, - template(value) { - // XXX the following is duplicated from editor.js and should be - // abstracted to keep things DRY - // 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; - const valueSpan = (colorName === value) - ? `<span class="quiet">${value}</span>` - : value; - return (colorName && colorName !== '') - ? `<div class="minicard-label card-label-${colorName}" - title="${value}"></div> ${valueSpan}` - : value; + template(label) { + return Blaze.toHTMLWithData(Template.autocompleteLabelLine, { + hasNoName: !Boolean(label.name), + colorName: label.color, + labelName: label.name || label.color, + }); }, replace(label) { - return `#${label} `; + toggleValueInReactiveArray(editor.labels, label._id); + return ''; }, index: 1, }, - ]); - - // customize hooks for dealing with the dropdowns - $textarea.on({ - 'textComplete:show'() { - dropdownMenuIsOpened = true; - }, - 'textComplete:hide'() { - Tracker.afterFlush(() => { - dropdownMenuIsOpened = false; - }); + ], { + // 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; + } }, }); - - EscapeActions.register('textcomplete', - () => {}, - () => dropdownMenuIsOpened - ); }, }).register('addCardForm'); diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index dbf9fced..d660508a 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -5,7 +5,7 @@ BlazeComponent.extendComponent({ editTitle(evt) { evt.preventDefault(); - const newTitle = this.childrenComponents('inlinedForm')[0].getValue().trim(); + const newTitle = this.childComponents('inlinedForm')[0].getValue().trim(); const list = this.currentData(); if (newTitle) { list.rename(newTitle.trim()); |