diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js index a6013baf2..5bfc34ebc 100644 --- a/client/components/main/globalSearch.js +++ b/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({ '^(?["\'])(?.*?)\\k(\\s+|$)', 'u', ); + const reNegatedOperator = new RegExp('^-(?.*)$'); 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()) { diff --git a/models/cardComments.js b/models/cardComments.js index 64631c15e..e77ae164b 100644 --- a/models/cardComments.js +++ b/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 diff --git a/models/usersessiondata.js b/models/usersessiondata.js index 003b35c91..8bd516897 100644 --- a/models/usersessiondata.js +++ b/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 => { diff --git a/package-lock.json b/package-lock.json index 4889211cc..ccdf89bf3 100644 --- a/package-lock.json +++ b/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" diff --git a/server/publications/cards.js b/server/publications/cards.js index e61dce1f1..5f4ad7746 100644 --- a/server/publications/cards.js +++ b/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 })]; +}