mirror of
https://github.com/wekan/wekan.git
synced 2025-04-22 04:57:07 -04:00
commit
4f9b4059a6
5 changed files with 119 additions and 18 deletions
|
@ -222,6 +222,7 @@ BlazeComponent.extendComponent({
|
|||
'operator-created': 'createdAt',
|
||||
'operator-modified': 'modifiedAt',
|
||||
'operator-comment': 'comments',
|
||||
'operator-has': 'has',
|
||||
};
|
||||
|
||||
const predicates = {
|
||||
|
@ -238,12 +239,19 @@ BlazeComponent.extendComponent({
|
|||
'predicate-archived': 'archived',
|
||||
'predicate-all': 'all',
|
||||
'predicate-ended': 'ended',
|
||||
'predicate-public': 'public',
|
||||
'predicate-private': 'private',
|
||||
},
|
||||
sorts: {
|
||||
'predicate-due': 'dueAt',
|
||||
'predicate-created': 'createdAt',
|
||||
'predicate-modified': 'modifiedAt',
|
||||
},
|
||||
has: {
|
||||
'predicate-description': 'description',
|
||||
'predicate-checklist': 'checklist',
|
||||
'predicate-attachment': 'attachment',
|
||||
},
|
||||
};
|
||||
const predicateTranslations = {};
|
||||
Object.entries(predicates).forEach(([category, catPreds]) => {
|
||||
|
@ -276,6 +284,7 @@ BlazeComponent.extendComponent({
|
|||
createdAt: null,
|
||||
modifiedAt: null,
|
||||
comments: [],
|
||||
has: [],
|
||||
};
|
||||
|
||||
let text = '';
|
||||
|
@ -296,6 +305,7 @@ BlazeComponent.extendComponent({
|
|||
} else {
|
||||
op = m.groups.abbrev.toLowerCase();
|
||||
}
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (operatorMap.hasOwnProperty(op)) {
|
||||
let value = m.groups.value;
|
||||
if (operatorMap[op] === 'labels') {
|
||||
|
@ -353,6 +363,15 @@ BlazeComponent.extendComponent({
|
|||
} else {
|
||||
value = predicateTranslations.status[value];
|
||||
}
|
||||
} else if (operatorMap[op] === 'has') {
|
||||
if (!predicateTranslations.has[value]) {
|
||||
this.parsingErrors.push({
|
||||
tag: 'operator-has-invalid',
|
||||
value,
|
||||
});
|
||||
} else {
|
||||
value = predicateTranslations.has[value];
|
||||
}
|
||||
}
|
||||
if (Array.isArray(params[operatorMap[op]])) {
|
||||
params[operatorMap[op]].push(value);
|
||||
|
@ -511,6 +530,7 @@ BlazeComponent.extendComponent({
|
|||
operator_created: TAPi18n.__('operator-created'),
|
||||
operator_modified: TAPi18n.__('operator-modified'),
|
||||
operator_status: TAPi18n.__('operator-status'),
|
||||
operator_has: TAPi18n.__('operator-has'),
|
||||
predicate_overdue: TAPi18n.__('predicate-overdue'),
|
||||
predicate_archived: TAPi18n.__('predicate-archived'),
|
||||
predicate_all: TAPi18n.__('predicate-all'),
|
||||
|
@ -519,6 +539,11 @@ BlazeComponent.extendComponent({
|
|||
predicate_month: TAPi18n.__('predicate-month'),
|
||||
predicate_quarter: TAPi18n.__('predicate-quarter'),
|
||||
predicate_year: TAPi18n.__('predicate-year'),
|
||||
predicate_attachment: TAPi18n.__('predicate-attachment'),
|
||||
predicate_description: TAPi18n.__('predicate-description'),
|
||||
predicate_checklist: TAPi18n.__('predicate-checklist'),
|
||||
predicate_public: TAPi18n.__('predicate-public'),
|
||||
predicate_private: TAPi18n.__('predicate-private'),
|
||||
};
|
||||
|
||||
text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
|
||||
|
@ -574,9 +599,19 @@ BlazeComponent.extendComponent({
|
|||
'globalSearch-instructions-status-archived',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__(
|
||||
'globalSearch-instructions-status-public',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__(
|
||||
'globalSearch-instructions-status-private',
|
||||
tags,
|
||||
)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-status-all', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-status-ended', tags)}`;
|
||||
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-operator-has', tags)}`;
|
||||
|
||||
text += `\n## ${TAPi18n.__('heading-notes')}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-1', tags)}`;
|
||||
text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-2', tags)}`;
|
||||
|
|
|
@ -906,6 +906,7 @@
|
|||
"operator-modified": "modified",
|
||||
"operator-sort": "sort",
|
||||
"operator-comment": "comment",
|
||||
"operator-has": "has",
|
||||
"predicate-archived": "archived",
|
||||
"predicate-ended": "ended",
|
||||
"predicate-all": "all",
|
||||
|
@ -917,10 +918,16 @@
|
|||
"predicate-due": "due",
|
||||
"predicate-modified": "modified",
|
||||
"predicate-created": "created",
|
||||
"predicate-attachment": "attachment",
|
||||
"predicate-description": "description",
|
||||
"predicate-checklist": "checklist",
|
||||
"predicate-public": "public",
|
||||
"predicate-private": "private",
|
||||
"operator-unknown-error": "%s is not an operator",
|
||||
"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",
|
||||
"operator-has-invalid": "%s is not a valid existence check",
|
||||
"next-page": "Next Page",
|
||||
"previous-page": "Previous Page",
|
||||
"heading-notes": "Notes",
|
||||
|
@ -943,12 +950,15 @@
|
|||
"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-status-public": "`__operator_status__:__predicate_public__` - cards only in public boards.",
|
||||
"globalSearch-instructions-status-private": "`__operator_status__:__predicate_private__` - cards only in private boards.",
|
||||
"globalSearch-instructions-operator-has": "`__operator_has__:field` - where *field* is one of `__predicate_attachment__`, `__predicate_checklist__` or `__predicate_description__`",
|
||||
"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-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-5": "Currently archived cards are not searched.",
|
||||
"globalSearch-instructions-notes-5": "By default archived cards are not searched.",
|
||||
"link-to-search": "Link to this search",
|
||||
"excel-font": "Arial",
|
||||
"number": "Number",
|
||||
|
|
|
@ -1420,15 +1420,17 @@ if (Meteor.isServer) {
|
|||
},
|
||||
myLabelNames() {
|
||||
let names = [];
|
||||
Boards.userBoards(Meteor.userId()).forEach(board => {
|
||||
names = names.concat(
|
||||
board.labels
|
||||
.filter(label => !!label.name)
|
||||
.map(label => {
|
||||
return label.name;
|
||||
}),
|
||||
);
|
||||
});
|
||||
Boards.userBoards(Meteor.userId(), false, { type: 'board' }).forEach(
|
||||
board => {
|
||||
names = names.concat(
|
||||
board.labels
|
||||
.filter(label => !!label.name)
|
||||
.map(label => {
|
||||
return label.name;
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
return _.uniq(names).sort();
|
||||
},
|
||||
myBoardNames() {
|
||||
|
|
|
@ -134,7 +134,10 @@ SessionData.helpers({
|
|||
|
||||
SessionData.unpickle = pickle => {
|
||||
return JSON.parse(pickle, (key, value) => {
|
||||
if (typeof value === 'object') {
|
||||
if (value === null) {
|
||||
return null;
|
||||
} else if (typeof value === 'object') {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (value.hasOwnProperty('$$class')) {
|
||||
if (value.$$class === 'RegExp') {
|
||||
return new RegExp(value.source, value.flags);
|
||||
|
@ -147,7 +150,9 @@ SessionData.unpickle = pickle => {
|
|||
|
||||
SessionData.pickle = value => {
|
||||
return JSON.stringify(value, (key, value) => {
|
||||
if (typeof value === 'object') {
|
||||
if (value === null) {
|
||||
return null;
|
||||
} else if (typeof value === 'object') {
|
||||
if (value.constructor.name === 'RegExp') {
|
||||
return {
|
||||
$$class: 'RegExp',
|
||||
|
|
|
@ -263,6 +263,8 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
if (queryParams.selector) {
|
||||
selector = queryParams.selector;
|
||||
} else {
|
||||
const boardsSelector = {};
|
||||
|
||||
let archived = false;
|
||||
let endAt = null;
|
||||
if (queryParams.status.length) {
|
||||
|
@ -273,6 +275,8 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
archived = null;
|
||||
} else if (status === 'ended') {
|
||||
endAt = { $nin: [null, ''] };
|
||||
} else if (['private', 'public'].includes(status)) {
|
||||
boardsSelector.permission = status;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -282,27 +286,35 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
$and: [],
|
||||
};
|
||||
|
||||
const boardsSelector = {};
|
||||
if (archived !== null) {
|
||||
boardsSelector.archived = archived;
|
||||
if (archived) {
|
||||
selector.boardId = { $in: Boards.userBoardIds(userId, null) };
|
||||
selector.boardId = {
|
||||
$in: Boards.userBoardIds(userId, null, boardsSelector),
|
||||
};
|
||||
selector.$and.push({
|
||||
$or: [
|
||||
{ boardId: { $in: Boards.userBoardIds(userId, archived) } },
|
||||
{
|
||||
boardId: {
|
||||
$in: Boards.userBoardIds(userId, archived, boardsSelector),
|
||||
},
|
||||
},
|
||||
{ swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } },
|
||||
{ listId: { $in: Lists.archivedListIds() } },
|
||||
{ archived: true },
|
||||
],
|
||||
});
|
||||
} else {
|
||||
selector.boardId = { $in: Boards.userBoardIds(userId, false) };
|
||||
selector.boardId = {
|
||||
$in: Boards.userBoardIds(userId, false, boardsSelector),
|
||||
};
|
||||
selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() };
|
||||
selector.listId = { $nin: Lists.archivedListIds() };
|
||||
selector.archived = false;
|
||||
}
|
||||
} else {
|
||||
selector.boardId = { $in: Boards.userBoardIds(userId, null) };
|
||||
selector.boardId = {
|
||||
$in: Boards.userBoardIds(userId, null, boardsSelector),
|
||||
};
|
||||
}
|
||||
if (endAt !== null) {
|
||||
selector.endAt = endAt;
|
||||
|
@ -341,6 +353,7 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) {
|
||||
selector.swimlaneId = { $in: [] };
|
||||
}
|
||||
|
@ -362,6 +375,7 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (!selector.hasOwnProperty('listId')) {
|
||||
selector.listId = { $in: [] };
|
||||
}
|
||||
|
@ -505,9 +519,39 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
});
|
||||
}
|
||||
|
||||
if (queryParams.has.length) {
|
||||
queryParams.has.forEach(has => {
|
||||
if (has === 'description') {
|
||||
selector.description = { $exists: true, $nin: [null, ''] };
|
||||
} else if (has === 'attachment') {
|
||||
const attachments = Attachments.find({}, { fields: { cardId: 1 } });
|
||||
selector.$and.push({ _id: { $in: attachments.map(a => a.cardId) } });
|
||||
} else if (has === 'checklist') {
|
||||
const checklists = Checklists.find({}, { fields: { cardId: 1 } });
|
||||
selector.$and.push({ _id: { $in: checklists.map(a => a.cardId) } });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (queryParams.text) {
|
||||
const regex = new RegExp(escapeForRegex(queryParams.text), 'i');
|
||||
|
||||
const items = ChecklistItems.find(
|
||||
{ title: regex },
|
||||
{ fields: { cardId: 1 } },
|
||||
);
|
||||
const checklists = Checklists.find(
|
||||
{
|
||||
$or: [
|
||||
{ title: regex },
|
||||
{ _id: { $in: items.map(item => item.checklistId) } },
|
||||
],
|
||||
},
|
||||
{ fields: { cardId: 1 } },
|
||||
);
|
||||
|
||||
const attachments = Attachments.find({ 'original.name': regex });
|
||||
|
||||
selector.$and.push({
|
||||
$or: [
|
||||
{ title: regex },
|
||||
|
@ -520,6 +564,8 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
),
|
||||
},
|
||||
},
|
||||
{ _id: { $in: checklists.map(list => list.cardId) } },
|
||||
{ _id: { $in: attachments.map(attach => attach.cardId) } },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
@ -686,6 +732,9 @@ Meteor.publish('globalSearch', function(sessionId, queryParams) {
|
|||
Lists.find({ _id: { $in: lists } }, { fields }),
|
||||
CustomFields.find({ _id: { $in: customFieldIds } }),
|
||||
Users.find({ _id: { $in: users } }, { fields: Users.safeFields }),
|
||||
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 }),
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue