瀏覽代碼

Merge pull request #3492 from jrsupplee/new-search

Global Search Update
Lauri Ojansivu 4 年之前
父節點
當前提交
c10f32cc30

+ 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

+ 3 - 0
client/components/cards/resultCard.styl

@@ -19,3 +19,6 @@
 
 
 .result-card-context-list
 .result-card-context-list
   margin-bottom: 0.7rem
   margin-bottom: 0.7rem
+
+.result-card-block-wrapper
+  display: inline-block

+ 12 - 2
client/components/main/globalSearch.jade

@@ -30,13 +30,23 @@ template(name="globalSearch")
             div
             div
               each msg in errorMessages
               each msg in errorMessages
                 span.global-search-error-messages
                 span.global-search-error-messages
-                  | {{_ msg.tag msg.value }}
+                  = msg
           else
           else
             h1
             h1
               = resultsHeading.get
               = resultsHeading.get
               a.fa.fa-link(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
               a.fa.fa-link(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
-            each card in results
+            each card in results.get
               +resultCard(card)
               +resultCard(card)
+            table.global-search-footer
+              tr
+                td.global-search-previous-page
+                  if hasPreviousPage.get
+                    button.js-previous-page
+                      | {{_ 'previous-page' }}
+                td.global-search-next-page(align="right")
+                  if hasNextPage.get
+                    button.js-next-page
+                      | {{_ 'next-page' }}
       else
       else
         .global-search-instructions
         .global-search-instructions
           h2 {{_ 'boards' }}
           h2 {{_ 'boards' }}

+ 286 - 114
client/components/main/globalSearch.js

@@ -45,19 +45,16 @@ BlazeComponent.extendComponent({
     this.myLists = new ReactiveVar([]);
     this.myLists = new ReactiveVar([]);
     this.myLabelNames = new ReactiveVar([]);
     this.myLabelNames = new ReactiveVar([]);
     this.myBoardNames = new ReactiveVar([]);
     this.myBoardNames = new ReactiveVar([]);
+    this.results = new ReactiveVar([]);
+    this.hasNextPage = new ReactiveVar(false);
+    this.hasPreviousPage = new ReactiveVar(false);
     this.queryParams = null;
     this.queryParams = null;
     this.parsingErrors = [];
     this.parsingErrors = [];
     this.resultsCount = 0;
     this.resultsCount = 0;
     this.totalHits = 0;
     this.totalHits = 0;
     this.queryErrors = null;
     this.queryErrors = null;
     this.colorMap = null;
     this.colorMap = null;
-    // this.colorMap = {};
-    // for (const color of Boards.simpleSchema()._schema['labels.$.color']
-    //   .allowedValues) {
-    //   this.colorMap[TAPi18n.__(`color-${color}`)] = color;
-    // }
-    // // eslint-disable-next-line no-console
-    // console.log('colorMap:', this.colorMap);
+    this.resultsPerPage = 25;
 
 
     Meteor.call('myLists', (err, data) => {
     Meteor.call('myLists', (err, data) => {
       if (!err) {
       if (!err) {
@@ -80,6 +77,15 @@ BlazeComponent.extendComponent({
 
 
   onRendered() {
   onRendered() {
     Meteor.subscribe('setting');
     Meteor.subscribe('setting');
+
+    this.colorMap = {};
+    for (const color of Boards.simpleSchema()._schema['labels.$.color']
+      .allowedValues) {
+      this.colorMap[TAPi18n.__(`color-${color}`)] = color;
+    }
+    // // eslint-disable-next-line no-console
+    // console.log('colorMap:', this.colorMap);
+
     if (Session.get('globalQuery')) {
     if (Session.get('globalQuery')) {
       this.searchAllBoards(Session.get('globalQuery'));
       this.searchAllBoards(Session.get('globalQuery'));
     }
     }
@@ -87,6 +93,7 @@ BlazeComponent.extendComponent({
 
 
   resetSearch() {
   resetSearch() {
     this.searching.set(false);
     this.searching.set(false);
+    this.results.set([]);
     this.hasResults.set(false);
     this.hasResults.set(false);
     this.hasQueryErrors.set(false);
     this.hasQueryErrors.set(false);
     this.resultsHeading.set('');
     this.resultsHeading.set('');
@@ -96,79 +103,83 @@ BlazeComponent.extendComponent({
     this.queryErrors = null;
     this.queryErrors = null;
   },
   },
 
 
-  results() {
+  getSessionData() {
+    return SessionData.findOne({
+      userId: Meteor.userId(),
+      sessionId: SessionData.getSessionId(),
+    });
+  },
+
+  getResults() {
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
     // console.log('getting results');
     // console.log('getting results');
     if (this.queryParams) {
     if (this.queryParams) {
-      const results = Cards.globalSearch(this.queryParams);
-      this.queryErrors = results.errors;
+      const sessionData = this.getSessionData();
       // eslint-disable-next-line no-console
       // eslint-disable-next-line no-console
-      // console.log('errors:', this.queryErrors);
-      if (this.errorMessages().length) {
+      console.log('selector:', sessionData.getSelector());
+      // console.log('session data:', sessionData);
+      const cards = Cards.find({ _id: { $in: sessionData.cards } });
+      this.queryErrors = sessionData.errors;
+      if (this.queryErrors.length) {
         this.hasQueryErrors.set(true);
         this.hasQueryErrors.set(true);
         return null;
         return null;
       }
       }
 
 
-      if (results.cards) {
-        const sessionData = SessionData.findOne({ userId: Meteor.userId() });
+      if (cards) {
         this.totalHits = sessionData.totalHits;
         this.totalHits = sessionData.totalHits;
-        this.resultsCount = results.cards.count();
+        this.resultsCount = cards.count();
+        this.resultsStart = sessionData.lastHit - this.resultsCount + 1;
+        this.resultsEnd = sessionData.lastHit;
         this.resultsHeading.set(this.getResultsHeading());
         this.resultsHeading.set(this.getResultsHeading());
-        return results.cards;
+        this.results.set(cards);
+        this.hasNextPage.set(sessionData.lastHit < sessionData.totalHits);
+        this.hasPreviousPage.set(
+          sessionData.lastHit - sessionData.resultsCount > 0,
+        );
       }
       }
     }
     }
     this.resultsCount = 0;
     this.resultsCount = 0;
-    return [];
+    return null;
   },
   },
 
 
   errorMessages() {
   errorMessages() {
-    const messages = [];
-
-    if (this.queryErrors) {
-      this.queryErrors.notFound.boards.forEach(board => {
-        messages.push({ tag: 'board-title-not-found', value: board });
-      });
-      this.queryErrors.notFound.swimlanes.forEach(swim => {
-        messages.push({ tag: 'swimlane-title-not-found', value: swim });
-      });
-      this.queryErrors.notFound.lists.forEach(list => {
-        messages.push({ tag: 'list-title-not-found', value: list });
-      });
-      this.queryErrors.notFound.labels.forEach(label => {
-        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[0],
-          });
-        } else {
-          messages.push({ tag: 'label-not-found', value: label });
-        }
-      });
-      this.queryErrors.notFound.users.forEach(user => {
-        messages.push({ tag: 'user-username-not-found', value: user });
-      });
-      this.queryErrors.notFound.members.forEach(user => {
-        messages.push({ tag: 'user-username-not-found', value: user });
-      });
-      this.queryErrors.notFound.assignees.forEach(user => {
-        messages.push({ tag: 'user-username-not-found', value: user });
-      });
+    if (this.parsingErrors.length) {
+      return this.parsingErrorMessages();
     }
     }
+    return this.queryErrorMessages();
+  },
+
+  parsingErrorMessages() {
+    const messages = [];
 
 
     if (this.parsingErrors.length) {
     if (this.parsingErrors.length) {
       this.parsingErrors.forEach(err => {
       this.parsingErrors.forEach(err => {
-        messages.push(err);
+        messages.push(TAPi18n.__(err.tag, err.value));
       });
       });
     }
     }
 
 
     return messages;
     return messages;
   },
   },
 
 
+  queryErrorMessages() {
+    messages = [];
+
+    this.queryErrors.forEach(err => {
+      let value = err.color ? TAPi18n.__(`color-${err.value}`) : err.value;
+      if (!value) {
+        value = err.value;
+      }
+      messages.push(TAPi18n.__(err.tag, value));
+    });
+
+    return messages;
+  },
+
   searchAllBoards(query) {
   searchAllBoards(query) {
     query = query.trim();
     query = query.trim();
+    // eslint-disable-next-line no-console
+    console.log('query:', query);
+
     this.query.set(query);
     this.query.set(query);
 
 
     this.resetSearch();
     this.resetSearch();
@@ -177,23 +188,12 @@ BlazeComponent.extendComponent({
       return;
       return;
     }
     }
 
 
-    // eslint-disable-next-line no-console
-    // console.log('query:', query);
-
     this.searching.set(true);
     this.searching.set(true);
 
 
-    if (!this.colorMap) {
-      this.colorMap = {};
-      for (const color of Boards.simpleSchema()._schema['labels.$.color']
-        .allowedValues) {
-        this.colorMap[TAPi18n.__(`color-${color}`)] = color;
-      }
-    }
-
-    const reOperator1 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<value>\w+)(\s+|$)/;
-    const reOperator2 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/;
-    const reText = /^(?<text>\S+)(\s+|$)/;
-    const reQuotedText = /^(?<quote>["'])(?<text>\w+)\k<quote>(\s+|$)/;
+    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 reQuotedText = /^(?<quote>["'])(?<text>[\w\p{L}]+)\k<quote>(\s+|$)/u;
 
 
     const operators = {
     const operators = {
       'operator-board': 'boards',
       'operator-board': 'boards',
@@ -210,20 +210,53 @@ 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',
+      'operator-comment': 'comments',
     };
     };
 
 
-    const operatorMap = {};
-    for (const op in operators) {
-      operatorMap[TAPi18n.__(op).toLowerCase()] = operators[op];
-    }
+    const predicates = {
+      due: {
+        'predicate-overdue': 'overdue',
+      },
+      durations: {
+        'predicate-week': 'week',
+        'predicate-month': 'month',
+        'predicate-quarter': 'quarter',
+        'predicate-year': 'year',
+      },
+      status: {
+        'predicate-archived': 'archived',
+        'predicate-all': 'all',
+        'predicate-ended': 'ended',
+      },
+      sorts: {
+        'predicate-due': 'dueAt',
+        'predicate-created': 'createdAt',
+        'predicate-modified': 'modifiedAt',
+      },
+    };
+    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
     // eslint-disable-next-line no-console
-    console.log('operatorMap:', operatorMap);
+    // console.log('operatorMap:', operatorMap);
+
     const params = {
     const params = {
+      limit: this.resultsPerPage,
       boards: [],
       boards: [],
       swimlanes: [],
       swimlanes: [],
       lists: [],
       lists: [],
@@ -231,10 +264,11 @@ BlazeComponent.extendComponent({
       members: [],
       members: [],
       assignees: [],
       assignees: [],
       labels: [],
       labels: [],
-      is: [],
+      status: [],
       dueAt: null,
       dueAt: null,
       createdAt: null,
       createdAt: null,
       modifiedAt: null,
       modifiedAt: null,
+      comments: [],
     };
     };
 
 
     let text = '';
     let text = '';
@@ -255,48 +289,73 @@ BlazeComponent.extendComponent({
         } else {
         } else {
           op = m.groups.abbrev;
           op = m.groups.abbrev;
         }
         }
-        if (op !== '__proto__') {
-          if (op in operatorMap) {
-            let value = m.groups.value;
-            if (operatorMap[op] === 'labels') {
-              if (value in this.colorMap) {
-                value = this.colorMap[value];
+        if (operatorMap.hasOwnProperty(op)) {
+          let value = m.groups.value;
+          if (operatorMap[op] === 'labels') {
+            if (value in this.colorMap) {
+              value = this.colorMap[value];
+            }
+          } else if (
+            ['dueAt', 'createdAt', 'modifiedAt'].includes(operatorMap[op])
+          ) {
+            let days = parseInt(value, 10);
+            let duration = null;
+            if (isNaN(days)) {
+              if (predicateTranslations.durations[value]) {
+                duration = predicateTranslations.durations[value];
+                value = moment();
+              } else if (predicateTranslations.due[value] === 'overdue') {
+                value = moment();
+                duration = 'days';
+                days = 0;
+              } else {
+                this.parsingErrors.push({
+                  tag: 'operator-number-expected',
+                  value: { operator: op, value },
+                });
+                value = null;
               }
               }
-            } 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();
+            }
+            if (value) {
+              if (operatorMap[op] === 'dueAt') {
+                value = value.add(days, duration ? duration : 'days').format();
               } else {
               } else {
-                value = moment()
-                  .subtract(days, 'days')
+                value = value
+                  .subtract(days, duration ? duration : 'days')
                   .format();
                   .format();
               }
               }
             }
             }
-            if (Array.isArray(params[operatorMap[op]])) {
-              params[operatorMap[op]].push(value);
+          } else if (operatorMap[op] === 'sort') {
+            if (!predicateTranslations.sorts[value]) {
+              this.parsingErrors.push({
+                tag: 'operator-sort-invalid',
+                value,
+              });
             } else {
             } else {
-              params[operatorMap[op]] = value;
+              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]])) {
+            params[operatorMap[op]].push(value);
           } else {
           } else {
-            this.parsingErrors.push({
-              tag: 'operator-unknown-error',
-              value: op,
-            });
+            params[operatorMap[op]] = value;
           }
           }
+        } else {
+          this.parsingErrors.push({
+            tag: 'operator-unknown-error',
+            value: op,
+          });
         }
         }
         continue;
         continue;
       }
       }
@@ -324,11 +383,79 @@ BlazeComponent.extendComponent({
 
 
     this.queryParams = params;
     this.queryParams = params;
 
 
+    if (this.parsingErrors.length) {
+      this.searching.set(false);
+      this.queryErrors = this.parsingErrorMessages();
+      this.hasResults.set(true);
+      this.hasQueryErrors.set(true);
+      return;
+    }
+
     this.autorun(() => {
     this.autorun(() => {
-      const handle = subManager.subscribe('globalSearch', params);
+      const handle = Meteor.subscribe(
+        'globalSearch',
+        SessionData.getSessionId(),
+        params,
+      );
       Tracker.nonreactive(() => {
       Tracker.nonreactive(() => {
         Tracker.autorun(() => {
         Tracker.autorun(() => {
           if (handle.ready()) {
           if (handle.ready()) {
+            this.getResults();
+            this.searching.set(false);
+            this.hasResults.set(true);
+          }
+        });
+      });
+    });
+  },
+
+  nextPage() {
+    sessionData = this.getSessionData();
+
+    const params = {
+      limit: this.resultsPerPage,
+      selector: sessionData.getSelector(),
+      skip: sessionData.lastHit,
+    };
+
+    this.autorun(() => {
+      const handle = Meteor.subscribe(
+        'globalSearch',
+        SessionData.getSessionId(),
+        params,
+      );
+      Tracker.nonreactive(() => {
+        Tracker.autorun(() => {
+          if (handle.ready()) {
+            this.getResults();
+            this.searching.set(false);
+            this.hasResults.set(true);
+          }
+        });
+      });
+    });
+  },
+
+  previousPage() {
+    sessionData = this.getSessionData();
+
+    const params = {
+      limit: this.resultsPerPage,
+      selector: sessionData.getSelector(),
+      skip:
+        sessionData.lastHit - sessionData.resultsCount - this.resultsPerPage,
+    };
+
+    this.autorun(() => {
+      const handle = Meteor.subscribe(
+        'globalSearch',
+        SessionData.getSessionId(),
+        params,
+      );
+      Tracker.nonreactive(() => {
+        Tracker.autorun(() => {
+          if (handle.ready()) {
+            this.getResults();
             this.searching.set(false);
             this.searching.set(false);
             this.hasResults.set(true);
             this.hasResults.set(true);
           }
           }
@@ -347,8 +474,8 @@ BlazeComponent.extendComponent({
     }
     }
 
 
     return TAPi18n.__('n-n-of-n-cards-found', {
     return TAPi18n.__('n-n-of-n-cards-found', {
-      start: 1,
-      end: this.resultsCount,
+      start: this.resultsStart,
+      end: this.resultsEnd,
       total: this.totalHits,
       total: this.totalHits,
     });
     });
   },
   },
@@ -363,6 +490,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'),
@@ -371,6 +499,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')}`;
@@ -388,6 +528,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,
@@ -409,11 +553,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)}`;
 
 
@@ -435,10 +595,19 @@ BlazeComponent.extendComponent({
           evt.preventDefault();
           evt.preventDefault();
           this.searchAllBoards(evt.target.searchQuery.value);
           this.searchAllBoards(evt.target.searchQuery.value);
         },
         },
+        'click .js-next-page'(evt) {
+          evt.preventDefault();
+          this.nextPage();
+        },
+        'click .js-previous-page'(evt) {
+          evt.preventDefault();
+          this.previousPage();
+        },
         'click .js-label-color'(evt) {
         'click .js-label-color'(evt) {
           evt.preventDefault();
           evt.preventDefault();
+          const input = document.getElementById('global-search-input');
           this.query.set(
           this.query.set(
-            `${this.query.get()} ${TAPi18n.__('operator-label')}:"${
+            `${input.value} ${TAPi18n.__('operator-label')}:"${
               evt.currentTarget.textContent
               evt.currentTarget.textContent
             }"`,
             }"`,
           );
           );
@@ -446,8 +615,9 @@ BlazeComponent.extendComponent({
         },
         },
         'click .js-board-title'(evt) {
         'click .js-board-title'(evt) {
           evt.preventDefault();
           evt.preventDefault();
+          const input = document.getElementById('global-search-input');
           this.query.set(
           this.query.set(
-            `${this.query.get()} ${TAPi18n.__('operator-board')}:"${
+            `${input.value} ${TAPi18n.__('operator-board')}:"${
               evt.currentTarget.textContent
               evt.currentTarget.textContent
             }"`,
             }"`,
           );
           );
@@ -455,8 +625,9 @@ BlazeComponent.extendComponent({
         },
         },
         'click .js-list-title'(evt) {
         'click .js-list-title'(evt) {
           evt.preventDefault();
           evt.preventDefault();
+          const input = document.getElementById('global-search-input');
           this.query.set(
           this.query.set(
-            `${this.query.get()} ${TAPi18n.__('operator-list')}:"${
+            `${input.value} ${TAPi18n.__('operator-list')}:"${
               evt.currentTarget.textContent
               evt.currentTarget.textContent
             }"`,
             }"`,
           );
           );
@@ -464,8 +635,9 @@ BlazeComponent.extendComponent({
         },
         },
         'click .js-label-name'(evt) {
         'click .js-label-name'(evt) {
           evt.preventDefault();
           evt.preventDefault();
+          const input = document.getElementById('global-search-input');
           this.query.set(
           this.query.set(
-            `${this.query.get()} ${TAPi18n.__('operator-label')}:"${
+            `${input.value} ${TAPi18n.__('operator-label')}:"${
               evt.currentTarget.textContent
               evt.currentTarget.textContent
             }"`,
             }"`,
           );
           );

+ 12 - 0
client/components/main/globalSearch.styl

@@ -104,3 +104,15 @@ code
 
 
 .list-title
 .list-title
   background-color: darkgray
   background-color: darkgray
+
+.global-search-footer
+  border: none
+  width: 100%
+
+.global-search-next-page
+  border: none
+  text-align: right;
+
+.global-search-previous-page
+  border: none
+  text-align: left;

+ 379 - 368
i18n/ar-EG.i18n.json

@@ -1,6 +1,6 @@
 {
 {
-  "accept": "Accept",
-  "act-activity-notify": "Activity Notification",
+  "accept": "قبول",
+  "act-activity-notify": "اشعار النشاط",
   "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
@@ -40,133 +40,133 @@
   "act-removeBoardMember": "removed member __member__ from board __board__",
   "act-removeBoardMember": "removed member __member__ from board __board__",
   "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
   "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__",
   "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
-  "act-withBoardTitle": "__board__",
+  "act-withBoardTitle": "__لوح__",
   "act-withCardTitle": "[__board__] __card__",
   "act-withCardTitle": "[__board__] __card__",
-  "actions": "Actions",
-  "activities": "Activities",
-  "activity": "Activity",
-  "activity-added": "added %s to %s",
-  "activity-archived": "%s moved to Archive",
-  "activity-attached": "attached %s to %s",
-  "activity-created": "created %s",
-  "activity-customfield-created": "created custom field %s",
-  "activity-excluded": "excluded %s from %s",
+  "actions": "الإجراءات",
+  "activities": "الأنشطة",
+  "activity": "النشاط",
+  "activity-added": "تمت إضافة %s ل %s",
+  "activity-archived": "%s انتقل الى الارشيف",
+  "activity-attached": "إرفاق %s ل %s",
+  "activity-created": "أنشأ %s",
+  "activity-customfield-created": "%s احدت حقل مخصص",
+  "activity-excluded": "استبعاد %s عن %s",
   "activity-imported": "imported %s into %s from %s",
   "activity-imported": "imported %s into %s from %s",
   "activity-imported-board": "imported %s from %s",
   "activity-imported-board": "imported %s from %s",
-  "activity-joined": "joined %s",
-  "activity-moved": "moved %s from %s to %s",
-  "activity-on": "on %s",
-  "activity-removed": "removed %s from %s",
-  "activity-sent": "sent %s to %s",
-  "activity-unjoined": "unjoined %s",
-  "activity-subtask-added": "added subtask to %s",
-  "activity-checked-item": "checked %s in checklist %s of %s",
-  "activity-unchecked-item": "unchecked %s in checklist %s of %s",
-  "activity-checklist-added": "added checklist to %s",
-  "activity-checklist-removed": "removed a checklist from %s",
+  "activity-joined": "انضم %s",
+  "activity-moved": "تم نقل %s من %s إلى %s",
+  "activity-on": "على %s",
+  "activity-removed": "حذف %s إلى %s",
+  "activity-sent": "إرسال %s إلى %s",
+  "activity-unjoined": "غادر %s",
+  "activity-subtask-added": "تم اضافة مهمة فرعية الى %s",
+  "activity-checked-item": "تحقق %s في قائمة التحقق %s من %s",
+  "activity-unchecked-item": "ازالة تحقق %s من قائمة التحقق %s من %s",
+  "activity-checklist-added": "أضاف قائمة تحقق إلى %s",
+  "activity-checklist-removed": "ازالة قائمة التحقق من %s",
   "activity-checklist-completed": "completed checklist %s of %s",
   "activity-checklist-completed": "completed checklist %s of %s",
-  "activity-checklist-uncompleted": "uncompleted the checklist %s of %s",
-  "activity-checklist-item-added": "added checklist item to '%s' in %s",
-  "activity-checklist-item-removed": "removed a checklist item from '%s' in %s",
-  "add": "Add",
+  "activity-checklist-uncompleted": "لم يتم انجاز قائمة التحقق %s من %s",
+  "activity-checklist-item-added": "تم اضافة عنصر قائمة التحقق الى '%s' في %s",
+  "activity-checklist-item-removed": "تم ازالة عنصر قائمة التحقق الى '%s' في %s",
+  "add": "أضف",
   "activity-checked-item-card": "checked %s in checklist %s",
   "activity-checked-item-card": "checked %s in checklist %s",
   "activity-unchecked-item-card": "unchecked %s in checklist %s",
   "activity-unchecked-item-card": "unchecked %s in checklist %s",
   "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__",
   "activity-checklist-uncompleted-card": "uncompleted the checklist %s",
   "activity-checklist-uncompleted-card": "uncompleted the checklist %s",
   "activity-editComment": "edited comment %s",
   "activity-editComment": "edited comment %s",
-  "activity-deleteComment": "deleted comment %s",
+  "activity-deleteComment": "تعليق محذوف %s",
   "activity-receivedDate": "edited received date to %s of %s",
   "activity-receivedDate": "edited received date to %s of %s",
   "activity-startDate": "edited start date to %s of %s",
   "activity-startDate": "edited start date to %s of %s",
   "activity-dueDate": "edited due date to %s of %s",
   "activity-dueDate": "edited due date to %s of %s",
   "activity-endDate": "edited end date to %s of %s",
   "activity-endDate": "edited end date to %s of %s",
-  "add-attachment": "Add Attachment",
-  "add-board": "Add Board",
-  "add-card": "Add Card",
+  "add-attachment": "إضافة مرفق",
+  "add-board": "إضافة لوحة",
+  "add-card": "إضافة بطاقة",
   "add-swimlane": "Add Swimlane",
   "add-swimlane": "Add Swimlane",
-  "add-subtask": "Add Subtask",
-  "add-checklist": "Add Checklist",
-  "add-checklist-item": "Add an item to checklist",
-  "add-cover": "Add Cover",
-  "add-label": "Add Label",
-  "add-list": "Add List",
-  "add-members": "Add Members",
-  "added": "Added",
-  "addMemberPopup-title": "Members",
-  "admin": "Admin",
-  "admin-desc": "Can view and edit cards, remove members, and change settings for the board.",
-  "admin-announcement": "Announcement",
+  "add-subtask": "إضافة مهمة فرعية",
+  "add-checklist": "إضافة قائمة تدقيق",
+  "add-checklist-item": "إضافة عنصر إلى قائمة التحقق",
+  "add-cover": "إضافة غلاف",
+  "add-label": "إضافة ملصق",
+  "add-list": "إضافة قائمة",
+  "add-members": "إضافة أعضاء",
+  "added": "أُضيف",
+  "addMemberPopup-title": "الأعضاء",
+  "admin": "المدير",
+  "admin-desc": "إمكانية مشاهدة و تعديل و حذف أعضاء ، و تعديل إعدادات اللوحة أيضا.",
+  "admin-announcement": "إعلان",
   "admin-announcement-active": "Active System-Wide Announcement",
   "admin-announcement-active": "Active System-Wide Announcement",
   "admin-announcement-title": "Announcement from Administrator",
   "admin-announcement-title": "Announcement from Administrator",
-  "all-boards": "All boards",
-  "and-n-other-card": "And __count__ other card",
-  "and-n-other-card_plural": "And __count__ other cards",
-  "apply": "Apply",
+  "all-boards": "كل اللوحات",
+  "and-n-other-card": "And __count__ other بطاقة",
+  "and-n-other-card_plural": "And __count__ other بطاقات",
+  "apply": "طبق",
   "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.",
   "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.",
-  "archive": "Move to Archive",
-  "archive-all": "Move All to Archive",
-  "archive-board": "Move Board to Archive",
-  "archive-card": "Move Card to Archive",
-  "archive-list": "Move List to Archive",
-  "archive-swimlane": "Move Swimlane to Archive",
-  "archive-selection": "Move selection to Archive",
-  "archiveBoardPopup-title": "Move Board to Archive?",
-  "archived-items": "Archive",
-  "archived-boards": "Boards in Archive",
-  "restore-board": "Restore Board",
-  "no-archived-boards": "No Boards in Archive.",
-  "archives": "Archive",
-  "template": "Template",
-  "templates": "Templates",
-  "assign-member": "Assign member",
-  "attached": "attached",
-  "attachment": "Attachment",
-  "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.",
-  "attachmentDeletePopup-title": "Delete Attachment?",
-  "attachments": "Attachments",
-  "auto-watch": "Automatically watch boards when they are created",
+  "archive": "نقل الى الارشيف",
+  "archive-all": "نقل الكل الى الارشيف",
+  "archive-board": "نقل اللوح الى الارشيف",
+  "archive-card": "نقل البطاقة الى الارشيف",
+  "archive-list": "نقل القائمة الى الارشيف",
+  "archive-swimlane": "نقل خط السباحة الى الارشيف",
+  "archive-selection": "نقل التحديد إلى الأرشيف",
+  "archiveBoardPopup-title": "نقل الوح إلى الأرشيف",
+  "archived-items": "أرشيف",
+  "archived-boards": "الالواح في الأرشيف",
+  "restore-board": "استعادة اللوحة",
+  "no-archived-boards": "لا توجد لوحات في الأرشيف.",
+  "archives": "أرشيف",
+  "template": "نموذج",
+  "templates": "نماذج",
+  "assign-member": "تعيين عضو",
+  "attached": "أُرفق)",
+  "attachment": "مرفق",
+  "attachment-delete-pop": "حذف المرق هو حذف نهائي . لا يمكن التراجع إذا حذف.",
+  "attachmentDeletePopup-title": "تريد حذف المرفق ?",
+  "attachments": "المرفقات",
+  "auto-watch": "مراقبة لوحات تلقائيا عندما يتم إنشاؤها",
   "avatar-too-big": "The avatar is too large (520KB max)",
   "avatar-too-big": "The avatar is too large (520KB max)",
-  "back": "Back",
-  "board-change-color": "Change color",
-  "board-nb-stars": "%s stars",
-  "board-not-found": "Board not found",
-  "board-private-info": "This board will be <strong>private</strong>.",
-  "board-public-info": "This board will be <strong>public</strong>.",
-  "boardChangeColorPopup-title": "Change Board Background",
-  "boardChangeTitlePopup-title": "Rename Board",
-  "boardChangeVisibilityPopup-title": "Change Visibility",
-  "boardChangeWatchPopup-title": "Change Watch",
+  "back": "رجوع",
+  "board-change-color": "تغيير اللومr",
+  "board-nb-stars": "%s نجوم",
+  "board-not-found": "لوحة مفقودة",
+  "board-private-info": "سوف تصبح هذه اللوحة <strong>خاصة</strong>",
+  "board-public-info": "سوف تصبح هذه اللوحة <strong>عامّة</strong>.",
+  "boardChangeColorPopup-title": "تعديل خلفية الشاشة",
+  "boardChangeTitlePopup-title": "إعادة تسمية اللوحة",
+  "boardChangeVisibilityPopup-title": "تعديل وضوح الرؤية",
+  "boardChangeWatchPopup-title": "تغيير المتابعة",
   "boardMenuPopup-title": "Board Settings",
   "boardMenuPopup-title": "Board Settings",
-  "boardChangeViewPopup-title": "Board View",
-  "boards": "Boards",
-  "board-view": "Board View",
-  "board-view-cal": "Calendar",
-  "board-view-swimlanes": "Swimlanes",
+  "boardChangeViewPopup-title": "عرض اللوحات",
+  "boards": "لوحات",
+  "board-view": "عرض اللوحات",
+  "board-view-cal": "التقويم",
+  "board-view-swimlanes": "خطوط السباحة",
   "board-view-collapse": "Collapse",
   "board-view-collapse": "Collapse",
   "board-view-gantt": "Gantt",
   "board-view-gantt": "Gantt",
-  "board-view-lists": "Lists",
-  "bucket-example": "Like “Bucket List” for example",
-  "cancel": "Cancel",
-  "card-archived": "This card is moved to Archive.",
-  "board-archived": "This board is moved to Archive.",
-  "card-comments-title": "This card has %s comment.",
-  "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.",
-  "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.",
-  "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.",
-  "card-due": "Due",
-  "card-due-on": "Due on",
-  "card-spent": "Spent Time",
-  "card-edit-attachments": "Edit attachments",
-  "card-edit-custom-fields": "Edit custom fields",
-  "card-edit-labels": "Edit labels",
-  "card-edit-members": "Edit members",
-  "card-labels-title": "Change the labels for the card.",
-  "card-members-title": "Add or remove members of the board from the card.",
-  "card-start": "Start",
-  "card-start-on": "Starts on",
-  "cardAttachmentsPopup-title": "Attach From",
-  "cardCustomField-datePopup-title": "Change date",
-  "cardCustomFieldsPopup-title": "Edit custom fields",
-  "cardStartVotingPopup-title": "Start a vote",
+  "board-view-lists": "اللستات",
+  "bucket-example": "مثل « todo list » على سبيل المثال",
+  "cancel": "إلغاء",
+  "card-archived": "البطاقة منقولة الى الارشيف",
+  "board-archived": "اللوحات منقولة الى الارشيف",
+  "card-comments-title": "%s تعليقات لهذه البطاقة",
+  "card-delete-notice": "هذا حذف أبديّ . سوف تفقد كل الإجراءات المنوطة بهذه البطاقة",
+  "card-delete-pop": "سيتم إزالة جميع الإجراءات من تبعات النشاط، وأنك لن تكون قادرا على إعادة فتح البطاقة. لا يوجد التراجع.",
+  "card-delete-suggest-archive": "يمكنك نقل بطاقة إلى الأرشيف لإزالتها من اللوحة والمحافظة على النشاط.",
+  "card-due": "مستحق",
+  "card-due-on": "مستحق في",
+  "card-spent": "امضى وقتا",
+  "card-edit-attachments": "تعديل المرفقات",
+  "card-edit-custom-fields": "تعديل الحقل المعدل",
+  "card-edit-labels": "تعديل العلامات",
+  "card-edit-members": "تعديل الأعضاء",
+  "card-labels-title": "تعديل علامات البطاقة.",
+  "card-members-title": "إضافة او حذف أعضاء للبطاقة.",
+  "card-start": "بداية",
+  "card-start-on": "يبدأ في",
+  "cardAttachmentsPopup-title": "إرفاق من",
+  "cardCustomField-datePopup-title": "تغير التاريخ",
+  "cardCustomFieldsPopup-title": "تعديل الحقل المعدل",
+  "cardStartVotingPopup-title": "ابدأ تصويت",
   "positiveVoteMembersPopup-title": "Proponents",
   "positiveVoteMembersPopup-title": "Proponents",
   "negativeVoteMembersPopup-title": "Opponents",
   "negativeVoteMembersPopup-title": "Opponents",
   "card-edit-voting": "Edit voting",
   "card-edit-voting": "Edit voting",
@@ -174,46 +174,46 @@
   "allowNonBoardMembers": "Allow all logged in users",
   "allowNonBoardMembers": "Allow all logged in users",
   "vote-question": "Voting question",
   "vote-question": "Voting question",
   "vote-public": "Show who voted what",
   "vote-public": "Show who voted what",
-  "vote-for-it": "for it",
-  "vote-against": "against",
+  "vote-for-it": "مع",
+  "vote-against": "ضد",
   "deleteVotePopup-title": "Delete vote?",
   "deleteVotePopup-title": "Delete vote?",
   "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
   "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
-  "cardDeletePopup-title": "Delete Card?",
-  "cardDetailsActionsPopup-title": "Card Actions",
-  "cardLabelsPopup-title": "Labels",
-  "cardMembersPopup-title": "Members",
-  "cardMorePopup-title": "More",
+  "cardDeletePopup-title": "حذف البطاقة ?",
+  "cardDetailsActionsPopup-title": "إجراءات على البطاقة",
+  "cardLabelsPopup-title": "علامات",
+  "cardMembersPopup-title": "أعضاء",
+  "cardMorePopup-title": "المزيد",
   "cardTemplatePopup-title": "Create template",
   "cardTemplatePopup-title": "Create template",
-  "cards": "Cards",
-  "cards-count": "Cards",
-  "casSignIn": "Sign In with CAS",
-  "cardType-card": "Card",
-  "cardType-linkedCard": "Linked Card",
+  "cards": "بطاقات",
+  "cards-count": "بطاقات",
+  "casSignIn": "تسجيل الدخول مع  CAS",
+  "cardType-card": "بطاقة",
+  "cardType-linkedCard": "البطاقة المرتبطة",
   "cardType-linkedBoard": "Linked Board",
   "cardType-linkedBoard": "Linked Board",
   "change": "Change",
   "change": "Change",
-  "change-avatar": "Change Avatar",
-  "change-password": "Change Password",
-  "change-permissions": "Change permissions",
-  "change-settings": "Change Settings",
-  "changeAvatarPopup-title": "Change Avatar",
-  "changeLanguagePopup-title": "Change Language",
-  "changePasswordPopup-title": "Change Password",
-  "changePermissionsPopup-title": "Change Permissions",
-  "changeSettingsPopup-title": "Change Settings",
-  "subtasks": "Subtasks",
-  "checklists": "Checklists",
-  "click-to-star": "Click to star this board.",
-  "click-to-unstar": "Click to unstar this board.",
+  "change-avatar": "تعديل الصورة الشخصية",
+  "change-password": "تغيير كلمة المرور",
+  "change-permissions": "تعديل الصلاحيات",
+  "change-settings": "تغيير الاعدادات",
+  "changeAvatarPopup-title": "تعديل الصورة الشخصية",
+  "changeLanguagePopup-title": "تغيير اللغة",
+  "changePasswordPopup-title": "تغيير كلمة المرور",
+  "changePermissionsPopup-title": "تعديل الصلاحيات",
+  "changeSettingsPopup-title": "تغيير الاعدادات",
+  "subtasks": "المهمات الفرعية",
+  "checklists": "قوائم التّدقيق",
+  "click-to-star": "اضغط لإضافة اللوحة للمفضلة.",
+  "click-to-unstar": "اضغط لحذف اللوحة من المفضلة.",
   "clipboard": "Clipboard or drag & drop",
   "clipboard": "Clipboard or drag & drop",
-  "close": "Close",
-  "close-board": "Close Board",
+  "close": "غلق",
+  "close-board": "غلق اللوحة",
   "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.",
   "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.",
   "color-black": "black",
   "color-black": "black",
   "color-blue": "blue",
   "color-blue": "blue",
   "color-crimson": "crimson",
   "color-crimson": "crimson",
-  "color-darkgreen": "darkgreen",
-  "color-gold": "gold",
-  "color-gray": "gray",
+  "color-darkgreen": "اخضر غامق",
+  "color-gold": "ذهبي",
+  "color-gray": "رمادي",
   "color-green": "green",
   "color-green": "green",
   "color-indigo": "indigo",
   "color-indigo": "indigo",
   "color-lime": "lime",
   "color-lime": "lime",
@@ -228,75 +228,75 @@
   "color-purple": "purple",
   "color-purple": "purple",
   "color-red": "red",
   "color-red": "red",
   "color-saddlebrown": "saddlebrown",
   "color-saddlebrown": "saddlebrown",
-  "color-silver": "silver",
+  "color-silver": "فضي",
   "color-sky": "sky",
   "color-sky": "sky",
   "color-slateblue": "slateblue",
   "color-slateblue": "slateblue",
-  "color-white": "white",
+  "color-white": "أبيض",
   "color-yellow": "yellow",
   "color-yellow": "yellow",
   "unset-color": "Unset",
   "unset-color": "Unset",
-  "comment": "Comment",
-  "comment-placeholder": "Write Comment",
-  "comment-only": "Comment only",
-  "comment-only-desc": "Can comment on cards only.",
-  "no-comments": "No comments",
+  "comment": "تعليق",
+  "comment-placeholder": "أكتب تعليق",
+  "comment-only": "التعليق فقط",
+  "comment-only-desc": "يمكن التعليق على بطاقات فقط.",
+  "no-comments": "لا يوجد تعليقات",
   "no-comments-desc": "Can not see comments and activities.",
   "no-comments-desc": "Can not see comments and activities.",
   "worker": "Worker",
   "worker": "Worker",
   "worker-desc": "Can only move cards, assign itself to card and comment.",
   "worker-desc": "Can only move cards, assign itself to card and comment.",
-  "computer": "Computer",
+  "computer": "حاسوب",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-subtask-delete-dialog": "Are you sure you want to delete subtask?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
   "confirm-checklist-delete-dialog": "Are you sure you want to delete checklist?",
-  "copy-card-link-to-clipboard": "Copy card link to clipboard",
-  "linkCardPopup-title": "Link Card",
-  "searchElementPopup-title": "Search",
-  "copyCardPopup-title": "Copy Card",
+  "copy-card-link-to-clipboard": "نسخ رابط البطاقة إلى الحافظة",
+  "linkCardPopup-title": "ربط البطاقة",
+  "searchElementPopup-title": "بحث",
+  "copyCardPopup-title": "نسخ البطاقة",
   "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
   "copyChecklistToManyCardsPopup-title": "Copy Checklist Template to Many Cards",
   "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
   "copyChecklistToManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format",
   "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]",
   "copyChecklistToManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]",
-  "create": "Create",
-  "createBoardPopup-title": "Create Board",
-  "chooseBoardSourcePopup-title": "Import board",
-  "createLabelPopup-title": "Create Label",
-  "createCustomField": "Create Field",
-  "createCustomFieldPopup-title": "Create Field",
-  "current": "current",
+  "create": "إنشاء",
+  "createBoardPopup-title": "إنشاء لوحة",
+  "chooseBoardSourcePopup-title": "استيراد لوحة",
+  "createLabelPopup-title": "إنشاء علامة",
+  "createCustomField": "انشاء حقل",
+  "createCustomFieldPopup-title": "انشاء حقل",
+  "current": "الحالي",
   "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
   "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
   "custom-field-checkbox": "Checkbox",
   "custom-field-checkbox": "Checkbox",
   "custom-field-currency": "Currency",
   "custom-field-currency": "Currency",
   "custom-field-currency-option": "Currency Code",
   "custom-field-currency-option": "Currency Code",
-  "custom-field-date": "Date",
+  "custom-field-date": "تاريخ",
   "custom-field-dropdown": "Dropdown List",
   "custom-field-dropdown": "Dropdown List",
   "custom-field-dropdown-none": "(none)",
   "custom-field-dropdown-none": "(none)",
   "custom-field-dropdown-options": "List Options",
   "custom-field-dropdown-options": "List Options",
   "custom-field-dropdown-options-placeholder": "Press enter to add more options",
   "custom-field-dropdown-options-placeholder": "Press enter to add more options",
   "custom-field-dropdown-unknown": "(unknown)",
   "custom-field-dropdown-unknown": "(unknown)",
-  "custom-field-number": "Number",
-  "custom-field-text": "Text",
+  "custom-field-number": "رقم",
+  "custom-field-text": "نص",
   "custom-fields": "Custom Fields",
   "custom-fields": "Custom Fields",
-  "date": "Date",
+  "date": "تاريخ",
   "decline": "Decline",
   "decline": "Decline",
-  "default-avatar": "Default avatar",
-  "delete": "Delete",
+  "default-avatar": "صورة شخصية افتراضية",
+  "delete": "حذف",
   "deleteCustomFieldPopup-title": "Delete Custom Field?",
   "deleteCustomFieldPopup-title": "Delete Custom Field?",
-  "deleteLabelPopup-title": "Delete Label?",
-  "description": "Description",
-  "disambiguateMultiLabelPopup-title": "Disambiguate Label Action",
-  "disambiguateMultiMemberPopup-title": "Disambiguate Member Action",
-  "discard": "Discard",
+  "deleteLabelPopup-title": "حذف العلامة ?",
+  "description": "وصف",
+  "disambiguateMultiLabelPopup-title": "تحديد الإجراء على العلامة",
+  "disambiguateMultiMemberPopup-title": "تحديد الإجراء على العضو",
+  "discard": "التخلص منها",
   "done": "Done",
   "done": "Done",
-  "download": "Download",
-  "edit": "Edit",
-  "edit-avatar": "Change Avatar",
-  "edit-profile": "Edit Profile",
+  "download": "تنزيل",
+  "edit": "تعديل",
+  "edit-avatar": "تعديل الصورة الشخصية",
+  "edit-profile": "تعديل الملف الشخصي",
   "edit-wip-limit": "Edit WIP Limit",
   "edit-wip-limit": "Edit WIP Limit",
   "soft-wip-limit": "Soft WIP Limit",
   "soft-wip-limit": "Soft WIP Limit",
-  "editCardStartDatePopup-title": "Change start date",
-  "editCardDueDatePopup-title": "Change due date",
+  "editCardStartDatePopup-title": "تغيير تاريخ البدء",
+  "editCardDueDatePopup-title": "تغيير تاريخ الاستحقاق",
   "editCustomFieldPopup-title": "Edit Field",
   "editCustomFieldPopup-title": "Edit Field",
   "editCardSpentTimePopup-title": "Change spent time",
   "editCardSpentTimePopup-title": "Change spent time",
-  "editLabelPopup-title": "Change Label",
-  "editNotificationPopup-title": "Edit Notification",
-  "editProfilePopup-title": "Edit Profile",
-  "email": "Email",
+  "editLabelPopup-title": "تعديل العلامة",
+  "editNotificationPopup-title": "تصحيح الإشعار",
+  "editProfilePopup-title": "تعديل الملف الشخصي",
+  "email": "البريد الإلكتروني",
   "email-enrollAccount-subject": "An account created for you on __siteName__",
   "email-enrollAccount-subject": "An account created for you on __siteName__",
   "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.",
   "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.",
   "email-fail": "Sending email failed",
   "email-fail": "Sending email failed",
@@ -319,10 +319,10 @@
   "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
   "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
   "error-list-doesNotExist": "This list does not exist",
   "error-list-doesNotExist": "This list does not exist",
   "error-user-doesNotExist": "This user does not exist",
   "error-user-doesNotExist": "This user does not exist",
-  "error-user-notAllowSelf": "You can not invite yourself",
+  "error-user-notAllowSelf": "لا يمكنك دعوة نفسك",
   "error-user-notCreated": "This user is not created",
   "error-user-notCreated": "This user is not created",
-  "error-username-taken": "This username is already taken",
-  "error-email-taken": "Email has already been taken",
+  "error-username-taken": "إسم المستخدم مأخوذ مسبقا",
+  "error-email-taken": "البريد الإلكتروني مأخوذ بالفعل",
   "export-board": "Export board",
   "export-board": "Export board",
   "export-board-json": "Export board to JSON",
   "export-board-json": "Export board to JSON",
   "export-board-csv": "Export board to CSV",
   "export-board-csv": "Export board to CSV",
@@ -340,289 +340,289 @@
   "list-label-short-modifiedAt": "(L)",
   "list-label-short-modifiedAt": "(L)",
   "list-label-short-title": "(N)",
   "list-label-short-title": "(N)",
   "list-label-short-sort": "(M)",
   "list-label-short-sort": "(M)",
-  "filter": "Filter",
+  "filter": "تصفية",
   "filter-cards": "Filter Cards or Lists",
   "filter-cards": "Filter Cards or Lists",
   "list-filter-label": "Filter List by Title",
   "list-filter-label": "Filter List by Title",
-  "filter-clear": "Clear filter",
+  "filter-clear": "مسح التصفية",
   "filter-labels-label": "Filter by label",
   "filter-labels-label": "Filter by label",
-  "filter-no-label": "No label",
+  "filter-no-label": "لا يوجد ملصق",
   "filter-member-label": "Filter by member",
   "filter-member-label": "Filter by member",
-  "filter-no-member": "No member",
+  "filter-no-member": "ليس هناك أي عضو",
   "filter-assignee-label": "Filter by assignee",
   "filter-assignee-label": "Filter by assignee",
   "filter-no-assignee": "No assignee",
   "filter-no-assignee": "No assignee",
   "filter-custom-fields-label": "Filter by Custom Fields",
   "filter-custom-fields-label": "Filter by Custom Fields",
   "filter-no-custom-fields": "No Custom Fields",
   "filter-no-custom-fields": "No Custom Fields",
   "filter-show-archive": "Show archived lists",
   "filter-show-archive": "Show archived lists",
   "filter-hide-empty": "Hide empty lists",
   "filter-hide-empty": "Hide empty lists",
-  "filter-on": "Filter is on",
-  "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.",
-  "filter-to-selection": "Filter to selection",
+  "filter-on": "التصفية تشتغل",
+  "filter-on-desc": "أنت بصدد تصفية بطاقات هذه اللوحة. اضغط هنا لتعديل التصفية.",
+  "filter-to-selection": "تصفية بالتحديد",
   "other-filters-label": "Other Filters",
   "other-filters-label": "Other Filters",
   "advanced-filter-label": "Advanced Filter",
   "advanced-filter-label": "Advanced Filter",
   "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i",
   "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i",
-  "fullname": "Full Name",
-  "header-logo-title": "Go back to your boards page.",
-  "hide-system-messages": "Hide system messages",
-  "headerBarCreateBoardPopup-title": "Create Board",
-  "home": "Home",
+  "fullname": "الإسم الكامل",
+  "header-logo-title": "الرجوع إلى صفحة اللوحات",
+  "hide-system-messages": "إخفاء رسائل النظام",
+  "headerBarCreateBoardPopup-title": "إنشاء لوحة",
+  "home": "الرئيسية",
   "import": "Import",
   "import": "Import",
   "impersonate-user": "Impersonate user",
   "impersonate-user": "Impersonate user",
-  "link": "Link",
-  "import-board": "import board",
-  "import-board-c": "Import board",
+  "link": "رابط",
+  "import-board": "استيراد لوحة",
+  "import-board-c": "استيراد لوحة",
   "import-board-title-trello": "Import board from Trello",
   "import-board-title-trello": "Import board from Trello",
   "import-board-title-wekan": "Import board from previous export",
   "import-board-title-wekan": "Import board from previous export",
   "import-board-title-csv": "Import board from CSV/TSV",
   "import-board-title-csv": "Import board from CSV/TSV",
-  "from-trello": "From Trello",
+  "from-trello": "من تريلو",
   "from-wekan": "From previous export",
   "from-wekan": "From previous export",
   "from-csv": "From CSV/TSV",
   "from-csv": "From CSV/TSV",
-  "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.",
+  "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text",
   "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
   "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
   "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
   "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.",
   "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.",
   "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.",
   "import-json-placeholder": "Paste your valid JSON data here",
   "import-json-placeholder": "Paste your valid JSON data here",
   "import-csv-placeholder": "Paste your valid CSV/TSV data here",
   "import-csv-placeholder": "Paste your valid CSV/TSV data here",
-  "import-map-members": "Map members",
+  "import-map-members": "رسم خريطة الأعضاء",
   "import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
   "import-members-map": "Your imported board has some members. Please map the members you want to import to your users",
   "import-show-user-mapping": "Review members mapping",
   "import-show-user-mapping": "Review members mapping",
   "import-user-select": "Pick your existing user you want to use as this member",
   "import-user-select": "Pick your existing user you want to use as this member",
   "importMapMembersAddPopup-title": "Select member",
   "importMapMembersAddPopup-title": "Select member",
-  "info": "Version",
-  "initials": "Initials",
-  "invalid-date": "Invalid date",
+  "info": "الإصدار",
+  "initials": "أولية",
+  "invalid-date": "تاريخ غير صالح",
   "invalid-time": "Invalid time",
   "invalid-time": "Invalid time",
   "invalid-user": "Invalid user",
   "invalid-user": "Invalid user",
-  "joined": "joined",
+  "joined": "انضمّ",
   "just-invited": "You are just invited to this board",
   "just-invited": "You are just invited to this board",
-  "keyboard-shortcuts": "Keyboard shortcuts",
-  "label-create": "Create Label",
-  "label-default": "%s label (default)",
-  "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.",
-  "labels": "Labels",
-  "language": "Language",
-  "last-admin-desc": "You can’t change roles because there must be at least one admin.",
-  "leave-board": "Leave Board",
+  "keyboard-shortcuts": "اختصار لوحة المفاتيح",
+  "label-create": "إنشاء علامة",
+  "label-default": "%s علامة (افتراضية)",
+  "label-delete-pop": "لا يوجد تراجع. سيؤدي هذا إلى إزالة هذه العلامة من جميع بطاقات والقضاء على تأريخها",
+  "labels": "علامات",
+  "language": "لغة",
+  "last-admin-desc": "لا يمكن تعديل الأدوار لأن ذلك يتطلب  صلاحيات المدير.",
+  "leave-board": "مغادرة اللوحة",
   "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.",
   "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.",
-  "leaveBoardPopup-title": "Leave Board ?",
-  "link-card": "Link to this card",
+  "leaveBoardPopup-title": "مغادرة اللوحة ؟",
+  "link-card": "ربط هذه البطاقة",
   "list-archive-cards": "Move all cards in this list to Archive",
   "list-archive-cards": "Move all cards in this list to Archive",
   "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.",
   "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.",
-  "list-move-cards": "Move all cards in this list",
-  "list-select-cards": "Select all cards in this list",
+  "list-move-cards": "نقل بطاقات هذه القائمة",
+  "list-select-cards": "تحديد بطاقات هذه القائمة",
   "set-color-list": "Set Color",
   "set-color-list": "Set Color",
-  "listActionPopup-title": "List Actions",
+  "listActionPopup-title": "قائمة الإجراءات",
   "settingsUserPopup-title": "User Settings",
   "settingsUserPopup-title": "User Settings",
   "swimlaneActionPopup-title": "Swimlane Actions",
   "swimlaneActionPopup-title": "Swimlane Actions",
   "swimlaneAddPopup-title": "Add a Swimlane below",
   "swimlaneAddPopup-title": "Add a Swimlane below",
   "listImportCardPopup-title": "Import a Trello card",
   "listImportCardPopup-title": "Import a Trello card",
   "listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
   "listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
-  "listMorePopup-title": "More",
-  "link-list": "Link to this list",
+  "listMorePopup-title": "المزيد",
+  "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",
-  "loginPopup-title": "Log In",
-  "memberMenuPopup-title": "Member Settings",
-  "members": "Members",
-  "menu": "Menu",
+  "lists": "استات",
+  "swimlanes": "خطوط السباحة",
+  "log-out": "تسجيل الخروج",
+  "log-in": "تسجيل الدخول",
+  "loginPopup-title": "تسجيل الدخول",
+  "memberMenuPopup-title": "أفضليات الأعضاء",
+  "members": "أعضاء",
+  "menu": "القائمة",
   "move-selection": "Move selection",
   "move-selection": "Move selection",
-  "moveCardPopup-title": "Move Card",
-  "moveCardToBottom-title": "Move to Bottom",
-  "moveCardToTop-title": "Move to Top",
+  "moveCardPopup-title": "نقل البطاقة",
+  "moveCardToBottom-title": "التحرك إلى القاع",
+  "moveCardToTop-title": "التحرك إلى الأعلى",
   "moveSelectionPopup-title": "Move selection",
   "moveSelectionPopup-title": "Move selection",
-  "multi-selection": "Multi-Selection",
+  "multi-selection": "تحديد أكثر من واحدة",
   "multi-selection-label": "Set label for selection",
   "multi-selection-label": "Set label for selection",
   "multi-selection-member": "Set member for selection",
   "multi-selection-member": "Set member for selection",
   "multi-selection-on": "Multi-Selection is on",
   "multi-selection-on": "Multi-Selection is on",
-  "muted": "Muted",
+  "muted": "مكتوم",
   "muted-info": "You will never be notified of any changes in this board",
   "muted-info": "You will never be notified of any changes in this board",
-  "my-boards": "My Boards",
-  "name": "Name",
+  "my-boards": "لوحاتي",
+  "name": "اسم",
   "no-archived-cards": "No cards in Archive.",
   "no-archived-cards": "No cards in Archive.",
   "no-archived-lists": "No lists in Archive.",
   "no-archived-lists": "No lists in Archive.",
   "no-archived-swimlanes": "No swimlanes in Archive.",
   "no-archived-swimlanes": "No swimlanes in Archive.",
-  "no-results": "No results",
-  "normal": "Normal",
-  "normal-desc": "Can view and edit cards. Can't change settings.",
+  "no-results": "لا توجد نتائج",
+  "normal": "عادي",
+  "normal-desc": "يمكن مشاهدة و تعديل  البطاقات. لا يمكن تغيير إعدادات الضبط.",
   "not-accepted-yet": "Invitation not accepted yet",
   "not-accepted-yet": "Invitation not accepted yet",
   "notify-participate": "Receive updates to any cards you participate as creater or member",
   "notify-participate": "Receive updates to any cards you participate as creater or member",
   "notify-watch": "Receive updates to any boards, lists, or cards you’re watching",
   "notify-watch": "Receive updates to any boards, lists, or cards you’re watching",
-  "optional": "optional",
+  "optional": "اختياري",
   "or": "or",
   "or": "or",
-  "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.",
-  "page-not-found": "Page not found.",
-  "password": "Password",
+  "page-maybe-private": "قدتكون هذه الصفحة خاصة . قد تستطيع مشاهدتها ب  <a href='%s'>تسجيل الدخول</a>.",
+  "page-not-found": "صفحة غير موجودة",
+  "password": "كلمة المرور",
   "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)",
   "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)",
-  "participating": "Participating",
+  "participating": "المشاركة",
   "preview": "Preview",
   "preview": "Preview",
   "previewAttachedImagePopup-title": "Preview",
   "previewAttachedImagePopup-title": "Preview",
   "previewClipboardImagePopup-title": "Preview",
   "previewClipboardImagePopup-title": "Preview",
-  "private": "Private",
-  "private-desc": "This board is private. Only people added to the board can view and edit it.",
-  "profile": "Profile",
-  "public": "Public",
-  "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.",
-  "quick-access-description": "Star a board to add a shortcut in this bar.",
-  "remove-cover": "Remove Cover",
-  "remove-from-board": "Remove from Board",
-  "remove-label": "Remove Label",
-  "listDeletePopup-title": "Delete List ?",
-  "remove-member": "Remove Member",
-  "remove-member-from-card": "Remove from Card",
-  "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.",
-  "removeMemberPopup-title": "Remove Member?",
-  "rename": "Rename",
-  "rename-board": "Rename Board",
-  "restore": "Restore",
-  "save": "Save",
-  "search": "Search",
+  "private": "خاص",
+  "private-desc": "هذه اللوحة خاصة . لا يسمح إلا للأعضاء .",
+  "profile": "ملف شخصي",
+  "public": "عامّ",
+  "public-desc": "هذه اللوحة عامة: مرئية لكلّ من يحصل على الرابط ، و هي مرئية أيضا  في محركات البحث مثل جوجل. التعديل مسموح به للأعضاء فقط.",
+  "quick-access-description": "أضف لوحة إلى المفضلة لإنشاء اختصار في هذا الشريط.",
+  "remove-cover": "حذف الغلاف",
+  "remove-from-board": "حذف من اللوحة",
+  "remove-label": "إزالة التصنيف",
+  "listDeletePopup-title": "حذف القائمة ؟",
+  "remove-member": "حذف العضو",
+  "remove-member-from-card": "حذف من البطاقة",
+  "remove-member-pop": "حذف __name__ (__username__) من __boardTitle__ ? سيتم حذف هذا العضو من جميع بطاقة اللوحة مع إرسال إشعار له بذاك.",
+  "removeMemberPopup-title": "حذف العضو ?",
+  "rename": "إعادة التسمية",
+  "rename-board": "إعادة تسمية اللوحة",
+  "restore": "استعادة",
+  "save": "حفظ",
+  "search": "بحث",
   "rules": "Rules",
   "rules": "Rules",
   "search-cards": "Search from card/list titles, descriptions and custom fields on this board",
   "search-cards": "Search from card/list titles, descriptions and custom fields on this board",
   "search-example": "Write text you search and press Enter",
   "search-example": "Write text you search and press Enter",
-  "select-color": "Select Color",
+  "select-color": "اختيار اللون",
   "select-board": "Select Board",
   "select-board": "Select Board",
   "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
   "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list",
   "setWipLimitPopup-title": "Set WIP Limit",
   "setWipLimitPopup-title": "Set WIP Limit",
   "shortcut-assign-self": "Assign yourself to current card",
   "shortcut-assign-self": "Assign yourself to current card",
-  "shortcut-autocomplete-emoji": "Autocomplete emoji",
-  "shortcut-autocomplete-members": "Autocomplete members",
-  "shortcut-clear-filters": "Clear all filters",
-  "shortcut-close-dialog": "Close Dialog",
-  "shortcut-filter-my-cards": "Filter my cards",
-  "shortcut-show-shortcuts": "Bring up this shortcuts list",
+  "shortcut-autocomplete-emoji": "الإكمال التلقائي  للرموز التعبيرية",
+  "shortcut-autocomplete-members": "الإكمال التلقائي لأسماء الأعضاء",
+  "shortcut-clear-filters": "مسح التصفيات",
+  "shortcut-close-dialog": "غلق النافذة",
+  "shortcut-filter-my-cards": "تصفية بطاقاتي",
+  "shortcut-show-shortcuts": "عرض قائمة الإختصارات ،تلك",
   "shortcut-toggle-filterbar": "Toggle Filter Sidebar",
   "shortcut-toggle-filterbar": "Toggle Filter Sidebar",
   "shortcut-toggle-searchbar": "Toggle Search Sidebar",
   "shortcut-toggle-searchbar": "Toggle Search Sidebar",
-  "shortcut-toggle-sidebar": "Toggle Board Sidebar",
-  "show-cards-minimum-count": "Show cards count if list contains more than",
-  "sidebar-open": "Open Sidebar",
-  "sidebar-close": "Close Sidebar",
-  "signupPopup-title": "Create an Account",
-  "star-board-title": "Click to star this board. It will show up at top of your boards list.",
-  "starred-boards": "Starred Boards",
-  "starred-boards-description": "Starred boards show up at the top of your boards list.",
-  "subscribe": "Subscribe",
-  "team": "Team",
-  "this-board": "this board",
-  "this-card": "this card",
+  "shortcut-toggle-sidebar": "إظهار-إخفاء الشريط الجانبي للوحة",
+  "show-cards-minimum-count": "إظهار عدد البطاقات إذا كانت القائمة تتضمن أكثر من",
+  "sidebar-open": "فتح الشريط الجانبي",
+  "sidebar-close": "إغلاق الشريط الجانبي",
+  "signupPopup-title": "إنشاء حساب",
+  "star-board-title": "اضغط لإضافة هذه اللوحة إلى المفضلة . سوف يتم إظهارها على رأس بقية اللوحات.",
+  "starred-boards": "اللوحات المفضلة",
+  "starred-boards-description": "تعرض اللوحات المفضلة على رأس بقية اللوحات.",
+  "subscribe": "اشتراك و متابعة",
+  "team": "فريق",
+  "this-board": "هذه اللوحة",
+  "this-card": "هذه البطاقة",
   "spent-time-hours": "Spent time (hours)",
   "spent-time-hours": "Spent time (hours)",
-  "overtime-hours": "Overtime (hours)",
-  "overtime": "Overtime",
+  "overtime-hours": "وقت اضافي (ساعات)",
+  "overtime": "وقت اضافي",
   "has-overtime-cards": "Has overtime cards",
   "has-overtime-cards": "Has overtime cards",
   "has-spenttime-cards": "Has spent time cards",
   "has-spenttime-cards": "Has spent time cards",
-  "time": "Time",
-  "title": "Title",
-  "tracking": "Tracking",
+  "time": "الوقت",
+  "title": "عنوان",
+  "tracking": "تتبع",
   "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
   "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.",
-  "type": "Type",
-  "unassign-member": "Unassign member",
-  "unsaved-description": "You have an unsaved description.",
-  "unwatch": "Unwatch",
+  "type": "النوع",
+  "unassign-member": "إلغاء تعيين العضو",
+  "unsaved-description": "لديك وصف غير محفوظ",
+  "unwatch": "غير مُشاهد",
   "upload": "Upload",
   "upload": "Upload",
-  "upload-avatar": "Upload an avatar",
-  "uploaded-avatar": "Uploaded an avatar",
+  "upload-avatar": "رفع صورة شخصية",
+  "uploaded-avatar": "تم رفع الصورة الشخصية",
   "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL",
   "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL",
   "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL",
   "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL",
   "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27",
   "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27",
   "custom-login-logo-image-url": "Custom Login Logo Image URL",
   "custom-login-logo-image-url": "Custom Login Logo Image URL",
   "custom-login-logo-link-url": "Custom Login Logo Link URL",
   "custom-login-logo-link-url": "Custom Login Logo Link URL",
   "text-below-custom-login-logo": "Text below Custom Login Logo",
   "text-below-custom-login-logo": "Text below Custom Login Logo",
-  "username": "Username",
-  "view-it": "View it",
+  "username": "اسم المستخدم",
+  "view-it": "شاهدها",
   "warn-list-archived": "warning: this card is in an list at Archive",
   "warn-list-archived": "warning: this card is in an list at Archive",
-  "watch": "Watch",
-  "watching": "Watching",
+  "watch": "مُشاهد",
+  "watching": "مشاهدة",
   "watching-info": "You will be notified of any change in this board",
   "watching-info": "You will be notified of any change in this board",
-  "welcome-board": "Welcome Board",
+  "welcome-board": "لوحة التّرحيب",
   "welcome-swimlane": "Milestone 1",
   "welcome-swimlane": "Milestone 1",
-  "welcome-list1": "Basics",
-  "welcome-list2": "Advanced",
+  "welcome-list1": "المبادئ",
+  "welcome-list2": "متقدم",
   "card-templates-swimlane": "Card Templates",
   "card-templates-swimlane": "Card Templates",
   "list-templates-swimlane": "List Templates",
   "list-templates-swimlane": "List Templates",
   "board-templates-swimlane": "Board Templates",
   "board-templates-swimlane": "Board Templates",
-  "what-to-do": "What do you want to do?",
+  "what-to-do": "ماذا تريد أن تنجز?",
   "wipLimitErrorPopup-title": "Invalid WIP Limit",
   "wipLimitErrorPopup-title": "Invalid WIP Limit",
   "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.",
   "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.",
   "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.",
   "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.",
-  "admin-panel": "Admin Panel",
-  "settings": "Settings",
-  "people": "People",
-  "registration": "Registration",
+  "admin-panel": "لوحة التحكم",
+  "settings": "الإعدادات",
+  "people": "الناس",
+  "registration": "تسجيل",
   "disable-self-registration": "Disable Self-Registration",
   "disable-self-registration": "Disable Self-Registration",
-  "invite": "Invite",
-  "invite-people": "Invite People",
-  "to-boards": "To board(s)",
-  "email-addresses": "Email Addresses",
+  "invite": "دعوة",
+  "invite-people": "الناس المدعوين",
+  "to-boards": "إلى اللوحات",
+  "email-addresses": "عناوين البريد الإلكتروني",
   "smtp-host-description": "The address of the SMTP server that handles your emails.",
   "smtp-host-description": "The address of the SMTP server that handles your emails.",
   "smtp-port-description": "The port your SMTP server uses for outgoing emails.",
   "smtp-port-description": "The port your SMTP server uses for outgoing emails.",
-  "smtp-tls-description": "Enable TLS support for SMTP server",
-  "smtp-host": "SMTP Host",
-  "smtp-port": "SMTP Port",
-  "smtp-username": "Username",
-  "smtp-password": "Password",
-  "smtp-tls": "TLS support",
-  "send-from": "From",
+  "smtp-tls-description": "تفعيل دعم TLS من اجل خادم SMTP",
+  "smtp-host": "مضيف SMTP",
+  "smtp-port": "منفذ SMTP",
+  "smtp-username": "اسم المستخدم",
+  "smtp-password": "كلمة المرور",
+  "smtp-tls": "دعم التي ال سي",
+  "send-from": "من",
   "send-smtp-test": "Send a test email to yourself",
   "send-smtp-test": "Send a test email to yourself",
-  "invitation-code": "Invitation Code",
-  "email-invite-register-subject": "__inviter__ sent you an invitation",
+  "invitation-code": "رمز الدعوة",
+  "email-invite-register-subject": "__inviter__ أرسل دعوة لك",
   "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.",
   "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.",
   "email-smtp-test-subject": "SMTP Test Email",
   "email-smtp-test-subject": "SMTP Test Email",
   "email-smtp-test-text": "You have successfully sent an email",
   "email-smtp-test-text": "You have successfully sent an email",
-  "error-invitation-code-not-exist": "Invitation code doesn't exist",
-  "error-notAuthorized": "You are not authorized to view this page.",
+  "error-invitation-code-not-exist": "رمز الدعوة غير موجود",
+  "error-notAuthorized": "أنتَ لا تملك الصلاحيات لرؤية هذه الصفحة.",
   "webhook-title": "Webhook Name",
   "webhook-title": "Webhook Name",
   "webhook-token": "Token (Optional for Authentication)",
   "webhook-token": "Token (Optional for Authentication)",
-  "outgoing-webhooks": "Outgoing Webhooks",
+  "outgoing-webhooks": "الويبهوك الصادرة",
   "bidirectional-webhooks": "Two-Way Webhooks",
   "bidirectional-webhooks": "Two-Way Webhooks",
-  "outgoingWebhooksPopup-title": "Outgoing Webhooks",
+  "outgoingWebhooksPopup-title": "الويبهوك الصادرة",
   "boardCardTitlePopup-title": "Card Title Filter",
   "boardCardTitlePopup-title": "Card Title Filter",
   "disable-webhook": "Disable This Webhook",
   "disable-webhook": "Disable This Webhook",
   "global-webhook": "Global Webhooks",
   "global-webhook": "Global Webhooks",
-  "new-outgoing-webhook": "New Outgoing Webhook",
-  "no-name": "(Unknown)",
-  "Node_version": "Node version",
+  "new-outgoing-webhook": "ويبهوك جديدة ",
+  "no-name": "(غير معروف)",
+  "Node_version": "إصدار النود",
   "Meteor_version": "Meteor version",
   "Meteor_version": "Meteor version",
   "MongoDB_version": "MongoDB version",
   "MongoDB_version": "MongoDB version",
   "MongoDB_storage_engine": "MongoDB storage engine",
   "MongoDB_storage_engine": "MongoDB storage engine",
   "MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
   "MongoDB_Oplog_enabled": "MongoDB Oplog enabled",
-  "OS_Arch": "OS Arch",
-  "OS_Cpus": "OS CPU Count",
-  "OS_Freemem": "OS Free Memory",
-  "OS_Loadavg": "OS Load Average",
-  "OS_Platform": "OS Platform",
-  "OS_Release": "OS Release",
-  "OS_Totalmem": "OS Total Memory",
-  "OS_Type": "OS Type",
-  "OS_Uptime": "OS Uptime",
-  "days": "days",
-  "hours": "hours",
-  "minutes": "minutes",
-  "seconds": "seconds",
+  "OS_Arch": "معمارية نظام التشغيل",
+  "OS_Cpus": "استهلاك وحدة المعالجة المركزية لنظام التشغيل",
+  "OS_Freemem": "الذاكرة الحرة لنظام التشغيل",
+  "OS_Loadavg": "متوسط حمل نظام التشغيل",
+  "OS_Platform": "منصة نظام التشغيل",
+  "OS_Release": "إصدار نظام التشغيل",
+  "OS_Totalmem": "الذاكرة الكلية لنظام التشغيل",
+  "OS_Type": "نوع نظام التشغيل",
+  "OS_Uptime": "مدة تشغيل نظام التشغيل",
+  "days": "أيام",
+  "hours": "الساعات",
+  "minutes": "الدقائق",
+  "seconds": "الثواني",
   "show-field-on-card": "Show this field on card",
   "show-field-on-card": "Show this field on card",
   "automatically-field-on-card": "Add field to new cards",
   "automatically-field-on-card": "Add field to new cards",
   "always-field-on-card": "Add field to all cards",
   "always-field-on-card": "Add field to all cards",
   "showLabel-field-on-card": "Show field label on minicard",
   "showLabel-field-on-card": "Show field label on minicard",
-  "yes": "Yes",
-  "no": "No",
-  "accounts": "Accounts",
-  "accounts-allowEmailChange": "Allow Email Change",
+  "yes": "نعم",
+  "no": "لا",
+  "accounts": "الحسابات",
+  "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",
+  "active": "نشط",
   "card-received": "Received",
   "card-received": "Received",
   "card-received-on": "Received on",
   "card-received-on": "Received on",
   "card-end": "End",
   "card-end": "End",
   "card-end-on": "Ends on",
   "card-end-on": "Ends on",
   "editCardReceivedDatePopup-title": "Change received date",
   "editCardReceivedDatePopup-title": "Change received date",
   "editCardEndDatePopup-title": "Change end date",
   "editCardEndDatePopup-title": "Change end date",
-  "setCardColorPopup-title": "Set color",
-  "setCardActionsColorPopup-title": "Choose a color",
-  "setSwimlaneColorPopup-title": "Choose a color",
-  "setListColorPopup-title": "Choose a color",
+  "setCardColorPopup-title": "حدد اللون",
+  "setCardActionsColorPopup-title": "اختر لوناً",
+  "setSwimlaneColorPopup-title": "اختر لوناً",
+  "setListColorPopup-title": "اختر لوناً",
   "assigned-by": "Assigned By",
   "assigned-by": "Assigned By",
   "requested-by": "Requested By",
   "requested-by": "Requested By",
   "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.",
   "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.",
@@ -699,10 +699,10 @@
   "r-top-of": "Top of",
   "r-top-of": "Top of",
   "r-bottom-of": "Bottom of",
   "r-bottom-of": "Bottom of",
   "r-its-list": "its list",
   "r-its-list": "its list",
-  "r-archive": "Move to Archive",
+  "r-archive": "نقل الى الارشيف",
   "r-unarchive": "Restore from Archive",
   "r-unarchive": "Restore from Archive",
   "r-card": "card",
   "r-card": "card",
-  "r-add": "Add",
+  "r-add": "أضف",
   "r-remove": "Remove",
   "r-remove": "Remove",
   "r-label": "label",
   "r-label": "label",
   "r-member": "member",
   "r-member": "member",
@@ -855,7 +855,7 @@
   "website": "Website",
   "website": "Website",
   "person": "Person",
   "person": "Person",
   "my-cards": "My Cards",
   "my-cards": "My Cards",
-  "card": "Card",
+  "card": "بطاقة",
   "board": "Board",
   "board": "Board",
   "context-separator": "/",
   "context-separator": "/",
   "myCardsSortChange-title": "My Cards Sort",
   "myCardsSortChange-title": "My Cards Sort",
@@ -869,30 +869,30 @@
   "dueCardsViewChange-choice-all": "All Users",
   "dueCardsViewChange-choice-all": "All Users",
   "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.",
   "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.",
   "broken-cards": "Broken Cards",
   "broken-cards": "Broken Cards",
-  "board-title-not-found": "Board '%s' not found.",
-  "swimlane-title-not-found": "Swimlane '%s' not found.",
-  "list-title-not-found": "List '%s' not found.",
-  "label-not-found": "Label '%s' not found.",
+  "board-title-not-found": "لوحة '%s' غير موجود.",
+  "swimlane-title-not-found": "صف '%s' غير موجود.",
+  "list-title-not-found": "لستة '%s' غير موجود.",
+  "label-not-found": "ختم '%s' غير موجود.",
   "label-color-not-found": "Label color %s not found.",
   "label-color-not-found": "Label color %s not found.",
   "user-username-not-found": "Username '%s' not found.",
   "user-username-not-found": "Username '%s' not found.",
-  "globalSearch-title": "Search All Boards",
+  "globalSearch-title": "بحث في كل لوحة",
   "no-cards-found": "No Cards Found",
   "no-cards-found": "No Cards Found",
   "one-card-found": "One Card Found",
   "one-card-found": "One Card Found",
-  "n-cards-found": "%s Cards Found",
-  "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found",
-  "operator-board": "board",
+  "n-cards-found": "%s بطاقة",
+  "n-n-of-n-cards-found": "__start__-__end__ من __total__",
+  "operator-board": "لوحة",
   "operator-board-abbrev": "b",
   "operator-board-abbrev": "b",
-  "operator-swimlane": "swimlane",
+  "operator-swimlane": "صف",
   "operator-swimlane-abbrev": "s",
   "operator-swimlane-abbrev": "s",
-  "operator-list": "list",
+  "operator-list": "لستة",
   "operator-list-abbrev": "l",
   "operator-list-abbrev": "l",
-  "operator-label": "label",
+  "operator-label": "ختم",
   "operator-label-abbrev": "#",
   "operator-label-abbrev": "#",
-  "operator-user": "user",
+  "operator-user": "مستخدم",
   "operator-user-abbrev": "@",
   "operator-user-abbrev": "@",
-  "operator-member": "member",
+  "operator-member": "مشارك",
   "operator-member-abbrev": "m",
   "operator-member-abbrev": "m",
-  "operator-assignee": "assignee",
+  "operator-assignee": "مسؤول",
   "operator-assignee-abbrev": "a",
   "operator-assignee-abbrev": "a",
   "operator-is": "is",
   "operator-is": "is",
   "operator-due": "due",
   "operator-due": "due",
@@ -900,8 +900,19 @@
   "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__'",
-  "heading-notes": "Notes",
-  "globalSearch-instructions-heading": "Search Instructions",
+  "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": "ملاحظات",
+  "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\"`).",
   "globalSearch-instructions-operators": "Available operators:",
   "globalSearch-instructions-operators": "Available operators:",
   "globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
   "globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
@@ -920,7 +931,7 @@
   "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",
   "excel-font": "Arial",
   "excel-font": "Arial",
-  "number": "Number",
-  "label-colors": "Label Colors",
-  "label-names": "Label Names"
+  "number": "رقم",
+  "label-colors": "الوان الختم",
+  "label-names": "أسماء الختم"
 }
 }

+ 28 - 2
i18n/en.i18n.json

@@ -876,6 +876,7 @@
   "label-not-found": "Label '%s' not found.",
   "label-not-found": "Label '%s' not found.",
   "label-color-not-found": "Label color %s not found.",
   "label-color-not-found": "Label color %s not found.",
   "user-username-not-found": "Username '%s' not found.",
   "user-username-not-found": "Username '%s' not found.",
+  "comment-not-found": "Card with comment containing text '%s' not found.",
   "globalSearch-title": "Search All Boards",
   "globalSearch-title": "Search All Boards",
   "no-cards-found": "No Cards Found",
   "no-cards-found": "No Cards Found",
   "one-card-found": "One Card Found",
   "one-card-found": "One Card Found",
@@ -895,12 +896,29 @@
   "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-comment": "comment",
+  "predicate-archived": "archived",
+  "predicate-ended": "ended",
+  "predicate-all": "all",
+  "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-status-invalid": "'%s' is not a valid status",
+  "next-page": "Next Page",
+  "previous-page": "Previous Page",
   "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\"`).",
@@ -908,15 +926,23 @@
   "globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
   "globalSearch-instructions-operator-board": "`__operator_board__:title` - cards in boards matching the specified title",
   "globalSearch-instructions-operator-list": "`__operator_list__:title` - cards in lists matching the specified title",
   "globalSearch-instructions-operator-list": "`__operator_list__:title` - cards in lists matching the specified title",
   "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:title` - cards in swimlanes matching the specified title",
   "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:title` - cards in swimlanes matching the specified title",
+  "globalSearch-instructions-operator-comment": "`__operator_comment__:text` - cards with with a comment containing *text*.",
   "globalSearch-instructions-operator-label": "`__operator_label__:color` `__operator_label__:name` - cards that have a label matching the given color or name",
   "globalSearch-instructions-operator-label": "`__operator_label__:color` `__operator_label__:name` - cards that have a label matching the given color or name",
   "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__label` - shorthand for `__operator_label__:label`",
   "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__label` - shorthand for `__operator_label__:label`",
   "globalSearch-instructions-operator-user": "`__operator_user__:username` - cards where the specified user is a *member* or *assignee*",
   "globalSearch-instructions-operator-user": "`__operator_user__:username` - cards where the specified user is a *member* or *assignee*",
   "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 cards past their due date.",
+  "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-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;
   });
   });
 };
 };

+ 23 - 0
models/cardComments.js

@@ -1,3 +1,4 @@
+const escapeForRegex = require('escape-string-regexp');
 CardComments = new Mongo.Collection('card_comments');
 CardComments = new Mongo.Collection('card_comments');
 
 
 /**
 /**
@@ -109,6 +110,28 @@ function commentCreation(userId, doc) {
   });
   });
 }
 }
 
 
+CardComments.textSearch = (userId, textArray) => {
+  const selector = {
+    boardId: { $in: Boards.userBoardIds(userId) },
+    $and: [],
+  };
+
+  for (const text of textArray) {
+    selector.$and.push({ text: new RegExp(escapeForRegex(text)) });
+  }
+
+  // eslint-disable-next-line no-console
+  // console.log('cardComments selector:', selector);
+
+  const comments = CardComments.find(selector);
+  // eslint-disable-next-line no-console
+  // console.log('count:', comments.count());
+  // eslint-disable-next-line no-console
+  // console.log('cards with comments:', comments.map(com => { return com.cardId }));
+
+  return comments;
+};
+
 if (Meteor.isServer) {
 if (Meteor.isServer) {
   // Comments are often fetched within a card, so we create an index to make these
   // Comments are often fetched within a card, so we create an index to make these
   // queries more efficient.
   // queries more efficient.

+ 0 - 256
models/cards.js

@@ -1863,262 +1863,6 @@ Cards.mutations({
   },
   },
 });
 });
 
 
-Cards.globalSearch = queryParams => {
-  const userId = Meteor.userId();
-  // eslint-disable-next-line no-console
-  // console.log('userId:', userId);
-
-  const errors = new (class {
-    constructor() {
-      this.notFound = {
-        boards: [],
-        swimlanes: [],
-        lists: [],
-        labels: [],
-        users: [],
-        members: [],
-        assignees: [],
-        is: [],
-      };
-    }
-
-    hasErrors() {
-      for (const prop in this.notFound) {
-        if (this.notFound[prop].length) {
-          return true;
-        }
-      }
-      return false;
-    }
-  })();
-
-  const selector = {
-    archived: false,
-    type: 'cardType-card',
-    boardId: { $in: Boards.userBoardIds(userId) },
-    swimlaneId: { $nin: Swimlanes.archivedSwimlaneIds() },
-    listId: { $nin: Lists.archivedListIds() },
-  };
-
-  if (queryParams.boards.length) {
-    const queryBoards = [];
-    queryParams.boards.forEach(query => {
-      const boards = Boards.userSearch(userId, {
-        title: new RegExp(query, 'i'),
-      });
-      if (boards.count()) {
-        boards.forEach(board => {
-          queryBoards.push(board._id);
-        });
-      } else {
-        errors.notFound.boards.push(query);
-      }
-    });
-
-    selector.boardId.$in = queryBoards;
-  }
-
-  if (queryParams.swimlanes.length) {
-    const querySwimlanes = [];
-    queryParams.swimlanes.forEach(query => {
-      const swimlanes = Swimlanes.find({
-        title: new RegExp(query, 'i'),
-      });
-      if (swimlanes.count()) {
-        swimlanes.forEach(swim => {
-          querySwimlanes.push(swim._id);
-        });
-      } else {
-        errors.notFound.swimlanes.push(query);
-      }
-    });
-
-    selector.swimlaneId.$in = querySwimlanes;
-  }
-
-  if (queryParams.lists.length) {
-    const queryLists = [];
-    queryParams.lists.forEach(query => {
-      const lists = Lists.find({
-        title: new RegExp(query, 'i'),
-      });
-      if (lists.count()) {
-        lists.forEach(list => {
-          queryLists.push(list._id);
-        });
-      } else {
-        errors.notFound.lists.push(query);
-      }
-    });
-
-    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) {
-    queryParams.users.forEach(query => {
-      const users = Users.find({
-        username: query,
-      });
-      if (users.count()) {
-        users.forEach(user => {
-          queryMembers.push(user._id);
-          queryAssignees.push(user._id);
-        });
-      } else {
-        errors.notFound.users.push(query);
-      }
-    });
-  }
-
-  if (queryParams.members.length) {
-    queryParams.members.forEach(query => {
-      const users = Users.find({
-        username: query,
-      });
-      if (users.count()) {
-        users.forEach(user => {
-          queryMembers.push(user._id);
-        });
-      } else {
-        errors.notFound.members.push(query);
-      }
-    });
-  }
-
-  if (queryParams.assignees.length) {
-    queryParams.assignees.forEach(query => {
-      const users = Users.find({
-        username: query,
-      });
-      if (users.count()) {
-        users.forEach(user => {
-          queryAssignees.push(user._id);
-        });
-      } else {
-        errors.notFound.assignees.push(query);
-      }
-    });
-  }
-
-  if (queryMembers.length && queryAssignees.length) {
-    selector.$or = [
-      { members: { $in: queryMembers } },
-      { assignees: { $in: queryAssignees } },
-    ];
-  } else if (queryMembers.length) {
-    selector.members = { $in: queryMembers };
-  } else if (queryAssignees.length) {
-    selector.assignees = { $in: queryAssignees };
-  }
-
-  if (queryParams.labels.length) {
-    queryParams.labels.forEach(label => {
-      const queryLabels = [];
-
-      let boards = Boards.userSearch(userId, {
-        labels: { $elemMatch: { color: label.toLowerCase() } },
-      });
-
-      if (boards.count()) {
-        boards.forEach(board => {
-          // eslint-disable-next-line no-console
-          // console.log('board:', board);
-          // eslint-disable-next-line no-console
-          // console.log('board.labels:', board.labels);
-          board.labels
-            .filter(boardLabel => {
-              return boardLabel.color === label.toLowerCase();
-            })
-            .forEach(boardLabel => {
-              queryLabels.push(boardLabel._id);
-            });
-        });
-      } else {
-        // eslint-disable-next-line no-console
-        // console.log('label:', label);
-        const reLabel = new RegExp(label, 'i');
-        // eslint-disable-next-line no-console
-        // console.log('reLabel:', reLabel);
-        boards = Boards.userSearch(userId, {
-          labels: { $elemMatch: { name: reLabel } },
-        });
-
-        if (boards.count()) {
-          boards.forEach(board => {
-            board.labels
-              .filter(boardLabel => {
-                return boardLabel.name.match(reLabel);
-              })
-              .forEach(boardLabel => {
-                queryLabels.push(boardLabel._id);
-              });
-          });
-        } else {
-          errors.notFound.labels.push(label);
-        }
-      }
-
-      selector.labelIds = { $in: queryLabels };
-    });
-  }
-
-  if (errors.hasErrors()) {
-    return { cards: null, errors };
-  }
-
-  if (queryParams.text) {
-    const regex = new RegExp(queryParams.text, 'i');
-
-    selector.$or = [
-      { title: regex },
-      { description: regex },
-      { customFields: { $elemMatch: { value: regex } } },
-    ];
-  }
-
-  // eslint-disable-next-line no-console
-  // console.log('selector:', selector);
-  const cards = Cards.find(selector, {
-    fields: {
-      _id: 1,
-      archived: 1,
-      boardId: 1,
-      swimlaneId: 1,
-      listId: 1,
-      title: 1,
-      type: 1,
-      sort: 1,
-      members: 1,
-      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());
-
-  return { cards, errors };
-};
-
 //FUNCTIONS FOR creation of Activities
 //FUNCTIONS FOR creation of Activities
 
 
 function updateActivities(doc, fieldNames, modifier) {
 function updateActivities(doc, fieldNames, modifier) {

+ 102 - 0
models/usersessiondata.js

@@ -25,6 +25,13 @@ SessionData.attachSchema(
       type: String,
       type: String,
       optional: false,
       optional: false,
     },
     },
+    sessionId: {
+      /**
+       * unique session ID
+       */
+      type: String,
+      optional: false,
+    },
     totalHits: {
     totalHits: {
       /**
       /**
        * total number of hits in the last report query
        * total number of hits in the last report query
@@ -32,6 +39,13 @@ SessionData.attachSchema(
       type: Number,
       type: Number,
       optional: true,
       optional: true,
     },
     },
+    resultsCount: {
+      /**
+       * number of results returned
+       */
+      type: Number,
+      optional: true,
+    },
     lastHit: {
     lastHit: {
       /**
       /**
        * the last hit returned from a report query
        * the last hit returned from a report query
@@ -39,6 +53,48 @@ SessionData.attachSchema(
       type: Number,
       type: Number,
       optional: true,
       optional: true,
     },
     },
+    cards: {
+      type: [String],
+      optional: true,
+    },
+    selector: {
+      type: String,
+      optional: true,
+      blackbox: true,
+    },
+    errorMessages: {
+      type: [String],
+      optional: true,
+    },
+    errors: {
+      type: [Object],
+      optional: true,
+      defaultValue: [],
+    },
+    'errors.$': {
+      type: new SimpleSchema({
+        tag: {
+          /**
+           * i18n tag
+           */
+          type: String,
+          optional: false,
+        },
+        value: {
+          /**
+           * value for the tag
+           */
+          type: String,
+          optional: true,
+          defaultValue: null,
+        },
+        color: {
+          type: Boolean,
+          optional: true,
+          defaultValue: false,
+        },
+      }),
+    },
     createdAt: {
     createdAt: {
       /**
       /**
        * creation date of the team
        * creation date of the team
@@ -70,4 +126,50 @@ SessionData.attachSchema(
   }),
   }),
 );
 );
 
 
+SessionData.helpers({
+  getSelector() {
+    return SessionData.unpickle(this.selector);
+  },
+});
+
+SessionData.unpickle = pickle => {
+  return JSON.parse(pickle, (key, value) => {
+    if (typeof value === 'object') {
+      if (value.hasOwnProperty('$$class')) {
+        if (value.$$class === 'RegExp') {
+          return new RegExp(value.source, value.flags);
+        }
+      }
+    }
+    return value;
+  });
+};
+
+SessionData.pickle = value => {
+  return JSON.stringify(value, (key, value) => {
+    if (typeof value === 'object') {
+      if (value.constructor.name === 'RegExp') {
+        return {
+          $$class: 'RegExp',
+          source: value.source,
+          flags: value.flags,
+        };
+      }
+    }
+    return value;
+  });
+};
+
+if (!Meteor.isServer) {
+  SessionData.getSessionId = () => {
+    let sessionId = Session.get('sessionId');
+    if (!sessionId) {
+      sessionId = `${String(Meteor.userId())}-${String(Math.random())}`;
+      Session.set('sessionId', sessionId);
+    }
+
+    return sessionId;
+  };
+}
+
 export default SessionData;
 export default SessionData;

+ 502 - 37
server/publications/cards.js

@@ -1,3 +1,5 @@
+const escapeForRegex = require('escape-string-regexp');
+
 Meteor.publish('card', cardId => {
 Meteor.publish('card', cardId => {
   check(cardId, String);
   check(cardId, String);
   return Cards.find({ _id: cardId });
   return Cards.find({ _id: cardId });
@@ -173,59 +175,522 @@ Meteor.publish('dueCards', function(allUsers = false) {
   ];
   ];
 });
 });
 
 
-Meteor.publish('globalSearch', function(queryParams) {
+Meteor.publish('globalSearch', function(sessionId, queryParams) {
+  check(sessionId, String);
   check(queryParams, Object);
   check(queryParams, Object);
 
 
   // eslint-disable-next-line no-console
   // eslint-disable-next-line no-console
   // console.log('queryParams:', queryParams);
   // console.log('queryParams:', queryParams);
 
 
-  const cards = Cards.globalSearch(queryParams).cards;
+  const userId = Meteor.userId();
+  // eslint-disable-next-line no-console
+  // console.log('userId:', userId);
+
+  const errors = new (class {
+    constructor() {
+      this.notFound = {
+        boards: [],
+        swimlanes: [],
+        lists: [],
+        labels: [],
+        users: [],
+        members: [],
+        assignees: [],
+        status: [],
+        comments: [],
+      };
+
+      this.colorMap = {};
+      for (const color of Boards.simpleSchema()._schema['labels.$.color']
+        .allowedValues) {
+        this.colorMap[TAPi18n.__(`color-${color}`)] = color;
+      }
+    }
+
+    hasErrors() {
+      for (const value of Object.values(this.notFound)) {
+        if (value.length) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    errorMessages() {
+      const messages = [];
+
+      this.notFound.boards.forEach(board => {
+        messages.push({ tag: 'board-title-not-found', value: board });
+      });
+      this.notFound.swimlanes.forEach(swim => {
+        messages.push({ tag: 'swimlane-title-not-found', value: swim });
+      });
+      this.notFound.lists.forEach(list => {
+        messages.push({ tag: 'list-title-not-found', value: list });
+      });
+      this.notFound.comments.forEach(comments => {
+        comments.forEach(text => {
+          messages.push({ tag: 'comment-not-found', value: text });
+        });
+      });
+      this.notFound.labels.forEach(label => {
+        messages.push({ tag: 'label-not-found', value: label, color: true });
+      });
+      this.notFound.users.forEach(user => {
+        messages.push({ tag: 'user-username-not-found', value: user });
+      });
+      this.notFound.members.forEach(user => {
+        messages.push({ tag: 'user-username-not-found', value: user });
+      });
+      this.notFound.assignees.forEach(user => {
+        messages.push({ tag: 'user-username-not-found', value: user });
+      });
+
+      return messages;
+    }
+  })();
 
 
-  if (!cards) {
-    return [];
+  let selector = {};
+  let skip = 0;
+  if (queryParams.skip) {
+    skip = queryParams.skip;
+  }
+  let limit = 25;
+  if (queryParams.limit) {
+    limit = queryParams.limit;
   }
   }
 
 
-  SessionData.upsert(
-    { userId: this.userId },
-    {
-      $set: {
-        totalHits: cards.count(),
-        lastHit: cards.count() > 50 ? 50 : cards.count(),
-      },
-    },
-  );
+  if (queryParams.selector) {
+    selector = queryParams.selector;
+  } else {
+    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, ''] };
+        }
+      });
+    }
+    selector = {
+      type: 'cardType-card',
+      // 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;
+    }
 
 
-  const boards = [];
-  const swimlanes = [];
-  const lists = [];
-  const users = [this.userId];
+    if (queryParams.boards.length) {
+      const queryBoards = [];
+      queryParams.boards.forEach(query => {
+        const boards = Boards.userSearch(userId, {
+          title: new RegExp(escapeForRegex(query), 'i'),
+        });
+        if (boards.count()) {
+          boards.forEach(board => {
+            queryBoards.push(board._id);
+          });
+        } else {
+          errors.notFound.boards.push(query);
+        }
+      });
 
 
-  cards.forEach(card => {
-    if (card.boardId) boards.push(card.boardId);
-    if (card.swimlaneId) swimlanes.push(card.swimlaneId);
-    if (card.listId) lists.push(card.listId);
-    if (card.members) {
-      card.members.forEach(userId => {
-        users.push(userId);
+      selector.boardId.$in = queryBoards;
+    }
+
+    if (queryParams.swimlanes.length) {
+      const querySwimlanes = [];
+      queryParams.swimlanes.forEach(query => {
+        const swimlanes = Swimlanes.find({
+          title: new RegExp(escapeForRegex(query), 'i'),
+        });
+        if (swimlanes.count()) {
+          swimlanes.forEach(swim => {
+            querySwimlanes.push(swim._id);
+          });
+        } else {
+          errors.notFound.swimlanes.push(query);
+        }
       });
       });
+
+      if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) {
+        selector.swimlaneId = { $in: [] };
+      }
+      selector.swimlaneId.$in = querySwimlanes;
     }
     }
-    if (card.assignees) {
-      card.assignees.forEach(userId => {
-        users.push(userId);
+
+    if (queryParams.lists.length) {
+      const queryLists = [];
+      queryParams.lists.forEach(query => {
+        const lists = Lists.find({
+          title: new RegExp(escapeForRegex(query), 'i'),
+        });
+        if (lists.count()) {
+          lists.forEach(list => {
+            queryLists.push(list._id);
+          });
+        } else {
+          errors.notFound.lists.push(query);
+        }
       });
       });
+
+      if (!selector.hasOwnProperty('listId')) {
+        selector.listId = { $in: [] };
+      }
+      selector.listId.$in = queryLists;
+    }
+
+    if (queryParams.comments.length) {
+      const cardIds = CardComments.textSearch(userId, queryParams.comments).map(
+        com => {
+          return com.cardId;
+        },
+      );
+      if (cardIds.length) {
+        selector._id = { $in: cardIds };
+      } else {
+        errors.notFound.comments.push(queryParams.comments);
+      }
+    }
+
+    if (queryParams.dueAt !== null) {
+      selector.dueAt = { $lte: 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) {
+      queryParams.users.forEach(query => {
+        const users = Users.find({
+          username: query,
+        });
+        if (users.count()) {
+          users.forEach(user => {
+            queryMembers.push(user._id);
+            queryAssignees.push(user._id);
+          });
+        } else {
+          errors.notFound.users.push(query);
+        }
+      });
+    }
+
+    if (queryParams.members.length) {
+      queryParams.members.forEach(query => {
+        const users = Users.find({
+          username: query,
+        });
+        if (users.count()) {
+          users.forEach(user => {
+            queryMembers.push(user._id);
+          });
+        } else {
+          errors.notFound.members.push(query);
+        }
+      });
+    }
+
+    if (queryParams.assignees.length) {
+      queryParams.assignees.forEach(query => {
+        const users = Users.find({
+          username: query,
+        });
+        if (users.count()) {
+          users.forEach(user => {
+            queryAssignees.push(user._id);
+          });
+        } else {
+          errors.notFound.assignees.push(query);
+        }
+      });
+    }
+
+    if (queryMembers.length && queryAssignees.length) {
+      selector.$and.push({
+        $or: [
+          { members: { $in: queryMembers } },
+          { assignees: { $in: queryAssignees } },
+        ],
+      });
+    } else if (queryMembers.length) {
+      selector.members = { $in: queryMembers };
+    } else if (queryAssignees.length) {
+      selector.assignees = { $in: queryAssignees };
+    }
+
+    if (queryParams.labels.length) {
+      queryParams.labels.forEach(label => {
+        const queryLabels = [];
+
+        let boards = Boards.userSearch(userId, {
+          labels: { $elemMatch: { color: label.toLowerCase() } },
+        });
+
+        if (boards.count()) {
+          boards.forEach(board => {
+            // eslint-disable-next-line no-console
+            // console.log('board:', board);
+            // eslint-disable-next-line no-console
+            // console.log('board.labels:', board.labels);
+            board.labels
+              .filter(boardLabel => {
+                return boardLabel.color === label.toLowerCase();
+              })
+              .forEach(boardLabel => {
+                queryLabels.push(boardLabel._id);
+              });
+          });
+        } else {
+          // eslint-disable-next-line no-console
+          // console.log('label:', label);
+          const reLabel = new RegExp(escapeForRegex(label), 'i');
+          // eslint-disable-next-line no-console
+          // console.log('reLabel:', reLabel);
+          boards = Boards.userSearch(userId, {
+            labels: { $elemMatch: { name: reLabel } },
+          });
+
+          if (boards.count()) {
+            boards.forEach(board => {
+              board.labels
+                .filter(boardLabel => {
+                  return boardLabel.name.match(reLabel);
+                })
+                .forEach(boardLabel => {
+                  queryLabels.push(boardLabel._id);
+                });
+            });
+          } else {
+            errors.notFound.labels.push(label);
+          }
+        }
+
+        selector.labelIds = { $in: queryLabels };
+      });
+    }
+
+    if (queryParams.text) {
+      const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
+
+      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('users:', users);
-  return [
-    cards,
-    Boards.find({ _id: { $in: boards } }),
-    Swimlanes.find({ _id: { $in: swimlanes } }),
-    Lists.find({ _id: { $in: lists } }),
-    Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
-    SessionData.find({ userId: this.userId }),
-  ];
+  // console.log('selector:', selector);
+  // eslint-disable-next-line no-console
+  // console.log('selector.$and:', selector.$and);
+
+  let cards = null;
+
+  if (!errors.hasErrors()) {
+    const projection = {
+      fields: {
+        _id: 1,
+        archived: 1,
+        boardId: 1,
+        swimlaneId: 1,
+        listId: 1,
+        title: 1,
+        type: 1,
+        sort: 1,
+        members: 1,
+        assignees: 1,
+        colors: 1,
+        dueAt: 1,
+        createdAt: 1,
+        modifiedAt: 1,
+        labelIds: 1,
+        customFields: 1,
+      },
+      skip,
+      limit,
+    };
+
+    if (queryParams.sort === 'due') {
+      projection.sort = {
+        dueAt: 1,
+        boardId: 1,
+        swimlaneId: 1,
+        listId: 1,
+        sort: 1,
+      };
+    } else if (queryParams.sort === 'modified') {
+      projection.sort = {
+        modifiedAt: -1,
+        boardId: 1,
+        swimlaneId: 1,
+        listId: 1,
+        sort: 1,
+      };
+    } else if (queryParams.sort === 'created') {
+      projection.sort = {
+        createdAt: -1,
+        boardId: 1,
+        swimlaneId: 1,
+        listId: 1,
+        sort: 1,
+      };
+    } else if (queryParams.sort === 'system') {
+      projection.sort = {
+        boardId: 1,
+        swimlaneId: 1,
+        listId: 1,
+        modifiedAt: 1,
+        sort: 1,
+      };
+    }
+
+    // eslint-disable-next-line no-console
+    // console.log('projection:', projection);
+    cards = Cards.find(selector, projection);
+
+    // eslint-disable-next-line no-console
+    // console.log('count:', cards.count());
+  }
+
+  const update = {
+    $set: {
+      totalHits: 0,
+      lastHit: 0,
+      resultsCount: 0,
+      cards: [],
+      errors: errors.errorMessages(),
+      selector: SessionData.pickle(selector),
+    },
+  };
+
+  if (cards) {
+    update.$set.totalHits = cards.count();
+    update.$set.lastHit =
+      skip + limit < cards.count() ? skip + limit : cards.count();
+    update.$set.cards = cards.map(card => {
+      return card._id;
+    });
+    update.$set.resultsCount = update.$set.cards.length;
+  }
+
+  SessionData.upsert({ userId, sessionId }, update);
+
+  // remove old session data
+  SessionData.remove({
+    userId,
+    modifiedAt: {
+      $lt: new Date(
+        moment()
+          .subtract(1, 'day')
+          .format(),
+      ),
+    },
+  });
+
+  if (cards) {
+    const boards = [];
+    const swimlanes = [];
+    const lists = [];
+    const customFieldIds = [];
+    const users = [this.userId];
+
+    cards.forEach(card => {
+      if (card.boardId) boards.push(card.boardId);
+      if (card.swimlaneId) swimlanes.push(card.swimlaneId);
+      if (card.listId) lists.push(card.listId);
+      if (card.members) {
+        card.members.forEach(userId => {
+          users.push(userId);
+        });
+      }
+      if (card.assignees) {
+        card.assignees.forEach(userId => {
+          users.push(userId);
+        });
+      }
+      if (card.customFields) {
+        card.customFields.forEach(field => {
+          customFieldIds.push(field._id);
+        });
+      }
+    });
+
+    const fields = {
+      _id: 1,
+      title: 1,
+      archived: 1,
+      sort: 1,
+      type: 1,
+    };
+
+    return [
+      cards,
+      Boards.find(
+        { _id: { $in: boards } },
+        { fields: { ...fields, labels: 1, color: 1 } },
+      ),
+      Swimlanes.find(
+        { _id: { $in: swimlanes } },
+        { fields: { ...fields, color: 1 } },
+      ),
+      Lists.find({ _id: { $in: lists } }, { fields }),
+      CustomFields.find({ _id: { $in: customFieldIds } }),
+      Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
+      SessionData.find({ userId: this.userId, sessionId }),
+    ];
+  }
+
+  return [SessionData.find({ userId: this.userId, sessionId })];
 });
 });
 
 
 Meteor.publish('brokenCards', function() {
 Meteor.publish('brokenCards', function() {