Преглед изворни кода

Add new `has` operator for searching

John R. Supplee пре 4 година
родитељ
комит
726be664c8
4 измењених фајлова са 43 додато и 2 уклоњено
  1. 17 0
      client/components/main/globalSearch.js
  2. 5 0
      i18n/en.i18n.json
  3. 7 2
      models/usersessiondata.js
  4. 14 0
      server/publications/cards.js

+ 17 - 0
client/components/main/globalSearch.js

@@ -222,6 +222,7 @@ BlazeComponent.extendComponent({
       'operator-created': 'createdAt',
       'operator-created': 'createdAt',
       'operator-modified': 'modifiedAt',
       'operator-modified': 'modifiedAt',
       'operator-comment': 'comments',
       'operator-comment': 'comments',
+      'operator-has': 'has',
     };
     };
 
 
     const predicates = {
     const predicates = {
@@ -244,6 +245,11 @@ BlazeComponent.extendComponent({
         'predicate-created': 'createdAt',
         'predicate-created': 'createdAt',
         'predicate-modified': 'modifiedAt',
         'predicate-modified': 'modifiedAt',
       },
       },
+      has: {
+        'predicate-description': 'description',
+        'predicate-checklist': 'checklist',
+        'predicate-attachment': 'attachment',
+      },
     };
     };
     const predicateTranslations = {};
     const predicateTranslations = {};
     Object.entries(predicates).forEach(([category, catPreds]) => {
     Object.entries(predicates).forEach(([category, catPreds]) => {
@@ -276,6 +282,7 @@ BlazeComponent.extendComponent({
       createdAt: null,
       createdAt: null,
       modifiedAt: null,
       modifiedAt: null,
       comments: [],
       comments: [],
+      has: [],
     };
     };
 
 
     let text = '';
     let text = '';
@@ -296,6 +303,7 @@ BlazeComponent.extendComponent({
         } else {
         } else {
           op = m.groups.abbrev.toLowerCase();
           op = m.groups.abbrev.toLowerCase();
         }
         }
+        // eslint-disable-next-line no-prototype-builtins
         if (operatorMap.hasOwnProperty(op)) {
         if (operatorMap.hasOwnProperty(op)) {
           let value = m.groups.value;
           let value = m.groups.value;
           if (operatorMap[op] === 'labels') {
           if (operatorMap[op] === 'labels') {
@@ -353,6 +361,15 @@ BlazeComponent.extendComponent({
             } else {
             } else {
               value = predicateTranslations.status[value];
               value = predicateTranslations.status[value];
             }
             }
+          } else if (operatorMap[op] === 'has') {
+            if (!predicateTranslations.has[value]) {
+              this.parsingErrors.push({
+                tag: 'operator-has-invalid',
+                value,
+              });
+            } else {
+              value = predicateTranslations.has[value];
+            }
           }
           }
           if (Array.isArray(params[operatorMap[op]])) {
           if (Array.isArray(params[operatorMap[op]])) {
             params[operatorMap[op]].push(value);
             params[operatorMap[op]].push(value);

+ 5 - 0
i18n/en.i18n.json

@@ -906,6 +906,7 @@
   "operator-modified": "modified",
   "operator-modified": "modified",
   "operator-sort": "sort",
   "operator-sort": "sort",
   "operator-comment": "comment",
   "operator-comment": "comment",
+  "operator-has": "has",
   "predicate-archived": "archived",
   "predicate-archived": "archived",
   "predicate-ended": "ended",
   "predicate-ended": "ended",
   "predicate-all": "all",
   "predicate-all": "all",
@@ -917,10 +918,14 @@
   "predicate-due": "due",
   "predicate-due": "due",
   "predicate-modified": "modified",
   "predicate-modified": "modified",
   "predicate-created": "created",
   "predicate-created": "created",
+  "predicate-attachment": "attachment",
+  "predicate-description": "description",
+  "predicate-checklist": "checklist",
   "operator-unknown-error": "%s is not an operator",
   "operator-unknown-error": "%s is not an operator",
   "operator-number-expected": "operator __operator__ expected a number, got '__value__'",
   "operator-number-expected": "operator __operator__ expected a number, got '__value__'",
   "operator-sort-invalid": "sort of '%s' is invalid",
   "operator-sort-invalid": "sort of '%s' is invalid",
   "operator-status-invalid": "'%s' is not a valid status",
   "operator-status-invalid": "'%s' is not a valid status",
+  "operator-has-invalid": "%s is not a valid existence check",
   "next-page": "Next Page",
   "next-page": "Next Page",
   "previous-page": "Previous Page",
   "previous-page": "Previous Page",
   "heading-notes": "Notes",
   "heading-notes": "Notes",

+ 7 - 2
models/usersessiondata.js

@@ -134,7 +134,10 @@ SessionData.helpers({
 
 
 SessionData.unpickle = pickle => {
 SessionData.unpickle = pickle => {
   return JSON.parse(pickle, (key, value) => {
   return JSON.parse(pickle, (key, value) => {
-    if (typeof value === 'object') {
+    if (value === null) {
+      return null;
+    } else if (typeof value === 'object') {
+      // eslint-disable-next-line no-prototype-builtins
       if (value.hasOwnProperty('$$class')) {
       if (value.hasOwnProperty('$$class')) {
         if (value.$$class === 'RegExp') {
         if (value.$$class === 'RegExp') {
           return new RegExp(value.source, value.flags);
           return new RegExp(value.source, value.flags);
@@ -147,7 +150,9 @@ SessionData.unpickle = pickle => {
 
 
 SessionData.pickle = value => {
 SessionData.pickle = value => {
   return JSON.stringify(value, (key, value) => {
   return JSON.stringify(value, (key, value) => {
-    if (typeof value === 'object') {
+    if (value === null) {
+      return null;
+    } else if (typeof value === 'object') {
       if (value.constructor.name === 'RegExp') {
       if (value.constructor.name === 'RegExp') {
         return {
         return {
           $$class: 'RegExp',
           $$class: 'RegExp',

+ 14 - 0
server/publications/cards.js

@@ -507,6 +507,20 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
       });
       });
     }
     }
 
 
+    if (queryParams.has.length) {
+      queryParams.has.forEach(has => {
+        if (has === 'description') {
+          selector.description = { $exists: true, $nin: [null, ''] };
+        } else if (has === 'attachment') {
+          const attachments = Attachments.find({}, { fields: { cardId: 1 } });
+          selector.$and.push({ _id: { $in: attachments.map(a => a.cardId) } });
+        } else if (has === 'checklist') {
+          const checklists = Checklists.find({}, { fields: { cardId: 1 } });
+          selector.$and.push({ _id: { $in: checklists.map(a => a.cardId) } });
+        }
+      });
+    }
+
     if (queryParams.text) {
     if (queryParams.text) {
       const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
       const regex = new RegExp(escapeForRegex(queryParams.text), 'i');