|
@@ -36,164 +36,300 @@ BlazeComponent.extendComponent({
|
|
|
|
|
|
BlazeComponent.extendComponent({
|
|
BlazeComponent.extendComponent({
|
|
onCreated() {
|
|
onCreated() {
|
|
- this.isPageReady = new ReactiveVar(true);
|
|
|
|
this.searching = new ReactiveVar(false);
|
|
this.searching = new ReactiveVar(false);
|
|
this.hasResults = new ReactiveVar(false);
|
|
this.hasResults = new ReactiveVar(false);
|
|
|
|
+ this.hasQueryErrors = new ReactiveVar(false);
|
|
this.query = new ReactiveVar('');
|
|
this.query = new ReactiveVar('');
|
|
|
|
+ this.resultsHeading = new ReactiveVar('');
|
|
|
|
+ this.searchLink = new ReactiveVar(null);
|
|
this.queryParams = null;
|
|
this.queryParams = null;
|
|
- this.resultsCount = new ReactiveVar(0);
|
|
|
|
- this.totalHits = new ReactiveVar(0);
|
|
|
|
- this.queryErrors = new ReactiveVar(null);
|
|
|
|
|
|
+ this.parsingErrors = [];
|
|
|
|
+ this.resultsCount = 0;
|
|
|
|
+ this.totalHits = 0;
|
|
|
|
+ this.queryErrors = null;
|
|
Meteor.subscribe('setting');
|
|
Meteor.subscribe('setting');
|
|
|
|
+ if (Session.get('globalQuery')) {
|
|
|
|
+ this.searchAllBoards(Session.get('globalQuery'));
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ resetSearch() {
|
|
|
|
+ this.searching.set(false);
|
|
|
|
+ this.hasResults.set(false);
|
|
|
|
+ this.hasQueryErrors.set(false);
|
|
|
|
+ this.resultsHeading.set('');
|
|
|
|
+ this.parsingErrors = [];
|
|
|
|
+ this.resultsCount = 0;
|
|
|
|
+ this.totalHits = 0;
|
|
|
|
+ this.queryErrors = null;
|
|
},
|
|
},
|
|
|
|
|
|
results() {
|
|
results() {
|
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
|
+ // console.log('getting results');
|
|
if (this.queryParams) {
|
|
if (this.queryParams) {
|
|
const results = Cards.globalSearch(this.queryParams);
|
|
const results = Cards.globalSearch(this.queryParams);
|
|
|
|
+ this.queryErrors = results.errors;
|
|
// eslint-disable-next-line no-console
|
|
// eslint-disable-next-line no-console
|
|
- // console.log('user:', Meteor.user());
|
|
|
|
- // eslint-disable-next-line no-console
|
|
|
|
- // console.log('user:', Meteor.user().sessionData);
|
|
|
|
- // console.log('errors:', results.errors);
|
|
|
|
- this.totalHits.set(Meteor.user().sessionData.totalHits);
|
|
|
|
- this.resultsCount.set(results.cards.count());
|
|
|
|
- this.queryErrors.set(results.errors);
|
|
|
|
- return results.cards;
|
|
|
|
|
|
+ // console.log('errors:', this.queryErrors);
|
|
|
|
+ if (this.errorMessages().length) {
|
|
|
|
+ this.hasQueryErrors.set(true);
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (results.cards) {
|
|
|
|
+ const sessionData = SessionData.findOne({ userId: Meteor.userId() });
|
|
|
|
+ this.totalHits = sessionData.totalHits;
|
|
|
|
+ this.resultsCount = results.cards.count();
|
|
|
|
+ this.resultsHeading.set(this.getResultsHeading());
|
|
|
|
+ return results.cards;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- this.resultsCount.set(0);
|
|
|
|
|
|
+ this.resultsCount = 0;
|
|
return [];
|
|
return [];
|
|
},
|
|
},
|
|
|
|
|
|
errorMessages() {
|
|
errorMessages() {
|
|
- const errors = this.queryErrors.get();
|
|
|
|
const messages = [];
|
|
const messages = [];
|
|
|
|
|
|
- errors.notFound.boards.forEach(board => {
|
|
|
|
- messages.push({ tag: 'board-title-not-found', value: board });
|
|
|
|
- });
|
|
|
|
- errors.notFound.swimlanes.forEach(swim => {
|
|
|
|
- messages.push({ tag: 'swimlane-title-not-found', value: swim });
|
|
|
|
- });
|
|
|
|
- errors.notFound.lists.forEach(list => {
|
|
|
|
- messages.push({ tag: 'list-title-not-found', value: list });
|
|
|
|
- });
|
|
|
|
- errors.notFound.users.forEach(user => {
|
|
|
|
- messages.push({ tag: 'user-username-not-found', value: user });
|
|
|
|
- });
|
|
|
|
|
|
+ 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 => {
|
|
|
|
+ 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) {
|
|
|
|
+ this.parsingErrors.forEach(err => {
|
|
|
|
+ messages.push(err);
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
|
|
return messages;
|
|
return messages;
|
|
},
|
|
},
|
|
|
|
|
|
- events() {
|
|
|
|
- return [
|
|
|
|
- {
|
|
|
|
- 'submit .js-search-query-form'(evt) {
|
|
|
|
- evt.preventDefault();
|
|
|
|
- this.query.set(evt.target.searchQuery.value);
|
|
|
|
- this.queryErrors.set(null);
|
|
|
|
|
|
+ searchAllBoards(query) {
|
|
|
|
+ this.query.set(query);
|
|
|
|
|
|
- if (!this.query.get()) {
|
|
|
|
- this.searching.set(false);
|
|
|
|
- this.hasResults.set(false);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
|
|
+ this.resetSearch();
|
|
|
|
|
|
- this.searching.set(true);
|
|
|
|
- this.hasResults.set(false);
|
|
|
|
|
|
+ if (!query) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- let query = this.query.get();
|
|
|
|
- // eslint-disable-next-line no-console
|
|
|
|
- // console.log('query:', query);
|
|
|
|
-
|
|
|
|
- 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 operatorMap = {};
|
|
|
|
- operatorMap[TAPi18n.__('operator-board')] = 'boards';
|
|
|
|
- operatorMap[TAPi18n.__('operator-board-abbrev')] = 'boards';
|
|
|
|
- operatorMap[TAPi18n.__('operator-swimlane')] = 'swimlanes';
|
|
|
|
- operatorMap[TAPi18n.__('operator-swimlane-abbrev')] = 'swimlanes';
|
|
|
|
- operatorMap[TAPi18n.__('operator-list')] = 'lists';
|
|
|
|
- operatorMap[TAPi18n.__('operator-list-abbrev')] = 'lists';
|
|
|
|
- operatorMap[TAPi18n.__('operator-label')] = 'labels';
|
|
|
|
- operatorMap[TAPi18n.__('operator-label-abbrev')] = 'labels';
|
|
|
|
- operatorMap[TAPi18n.__('operator-user')] = 'users';
|
|
|
|
- operatorMap[TAPi18n.__('operator-user-abbrev')] = 'users';
|
|
|
|
- operatorMap[TAPi18n.__('operator-is')] = 'is';
|
|
|
|
|
|
+ this.searching.set(true);
|
|
|
|
+
|
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
|
+ // console.log('query:', query);
|
|
|
|
+
|
|
|
|
+ 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 operatorMap = {};
|
|
|
|
+ operatorMap[TAPi18n.__('operator-board')] = 'boards';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-board-abbrev')] = 'boards';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-swimlane')] = 'swimlanes';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-swimlane-abbrev')] = 'swimlanes';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-list')] = 'lists';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-list-abbrev')] = 'lists';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-label')] = 'labels';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-label-abbrev')] = 'labels';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-user')] = 'users';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-user-abbrev')] = 'users';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-member')] = 'members';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-member-abbrev')] = 'members';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-assignee')] = 'assignees';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-assignee-abbrev')] = 'assignees';
|
|
|
|
+ operatorMap[TAPi18n.__('operator-is')] = 'is';
|
|
|
|
+
|
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
|
+ // console.log('operatorMap:', operatorMap);
|
|
|
|
+ const params = {
|
|
|
|
+ boards: [],
|
|
|
|
+ swimlanes: [],
|
|
|
|
+ lists: [],
|
|
|
|
+ users: [],
|
|
|
|
+ members: [],
|
|
|
|
+ assignees: [],
|
|
|
|
+ labels: [],
|
|
|
|
+ is: [],
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ let text = '';
|
|
|
|
+ while (query) {
|
|
|
|
+ m = query.match(reOperator1);
|
|
|
|
+ if (!m) {
|
|
|
|
+ m = query.match(reOperator2);
|
|
|
|
+ if (m) {
|
|
|
|
+ query = query.replace(reOperator2, '');
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ query = query.replace(reOperator1, '');
|
|
|
|
+ }
|
|
|
|
+ if (m) {
|
|
|
|
+ let op;
|
|
|
|
+ if (m.groups.operator) {
|
|
|
|
+ op = m.groups.operator.toLowerCase();
|
|
|
|
+ } else {
|
|
|
|
+ op = m.groups.abbrev;
|
|
|
|
+ }
|
|
|
|
+ if (op in operatorMap) {
|
|
|
|
+ params[operatorMap[op]].push(m.groups.value);
|
|
|
|
+ } else {
|
|
|
|
+ this.parsingErrors.push({
|
|
|
|
+ tag: 'operator-unknown-error',
|
|
|
|
+ value: op,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ m = query.match(reQuotedText);
|
|
|
|
+ if (!m) {
|
|
|
|
+ m = query.match(reText);
|
|
|
|
+ if (m) {
|
|
|
|
+ query = query.replace(reText, '');
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ query = query.replace(reQuotedText, '');
|
|
|
|
+ }
|
|
|
|
+ if (m) {
|
|
|
|
+ text += (text ? ' ' : '') + m.groups.text;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
|
+ // console.log('text:', text);
|
|
|
|
+ params.text = text;
|
|
|
|
+
|
|
|
|
+ // eslint-disable-next-line no-console
|
|
|
|
+ // console.log('params:', params);
|
|
|
|
+
|
|
|
|
+ this.queryParams = params;
|
|
|
|
+
|
|
|
|
+ this.autorun(() => {
|
|
|
|
+ const handle = subManager.subscribe('globalSearch', params);
|
|
|
|
+ Tracker.nonreactive(() => {
|
|
|
|
+ Tracker.autorun(() => {
|
|
// eslint-disable-next-line no-console
|
|
// eslint-disable-next-line no-console
|
|
- // console.log('operatorMap:', operatorMap);
|
|
|
|
- const params = {
|
|
|
|
- boards: [],
|
|
|
|
- swimlanes: [],
|
|
|
|
- lists: [],
|
|
|
|
- users: [],
|
|
|
|
- labels: [],
|
|
|
|
- is: [],
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- let text = '';
|
|
|
|
- while (query) {
|
|
|
|
- m = query.match(reOperator1);
|
|
|
|
- if (!m) {
|
|
|
|
- m = query.match(reOperator2);
|
|
|
|
- if (m) {
|
|
|
|
- query = query.replace(reOperator2, '');
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- query = query.replace(reOperator1, '');
|
|
|
|
- }
|
|
|
|
- if (m) {
|
|
|
|
- let op;
|
|
|
|
- if (m.groups.operator) {
|
|
|
|
- op = m.groups.operator.toLowerCase();
|
|
|
|
- } else {
|
|
|
|
- op = m.groups.abbrev;
|
|
|
|
- }
|
|
|
|
- if (op in operatorMap) {
|
|
|
|
- params[operatorMap[op]].push(m.groups.value);
|
|
|
|
- }
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- m = query.match(reQuotedText);
|
|
|
|
- if (!m) {
|
|
|
|
- m = query.match(reText);
|
|
|
|
- if (m) {
|
|
|
|
- query = query.replace(reText, '');
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- query = query.replace(reQuotedText, '');
|
|
|
|
- }
|
|
|
|
- if (m) {
|
|
|
|
- text += (text ? ' ' : '') + m.groups.text;
|
|
|
|
- }
|
|
|
|
|
|
+ // console.log('ready:', handle.ready());
|
|
|
|
+ if (handle.ready()) {
|
|
|
|
+ this.searching.set(false);
|
|
|
|
+ this.hasResults.set(true);
|
|
}
|
|
}
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
|
|
- // eslint-disable-next-line no-console
|
|
|
|
- // console.log('text:', text);
|
|
|
|
- params.text = text;
|
|
|
|
|
|
+ getResultsHeading() {
|
|
|
|
+ if (this.resultsCount === 0) {
|
|
|
|
+ return TAPi18n.__('no-cards-found');
|
|
|
|
+ } else if (this.resultsCount === 1) {
|
|
|
|
+ return TAPi18n.__('one-card-found');
|
|
|
|
+ } else if (this.resultsCount === this.totalHits) {
|
|
|
|
+ return TAPi18n.__('n-cards-found', this.resultsCount);
|
|
|
|
+ }
|
|
|
|
|
|
- // eslint-disable-next-line no-console
|
|
|
|
- // console.log('params:', params);
|
|
|
|
-
|
|
|
|
- this.queryParams = params;
|
|
|
|
-
|
|
|
|
- this.autorun(() => {
|
|
|
|
- const handle = subManager.subscribe('globalSearch', params);
|
|
|
|
- Tracker.nonreactive(() => {
|
|
|
|
- Tracker.autorun(() => {
|
|
|
|
- // eslint-disable-next-line no-console
|
|
|
|
- // console.log('ready:', handle.ready());
|
|
|
|
- if (handle.ready()) {
|
|
|
|
- this.searching.set(false);
|
|
|
|
- this.hasResults.set(true);
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
|
|
+ return TAPi18n.__('n-n-of-n-cards-found', {
|
|
|
|
+ start: 1,
|
|
|
|
+ end: this.resultsCount,
|
|
|
|
+ total: this.totalHits,
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ getSearchHref() {
|
|
|
|
+ const baseUrl = window.location.href.replace(/([?#].*$|\s*$)/, '');
|
|
|
|
+ return `${baseUrl}?q=${encodeURIComponent(this.query.get())}`;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ searchInstructions() {
|
|
|
|
+ tags = {
|
|
|
|
+ operator_board: TAPi18n.__('operator-board'),
|
|
|
|
+ operator_list: TAPi18n.__('operator-list'),
|
|
|
|
+ operator_swimlane: TAPi18n.__('operator-swimlane'),
|
|
|
|
+ operator_label: TAPi18n.__('operator-label'),
|
|
|
|
+ operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
|
|
|
|
+ operator_user: TAPi18n.__('operator-user'),
|
|
|
|
+ operator_user_abbrev: TAPi18n.__('operator-user-abbrev'),
|
|
|
|
+ operator_member: TAPi18n.__('operator-member'),
|
|
|
|
+ operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
|
|
|
|
+ operator_assignee: TAPi18n.__('operator-assignee'),
|
|
|
|
+ operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
|
|
|
|
+ text += `\n${TAPi18n.__('globalSearch-instructions-description', tags)}`;
|
|
|
|
+ text += `\n${TAPi18n.__('globalSearch-instructions-operators', tags)}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-board',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-list',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-swimlane',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-label',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-hash',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-user',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__('globalSearch-instructions-operator-at', tags)}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-member',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__(
|
|
|
|
+ 'globalSearch-instructions-operator-assignee',
|
|
|
|
+ tags,
|
|
|
|
+ )}`;
|
|
|
|
+
|
|
|
|
+ text += `\n## ${TAPi18n.__('heading-notes')}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-1', tags)}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-2', tags)}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3', tags)}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-4', tags)}`;
|
|
|
|
+ text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-5', tags)}`;
|
|
|
|
+
|
|
|
|
+ return text;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ events() {
|
|
|
|
+ return [
|
|
|
|
+ {
|
|
|
|
+ 'submit .js-search-query-form'(evt) {
|
|
|
|
+ evt.preventDefault();
|
|
|
|
+ this.searchAllBoards(evt.target.searchQuery.value);
|
|
},
|
|
},
|
|
},
|
|
},
|
|
];
|
|
];
|