From bfcfd2ebda283bed1caa973f27e1af6b81fe621b Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Sat, 3 Oct 2015 15:58:36 -0600 Subject: Initial support for @user and #label use in new cards. When creating a new [mini]card, typing `@` or `#` brings up an auto-complete box for board members and labels which will get applied to the card upon creation. These textual tags are removed from the card title before saving to maintain sanity. If a label doesn't have a name, it's colour is used (i.e. `red`, `purple`, etc). This was developed to ease the creation of new cards and allow users to rapidly create cards without having to click numerous times just to apply labels & members. --- client/components/lists/listBody.js | 98 ++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 2e00cb4f..9d6aab88 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -26,7 +26,7 @@ BlazeComponent.extendComponent({ const firstCardDom = this.find('.js-minicard:first'); const lastCardDom = this.find('.js-minicard:last'); const textarea = $(evt.currentTarget).find('textarea'); - const title = textarea.val(); + let title = textarea.val(); const position = Blaze.getData(evt.currentTarget).position; let sortIndex; if (position === 'top') { @@ -36,10 +36,41 @@ BlazeComponent.extendComponent({ } if ($.trim(title)) { + // 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 = []; + currentBoard.members.forEach(member => { + const username = Users.findOne(member.userId).username; + let nameNdx = title.indexOf('@' + username); + if(nameNdx !== -1) { + foundUserIds.push(member.userId); + title = title.substr(0, nameNdx) + title.substr(nameNdx + username.length + 1); + } + }); + + // 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 = []; + currentBoard.labels.forEach(label => { + const labelName = (!label.name || label.name === "") ? label.color : label.name; + let labelNdx = title.indexOf('#' + labelName); + if(labelNdx !== -1) { + foundLabelIds.push(label._id); + title = title.substr(0, labelNdx) + title.substr(labelNdx + labelName.length + 1); + } + }); + 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 @@ -100,12 +131,18 @@ BlazeComponent.extendComponent({ }, }).register('listBody'); +let dropdownMenuIsOpened = false; BlazeComponent.extendComponent({ template() { return 'addCardForm'; }, pressKey(evt) { + // don't do anything if the drop down is showing + if(dropDownIsOpened) { + return; + } + // Pressing Enter should submit the card if (evt.keyCode === 13) { evt.preventDefault(); @@ -140,4 +177,63 @@ BlazeComponent.extendComponent({ keydown: this.pressKey, }]; }, + + onCreated() { + dropDownIsOpened = false; + }, + + onRendered() { + const $textarea = this.$('textarea'); + $textarea.textcomplete([ + // 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; + })); + }, + template(value) { + return value; + }, + replace(username) { + return `@${username} `; + }, + index: 1, + }, + + // Labels + { + 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; + })); + }, + template(value) { + return value; + }, + replace(label) { + return `#${label} `; + }, + index: 1, + }, + ]); + + // customize hooks for dealing with the dropdowns + $textarea.on({ + 'textComplete:show'() { + dropDownIsOpened = true; + }, + 'textComplete:hide'() { + Tracker.afterFlush(() => { + dropDownIsOpened = false; + }); + }, + }); + }, }).register('addCardForm'); -- cgit v1.2.3-1-g7c22 From d105da5bc776446045a04bad83a0bb9d4a3fe50c Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Sat, 3 Oct 2015 15:59:13 -0600 Subject: Conformed to the 80-character line length limit. --- client/components/lists/listBody.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 9d6aab88..8bc828fb 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -40,28 +40,30 @@ BlazeComponent.extendComponent({ // 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. + // Find all @-mentioned usernames, collect a list of their IDs and strip + // their mention out of the title. let foundUserIds = []; currentBoard.members.forEach(member => { const username = Users.findOne(member.userId).username; let nameNdx = title.indexOf('@' + username); if(nameNdx !== -1) { foundUserIds.push(member.userId); - title = title.substr(0, nameNdx) + title.substr(nameNdx + username.length + 1); + title = title.substr(0, nameNdx) + + title.substr(nameNdx + username.length + 1); } }); - // Find all #-mentioned labels (based on their colour or name), - // collect a list of their IDs, and strip their mention out of - // the title. + // 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 = []; currentBoard.labels.forEach(label => { - const labelName = (!label.name || label.name === "") ? label.color : label.name; + const labelName = (!label.name || label.name === "") + ? label.color : label.name; let labelNdx = title.indexOf('#' + labelName); if(labelNdx !== -1) { foundLabelIds.push(label._id); - title = title.substr(0, labelNdx) + title.substr(labelNdx + labelName.length + 1); + title = title.substr(0, labelNdx) + + title.substr(labelNdx + labelName.length + 1); } }); @@ -138,7 +140,7 @@ BlazeComponent.extendComponent({ }, pressKey(evt) { - // don't do anything if the drop down is showing + // Don't do anything if the drop down is showing if(dropDownIsOpened) { return; } -- cgit v1.2.3-1-g7c22 From 8010ed8d6d15d8735aeec7a16b6e942c4bedd9dd Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Sat, 3 Oct 2015 16:38:43 -0600 Subject: Added package.json for meteor deployment --- package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..8c9c7ff7 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "Wekan", + "description": "The open-source Trello-like kanban", + "repository": "https://github.com/FuzzyWuzzie/wekan", + "logo": "https://raw.githubusercontent.com/wekan/wekan/master/meta/icons/wekan-150.png", + "keywords": ["productivity", "tool", "team", "kanban"], + "website": "http://wekan.io", + "engines": { + "node": "0.10.40" + } +} \ No newline at end of file -- cgit v1.2.3-1-g7c22 From 429686ef480a56039e0d1a6da0a8668755b2bfa5 Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Sat, 3 Oct 2015 16:53:45 -0600 Subject: Made eslinter happy. --- client/components/lists/listBody.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 8bc828fb..a96e964c 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -42,10 +42,10 @@ BlazeComponent.extendComponent({ // Find all @-mentioned usernames, collect a list of their IDs and strip // their mention out of the title. - let foundUserIds = []; - currentBoard.members.forEach(member => { + let foundUserIds = []; // eslint-disable-line prefer-const + currentBoard.members.forEach((member) => { const username = Users.findOne(member.userId).username; - let nameNdx = title.indexOf('@' + username); + const nameNdx = title.indexOf(`@${username}!`); if(nameNdx !== -1) { foundUserIds.push(member.userId); title = title.substr(0, nameNdx) @@ -55,11 +55,11 @@ BlazeComponent.extendComponent({ // 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 = []; - currentBoard.labels.forEach(label => { - const labelName = (!label.name || label.name === "") + let foundLabelIds = []; // eslint-disable-line prefer-const + currentBoard.labels.forEach((label) => { + const labelName = (!label.name || label.name === '') ? label.color : label.name; - let labelNdx = title.indexOf('#' + labelName); + const labelNdx = title.indexOf(`#${labelName}`); if(labelNdx !== -1) { foundLabelIds.push(label._id); title = title.substr(0, labelNdx) @@ -141,7 +141,7 @@ BlazeComponent.extendComponent({ pressKey(evt) { // Don't do anything if the drop down is showing - if(dropDownIsOpened) { + if(dropdownMenuIsOpened) { return; } @@ -181,7 +181,7 @@ BlazeComponent.extendComponent({ }, onCreated() { - dropDownIsOpened = false; + dropdownMenuIsOpened = false; }, onRendered() { @@ -212,7 +212,7 @@ BlazeComponent.extendComponent({ 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; + const labelName = (!label.name || label.name === '') ? label.color : label.name; return labelName.indexOf(term) === 0 ? labelName : null; })); }, @@ -229,11 +229,11 @@ BlazeComponent.extendComponent({ // customize hooks for dealing with the dropdowns $textarea.on({ 'textComplete:show'() { - dropDownIsOpened = true; + dropdownMenuIsOpened = true; }, 'textComplete:hide'() { Tracker.afterFlush(() => { - dropDownIsOpened = false; + dropdownMenuIsOpened = false; }); }, }); -- cgit v1.2.3-1-g7c22 From e4c5d2cbe64f42f1fc2e49aea6a9a25bc0d686cb Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Sat, 3 Oct 2015 16:56:27 -0600 Subject: Fixed typo in template for quick-adding a user. --- client/components/lists/listBody.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index a96e964c..61e26975 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -45,7 +45,7 @@ BlazeComponent.extendComponent({ let foundUserIds = []; // eslint-disable-line prefer-const currentBoard.members.forEach((member) => { const username = Users.findOne(member.userId).username; - const nameNdx = title.indexOf(`@${username}!`); + const nameNdx = title.indexOf(`@${username}`); if(nameNdx !== -1) { foundUserIds.push(member.userId); title = title.substr(0, nameNdx) -- cgit v1.2.3-1-g7c22 From 77ca52d8c20211170a0f6d28e751768a4f9c3b8c Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Thu, 8 Oct 2015 12:22:03 -0600 Subject: Fixed issue with possible race condition, suggested by @mquandalle --- client/components/lists/listBody.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 61e26975..7c524b93 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -45,11 +45,9 @@ BlazeComponent.extendComponent({ let foundUserIds = []; // eslint-disable-line prefer-const currentBoard.members.forEach((member) => { const username = Users.findOne(member.userId).username; - const nameNdx = title.indexOf(`@${username}`); - if(nameNdx !== -1) { + if(title.indexOf(`@${username}`) !== -1) { foundUserIds.push(member.userId); - title = title.substr(0, nameNdx) - + title.substr(nameNdx + username.length + 1); + title = title.replace(`@${username}`, ''); } }); @@ -59,11 +57,9 @@ BlazeComponent.extendComponent({ currentBoard.labels.forEach((label) => { const labelName = (!label.name || label.name === '') ? label.color : label.name; - const labelNdx = title.indexOf(`#${labelName}`); - if(labelNdx !== -1) { + if(title.indexOf(`#${labelName}`) !== -1) { foundLabelIds.push(label._id); - title = title.substr(0, labelNdx) - + title.substr(labelNdx + labelName.length + 1); + title = title.replace(`#${labelName}`, ''); } }); -- cgit v1.2.3-1-g7c22 From a212b1310cf5c722a397e2c50af9a7b289e77e5a Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Thu, 8 Oct 2015 12:22:55 -0600 Subject: Added space after if to conform to formatting --- client/components/lists/listBody.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 7c524b93..ce095ed6 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -45,7 +45,7 @@ BlazeComponent.extendComponent({ let foundUserIds = []; // eslint-disable-line prefer-const currentBoard.members.forEach((member) => { const username = Users.findOne(member.userId).username; - if(title.indexOf(`@${username}`) !== -1) { + if (title.indexOf(`@${username}`) !== -1) { foundUserIds.push(member.userId); title = title.replace(`@${username}`, ''); } @@ -57,7 +57,7 @@ BlazeComponent.extendComponent({ currentBoard.labels.forEach((label) => { const labelName = (!label.name || label.name === '') ? label.color : label.name; - if(title.indexOf(`#${labelName}`) !== -1) { + if (title.indexOf(`#${labelName}`) !== -1) { foundLabelIds.push(label._id); title = title.replace(`#${labelName}`, ''); } @@ -137,7 +137,7 @@ BlazeComponent.extendComponent({ pressKey(evt) { // Don't do anything if the drop down is showing - if(dropdownMenuIsOpened) { + if (dropdownMenuIsOpened) { return; } -- cgit v1.2.3-1-g7c22 From c2cb17c5dff153fb5b11572036f8acc3155ea7e3 Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Thu, 8 Oct 2015 12:25:58 -0600 Subject: Now cards with *only* metadata aren't created empty --- client/components/lists/listBody.js | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index ce095ed6..6c191a71 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -34,35 +34,35 @@ BlazeComponent.extendComponent({ } 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}`, ''); + } + }); - if ($.trim(title)) { - // 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}`, ''); - } - }); + // 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, -- cgit v1.2.3-1-g7c22 From f5be121cf368415652ec3bb14aeaccecab6f663e Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Thu, 8 Oct 2015 12:32:31 -0600 Subject: Pressing escape while autocomplete is open no longer closes the minicard --- client/components/lists/listBody.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 6c191a71..e311ac76 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -233,5 +233,10 @@ BlazeComponent.extendComponent({ }); }, }); + + EscapeActions.register('textcomplete', + () => {}, + () => dropdownMenuIsOpened + ); }, }).register('addCardForm'); -- cgit v1.2.3-1-g7c22 From fde2a39ee3dbbdfd5a6b648dbe17541343ac3b2a Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Thu, 8 Oct 2015 13:10:46 -0600 Subject: Added coloured label badges in autocomplete list --- client/components/forms/forms.styl | 7 +++++++ client/components/lists/listBody.js | 19 ++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index 83d25370..2d92aca9 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -617,6 +617,13 @@ button margin-right: 5px vertical-align: middle + .minicard-label + width: 11px + height: @width + border-radius: 2px + margin-right: 3px + display: inline-block + &.active background: #005377 diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index e311ac76..e248f203 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -34,7 +34,7 @@ BlazeComponent.extendComponent({ } 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')); @@ -182,12 +182,12 @@ BlazeComponent.extendComponent({ onRendered() { const $textarea = this.$('textarea'); + const currentBoard = Boards.findOne(Session.get('currentBoard')); $textarea.textcomplete([ // 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; @@ -206,14 +206,23 @@ 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; + const labelName = (!label.name || label.name === '') + ? label.color + : label.name; return labelName.indexOf(term) === 0 ? labelName : null; })); }, template(value) { - return 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 !== '') + ? `
${value}` + : value; }, replace(label) { return `#${label} `; -- cgit v1.2.3-1-g7c22 From 3507c6565bb16b5f45c6f269f7376902f8b1ff37 Mon Sep 17 00:00:00 2001 From: Kenton Hamaluik Date: Sat, 10 Oct 2015 23:08:50 -0600 Subject: Made colours light grey in the labels dropdown --- client/components/lists/listBody.js | 7 ++++++- package.json | 11 ----------- 2 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 package.json diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index e248f203..a60ffe25 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -214,14 +214,19 @@ BlazeComponent.extendComponent({ })); }, 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) + ? `${value}` + : value; return (colorName && colorName !== '') ? `
${value}` + title="${value}"> ${valueSpan}` : value; }, replace(label) { diff --git a/package.json b/package.json deleted file mode 100644 index 8c9c7ff7..00000000 --- a/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "Wekan", - "description": "The open-source Trello-like kanban", - "repository": "https://github.com/FuzzyWuzzie/wekan", - "logo": "https://raw.githubusercontent.com/wekan/wekan/master/meta/icons/wekan-150.png", - "keywords": ["productivity", "tool", "team", "kanban"], - "website": "http://wekan.io", - "engines": { - "node": "0.10.40" - } -} \ No newline at end of file -- cgit v1.2.3-1-g7c22 From 5d77ad4f6ba70038486d734e97844c547e664e88 Mon Sep 17 00:00:00 2001 From: Maxime Quandalle Date: Sat, 31 Oct 2015 09:26:55 -0700 Subject: 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 --- client/components/cards/cardDetails.js | 2 +- client/components/forms/forms.styl | 4 +- client/components/lists/list.js | 2 +- client/components/lists/listBody.jade | 15 +++- client/components/lists/listBody.js | 156 +++++++++++++++------------------ client/components/lists/listHeader.js | 2 +- client/components/sidebar/sidebar.js | 2 +- client/lib/textComplete.js | 32 ++++++- 8 files changed, 118 insertions(+), 97 deletions(-) diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index fa818c5a..b4fdca52 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -13,7 +13,7 @@ BlazeComponent.extendComponent({ }, reachNextPeak() { - const activitiesComponent = this.childrenComponents('activities')[0]; + const activitiesComponent = this.childComponents('activities')[0]; activitiesComponent.loadNextPage(); }, diff --git a/client/components/forms/forms.styl b/client/components/forms/forms.styl index 2d92aca9..9ae95140 100644 --- a/client/components/forms/forms.styl +++ b/client/components/forms/forms.styl @@ -621,11 +621,11 @@ button width: 11px height: @width border-radius: 2px - margin-right: 3px + margin: 2px 7px -2px -2px display: inline-block &.active background: #005377 - a + a, .quiet color: white 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) - ? `${value}` - : value; - return (colorName && colorName !== '') - ? `
${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()); diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index ccb9f2f5..ef071fe0 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -54,7 +54,7 @@ BlazeComponent.extendComponent({ }, reachNextPeak() { - const activitiesComponent = this.childrenComponents('activities')[0]; + const activitiesComponent = this.childComponents('activities')[0]; activitiesComponent.loadNextPage(); }, diff --git a/client/lib/textComplete.js b/client/lib/textComplete.js index e50d7cbc..3e69d07f 100644 --- a/client/lib/textComplete.js +++ b/client/lib/textComplete.js @@ -3,8 +3,23 @@ // of the vanilla `textcomplete`. let dropdownMenuIsOpened = false; -$.fn.escapeableTextComplete = function(...args) { - this.textcomplete(...args); +$.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) { + // 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 // opened dropdown menu when the user press Escape. This behavior conflicts @@ -18,7 +33,14 @@ $.fn.escapeableTextComplete = function(...args) { }, 'textComplete:hide'() { 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', () => {}, - () => dropdownMenuIsOpened + () => dropdownMenuIsOpened, { + noClickEscapeOn: '.textcomplete-dropdown', + } ); -- cgit v1.2.3-1-g7c22