mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 23:40:13 +01:00
Merge pull request #3459 from jrsupplee/new-search
Search All Boards: Added list of board, list and color names.
This commit is contained in:
commit
1df060b8f6
7 changed files with 198 additions and 9 deletions
|
|
@ -14,7 +14,14 @@ template(name="globalSearch")
|
||||||
if currentUser
|
if currentUser
|
||||||
.wrapper
|
.wrapper
|
||||||
form.global-search-instructions.js-search-query-form
|
form.global-search-instructions.js-search-query-form
|
||||||
input.global-search-query-input(type="text" name="searchQuery" placeholder="{{_ 'search-example'}}" value="{{ query.get }}" autofocus dir="auto")
|
input.global-search-query-input(
|
||||||
|
id="global-search-input"
|
||||||
|
type="text"
|
||||||
|
name="searchQuery"
|
||||||
|
placeholder="{{_ 'search-example'}}"
|
||||||
|
value="{{ query.get }}"
|
||||||
|
autofocus dir="auto"
|
||||||
|
)
|
||||||
if searching.get
|
if searching.get
|
||||||
+spinner
|
+spinner
|
||||||
else if hasResults.get
|
else if hasResults.get
|
||||||
|
|
@ -32,6 +39,26 @@ template(name="globalSearch")
|
||||||
+resultCard(card)
|
+resultCard(card)
|
||||||
else
|
else
|
||||||
.global-search-instructions
|
.global-search-instructions
|
||||||
|
h2 {{_ 'boards' }}
|
||||||
|
.lists-wrapper
|
||||||
|
each title in myBoardNames.get
|
||||||
|
span.card-label.list-title.js-board-title
|
||||||
|
= title
|
||||||
|
h2 {{_ 'lists' }}
|
||||||
|
.lists-wrapper
|
||||||
|
each title in myLists.get
|
||||||
|
span.card-label.list-title.js-list-title
|
||||||
|
= title
|
||||||
|
h2 {{_ 'label-colors' }}
|
||||||
|
.palette-colors: each label in labelColors
|
||||||
|
span.card-label.palette-color.js-label-color(class="card-label-{{label.color}}")
|
||||||
|
= label.name
|
||||||
|
if myLabelNames.get.length
|
||||||
|
h2 {{_ 'label-names' }}
|
||||||
|
.lists-wrapper
|
||||||
|
each name in myLabelNames.get
|
||||||
|
span.card-label.list-title.js-label-name
|
||||||
|
= name
|
||||||
+viewer
|
+viewer
|
||||||
= searchInstructions
|
= searchInstructions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ BlazeComponent.extendComponent({
|
||||||
this.query = new ReactiveVar('');
|
this.query = new ReactiveVar('');
|
||||||
this.resultsHeading = new ReactiveVar('');
|
this.resultsHeading = new ReactiveVar('');
|
||||||
this.searchLink = new ReactiveVar(null);
|
this.searchLink = new ReactiveVar(null);
|
||||||
|
this.myLists = new ReactiveVar([]);
|
||||||
|
this.myLabelNames = new ReactiveVar([]);
|
||||||
|
this.myBoardNames = new ReactiveVar([]);
|
||||||
this.queryParams = null;
|
this.queryParams = null;
|
||||||
this.parsingErrors = [];
|
this.parsingErrors = [];
|
||||||
this.resultsCount = 0;
|
this.resultsCount = 0;
|
||||||
|
|
@ -55,6 +58,25 @@ BlazeComponent.extendComponent({
|
||||||
// }
|
// }
|
||||||
// // eslint-disable-next-line no-console
|
// // eslint-disable-next-line no-console
|
||||||
// console.log('colorMap:', this.colorMap);
|
// console.log('colorMap:', this.colorMap);
|
||||||
|
|
||||||
|
Meteor.call('myLists', (err, data) => {
|
||||||
|
if (!err) {
|
||||||
|
this.myLists.set(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.call('myLabelNames', (err, data) => {
|
||||||
|
if (!err) {
|
||||||
|
this.myLabelNames.set(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.call('myBoardNames', (err, data) => {
|
||||||
|
if (!err) {
|
||||||
|
this.myBoardNames.set(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Meteor.subscribe('setting');
|
Meteor.subscribe('setting');
|
||||||
if (Session.get('globalQuery')) {
|
if (Session.get('globalQuery')) {
|
||||||
this.searchAllBoards(Session.get('globalQuery'));
|
this.searchAllBoards(Session.get('globalQuery'));
|
||||||
|
|
@ -111,11 +133,13 @@ BlazeComponent.extendComponent({
|
||||||
messages.push({ tag: 'list-title-not-found', value: list });
|
messages.push({ tag: 'list-title-not-found', value: list });
|
||||||
});
|
});
|
||||||
this.queryErrors.notFound.labels.forEach(label => {
|
this.queryErrors.notFound.labels.forEach(label => {
|
||||||
const color = TAPi18n.__(`color-${label}`);
|
const color = Object.entries(this.colorMap)
|
||||||
if (color) {
|
.filter(value => value[1] === label)
|
||||||
|
.map(value => value[0]);
|
||||||
|
if (color.length) {
|
||||||
messages.push({
|
messages.push({
|
||||||
tag: 'label-color-not-found',
|
tag: 'label-color-not-found',
|
||||||
value: color,
|
value: color[0],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
messages.push({ tag: 'label-not-found', value: label });
|
messages.push({ tag: 'label-not-found', value: label });
|
||||||
|
|
@ -185,9 +209,12 @@ BlazeComponent.extendComponent({
|
||||||
operatorMap[TAPi18n.__('operator-assignee')] = 'assignees';
|
operatorMap[TAPi18n.__('operator-assignee')] = 'assignees';
|
||||||
operatorMap[TAPi18n.__('operator-assignee-abbrev')] = 'assignees';
|
operatorMap[TAPi18n.__('operator-assignee-abbrev')] = 'assignees';
|
||||||
operatorMap[TAPi18n.__('operator-is')] = 'is';
|
operatorMap[TAPi18n.__('operator-is')] = 'is';
|
||||||
|
operatorMap[TAPi18n.__('operator-due')] = 'dueAt';
|
||||||
|
operatorMap[TAPi18n.__('operator-created')] = 'createdAt';
|
||||||
|
operatorMap[TAPi18n.__('operator-modified')] = 'modifiedAt';
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
// console.log('operatorMap:', operatorMap);
|
console.log('operatorMap:', operatorMap);
|
||||||
const params = {
|
const params = {
|
||||||
boards: [],
|
boards: [],
|
||||||
swimlanes: [],
|
swimlanes: [],
|
||||||
|
|
@ -197,6 +224,9 @@ BlazeComponent.extendComponent({
|
||||||
assignees: [],
|
assignees: [],
|
||||||
labels: [],
|
labels: [],
|
||||||
is: [],
|
is: [],
|
||||||
|
dueAt: null,
|
||||||
|
createdAt: null,
|
||||||
|
modifiedAt: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = '';
|
let text = '';
|
||||||
|
|
@ -223,8 +253,33 @@ BlazeComponent.extendComponent({
|
||||||
if (value in this.colorMap) {
|
if (value in this.colorMap) {
|
||||||
value = this.colorMap[value];
|
value = this.colorMap[value];
|
||||||
}
|
}
|
||||||
|
} 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()
|
||||||
|
.subtract(days, 'days')
|
||||||
|
.format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(params[operatorMap[op]])) {
|
||||||
|
params[operatorMap[op]].push(value);
|
||||||
|
} else {
|
||||||
|
params[operatorMap[op]] = value;
|
||||||
}
|
}
|
||||||
params[operatorMap[op]].push(value);
|
|
||||||
} else {
|
} else {
|
||||||
this.parsingErrors.push({
|
this.parsingErrors.push({
|
||||||
tag: 'operator-unknown-error',
|
tag: 'operator-unknown-error',
|
||||||
|
|
@ -355,6 +410,14 @@ BlazeComponent.extendComponent({
|
||||||
return text;
|
return text;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
labelColors() {
|
||||||
|
return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map(
|
||||||
|
color => {
|
||||||
|
return { color, name: TAPi18n.__(`color-${color}`) };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -362,6 +425,42 @@ BlazeComponent.extendComponent({
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
this.searchAllBoards(evt.target.searchQuery.value);
|
this.searchAllBoards(evt.target.searchQuery.value);
|
||||||
},
|
},
|
||||||
|
'click .js-label-color'(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.query.set(
|
||||||
|
`${this.query.get()} ${TAPi18n.__('operator-label')}:"${
|
||||||
|
evt.currentTarget.textContent
|
||||||
|
}"`,
|
||||||
|
);
|
||||||
|
document.getElementById('global-search-input').focus();
|
||||||
|
},
|
||||||
|
'click .js-board-title'(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.query.set(
|
||||||
|
`${this.query.get()} ${TAPi18n.__('operator-board')}:"${
|
||||||
|
evt.currentTarget.textContent
|
||||||
|
}"`,
|
||||||
|
);
|
||||||
|
document.getElementById('global-search-input').focus();
|
||||||
|
},
|
||||||
|
'click .js-list-title'(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.query.set(
|
||||||
|
`${this.query.get()} ${TAPi18n.__('operator-list')}:"${
|
||||||
|
evt.currentTarget.textContent
|
||||||
|
}"`,
|
||||||
|
);
|
||||||
|
document.getElementById('global-search-input').focus();
|
||||||
|
},
|
||||||
|
'click .js-label-name'(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.query.set(
|
||||||
|
`${this.query.get()} ${TAPi18n.__('operator-label')}:"${
|
||||||
|
evt.currentTarget.textContent
|
||||||
|
}"`,
|
||||||
|
);
|
||||||
|
document.getElementById('global-search-input').focus();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,12 @@
|
||||||
margin-left: auto
|
margin-left: auto
|
||||||
line-height: 150%
|
line-height: 150%
|
||||||
|
|
||||||
|
.global-search-instructions h1
|
||||||
|
margin-top: 2rem;
|
||||||
|
|
||||||
|
.global-search-instructions h2
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
.global-search-query-input
|
.global-search-query-input
|
||||||
width: 90% !important
|
width: 90% !important
|
||||||
margin-right: auto
|
margin-right: auto
|
||||||
|
|
@ -95,3 +101,6 @@ code
|
||||||
background-color: lightgrey
|
background-color: lightgrey
|
||||||
padding: 0.1rem !important
|
padding: 0.1rem !important
|
||||||
font-size: 0.7rem !important
|
font-size: 0.7rem !important
|
||||||
|
|
||||||
|
.list-title
|
||||||
|
background-color: darkgray
|
||||||
|
|
|
||||||
|
|
@ -895,7 +895,11 @@
|
||||||
"operator-assignee": "assignee",
|
"operator-assignee": "assignee",
|
||||||
"operator-assignee-abbrev": "a",
|
"operator-assignee-abbrev": "a",
|
||||||
"operator-is": "is",
|
"operator-is": "is",
|
||||||
|
"operator-due": "due",
|
||||||
|
"operator-created": "created",
|
||||||
|
"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__'",
|
||||||
"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\"`).",
|
||||||
|
|
@ -916,5 +920,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"
|
"number": "Number",
|
||||||
|
"label-colors": "Label Colors",
|
||||||
|
"label-names": "Label Names"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1343,6 +1343,26 @@ 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;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return _.uniq(names).sort();
|
||||||
|
},
|
||||||
|
myBoardNames() {
|
||||||
|
return _.uniq(
|
||||||
|
Boards.userBoards(Meteor.userId()).map(board => {
|
||||||
|
return board.title;
|
||||||
|
}),
|
||||||
|
).sort();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
|
|
|
||||||
|
|
@ -1954,6 +1954,18 @@ Cards.globalSearch = queryParams => {
|
||||||
selector.listId.$in = queryLists;
|
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 queryMembers = [];
|
||||||
const queryAssignees = [];
|
const queryAssignees = [];
|
||||||
if (queryParams.users.length) {
|
if (queryParams.users.length) {
|
||||||
|
|
@ -2079,7 +2091,7 @@ Cards.globalSearch = queryParams => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
// console.log('selector:', selector);
|
console.log('selector:', selector);
|
||||||
const cards = Cards.find(selector, {
|
const cards = Cards.find(selector, {
|
||||||
fields: {
|
fields: {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
|
|
@ -2094,13 +2106,15 @@ Cards.globalSearch = queryParams => {
|
||||||
assignees: 1,
|
assignees: 1,
|
||||||
colors: 1,
|
colors: 1,
|
||||||
dueAt: 1,
|
dueAt: 1,
|
||||||
|
createdAt: 1,
|
||||||
|
modifiedAt: 1,
|
||||||
labelIds: 1,
|
labelIds: 1,
|
||||||
},
|
},
|
||||||
limit: 50,
|
limit: 50,
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
// console.log('count:', cards.count());
|
console.log('count:', cards.count());
|
||||||
|
|
||||||
return { cards, errors };
|
return { cards, errors };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -362,6 +362,20 @@ Meteor.methods({
|
||||||
const list = Lists.findOne({ _id: listId });
|
const list = Lists.findOne({ _id: listId });
|
||||||
list.toggleSoftLimit(!list.getWipLimit('soft'));
|
list.toggleSoftLimit(!list.getWipLimit('soft'));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
myLists() {
|
||||||
|
// my lists
|
||||||
|
return _.uniq(
|
||||||
|
Lists.find(
|
||||||
|
{ boardId: { $in: Boards.userBoardIds(this.userId) } },
|
||||||
|
{ fields: { title: 1 } },
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
.map(list => {
|
||||||
|
return list.title;
|
||||||
|
}),
|
||||||
|
).sort();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Lists.hookOptions.after.update = { fetchPrevious: false };
|
Lists.hookOptions.after.update = { fetchPrevious: false };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue