浏览代码

Fix sort operator
* Add server publications for next and previous page
* Add ability to sort ascending or descending

John R. Supplee 4 年之前
父节点
当前提交
43f40c4085
共有 5 个文件被更改,包括 237 次插入172 次删除
  1. 22 27
      client/components/main/globalSearch.js
  2. 1 1
      models/cardComments.js
  3. 9 0
      models/usersessiondata.js
  4. 15 7
      package-lock.json
  5. 190 137
      server/publications/cards.js

+ 22 - 27
client/components/main/globalSearch.js

@@ -116,7 +116,12 @@ BlazeComponent.extendComponent({
       // eslint-disable-next-line no-console
       // console.log('selector:', sessionData.getSelector());
       // console.log('session data:', sessionData);
-      const cards = Cards.find({ _id: { $in: sessionData.cards } });
+      const projection = sessionData.getProjection();
+      projection.skip = 0;
+      const cards = Cards.find(
+        { _id: { $in: sessionData.cards } },
+        projection,
+      );
       this.queryErrors = sessionData.errors;
       if (this.queryErrors.length) {
         this.hasQueryErrors.set(true);
@@ -201,6 +206,7 @@ BlazeComponent.extendComponent({
       '^(?<quote>["\'])(?<text>.*?)\\k<quote>(\\s+|$)',
       'u',
     );
+    const reNegatedOperator = new RegExp('^-(?<operator>.*)$');
 
     const operators = {
       'operator-board': 'boards',
@@ -223,6 +229,7 @@ BlazeComponent.extendComponent({
       'operator-modified': 'modifiedAt',
       'operator-comment': 'comments',
       'operator-has': 'has',
+      'operator-sort': 'sort',
     };
 
     const predicates = {
@@ -346,13 +353,22 @@ BlazeComponent.extendComponent({
               }
             }
           } else if (operatorMap[op] === 'sort') {
+            let negated = false;
+            const m = value.match(reNegatedOperator);
+            if (m) {
+              value = m.groups.operator;
+              negated = true;
+            }
             if (!predicateTranslations.sorts[value]) {
               this.parsingErrors.push({
                 tag: 'operator-sort-invalid',
                 value,
               });
             } else {
-              value = predicateTranslations.sorts[value];
+              value = {
+                name: predicateTranslations.sorts[value],
+                order: negated ? 'des' : 'asc',
+              };
             }
           } else if (operatorMap[op] === 'status') {
             if (!predicateTranslations.status[value]) {
@@ -437,20 +453,10 @@ BlazeComponent.extendComponent({
   },
 
   nextPage() {
-    sessionData = this.getSessionData();
-
-    const params = {
-      limit: this.resultsPerPage,
-      selector: sessionData.getSelector(),
-      skip: sessionData.lastHit,
-    };
+    const sessionData = this.getSessionData();
 
     this.autorun(() => {
-      const handle = Meteor.subscribe(
-        'globalSearch',
-        SessionData.getSessionId(),
-        params,
-      );
+      const handle = Meteor.subscribe('nextPage', sessionData.sessionId);
       Tracker.nonreactive(() => {
         Tracker.autorun(() => {
           if (handle.ready()) {
@@ -464,21 +470,10 @@ BlazeComponent.extendComponent({
   },
 
   previousPage() {
-    sessionData = this.getSessionData();
-
-    const params = {
-      limit: this.resultsPerPage,
-      selector: sessionData.getSelector(),
-      skip:
-        sessionData.lastHit - sessionData.resultsCount - this.resultsPerPage,
-    };
+    const sessionData = this.getSessionData();
 
     this.autorun(() => {
-      const handle = Meteor.subscribe(
-        'globalSearch',
-        SessionData.getSessionId(),
-        params,
-      );
+      const handle = Meteor.subscribe('previousPage', sessionData.sessionId);
       Tracker.nonreactive(() => {
         Tracker.autorun(() => {
           if (handle.ready()) {

+ 1 - 1
models/cardComments.js

@@ -117,7 +117,7 @@ CardComments.textSearch = (userId, textArray) => {
   };
 
   for (const text of textArray) {
-    selector.$and.push({ text: new RegExp(escapeForRegex(text)) });
+    selector.$and.push({ text: new RegExp(escapeForRegex(text), 'i') });
   }
 
   // eslint-disable-next-line no-console

+ 9 - 0
models/usersessiondata.js

@@ -62,6 +62,12 @@ SessionData.attachSchema(
       optional: true,
       blackbox: true,
     },
+    projection: {
+      type: String,
+      optional: true,
+      blackbox: true,
+      defaultValue: {},
+    },
     errorMessages: {
       type: [String],
       optional: true,
@@ -130,6 +136,9 @@ SessionData.helpers({
   getSelector() {
     return SessionData.unpickle(this.selector);
   },
+  getProjection() {
+    return SessionData.unpickle(this.projection);
+  },
 });
 
 SessionData.unpickle = pickle => {

+ 15 - 7
package-lock.json

@@ -718,6 +718,19 @@
       "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
       "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
     },
+    "babel-eslint": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
+      "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==",
+      "requires": {
+        "@babel/code-frame": "^7.0.0",
+        "@babel/parser": "^7.7.0",
+        "@babel/traverse": "^7.7.0",
+        "@babel/types": "^7.7.0",
+        "eslint-visitor-keys": "^1.0.0",
+        "resolve": "^1.12.0"
+      }
+    },
     "babel-runtime": {
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@@ -2384,8 +2397,7 @@
     "function-bind": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
     },
     "functional-red-black-tree": {
       "version": "1.0.1",
@@ -2525,7 +2537,6 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
       "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
-      "dev": true,
       "requires": {
         "function-bind": "^1.1.1"
       }
@@ -2810,7 +2821,6 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
       "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
-      "dev": true,
       "requires": {
         "has": "^1.0.3"
       }
@@ -4941,8 +4951,7 @@
     "path-parse": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
-      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
-      "dev": true
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
     },
     "path-to-regexp": {
       "version": "1.2.1",
@@ -5625,7 +5634,6 @@
       "version": "1.20.0",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
       "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
-      "dev": true,
       "requires": {
         "is-core-module": "^2.2.0",
         "path-parse": "^1.0.6"

+ 190 - 137
server/publications/cards.js

@@ -552,6 +552,11 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
 
       const attachments = Attachments.find({ 'original.name': regex });
 
+      // const comments = CardComments.find(
+      //   { text: regex },
+      //   { fields: { cardId: 1 } },
+      // );
+
       selector.$and.push({
         $or: [
           { title: regex },
@@ -566,6 +571,7 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
           },
           { _id: { $in: checklists.map(list => list.cardId) } },
           { _id: { $in: attachments.map(attach => attach.cardId) } },
+          // { _id: { $in: comments.map(com => com.cardId) } },
         ],
       });
     }
@@ -580,89 +586,206 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
   // 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,
-    };
+  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,
+    },
+    sort: {
+      boardId: 1,
+      swimlaneId: 1,
+      listId: 1,
+      sort: 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,
-      };
+  if (queryParams.sort) {
+    const order = queryParams.sort.order === 'asc' ? 1 : -1;
+    switch (queryParams.sort.name) {
+      case 'dueAt':
+        projection.sort = {
+          dueAt: order,
+          boardId: 1,
+          swimlaneId: 1,
+          listId: 1,
+          sort: 1,
+        };
+        break;
+      case 'modifiedAt':
+        projection.sort = {
+          modifiedAt: order,
+          boardId: 1,
+          swimlaneId: 1,
+          listId: 1,
+          sort: 1,
+        };
+        break;
+      case 'createdAt':
+        projection.sort = {
+          createdAt: order,
+          boardId: 1,
+          swimlaneId: 1,
+          listId: 1,
+          sort: 1,
+        };
+        break;
+      case 'system':
+        projection.sort = {
+          boardId: order,
+          swimlaneId: order,
+          listId: order,
+          modifiedAt: order,
+          sort: order,
+        };
+        break;
     }
+  }
 
-    // eslint-disable-next-line no-console
-    // console.log('projection:', projection);
-    cards = Cards.find(selector, projection);
+  // eslint-disable-next-line no-console
+  // console.log('projection:', projection);
+
+  return findCards(sessionId, selector, projection, errors);
+});
+
+Meteor.publish('brokenCards', function() {
+  const user = Users.findOne({ _id: this.userId });
+
+  const permiitedBoards = [null];
+  let selector = {};
+  selector.$or = [
+    { permission: 'public' },
+    { members: { $elemMatch: { userId: user._id, isActive: true } } },
+  ];
+
+  Boards.find(selector).forEach(board => {
+    permiitedBoards.push(board._id);
+  });
+
+  selector = {
+    boardId: { $in: permiitedBoards },
+    $or: [
+      { boardId: { $in: [null, ''] } },
+      { swimlaneId: { $in: [null, ''] } },
+      { listId: { $in: [null, ''] } },
+    ],
+  };
+
+  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,
+    },
+  });
+
+  const boards = [];
+  const swimlanes = [];
+  const lists = [];
+  const users = [];
+
+  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);
+      });
+    }
+  });
+
+  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 }),
+  ];
+});
+
+Meteor.publish('nextPage', function(sessionId) {
+  check(sessionId, String);
 
-    // eslint-disable-next-line no-console
-    // console.log('count:', cards.count());
+  const session = SessionData.findOne({ sessionId });
+  const projection = session.getProjection();
+  projection.skip = session.lastHit;
+
+  return findCards(sessionId, session.getSelector(), projection);
+});
+
+Meteor.publish('previousPage', function(sessionId) {
+  check(sessionId, String);
+
+  const session = SessionData.findOne({ sessionId });
+  const projection = session.getProjection();
+  projection.skip = session.lastHit - session.resultsCount - projection.limit;
+
+  return findCards(sessionId, session.getSelector(), projection);
+});
+
+function findCards(sessionId, selector, projection, errors = null) {
+  // check(selector, Object);
+  // check(projection, Object);
+  const userId = Meteor.userId();
+
+  let cards;
+  if (!errors || !errors.hasErrors()) {
+    cards = Cards.find(selector, projection);
   }
 
+  console.log('selector:', selector);
+  console.log('projection:', projection);
+  console.log('count:', cards.count());
   const update = {
     $set: {
       totalHits: 0,
       lastHit: 0,
       resultsCount: 0,
       cards: [],
-      errors: errors.errorMessages(),
       selector: SessionData.pickle(selector),
+      projection: SessionData.pickle(projection),
     },
   };
+  if (errors) {
+    update.$set.errors = errors.errorMessages();
+  }
 
   if (cards) {
     update.$set.totalHits = cards.count();
     update.$set.lastHit =
-      skip + limit < cards.count() ? skip + limit : cards.count();
+      projection.skip + projection.limit < cards.count()
+        ? projection.skip + projection.limit
+        : cards.count();
     update.$set.cards = cards.map(card => {
       return card._id;
     });
@@ -735,79 +858,9 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
       Checklists.find({ cardId: { $in: cards.map(c => c._id) } }),
       Attachments.find({ cardId: { $in: cards.map(c => c._id) } }),
       CardComments.find({ cardId: { $in: cards.map(c => c._id) } }),
-      SessionData.find({ userId: this.userId, sessionId }),
+      SessionData.find({ userId, sessionId }),
     ];
   }
 
-  return [SessionData.find({ userId: this.userId, sessionId })];
-});
-
-Meteor.publish('brokenCards', function() {
-  const user = Users.findOne({ _id: this.userId });
-
-  const permiitedBoards = [null];
-  let selector = {};
-  selector.$or = [
-    { permission: 'public' },
-    { members: { $elemMatch: { userId: user._id, isActive: true } } },
-  ];
-
-  Boards.find(selector).forEach(board => {
-    permiitedBoards.push(board._id);
-  });
-
-  selector = {
-    boardId: { $in: permiitedBoards },
-    $or: [
-      { boardId: { $in: [null, ''] } },
-      { swimlaneId: { $in: [null, ''] } },
-      { listId: { $in: [null, ''] } },
-    ],
-  };
-
-  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,
-    },
-  });
-
-  const boards = [];
-  const swimlanes = [];
-  const lists = [];
-  const users = [];
-
-  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);
-      });
-    }
-  });
-
-  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 }),
-  ];
-});
+  return [SessionData.find({ userId: userId, sessionId })];
+}