Ver Fonte

Add support for searching archived cards

* Add logic to search for archived or all cards
* Add icons to board, swimlane and list titles to indicate if they are archived
* Update search instructions
John R. Supplee há 4 anos atrás
pai
commit
6a32424a08

+ 15 - 6
client/components/cards/resultCard.jade

@@ -5,19 +5,28 @@ template(name="resultCard")
       //= card.title
       //= card.title
     ul.result-card-context-list
     ul.result-card-context-list
       li.result-card-context(title="{{_ 'board'}}")
       li.result-card-context(title="{{_ 'board'}}")
-        +viewer
-          = getBoard.title
+        .result-card-block-wrapper
+          +viewer
+            = getBoard.title
+        if getBoard.archived
+          i.fa.fa-archive
       li.result-card-context.result-card-context-separator
       li.result-card-context.result-card-context-separator
         = ' '
         = ' '
         | {{_ 'context-separator'}}
         | {{_ 'context-separator'}}
         = ' '
         = ' '
       li.result-card-context(title="{{_ 'swimlane'}}")
       li.result-card-context(title="{{_ 'swimlane'}}")
-        +viewer
-          = getSwimlane.title
+        .result-card-block-wrapper
+          +viewer
+            = getSwimlane.title
+        if getSwimlane.archived
+          i.fa.fa-archive
       li.result-card-context.result-card-context-separator
       li.result-card-context.result-card-context-separator
         = ' '
         = ' '
         | {{_ 'context-separator'}}
         | {{_ 'context-separator'}}
         = ' '
         = ' '
       li.result-card-context(title="{{_ 'list'}}")
       li.result-card-context(title="{{_ 'list'}}")
-        +viewer
-          = getList.title
+        .result-card-block-wrapper
+          +viewer
+            = getList.title
+        if getList.archived
+          i.fa.fa-archive

+ 66 - 19
client/components/main/globalSearch.js

@@ -177,8 +177,8 @@ BlazeComponent.extendComponent({
 
 
     this.searching.set(true);
     this.searching.set(true);
 
 
-    const reOperator1 = /^((?<operator>[\w\p{L}]+):|(?<abbrev>[#@]))(?<value>[\w\p{L}]+)(\s+|$)/iu;
-    const reOperator2 = /^((?<operator>[\w\p{L}]+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/iu;
+    const reOperator1 = /^((?<operator>[\p{Letter}\p{Mark}]+):|(?<abbrev>[#@]))(?<value>[\p{Letter}\p{Mark}]+)(\s+|$)/iu;
+    const reOperator2 = /^((?<operator>[\p{Letter}\p{Mark}]+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/iu;
     const reText = /^(?<text>\S+)(\s+|$)/u;
     const reText = /^(?<text>\S+)(\s+|$)/u;
     const reQuotedText = /^(?<quote>["'])(?<text>[\w\p{L}]+)\k<quote>(\s+|$)/u;
     const reQuotedText = /^(?<quote>["'])(?<text>[\w\p{L}]+)\k<quote>(\s+|$)/u;
 
 
@@ -197,7 +197,7 @@ BlazeComponent.extendComponent({
       'operator-member-abbrev': 'members',
       'operator-member-abbrev': 'members',
       'operator-assignee': 'assignees',
       'operator-assignee': 'assignees',
       'operator-assignee-abbrev': 'assignees',
       'operator-assignee-abbrev': 'assignees',
-      'operator-is': 'is',
+      'operator-status': 'status',
       'operator-due': 'dueAt',
       'operator-due': 'dueAt',
       'operator-created': 'createdAt',
       'operator-created': 'createdAt',
       'operator-modified': 'modifiedAt',
       'operator-modified': 'modifiedAt',
@@ -207,31 +207,33 @@ BlazeComponent.extendComponent({
     const predicates = {
     const predicates = {
       due: {
       due: {
         'predicate-overdue': 'overdue',
         'predicate-overdue': 'overdue',
-        'predicate-day': 'day',
-        'predicate-week': 'week',
-        'predicate-month': 'month',
-        'predicate-quarter': 'quarter',
-        'predicate-year': 'year',
       },
       },
-      date: {
-        'predicate-day': 'day',
+      durations: {
         'predicate-week': 'week',
         'predicate-week': 'week',
         'predicate-month': 'month',
         'predicate-month': 'month',
         'predicate-quarter': 'quarter',
         'predicate-quarter': 'quarter',
         'predicate-year': 'year',
         'predicate-year': 'year',
       },
       },
-      is: {
+      status: {
         'predicate-archived': 'archived',
         'predicate-archived': 'archived',
-        'predicate-active': 'active',
+        'predicate-all': 'all',
+        'predicate-ended': 'ended',
+      },
+      sorts: {
+        'predicate-due': 'dueAt',
+        'predicate-created': 'createdAt',
+        'predicate-modified': 'modifiedAt',
       },
       },
     };
     };
     const predicateTranslations = {};
     const predicateTranslations = {};
-    Object.entries(predicates, ([category, predicates]) => {
+    Object.entries(predicates).forEach(([category, catPreds]) => {
       predicateTranslations[category] = {};
       predicateTranslations[category] = {};
-      Object.entries(predicates, ([tag, value]) => {
+      Object.entries(catPreds).forEach(([tag, value]) => {
         predicateTranslations[category][TAPi18n.__(tag)] = value;
         predicateTranslations[category][TAPi18n.__(tag)] = value;
       });
       });
     });
     });
+    // eslint-disable-next-line no-console
+    // console.log('predicateTranslations:', predicateTranslations);
 
 
     const operatorMap = {};
     const operatorMap = {};
     Object.entries(operators).forEach(([key, value]) => {
     Object.entries(operators).forEach(([key, value]) => {
@@ -248,7 +250,7 @@ BlazeComponent.extendComponent({
       members: [],
       members: [],
       assignees: [],
       assignees: [],
       labels: [],
       labels: [],
-      is: [],
+      status: [],
       dueAt: null,
       dueAt: null,
       createdAt: null,
       createdAt: null,
       modifiedAt: null,
       modifiedAt: null,
@@ -285,8 +287,8 @@ BlazeComponent.extendComponent({
             let days = parseInt(value, 10);
             let days = parseInt(value, 10);
             let duration = null;
             let duration = null;
             if (isNaN(days)) {
             if (isNaN(days)) {
-              if (predicateTranslations.date.keys().includes(value)) {
-                duration = predicateTranslations.date[value];
+              if (predicateTranslations.durations[value]) {
+                duration = predicateTranslations.durations[value];
                 value = moment();
                 value = moment();
               } else if (predicateTranslations.due[value] === 'overdue') {
               } else if (predicateTranslations.due[value] === 'overdue') {
                 value = moment();
                 value = moment();
@@ -312,11 +314,22 @@ BlazeComponent.extendComponent({
               }
               }
             }
             }
           } else if (operatorMap[op] === 'sort') {
           } else if (operatorMap[op] === 'sort') {
-            if (!['due', 'modified', 'created', 'system'].includes(value)) {
+            if (!predicateTranslations.sorts[value]) {
               this.parsingErrors.push({
               this.parsingErrors.push({
                 tag: 'operator-sort-invalid',
                 tag: 'operator-sort-invalid',
                 value,
                 value,
               });
               });
+            } else {
+              value = predicateTranslations.sorts[value];
+            }
+          } else if (operatorMap[op] === 'status') {
+            if (!predicateTranslations.status[value]) {
+              this.parsingErrors.push({
+                tag: 'operator-status-invalid',
+                value,
+              });
+            } else {
+              value = predicateTranslations.status[value];
             }
             }
           }
           }
           if (Array.isArray(params[operatorMap[op]])) {
           if (Array.isArray(params[operatorMap[op]])) {
@@ -359,12 +372,13 @@ BlazeComponent.extendComponent({
     if (this.parsingErrors.length) {
     if (this.parsingErrors.length) {
       this.searching.set(false);
       this.searching.set(false);
       this.queryErrors = this.parsingErrorMessages();
       this.queryErrors = this.parsingErrorMessages();
+      this.hasResults.set(true);
       this.hasQueryErrors.set(true);
       this.hasQueryErrors.set(true);
       return;
       return;
     }
     }
 
 
     this.autorun(() => {
     this.autorun(() => {
-      const handle = subManager.subscribe(
+      const handle = Meteor.subscribe(
         'globalSearch',
         'globalSearch',
         SessionData.getSessionId(),
         SessionData.getSessionId(),
         params,
         params,
@@ -407,6 +421,7 @@ BlazeComponent.extendComponent({
       operator_board: TAPi18n.__('operator-board'),
       operator_board: TAPi18n.__('operator-board'),
       operator_list: TAPi18n.__('operator-list'),
       operator_list: TAPi18n.__('operator-list'),
       operator_swimlane: TAPi18n.__('operator-swimlane'),
       operator_swimlane: TAPi18n.__('operator-swimlane'),
+      operator_comment: TAPi18n.__('operator-comment'),
       operator_label: TAPi18n.__('operator-label'),
       operator_label: TAPi18n.__('operator-label'),
       operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
       operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
       operator_user: TAPi18n.__('operator-user'),
       operator_user: TAPi18n.__('operator-user'),
@@ -415,6 +430,18 @@ BlazeComponent.extendComponent({
       operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
       operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
       operator_assignee: TAPi18n.__('operator-assignee'),
       operator_assignee: TAPi18n.__('operator-assignee'),
       operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
       operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
+      operator_due: TAPi18n.__('operator-due'),
+      operator_created: TAPi18n.__('operator-created'),
+      operator_modified: TAPi18n.__('operator-modified'),
+      operator_status: TAPi18n.__('operator-status'),
+      predicate_overdue: TAPi18n.__('predicate-overdue'),
+      predicate_archived: TAPi18n.__('predicate-archived'),
+      predicate_all: TAPi18n.__('predicate-all'),
+      predicate_ended: TAPi18n.__('predicate-ended'),
+      predicate_week: TAPi18n.__('predicate-week'),
+      predicate_month: TAPi18n.__('predicate-month'),
+      predicate_quarter: TAPi18n.__('predicate-quarter'),
+      predicate_year: TAPi18n.__('predicate-year'),
     };
     };
 
 
     text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
     text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
@@ -432,6 +459,10 @@ BlazeComponent.extendComponent({
       'globalSearch-instructions-operator-swimlane',
       'globalSearch-instructions-operator-swimlane',
       tags,
       tags,
     )}`;
     )}`;
+    text += `\n* ${TAPi18n.__(
+      'globalSearch-instructions-operator-comment',
+      tags,
+    )}`;
     text += `\n* ${TAPi18n.__(
     text += `\n* ${TAPi18n.__(
       'globalSearch-instructions-operator-label',
       'globalSearch-instructions-operator-label',
       tags,
       tags,
@@ -453,11 +484,27 @@ BlazeComponent.extendComponent({
       'globalSearch-instructions-operator-assignee',
       'globalSearch-instructions-operator-assignee',
       tags,
       tags,
     )}`;
     )}`;
+    text += `\n* ${TAPi18n.__('globalSearch-instructions-operator-due', tags)}`;
+    text += `\n* ${TAPi18n.__(
+      'globalSearch-instructions-operator-created',
+      tags,
+    )}`;
+    text += `\n* ${TAPi18n.__(
+      'globalSearch-instructions-operator-modified',
+      tags,
+    )}`;
+    text += `\n* ${TAPi18n.__(
+      'globalSearch-instructions-status-archived',
+      tags,
+    )}`;
+    text += `\n* ${TAPi18n.__('globalSearch-instructions-status-all', tags)}`;
+    text += `\n* ${TAPi18n.__('globalSearch-instructions-status-ended', tags)}`;
 
 
     text += `\n## ${TAPi18n.__('heading-notes')}`;
     text += `\n## ${TAPi18n.__('heading-notes')}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-1', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-1', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-2', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-2', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3', tags)}`;
+    text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3-2', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-4', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-4', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-5', tags)}`;
     text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-5', tags)}`;
 
 

+ 15 - 4
i18n/ar-EG.i18n.json

@@ -143,7 +143,7 @@
   "board-view-swimlanes": "خطوط السباحة",
   "board-view-swimlanes": "خطوط السباحة",
   "board-view-collapse": "Collapse",
   "board-view-collapse": "Collapse",
   "board-view-gantt": "Gantt",
   "board-view-gantt": "Gantt",
-  "board-view-lists": "القائمات",
+  "board-view-lists": "اللستات",
   "bucket-example": "مثل « todo list » على سبيل المثال",
   "bucket-example": "مثل « todo list » على سبيل المثال",
   "cancel": "إلغاء",
   "cancel": "إلغاء",
   "card-archived": "البطاقة منقولة الى الارشيف",
   "card-archived": "البطاقة منقولة الى الارشيف",
@@ -420,7 +420,7 @@
   "link-list": "رابط إلى هذه القائمة",
   "link-list": "رابط إلى هذه القائمة",
   "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
   "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.",
   "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.",
   "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.",
-  "lists": "القائمات",
+  "lists": "استات",
   "swimlanes": "خطوط السباحة",
   "swimlanes": "خطوط السباحة",
   "log-out": "تسجيل الخروج",
   "log-out": "تسجيل الخروج",
   "log-in": "تسجيل الدخول",
   "log-in": "تسجيل الدخول",
@@ -609,8 +609,8 @@
   "accounts": "الحسابات",
   "accounts": "الحسابات",
   "accounts-allowEmailChange": "السماح بتغيير البريد الإلكتروني",
   "accounts-allowEmailChange": "السماح بتغيير البريد الإلكتروني",
   "accounts-allowUserNameChange": "Allow Username Change",
   "accounts-allowUserNameChange": "Allow Username Change",
-  "createdAt": "Created at",
-  "modifiedAt": "Modified at",
+  "createdAt": "تاريخ الإنشاء",
+  "modifiedAt": "تاريخ التعديل",
   "verified": "Verified",
   "verified": "Verified",
   "active": "نشط",
   "active": "نشط",
   "card-received": "Received",
   "card-received": "Received",
@@ -900,6 +900,17 @@
   "operator-modified": "modified",
   "operator-modified": "modified",
   "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__'",
+  "predicate-archived": "مؤرشف",
+  "predicate-ended": "ended",
+  "predicate-all": "كله",
+  "predicate-overdue": "متاخر",
+  "predicate-week": "اسبوع",
+  "predicate-month": "شهر",
+  "predicate-quarter": "ربع",
+  "predicate-year": "سنة",
+  "predicate-due": "due",
+  "predicate-modified": "متعديل",
+  "predicate-created": "created",
   "heading-notes": "ملاحظات",
   "heading-notes": "ملاحظات",
   "globalSearch-instructions-heading": "تعليمات البحث",
   "globalSearch-instructions-heading": "تعليمات البحث",
   "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\"`).",
   "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\"`).",

+ 17 - 4
i18n/en.i18n.json

@@ -896,18 +896,27 @@
   "operator-member-abbrev": "m",
   "operator-member-abbrev": "m",
   "operator-assignee": "assignee",
   "operator-assignee": "assignee",
   "operator-assignee-abbrev": "a",
   "operator-assignee-abbrev": "a",
-  "operator-is": "is",
+  "operator-status": "status",
   "operator-due": "due",
   "operator-due": "due",
   "operator-created": "created",
   "operator-created": "created",
   "operator-modified": "modified",
   "operator-modified": "modified",
   "operator-sort": "sort",
   "operator-sort": "sort",
   "operator-comment": "comment",
   "operator-comment": "comment",
   "predicate-archived": "archived",
   "predicate-archived": "archived",
-  "predicate-active": "active",
+  "predicate-ended": "ended",
+  "predicate-all": "all",
   "predicate-overdue": "overdue",
   "predicate-overdue": "overdue",
+  "predicate-week": "week",
+  "predicate-month": "month",
+  "predicate-quarter": "quarter",
+  "predicate-year": "year",
+  "predicate-due": "due",
+  "predicate-modified": "modified",
+  "predicate-created": "created",
   "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",
   "heading-notes": "Notes",
   "heading-notes": "Notes",
   "globalSearch-instructions-heading": "Search Instructions",
   "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\"`).",
   "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\"`).",
@@ -922,12 +931,16 @@
   "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:username`",
   "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:username`",
   "globalSearch-instructions-operator-member": "`__operator_member__:username` - cards where the specified user is a *member*",
   "globalSearch-instructions-operator-member": "`__operator_member__:username` - cards where the specified user is a *member*",
   "globalSearch-instructions-operator-assignee": "`__operator_assignee__:username` - cards where the specified user is an *assignee*",
   "globalSearch-instructions-operator-assignee": "`__operator_assignee__:username` - cards where the specified user is an *assignee*",
-  "globalSearch-instructions-operator-due": "`__operator_due__:n` - cards which are due *n* days from now.  `__operator_due__:__predicate_overdue__ lists all ",
+  "globalSearch-instructions-operator-due": "`__operator_due__:n` - cards which are due *n* days from now.  `__operator_due__:__predicate_overdue__ lists all cards past their due date.",
   "globalSearch-instructions-operator-created": "`__operator_created__:n` - cards which which were created *n* days ago",
   "globalSearch-instructions-operator-created": "`__operator_created__:n` - cards which which were created *n* days ago",
   "globalSearch-instructions-operator-modified": "`__operator_modified__:n` - cards which which were modified *n* days ago",
   "globalSearch-instructions-operator-modified": "`__operator_modified__:n` - cards which which were modified *n* days ago",
+  "globalSearch-instructions-status-archived": "`__operator_status__:__predicate_archived__` - cards that are archived.",
+  "globalSearch-instructions-status-all": "`__operator_status__:__predicate_all__` - all archived and unarchived cards.",
+  "globalSearch-instructions-status-ended": "`__operator_status__:__predicate_ended__` - cards with an end date.",
   "globalSearch-instructions-notes-1": "Multiple operators may be specified.",
   "globalSearch-instructions-notes-1": "Multiple operators may be specified.",
   "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together.  Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
   "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together.  Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.",
-  "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together.  Only cards that match all of the differing operators are returned.\n`__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
+  "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together.  Only cards that match all of the differing operators are returned.  `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.",
+  "globalSearch-instructions-notes-3-2": "Days can be specified as an integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__`",
   "globalSearch-instructions-notes-4": "Text searches are case insensitive.",
   "globalSearch-instructions-notes-4": "Text searches are case insensitive.",
   "globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
   "globalSearch-instructions-notes-5": "Currently archived cards are not searched.",
   "link-to-search": "Link to this search",
   "link-to-search": "Link to this search",

+ 17 - 21
models/boards.js

@@ -1278,37 +1278,33 @@ Boards.userSearch = (
   userId,
   userId,
   selector = {},
   selector = {},
   projection = {},
   projection = {},
-  includeArchived = false,
+  // includeArchived = false,
 ) => {
 ) => {
-  if (!includeArchived) {
-    selector.archived = false;
-  }
-  selector.$or = [
-    { permission: 'public' },
-    { members: { $elemMatch: { userId, isActive: true } } },
-  ];
+  // if (!includeArchived) {
+  //   selector.archived = false;
+  // }
+  selector.$or = [{ permission: 'public' }];
 
 
+  if (userId) {
+    selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } });
+  }
   return Boards.find(selector, projection);
   return Boards.find(selector, projection);
 };
 };
 
 
-Boards.userBoards = (userId, includeArchived = false, selector = {}) => {
-  check(userId, String);
-
-  if (!includeArchived) {
-    selector = {
-      archived: false,
-    };
+Boards.userBoards = (userId, archived = false, selector = {}) => {
+  if (typeof archived === 'boolean') {
+    selector.archived = archived;
   }
   }
-  selector.$or = [
-    { permission: 'public' },
-    { members: { $elemMatch: { userId, isActive: true } } },
-  ];
+  selector.$or = [{ permission: 'public' }];
 
 
+  if (userId) {
+    selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } });
+  }
   return Boards.find(selector);
   return Boards.find(selector);
 };
 };
 
 
-Boards.userBoardIds = (userId, includeArchived = false, selector = {}) => {
-  return Boards.userBoards(userId, includeArchived, selector).map(board => {
+Boards.userBoardIds = (userId, archived = false, selector = {}) => {
+  return Boards.userBoards(userId, archived, selector).map(board => {
     return board._id;
     return board._id;
   });
   });
 };
 };

+ 67 - 22
server/publications/cards.js

@@ -193,7 +193,7 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
         users: [],
         users: [],
         members: [],
         members: [],
         assignees: [],
         assignees: [],
-        is: [],
+        status: [],
         comments: [],
         comments: [],
       };
       };
 
 
@@ -247,14 +247,51 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
     }
     }
   })();
   })();
 
 
+  let archived = false;
+  let endAt = null;
+  if (queryParams.status.length) {
+    queryParams.status.forEach(status => {
+      if (status === 'archived') {
+        archived = true;
+      } else if (status === 'all') {
+        archived = null;
+      } else if (status === 'ended') {
+        endAt = { $nin: [null, ''] };
+      }
+    });
+  }
   const selector = {
   const selector = {
-    archived: false,
     type: 'cardType-card',
     type: 'cardType-card',
-    boardId: { $in: Boards.userBoardIds(userId) },
-    swimlaneId: { $nin: Swimlanes.archivedSwimlaneIds() },
-    listId: { $nin: Lists.archivedListIds() },
+    // boardId: { $in: Boards.userBoardIds(userId) },
+    $and: [],
   };
   };
 
 
+  const boardsSelector = {};
+  if (archived !== null) {
+    boardsSelector.archived = archived;
+    if (archived) {
+      selector.boardId = { $in: Boards.userBoardIds(userId, null) };
+      selector.$and.push({
+        $or: [
+          { boardId: { $in: Boards.userBoardIds(userId, archived) } },
+          { swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } },
+          { listId: { $in: Lists.archivedListIds() } },
+          { archived: true },
+        ],
+      });
+    } else {
+      selector.boardId = { $in: Boards.userBoardIds(userId, false) };
+      selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() };
+      selector.listId = { $nin: Lists.archivedListIds() };
+      selector.archived = false;
+    }
+  } else {
+    selector.boardId = { $in: Boards.userBoardIds(userId, null) };
+  }
+  if (endAt !== null) {
+    selector.endAt = endAt;
+  }
+
   if (queryParams.boards.length) {
   if (queryParams.boards.length) {
     const queryBoards = [];
     const queryBoards = [];
     queryParams.boards.forEach(query => {
     queryParams.boards.forEach(query => {
@@ -383,10 +420,12 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
   }
   }
 
 
   if (queryMembers.length && queryAssignees.length) {
   if (queryMembers.length && queryAssignees.length) {
-    selector.$or = [
-      { members: { $in: queryMembers } },
-      { assignees: { $in: queryAssignees } },
-    ];
+    selector.$and.push({
+      $or: [
+        { members: { $in: queryMembers } },
+        { assignees: { $in: queryAssignees } },
+      ],
+    });
   } else if (queryMembers.length) {
   } else if (queryMembers.length) {
     selector.members = { $in: queryMembers };
     selector.members = { $in: queryMembers };
   } else if (queryAssignees.length) {
   } else if (queryAssignees.length) {
@@ -450,24 +489,30 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
     if (queryParams.text) {
     if (queryParams.text) {
       const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
       const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
 
 
-      selector.$or = [
-        { title: regex },
-        { description: regex },
-        { customFields: { $elemMatch: { value: regex } } },
-        {
-          _id: {
-            $in: CardComments.textSearch(userId, [queryParams.text]).map(
-              com => com.cardId,
-            ),
+      selector.$and.push({
+        $or: [
+          { title: regex },
+          { description: regex },
+          { customFields: { $elemMatch: { value: regex } } },
+          {
+            _id: {
+              $in: CardComments.textSearch(userId, [queryParams.text]).map(
+                com => com.cardId,
+              ),
+            },
           },
           },
-        },
-      ];
+        ],
+      });
+    }
+
+    if (selector.$and.length === 0) {
+      delete selector.$and;
     }
     }
 
 
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
-    // console.log('selector:', selector);
+    console.log('selector:', selector);
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
-    // console.log('selector.$or:', selector.$or);
+    console.log('selector.$and:', selector.$and);
 
 
     const projection = {
     const projection = {
       fields: {
       fields: {