Browse Source

Move query parsing to Query class

John R. Supplee 4 years ago
parent
commit
6def7d6f70
3 changed files with 374 additions and 381 deletions
  1. 13 366
      client/components/main/globalSearch.js
  2. 347 3
      config/query-classes.js
  3. 14 12
      server/publications/cards.js

+ 13 - 366
client/components/main/globalSearch.js

@@ -1,48 +1,6 @@
 import { CardSearchPagedComponent } from '../../lib/cardSearch';
 import Boards from '../../../models/boards';
-import moment from 'moment';
-import {
-  OPERATOR_ASSIGNEE,
-  OPERATOR_BOARD,
-  OPERATOR_COMMENT,
-  OPERATOR_CREATED_AT,
-  OPERATOR_DUE,
-  OPERATOR_HAS,
-  OPERATOR_LABEL,
-  OPERATOR_LIMIT,
-  OPERATOR_LIST,
-  OPERATOR_MEMBER,
-  OPERATOR_MODIFIED_AT,
-  OPERATOR_SORT,
-  OPERATOR_STATUS,
-  OPERATOR_SWIMLANE,
-  OPERATOR_UNKNOWN,
-  OPERATOR_USER,
-  ORDER_ASCENDING,
-  ORDER_DESCENDING,
-  PREDICATE_ALL,
-  PREDICATE_ARCHIVED,
-  PREDICATE_ASSIGNEES,
-  PREDICATE_ATTACHMENT,
-  PREDICATE_CHECKLIST,
-  PREDICATE_CREATED_AT,
-  PREDICATE_DESCRIPTION,
-  PREDICATE_DUE_AT,
-  PREDICATE_END_AT,
-  PREDICATE_ENDED,
-  PREDICATE_MEMBERS,
-  PREDICATE_MODIFIED_AT,
-  PREDICATE_MONTH,
-  PREDICATE_OPEN,
-  PREDICATE_OVERDUE,
-  PREDICATE_PRIVATE,
-  PREDICATE_PUBLIC,
-  PREDICATE_QUARTER,
-  PREDICATE_START_AT,
-  PREDICATE_WEEK,
-  PREDICATE_YEAR,
-} from '../../../config/search-const';
-import { QueryErrors, QueryParams } from '../../../config/query-classes';
+import { Query, QueryErrors } from '../../../config/query-classes';
 
 // const subManager = new SubsManager();
 
@@ -62,24 +20,6 @@ Template.globalSearch.helpers({
   },
 });
 
-BlazeComponent.extendComponent({
-  events() {
-    return [
-      {
-        'click .js-due-cards-view-me'() {
-          Utils.setDueCardsView('me');
-          Popup.close();
-        },
-
-        'click .js-due-cards-view-all'() {
-          Utils.setDueCardsView('all');
-          Popup.close();
-        },
-      },
-    ];
-  },
-}).register('globalSearchViewChangePopup');
-
 class GlobalSearchComponent extends CardSearchPagedComponent {
   onCreated() {
     super.onCreated();
@@ -87,7 +27,6 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
     this.myLabelNames = new ReactiveVar([]);
     this.myBoardNames = new ReactiveVar([]);
     this.parsingErrors = new QueryErrors();
-    this.colorMap = null;
     this.queryParams = null;
 
     Meteor.call('myLists', (err, data) => {
@@ -114,9 +53,6 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
 
     // eslint-disable-next-line no-console
     //console.log('lang:', TAPi18n.getLanguage());
-    this.colorMap = Boards.colorMap();
-    // eslint-disable-next-line no-console
-    // console.log('colorMap:', this.colorMap);
 
     if (Session.get('globalQuery')) {
       this.searchAllBoards(Session.get('globalQuery'));
@@ -139,327 +75,38 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
     this.parsingErrors.errorMessages();
   }
 
-  searchAllBoards(query) {
-    query = query.trim();
+  searchAllBoards(queryText) {
+    queryText = queryText.trim();
     // eslint-disable-next-line no-console
-    //console.log('query:', query);
+    //console.log('queryText:', queryText);
 
-    this.query.set(query);
+    this.query.set(queryText);
 
     this.resetSearch();
 
-    if (!query) {
+    if (!queryText) {
       return;
     }
 
     this.searching.set(true);
 
-    const reOperator1 = new RegExp(
-      '^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\\p{Letter}\\p{Mark}]+)(\\s+|$)',
-      'iu',
-    );
-    const reOperator2 = new RegExp(
-      '^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["\']*)(?<value>.*?)\\k<quote>(\\s+|$)',
-      'iu',
-    );
-    const reText = new RegExp('^(?<text>\\S+)(\\s+|$)', 'u');
-    const reQuotedText = new RegExp(
-      '^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
-      'u',
-    );
-    const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
-
-    const operators = {
-      'operator-board': OPERATOR_BOARD,
-      'operator-board-abbrev': OPERATOR_BOARD,
-      'operator-swimlane': OPERATOR_SWIMLANE,
-      'operator-swimlane-abbrev': OPERATOR_SWIMLANE,
-      'operator-list': OPERATOR_LIST,
-      'operator-list-abbrev': OPERATOR_LIST,
-      'operator-label': OPERATOR_LABEL,
-      'operator-label-abbrev': OPERATOR_LABEL,
-      'operator-user': OPERATOR_USER,
-      'operator-user-abbrev': OPERATOR_USER,
-      'operator-member': OPERATOR_MEMBER,
-      'operator-member-abbrev': OPERATOR_MEMBER,
-      'operator-assignee': OPERATOR_ASSIGNEE,
-      'operator-assignee-abbrev': OPERATOR_ASSIGNEE,
-      'operator-status': OPERATOR_STATUS,
-      'operator-due': OPERATOR_DUE,
-      'operator-created': OPERATOR_CREATED_AT,
-      'operator-modified': OPERATOR_MODIFIED_AT,
-      'operator-comment': OPERATOR_COMMENT,
-      'operator-has': OPERATOR_HAS,
-      'operator-sort': OPERATOR_SORT,
-      'operator-limit': OPERATOR_LIMIT,
-    };
-
-    const predicates = {
-      due: {
-        'predicate-overdue': PREDICATE_OVERDUE,
-      },
-      durations: {
-        'predicate-week': PREDICATE_WEEK,
-        'predicate-month': PREDICATE_MONTH,
-        'predicate-quarter': PREDICATE_QUARTER,
-        'predicate-year': PREDICATE_YEAR,
-      },
-      status: {
-        'predicate-archived': PREDICATE_ARCHIVED,
-        'predicate-all': PREDICATE_ALL,
-        'predicate-open': PREDICATE_OPEN,
-        'predicate-ended': PREDICATE_ENDED,
-        'predicate-public': PREDICATE_PUBLIC,
-        'predicate-private': PREDICATE_PRIVATE,
-      },
-      sorts: {
-        'predicate-due': PREDICATE_DUE_AT,
-        'predicate-created': PREDICATE_CREATED_AT,
-        'predicate-modified': PREDICATE_MODIFIED_AT,
-      },
-      has: {
-        'predicate-description': PREDICATE_DESCRIPTION,
-        'predicate-checklist': PREDICATE_CHECKLIST,
-        'predicate-attachment': PREDICATE_ATTACHMENT,
-        'predicate-start': PREDICATE_START_AT,
-        'predicate-end': PREDICATE_END_AT,
-        'predicate-due': PREDICATE_DUE_AT,
-        'predicate-assignee': PREDICATE_ASSIGNEES,
-        'predicate-member': PREDICATE_MEMBERS,
-      },
-    };
-    const predicateTranslations = {};
-    Object.entries(predicates).forEach(([category, catPreds]) => {
-      predicateTranslations[category] = {};
-      Object.entries(catPreds).forEach(([tag, value]) => {
-        predicateTranslations[category][TAPi18n.__(tag)] = value;
-      });
-    });
-    // eslint-disable-next-line no-console
-    // console.log('predicateTranslations:', predicateTranslations);
-
-    const operatorMap = {};
-    Object.entries(operators).forEach(([key, value]) => {
-      operatorMap[TAPi18n.__(key).toLowerCase()] = value;
-    });
-    // eslint-disable-next-line no-console
-    // console.log('operatorMap:', operatorMap);
-
-    const params = new QueryParams();
-    let text = '';
-    while (query) {
-      let m = query.match(reOperator1);
-      if (!m) {
-        m = query.match(reOperator2);
-        if (m) {
-          query = query.replace(reOperator2, '');
-        }
-      } else {
-        query = query.replace(reOperator1, '');
-      }
-      if (m) {
-        let op;
-        if (m.groups.operator) {
-          op = m.groups.operator.toLowerCase();
-        } else {
-          op = m.groups.abbrev.toLowerCase();
-        }
-        // eslint-disable-next-line no-prototype-builtins
-        if (operatorMap.hasOwnProperty(op)) {
-          const operator = operatorMap[op];
-          let value = m.groups.value;
-          if (operator === OPERATOR_LABEL) {
-            if (value in this.colorMap) {
-              value = this.colorMap[value];
-              // console.log('found color:', value);
-            }
-          } else if (
-            [OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].includes(
-              operator,
-            )
-          ) {
-            const days = parseInt(value, 10);
-            let duration = null;
-            if (isNaN(days)) {
-              // duration was specified as text
-              if (predicateTranslations.durations[value]) {
-                duration = predicateTranslations.durations[value];
-                let date = null;
-                switch (duration) {
-                  case PREDICATE_WEEK:
-                    // eslint-disable-next-line no-case-declarations
-                    const week = moment().week();
-                    if (week === 52) {
-                      date = moment(1, 'W');
-                      date.set('year', date.year() + 1);
-                    } else {
-                      date = moment(week + 1, 'W');
-                    }
-                    break;
-                  case PREDICATE_MONTH:
-                    // eslint-disable-next-line no-case-declarations
-                    const month = moment().month();
-                    // .month() is zero indexed
-                    if (month === 11) {
-                      date = moment(1, 'M');
-                      date.set('year', date.year() + 1);
-                    } else {
-                      date = moment(month + 2, 'M');
-                    }
-                    break;
-                  case PREDICATE_QUARTER:
-                    // eslint-disable-next-line no-case-declarations
-                    const quarter = moment().quarter();
-                    if (quarter === 4) {
-                      date = moment(1, 'Q');
-                      date.set('year', date.year() + 1);
-                    } else {
-                      date = moment(quarter + 1, 'Q');
-                    }
-                    break;
-                  case PREDICATE_YEAR:
-                    date = moment(moment().year() + 1, 'YYYY');
-                    break;
-                }
-                if (date) {
-                  value = {
-                    operator: '$lt',
-                    value: date.format('YYYY-MM-DD'),
-                  };
-                }
-              } else if (
-                operator === OPERATOR_DUE &&
-                value === PREDICATE_OVERDUE
-              ) {
-                value = {
-                  operator: '$lt',
-                  value: moment().format('YYYY-MM-DD'),
-                };
-              } else {
-                this.parsingErrors.addError(OPERATOR_DUE, {
-                  tag: 'operator-number-expected',
-                  value: { operator: op, value },
-                });
-                continue;
-              }
-            } else if (operator === OPERATOR_DUE) {
-              value = {
-                operator: '$lt',
-                value: moment(moment().format('YYYY-MM-DD'))
-                  .add(days + 1, duration ? duration : 'days')
-                  .format(),
-              };
-            } else {
-              value = {
-                operator: '$gte',
-                value: moment(moment().format('YYYY-MM-DD'))
-                  .subtract(days, duration ? duration : 'days')
-                  .format(),
-              };
-            }
-          } else if (operator === OPERATOR_SORT) {
-            let negated = false;
-            const m = value.match(reNegatedOperator);
-            if (m) {
-              value = m.groups.operator;
-              negated = true;
-            }
-            if (!predicateTranslations.sorts[value]) {
-              this.parsingErrors.addError(OPERATOR_SORT, {
-                tag: 'operator-sort-invalid',
-                value,
-              });
-              continue;
-            } else {
-              value = {
-                name: predicateTranslations.sorts[value],
-                order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
-              };
-            }
-          } else if (operator === OPERATOR_STATUS) {
-            if (!predicateTranslations.status[value]) {
-              this.parsingErrors.addError(OPERATOR_STATUS, {
-                tag: 'operator-status-invalid',
-                value,
-              });
-              continue;
-            } else {
-              value = predicateTranslations.status[value];
-            }
-          } else if (operator === OPERATOR_HAS) {
-            let negated = false;
-            const m = value.match(reNegatedOperator);
-            if (m) {
-              value = m.groups.operator;
-              negated = true;
-            }
-            if (!predicateTranslations.has[value]) {
-              this.parsingErrors.addError(OPERATOR_HAS, {
-                tag: 'operator-has-invalid',
-                value,
-              });
-              continue;
-            } else {
-              value = {
-                field: predicateTranslations.has[value],
-                exists: !negated,
-              };
-            }
-          } else if (operator === OPERATOR_LIMIT) {
-            const limit = parseInt(value, 10);
-            if (isNaN(limit) || limit < 1) {
-              this.parsingErrors.addError(OPERATOR_LIMIT, {
-                tag: 'operator-limit-invalid',
-                value,
-              });
-              continue;
-            } else {
-              value = limit;
-            }
-          }
-
-          params.addPredicate(operator, value);
-        } else {
-          this.parsingErrors.addError(OPERATOR_UNKNOWN, {
-            tag: 'operator-unknown-error',
-            value: op,
-          });
-        }
-        continue;
-      }
-
-      m = query.match(reQuotedText);
-      if (!m) {
-        m = query.match(reText);
-        if (m) {
-          query = query.replace(reText, '');
-        }
-      } else {
-        query = query.replace(reQuotedText, '');
-      }
-      if (m) {
-        text += (text ? ' ' : '') + m.groups.text;
-      }
-    }
+    const query = new Query();
+    query.buildParams(queryText);
 
     // eslint-disable-next-line no-console
-    // console.log('text:', text);
-    params.text = text;
+    // console.log('params:', query.getParams());
 
-    // eslint-disable-next-line no-console
-    console.log('params:', params);
+    this.queryParams = query.getParams();
 
-    this.queryParams = params;
-
-    if (this.parsingErrors.hasErrors()) {
+    if (query.hasErrors()) {
       this.searching.set(false);
-      this.queryErrors = this.parsingErrorMessages();
+      this.queryErrors = query.errors();
       this.hasResults.set(true);
       this.hasQueryErrors.set(true);
       return;
     }
 
-    this.runGlobalSearch(params.getParams());
+    this.runGlobalSearch(query.getParams());
   }
 
   searchInstructions() {

+ 347 - 3
config/query-classes.js

@@ -2,13 +2,45 @@ import {
   OPERATOR_ASSIGNEE,
   OPERATOR_BOARD,
   OPERATOR_COMMENT,
+  OPERATOR_CREATED_AT,
+  OPERATOR_DUE,
+  OPERATOR_HAS,
   OPERATOR_LABEL,
+  OPERATOR_LIMIT,
   OPERATOR_LIST,
   OPERATOR_MEMBER,
+  OPERATOR_MODIFIED_AT,
+  OPERATOR_SORT,
+  OPERATOR_STATUS,
   OPERATOR_SWIMLANE,
+  OPERATOR_UNKNOWN,
   OPERATOR_USER,
+  ORDER_ASCENDING,
+  ORDER_DESCENDING,
+  PREDICATE_ALL,
+  PREDICATE_ARCHIVED,
+  PREDICATE_ASSIGNEES,
+  PREDICATE_ATTACHMENT,
+  PREDICATE_CHECKLIST,
+  PREDICATE_CREATED_AT,
+  PREDICATE_DESCRIPTION,
+  PREDICATE_DUE_AT,
+  PREDICATE_END_AT,
+  PREDICATE_ENDED,
+  PREDICATE_MEMBERS,
+  PREDICATE_MODIFIED_AT,
+  PREDICATE_MONTH,
+  PREDICATE_OPEN,
+  PREDICATE_OVERDUE,
+  PREDICATE_PRIVATE,
+  PREDICATE_PUBLIC,
+  PREDICATE_QUARTER,
+  PREDICATE_START_AT,
+  PREDICATE_WEEK,
+  PREDICATE_YEAR,
 } from './search-const';
 import Boards from '../models/boards';
+import moment from 'moment';
 
 export class QueryParams {
   text = '';
@@ -106,7 +138,8 @@ export class QueryErrors {
 
   errors() {
     const errs = [];
-    Object.entries(this._errors).forEach(([operator, errors]) => {
+    // eslint-disable-next-line no-unused-vars
+    Object.entries(this._errors).forEach(([, errors]) => {
       errors.forEach(err => {
         errs.push(err);
       });
@@ -116,7 +149,8 @@ export class QueryErrors {
 
   errorMessages() {
     const messages = [];
-    Object.entries(this._errors).forEach(([operator, errors]) => {
+    // eslint-disable-next-line no-unused-vars
+    Object.entries(this._errors).forEach(([, errors]) => {
       errors.forEach(err => {
         messages.push(TAPi18n.__(err.tag, err.value));
       });
@@ -126,12 +160,14 @@ export class QueryErrors {
 }
 
 export class Query {
-  params = {};
   selector = {};
   projection = {};
 
   constructor(selector, projection) {
     this._errors = new QueryErrors();
+    this.queryParams = new QueryParams();
+    this.colorMap = Boards.colorMap();
+
     if (selector) {
       this.selector = selector;
     }
@@ -152,4 +188,312 @@ export class Query {
   errorMessages() {
     return this._errors.errorMessages();
   }
+
+  getParams() {
+    return this.queryParams.getParams();
+  }
+
+  buildParams(queryText) {
+    queryText = queryText.trim();
+    // eslint-disable-next-line no-console
+    //console.log('query:', query);
+
+    if (!queryText) {
+      return;
+    }
+
+    const reOperator1 = new RegExp(
+      '^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\\p{Letter}\\p{Mark}]+)(\\s+|$)',
+      'iu',
+    );
+    const reOperator2 = new RegExp(
+      '^((?<operator>[\\p{Letter}\\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["\']*)(?<value>.*?)\\k<quote>(\\s+|$)',
+      'iu',
+    );
+    const reText = new RegExp('^(?<text>\\S+)(\\s+|$)', 'u');
+    const reQuotedText = new RegExp(
+      '^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
+      'u',
+    );
+    const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
+
+    const operators = {
+      'operator-board': OPERATOR_BOARD,
+      'operator-board-abbrev': OPERATOR_BOARD,
+      'operator-swimlane': OPERATOR_SWIMLANE,
+      'operator-swimlane-abbrev': OPERATOR_SWIMLANE,
+      'operator-list': OPERATOR_LIST,
+      'operator-list-abbrev': OPERATOR_LIST,
+      'operator-label': OPERATOR_LABEL,
+      'operator-label-abbrev': OPERATOR_LABEL,
+      'operator-user': OPERATOR_USER,
+      'operator-user-abbrev': OPERATOR_USER,
+      'operator-member': OPERATOR_MEMBER,
+      'operator-member-abbrev': OPERATOR_MEMBER,
+      'operator-assignee': OPERATOR_ASSIGNEE,
+      'operator-assignee-abbrev': OPERATOR_ASSIGNEE,
+      'operator-status': OPERATOR_STATUS,
+      'operator-due': OPERATOR_DUE,
+      'operator-created': OPERATOR_CREATED_AT,
+      'operator-modified': OPERATOR_MODIFIED_AT,
+      'operator-comment': OPERATOR_COMMENT,
+      'operator-has': OPERATOR_HAS,
+      'operator-sort': OPERATOR_SORT,
+      'operator-limit': OPERATOR_LIMIT,
+    };
+
+    const predicates = {
+      due: {
+        'predicate-overdue': PREDICATE_OVERDUE,
+      },
+      durations: {
+        'predicate-week': PREDICATE_WEEK,
+        'predicate-month': PREDICATE_MONTH,
+        'predicate-quarter': PREDICATE_QUARTER,
+        'predicate-year': PREDICATE_YEAR,
+      },
+      status: {
+        'predicate-archived': PREDICATE_ARCHIVED,
+        'predicate-all': PREDICATE_ALL,
+        'predicate-open': PREDICATE_OPEN,
+        'predicate-ended': PREDICATE_ENDED,
+        'predicate-public': PREDICATE_PUBLIC,
+        'predicate-private': PREDICATE_PRIVATE,
+      },
+      sorts: {
+        'predicate-due': PREDICATE_DUE_AT,
+        'predicate-created': PREDICATE_CREATED_AT,
+        'predicate-modified': PREDICATE_MODIFIED_AT,
+      },
+      has: {
+        'predicate-description': PREDICATE_DESCRIPTION,
+        'predicate-checklist': PREDICATE_CHECKLIST,
+        'predicate-attachment': PREDICATE_ATTACHMENT,
+        'predicate-start': PREDICATE_START_AT,
+        'predicate-end': PREDICATE_END_AT,
+        'predicate-due': PREDICATE_DUE_AT,
+        'predicate-assignee': PREDICATE_ASSIGNEES,
+        'predicate-member': PREDICATE_MEMBERS,
+      },
+    };
+    const predicateTranslations = {};
+    Object.entries(predicates).forEach(([category, catPreds]) => {
+      predicateTranslations[category] = {};
+      Object.entries(catPreds).forEach(([tag, value]) => {
+        predicateTranslations[category][TAPi18n.__(tag)] = value;
+      });
+    });
+    // eslint-disable-next-line no-console
+    // console.log('predicateTranslations:', predicateTranslations);
+
+    const operatorMap = {};
+    Object.entries(operators).forEach(([key, value]) => {
+      operatorMap[TAPi18n.__(key).toLowerCase()] = value;
+    });
+    // eslint-disable-next-line no-console
+    // console.log('operatorMap:', operatorMap);
+
+    let text = '';
+    while (queryText) {
+      let m = queryText.match(reOperator1);
+      if (!m) {
+        m = queryText.match(reOperator2);
+        if (m) {
+          queryText = queryText.replace(reOperator2, '');
+        }
+      } else {
+        queryText = queryText.replace(reOperator1, '');
+      }
+      if (m) {
+        let op;
+        if (m.groups.operator) {
+          op = m.groups.operator.toLowerCase();
+        } else {
+          op = m.groups.abbrev.toLowerCase();
+        }
+        // eslint-disable-next-line no-prototype-builtins
+        if (operatorMap.hasOwnProperty(op)) {
+          const operator = operatorMap[op];
+          let value = m.groups.value;
+          if (operator === OPERATOR_LABEL) {
+            if (value in this.colorMap) {
+              value = this.colorMap[value];
+              // console.log('found color:', value);
+            }
+          } else if (
+            [OPERATOR_DUE, OPERATOR_CREATED_AT, OPERATOR_MODIFIED_AT].includes(
+              operator,
+            )
+          ) {
+            const days = parseInt(value, 10);
+            let duration = null;
+            if (isNaN(days)) {
+              // duration was specified as text
+              if (predicateTranslations.durations[value]) {
+                duration = predicateTranslations.durations[value];
+                let date = null;
+                switch (duration) {
+                  case PREDICATE_WEEK:
+                    // eslint-disable-next-line no-case-declarations
+                    const week = moment().week();
+                    if (week === 52) {
+                      date = moment(1, 'W');
+                      date.set('year', date.year() + 1);
+                    } else {
+                      date = moment(week + 1, 'W');
+                    }
+                    break;
+                  case PREDICATE_MONTH:
+                    // eslint-disable-next-line no-case-declarations
+                    const month = moment().month();
+                    // .month() is zero indexed
+                    if (month === 11) {
+                      date = moment(1, 'M');
+                      date.set('year', date.year() + 1);
+                    } else {
+                      date = moment(month + 2, 'M');
+                    }
+                    break;
+                  case PREDICATE_QUARTER:
+                    // eslint-disable-next-line no-case-declarations
+                    const quarter = moment().quarter();
+                    if (quarter === 4) {
+                      date = moment(1, 'Q');
+                      date.set('year', date.year() + 1);
+                    } else {
+                      date = moment(quarter + 1, 'Q');
+                    }
+                    break;
+                  case PREDICATE_YEAR:
+                    date = moment(moment().year() + 1, 'YYYY');
+                    break;
+                }
+                if (date) {
+                  value = {
+                    operator: '$lt',
+                    value: date.format('YYYY-MM-DD'),
+                  };
+                }
+              } else if (
+                operator === OPERATOR_DUE &&
+                value === PREDICATE_OVERDUE
+              ) {
+                value = {
+                  operator: '$lt',
+                  value: moment().format('YYYY-MM-DD'),
+                };
+              } else {
+                this.errors.addError(OPERATOR_DUE, {
+                  tag: 'operator-number-expected',
+                  value: { operator: op, value },
+                });
+                continue;
+              }
+            } else if (operator === OPERATOR_DUE) {
+              value = {
+                operator: '$lt',
+                value: moment(moment().format('YYYY-MM-DD'))
+                  .add(days + 1, duration ? duration : 'days')
+                  .format(),
+              };
+            } else {
+              value = {
+                operator: '$gte',
+                value: moment(moment().format('YYYY-MM-DD'))
+                  .subtract(days, duration ? duration : 'days')
+                  .format(),
+              };
+            }
+          } else if (operator === OPERATOR_SORT) {
+            let negated = false;
+            const m = value.match(reNegatedOperator);
+            if (m) {
+              value = m.groups.operator;
+              negated = true;
+            }
+            if (!predicateTranslations.sorts[value]) {
+              this.errors.addError(OPERATOR_SORT, {
+                tag: 'operator-sort-invalid',
+                value,
+              });
+              continue;
+            } else {
+              value = {
+                name: predicateTranslations.sorts[value],
+                order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
+              };
+            }
+          } else if (operator === OPERATOR_STATUS) {
+            if (!predicateTranslations.status[value]) {
+              this.errors.addError(OPERATOR_STATUS, {
+                tag: 'operator-status-invalid',
+                value,
+              });
+              continue;
+            } else {
+              value = predicateTranslations.status[value];
+            }
+          } else if (operator === OPERATOR_HAS) {
+            let negated = false;
+            const m = value.match(reNegatedOperator);
+            if (m) {
+              value = m.groups.operator;
+              negated = true;
+            }
+            if (!predicateTranslations.has[value]) {
+              this.errors.addError(OPERATOR_HAS, {
+                tag: 'operator-has-invalid',
+                value,
+              });
+              continue;
+            } else {
+              value = {
+                field: predicateTranslations.has[value],
+                exists: !negated,
+              };
+            }
+          } else if (operator === OPERATOR_LIMIT) {
+            const limit = parseInt(value, 10);
+            if (isNaN(limit) || limit < 1) {
+              this.errors.addError(OPERATOR_LIMIT, {
+                tag: 'operator-limit-invalid',
+                value,
+              });
+              continue;
+            } else {
+              value = limit;
+            }
+          }
+
+          this.queryParams.addPredicate(operator, value);
+        } else {
+          this.errors.addError(OPERATOR_UNKNOWN, {
+            tag: 'operator-unknown-error',
+            value: op,
+          });
+        }
+        continue;
+      }
+
+      m = queryText.match(reQuotedText);
+      if (!m) {
+        m = queryText.match(reText);
+        if (m) {
+          queryText = queryText.replace(reText, '');
+        }
+      } else {
+        queryText = queryText.replace(reQuotedText, '');
+      }
+      if (m) {
+        text += (text ? ' ' : '') + m.groups.text;
+      }
+    }
+
+    // eslint-disable-next-line no-console
+    // console.log('text:', text);
+    this.queryParams.text = text;
+
+    // eslint-disable-next-line no-console
+    console.log('queryParams:', this.queryParams);
+  }
 }

+ 14 - 12
server/publications/cards.js

@@ -1,7 +1,9 @@
+import moment from 'moment';
 import Users from '../../models/users';
 import Boards from '../../models/boards';
 import Lists from '../../models/lists';
 import Swimlanes from '../../models/swimlanes';
+import Cards from '../../models/cards';
 import CardComments from '../../models/cardComments';
 import Attachments from '../../models/attachments';
 import Checklists from '../../models/checklists';
@@ -411,26 +413,26 @@ function buildSelector(queryParams) {
 
       const attachments = Attachments.find({ 'original.name': regex });
 
-      // const comments = CardComments.find(
-      //   { text: regex },
-      //   { fields: { cardId: 1 } },
-      // );
+      const comments = CardComments.find(
+        { text: regex },
+        { fields: { cardId: 1 } },
+      );
 
       selector.$and.push({
         $or: [
           { title: regex },
           { description: regex },
           { customFields: { $elemMatch: { value: regex } } },
-          {
-            _id: {
-              $in: CardComments.textSearch(userId, [queryParams.text]).map(
-                com => com.cardId,
-              ),
-            },
-          },
+          // {
+          //   _id: {
+          //     $in: CardComments.textSearch(userId, [queryParams.text]).map(
+          //       com => com.cardId,
+          //     ),
+          //   },
+          // },
           { _id: { $in: checklists.map(list => list.cardId) } },
           { _id: { $in: attachments.map(attach => attach.cardId) } },
-          // { _id: { $in: comments.map(com => com.cardId) } },
+          { _id: { $in: comments.map(com => com.cardId) } },
         ],
       });
     }