From 6d9928ea8fc761a469c22e424f0fcace50b78627 Mon Sep 17 00:00:00 2001 From: "John R. Supplee" Date: Wed, 20 Jan 2021 13:15:10 +0200 Subject: [PATCH 1/5] Add color palette and allow selection --- client/components/main/globalSearch.jade | 3 +++ client/components/main/globalSearch.js | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/client/components/main/globalSearch.jade b/client/components/main/globalSearch.jade index 961c5b436..cd5337296 100644 --- a/client/components/main/globalSearch.jade +++ b/client/components/main/globalSearch.jade @@ -34,6 +34,9 @@ template(name="globalSearch") .global-search-instructions +viewer = searchInstructions + .palette-colors: each label in labelColors + span.card-label.palette-color.js-palette-color(class="card-label-{{label.color}}") + = label.name template(name="globalSearchViewChangePopup") if currentUser diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index d8e4134ca..b3eea0ca4 100644 --- a/client/components/main/globalSearch.js +++ b/client/components/main/globalSearch.js @@ -355,6 +355,14 @@ BlazeComponent.extendComponent({ return text; }, + labelColors() { + return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map( + color => { + return { color, name: TAPi18n.__(`color-${color}`) }; + }, + ); + }, + events() { return [ { @@ -362,6 +370,11 @@ BlazeComponent.extendComponent({ evt.preventDefault(); this.searchAllBoards(evt.target.searchQuery.value); }, + 'click .js-palette-color'(evt) { + this.query.set( + `${this.query.get()} label:"${evt.currentTarget.textContent}"`, + ); + }, }, ]; }, From 52f920db12a17f5e4618ef5d2d4650a0fcffb84c Mon Sep 17 00:00:00 2001 From: "John R. Supplee" Date: Wed, 20 Jan 2021 21:52:27 +0200 Subject: [PATCH 2/5] Add clickable list titles --- client/components/main/globalSearch.jade | 8 ++++++-- client/components/main/globalSearch.js | 13 +++++++++++++ client/components/main/globalSearch.styl | 3 +++ models/lists.js | 14 ++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/client/components/main/globalSearch.jade b/client/components/main/globalSearch.jade index cd5337296..07979fd41 100644 --- a/client/components/main/globalSearch.jade +++ b/client/components/main/globalSearch.jade @@ -32,11 +32,15 @@ template(name="globalSearch") +resultCard(card) else .global-search-instructions - +viewer - = searchInstructions + h2 Label Colors .palette-colors: each label in labelColors span.card-label.palette-color.js-palette-color(class="card-label-{{label.color}}") = label.name + h2 Lists + .lists-wrapper + each title in myLists.get + span.card-label.list-title.js-list-title + = title template(name="globalSearchViewChangePopup") if currentUser diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index b3eea0ca4..c9787c517 100644 --- a/client/components/main/globalSearch.js +++ b/client/components/main/globalSearch.js @@ -42,6 +42,7 @@ BlazeComponent.extendComponent({ this.query = new ReactiveVar(''); this.resultsHeading = new ReactiveVar(''); this.searchLink = new ReactiveVar(null); + this.myLists = new ReactiveVar([]); this.queryParams = null; this.parsingErrors = []; this.resultsCount = 0; @@ -55,6 +56,13 @@ BlazeComponent.extendComponent({ // } // // eslint-disable-next-line no-console // console.log('colorMap:', this.colorMap); + + Meteor.call('myLists', (err, data) => { + if (!err) { + this.myLists.set(data); + } + }); + Meteor.subscribe('setting'); if (Session.get('globalQuery')) { this.searchAllBoards(Session.get('globalQuery')); @@ -375,6 +383,11 @@ BlazeComponent.extendComponent({ `${this.query.get()} label:"${evt.currentTarget.textContent}"`, ); }, + 'click .js-list-title'(evt) { + this.query.set( + `${this.query.get()} list:"${evt.currentTarget.textContent}"`, + ); + }, }, ]; }, diff --git a/client/components/main/globalSearch.styl b/client/components/main/globalSearch.styl index 4dc5b5f6d..242b5156f 100644 --- a/client/components/main/globalSearch.styl +++ b/client/components/main/globalSearch.styl @@ -95,3 +95,6 @@ code background-color: lightgrey padding: 0.1rem !important font-size: 0.7rem !important + +.list-title + background-color: darkgray diff --git a/models/lists.js b/models/lists.js index dcfd4294e..f1824ee5d 100644 --- a/models/lists.js +++ b/models/lists.js @@ -362,6 +362,20 @@ Meteor.methods({ const list = Lists.findOne({ _id: listId }); list.toggleSoftLimit(!list.getWipLimit('soft')); }, + + myLists() { + // my lists + return _.uniq( + Lists.find( + { boardId: { $in: Boards.userBoardIds(this.userId) } }, + { fields: { title: 1 } }, + ) + .fetch() + .map(list => { + return list.title; + }), + ); + }, }); Lists.hookOptions.after.update = { fetchPrevious: false }; From 61c691a267151dec48706cd3bec596c10497ef9b Mon Sep 17 00:00:00 2001 From: "John R. Supplee" Date: Thu, 21 Jan 2021 01:48:24 +0200 Subject: [PATCH 3/5] Add support for clicking label names and board titles --- client/components/main/globalSearch.jade | 21 ++++++++-- client/components/main/globalSearch.js | 50 +++++++++++++++++++++--- models/boards.js | 20 ++++++++++ models/lists.js | 2 +- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/client/components/main/globalSearch.jade b/client/components/main/globalSearch.jade index 07979fd41..d04d9045a 100644 --- a/client/components/main/globalSearch.jade +++ b/client/components/main/globalSearch.jade @@ -32,15 +32,28 @@ template(name="globalSearch") +resultCard(card) else .global-search-instructions - h2 Label Colors - .palette-colors: each label in labelColors - span.card-label.palette-color.js-palette-color(class="card-label-{{label.color}}") - = label.name + h2 Boards + .lists-wrapper + each title in myBoardNames.get + span.card-label.list-title.js-board-title + = title h2 Lists .lists-wrapper each title in myLists.get span.card-label.list-title.js-list-title = title + h2 Label Colors + .palette-colors: each label in labelColors + span.card-label.palette-color.js-label-color(class="card-label-{{label.color}}") + = label.name + if myLabelNames.get.length + h2 Label Names + .lists-wrapper + each name in myLabelNames.get + span.card-label.list-title.js-label-name + = name + +viewer + = searchInstructions template(name="globalSearchViewChangePopup") if currentUser diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index c9787c517..e274f671d 100644 --- a/client/components/main/globalSearch.js +++ b/client/components/main/globalSearch.js @@ -43,6 +43,8 @@ BlazeComponent.extendComponent({ this.resultsHeading = new ReactiveVar(''); this.searchLink = new ReactiveVar(null); this.myLists = new ReactiveVar([]); + this.myLabelNames = new ReactiveVar([]); + this.myBoardNames = new ReactiveVar([]); this.queryParams = null; this.parsingErrors = []; this.resultsCount = 0; @@ -63,6 +65,18 @@ BlazeComponent.extendComponent({ } }); + Meteor.call('myLabelNames', (err, data) => { + if (!err) { + this.myLabelNames.set(data); + } + }); + + Meteor.call('myBoardNames', (err, data) => { + if (!err) { + this.myBoardNames.set(data); + } + }); + Meteor.subscribe('setting'); if (Session.get('globalQuery')) { this.searchAllBoards(Session.get('globalQuery')); @@ -119,11 +133,13 @@ BlazeComponent.extendComponent({ messages.push({ tag: 'list-title-not-found', value: list }); }); this.queryErrors.notFound.labels.forEach(label => { - const color = TAPi18n.__(`color-${label}`); - if (color) { + const color = Object.entries(this.colorMap) + .filter(value => value[1] === label) + .map(value => value[0]); + if (color.length) { messages.push({ tag: 'label-color-not-found', - value: color, + value: color[0], }); } else { messages.push({ tag: 'label-not-found', value: label }); @@ -378,14 +394,36 @@ BlazeComponent.extendComponent({ evt.preventDefault(); this.searchAllBoards(evt.target.searchQuery.value); }, - 'click .js-palette-color'(evt) { + 'click .js-label-color'(evt) { + evt.preventDefault(); this.query.set( - `${this.query.get()} label:"${evt.currentTarget.textContent}"`, + `${this.query.get()} ${TAPi18n.__('operator-label')}:"${ + evt.currentTarget.textContent + }"`, + ); + }, + 'click .js-board-title'(evt) { + evt.preventDefault(); + this.query.set( + `${this.query.get()} ${TAPi18n.__('operator-board')}:"${ + evt.currentTarget.textContent + }"`, ); }, 'click .js-list-title'(evt) { + evt.preventDefault(); this.query.set( - `${this.query.get()} list:"${evt.currentTarget.textContent}"`, + `${this.query.get()} ${TAPi18n.__('operator-list')}:"${ + evt.currentTarget.textContent + }"`, + ); + }, + 'click .js-label-name'(evt) { + evt.preventDefault(); + this.query.set( + `${this.query.get()} ${TAPi18n.__('operator-label')}:"${ + evt.currentTarget.textContent + }"`, ); }, }, diff --git a/models/boards.js b/models/boards.js index cfeda7309..db03bc1ea 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1324,6 +1324,26 @@ if (Meteor.isServer) { }, }); }, + myLabelNames() { + let names = []; + Boards.userBoards(Meteor.userId()).forEach(board => { + names = names.concat( + board.labels + .filter(label => !!label.name) + .map(label => { + return label.name; + }), + ); + }); + return _.uniq(names).sort(); + }, + myBoardNames() { + return _.uniq( + Boards.userBoards(Meteor.userId()).map(board => { + return board.title; + }), + ).sort(); + }, }); Meteor.methods({ diff --git a/models/lists.js b/models/lists.js index f1824ee5d..920221683 100644 --- a/models/lists.js +++ b/models/lists.js @@ -374,7 +374,7 @@ Meteor.methods({ .map(list => { return list.title; }), - ); + ).sort(); }, }); From 7ced6318a57201dd184b89d1f3a2e12d68ba2e8e Mon Sep 17 00:00:00 2001 From: "John R. Supplee" Date: Thu, 21 Jan 2021 11:55:58 +0200 Subject: [PATCH 4/5] Global search * Make some heading translatable * set focus back to search phrase input after clicking a predicate * Some spacing issues --- client/components/main/globalSearch.jade | 17 ++++++++++++----- client/components/main/globalSearch.js | 4 ++++ client/components/main/globalSearch.styl | 6 ++++++ i18n/en.i18n.json | 4 +++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/client/components/main/globalSearch.jade b/client/components/main/globalSearch.jade index d04d9045a..3dac5c9ef 100644 --- a/client/components/main/globalSearch.jade +++ b/client/components/main/globalSearch.jade @@ -14,7 +14,14 @@ template(name="globalSearch") if currentUser .wrapper form.global-search-instructions.js-search-query-form - input.global-search-query-input(type="text" name="searchQuery" placeholder="{{_ 'search-example'}}" value="{{ query.get }}" autofocus dir="auto") + input.global-search-query-input( + id="global-search-input" + type="text" + name="searchQuery" + placeholder="{{_ 'search-example'}}" + value="{{ query.get }}" + autofocus dir="auto" + ) if searching.get +spinner else if hasResults.get @@ -32,22 +39,22 @@ template(name="globalSearch") +resultCard(card) else .global-search-instructions - h2 Boards + h2 {{_ 'boards' }} .lists-wrapper each title in myBoardNames.get span.card-label.list-title.js-board-title = title - h2 Lists + h2 {{_ 'lists' }} .lists-wrapper each title in myLists.get span.card-label.list-title.js-list-title = title - h2 Label Colors + h2 {{_ 'label-colors' }} .palette-colors: each label in labelColors span.card-label.palette-color.js-label-color(class="card-label-{{label.color}}") = label.name if myLabelNames.get.length - h2 Label Names + h2 {{_ 'label-names' }} .lists-wrapper each name in myLabelNames.get span.card-label.list-title.js-label-name diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index e274f671d..cfb698edc 100644 --- a/client/components/main/globalSearch.js +++ b/client/components/main/globalSearch.js @@ -401,6 +401,7 @@ BlazeComponent.extendComponent({ evt.currentTarget.textContent }"`, ); + document.getElementById('global-search-input').focus(); }, 'click .js-board-title'(evt) { evt.preventDefault(); @@ -409,6 +410,7 @@ BlazeComponent.extendComponent({ evt.currentTarget.textContent }"`, ); + document.getElementById('global-search-input').focus(); }, 'click .js-list-title'(evt) { evt.preventDefault(); @@ -417,6 +419,7 @@ BlazeComponent.extendComponent({ evt.currentTarget.textContent }"`, ); + document.getElementById('global-search-input').focus(); }, 'click .js-label-name'(evt) { evt.preventDefault(); @@ -425,6 +428,7 @@ BlazeComponent.extendComponent({ evt.currentTarget.textContent }"`, ); + document.getElementById('global-search-input').focus(); }, }, ]; diff --git a/client/components/main/globalSearch.styl b/client/components/main/globalSearch.styl index 242b5156f..b982f4eed 100644 --- a/client/components/main/globalSearch.styl +++ b/client/components/main/globalSearch.styl @@ -78,6 +78,12 @@ margin-left: auto line-height: 150% +.global-search-instructions h1 + margin-top: 2rem; + +.global-search-instructions h2 + margin-top: 1rem; + .global-search-query-input width: 90% !important margin-right: auto diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index b5d1df9c7..533b0fbb1 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -916,5 +916,7 @@ "globalSearch-instructions-notes-5": "Currently archived cards are not searched.", "link-to-search": "Link to this search", "excel-font": "Arial", - "number": "Number" + "number": "Number", + "label-colors": "Label Colors", + "label-names": "Label Names" } From 319783b00886105bdae16d3863d183adb2f53f67 Mon Sep 17 00:00:00 2001 From: "John R. Supplee" Date: Thu, 21 Jan 2021 18:11:09 +0200 Subject: [PATCH 5/5] Global search: add new operators * add operators for due, created and modified --- client/components/main/globalSearch.js | 35 ++++++++++++++++++++++++-- i18n/en.i18n.json | 4 +++ models/cards.js | 18 +++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index cfb698edc..8ad6aec2f 100644 --- a/client/components/main/globalSearch.js +++ b/client/components/main/globalSearch.js @@ -209,9 +209,12 @@ BlazeComponent.extendComponent({ operatorMap[TAPi18n.__('operator-assignee')] = 'assignees'; operatorMap[TAPi18n.__('operator-assignee-abbrev')] = 'assignees'; operatorMap[TAPi18n.__('operator-is')] = 'is'; + operatorMap[TAPi18n.__('operator-due')] = 'dueAt'; + operatorMap[TAPi18n.__('operator-created')] = 'createdAt'; + operatorMap[TAPi18n.__('operator-modified')] = 'modifiedAt'; // eslint-disable-next-line no-console - // console.log('operatorMap:', operatorMap); + console.log('operatorMap:', operatorMap); const params = { boards: [], swimlanes: [], @@ -221,6 +224,9 @@ BlazeComponent.extendComponent({ assignees: [], labels: [], is: [], + dueAt: null, + createdAt: null, + modifiedAt: null, }; let text = ''; @@ -247,8 +253,33 @@ BlazeComponent.extendComponent({ if (value in this.colorMap) { value = this.colorMap[value]; } + } else if ( + ['dueAt', 'createdAt', 'modifiedAt'].includes(operatorMap[op]) + ) { + const days = parseInt(value, 10); + if (isNaN(days)) { + if (['day', 'week', 'month', 'quarter', 'year'].includes(value)) { + value = moment() + .subtract(1, value) + .format(); + } else { + this.parsingErrors.push({ + tag: 'operator-number-expected', + value: { operator: op, value }, + }); + value = null; + } + } else { + value = moment() + .subtract(days, 'days') + .format(); + } + } + if (Array.isArray(params[operatorMap[op]])) { + params[operatorMap[op]].push(value); + } else { + params[operatorMap[op]] = value; } - params[operatorMap[op]].push(value); } else { this.parsingErrors.push({ tag: 'operator-unknown-error', diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 533b0fbb1..3e020dac7 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -895,7 +895,11 @@ "operator-assignee": "assignee", "operator-assignee-abbrev": "a", "operator-is": "is", + "operator-due": "due", + "operator-created": "created", + "operator-modified": "modified", "operator-unknown-error": "%s is not an operator", + "operator-number-expected": "operator __operator__ expected a number, got '__value__'", "heading-notes": "Notes", "globalSearch-instructions-heading": "Search Instructions", "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", diff --git a/models/cards.js b/models/cards.js index 65977ef2a..dd2b347bc 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1954,6 +1954,18 @@ Cards.globalSearch = queryParams => { selector.listId.$in = queryLists; } + if (queryParams.dueAt !== null) { + selector.dueAt = { $gte: new Date(queryParams.dueAt) }; + } + + if (queryParams.createdAt !== null) { + selector.createdAt = { $gte: new Date(queryParams.createdAt) }; + } + + if (queryParams.modifiedAt !== null) { + selector.modifiedAt = { $gte: new Date(queryParams.modifiedAt) }; + } + const queryMembers = []; const queryAssignees = []; if (queryParams.users.length) { @@ -2079,7 +2091,7 @@ Cards.globalSearch = queryParams => { } // eslint-disable-next-line no-console - // console.log('selector:', selector); + console.log('selector:', selector); const cards = Cards.find(selector, { fields: { _id: 1, @@ -2094,13 +2106,15 @@ Cards.globalSearch = queryParams => { assignees: 1, colors: 1, dueAt: 1, + createdAt: 1, + modifiedAt: 1, labelIds: 1, }, limit: 50, }); // eslint-disable-next-line no-console - // console.log('count:', cards.count()); + console.log('count:', cards.count()); return { cards, errors }; };