Переглянути джерело

Merge pull request #3459 from jrsupplee/new-search

Search All Boards: Added list of board, list and color names.
Lauri Ojansivu 4 роки тому
батько
коміт
1df060b8f6

+ 28 - 1
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,6 +39,26 @@ template(name="globalSearch")
               +resultCard(card)
       else
         .global-search-instructions
+          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
 

+ 104 - 5
client/components/main/globalSearch.js

@@ -42,6 +42,9 @@ BlazeComponent.extendComponent({
     this.query = new ReactiveVar('');
     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;
@@ -55,6 +58,25 @@ 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.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'));
@@ -111,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 });
@@ -185,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: [],
@@ -197,6 +224,9 @@ BlazeComponent.extendComponent({
       assignees: [],
       labels: [],
       is: [],
+      dueAt: null,
+      createdAt: null,
+      modifiedAt: null,
     };
 
     let text = '';
@@ -223,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',
@@ -355,6 +410,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 +425,42 @@ BlazeComponent.extendComponent({
           evt.preventDefault();
           this.searchAllBoards(evt.target.searchQuery.value);
         },
+        'click .js-label-color'(evt) {
+          evt.preventDefault();
+          this.query.set(
+            `${this.query.get()} ${TAPi18n.__('operator-label')}:"${
+              evt.currentTarget.textContent
+            }"`,
+          );
+          document.getElementById('global-search-input').focus();
+        },
+        'click .js-board-title'(evt) {
+          evt.preventDefault();
+          this.query.set(
+            `${this.query.get()} ${TAPi18n.__('operator-board')}:"${
+              evt.currentTarget.textContent
+            }"`,
+          );
+          document.getElementById('global-search-input').focus();
+        },
+        'click .js-list-title'(evt) {
+          evt.preventDefault();
+          this.query.set(
+            `${this.query.get()} ${TAPi18n.__('operator-list')}:"${
+              evt.currentTarget.textContent
+            }"`,
+          );
+          document.getElementById('global-search-input').focus();
+        },
+        'click .js-label-name'(evt) {
+          evt.preventDefault();
+          this.query.set(
+            `${this.query.get()} ${TAPi18n.__('operator-label')}:"${
+              evt.currentTarget.textContent
+            }"`,
+          );
+          document.getElementById('global-search-input').focus();
+        },
       },
     ];
   },

+ 9 - 0
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
@@ -95,3 +101,6 @@ code
   background-color: lightgrey
   padding: 0.1rem !important
   font-size: 0.7rem !important
+
+.list-title
+  background-color: darkgray

+ 7 - 1
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\"`).",
@@ -916,5 +920,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"
 }

+ 20 - 0
models/boards.js

@@ -1343,6 +1343,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({

+ 16 - 2
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 };
 };

+ 14 - 0
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;
+        }),
+    ).sort();
+  },
 });
 
 Lists.hookOptions.after.update = { fetchPrevious: false };