globalSearch.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. const subManager = new SubsManager();
  2. BlazeComponent.extendComponent({
  3. events() {
  4. return [
  5. {
  6. 'click .js-due-cards-view-change': Popup.open('globalSearchViewChange'),
  7. },
  8. ];
  9. },
  10. }).register('globalSearchHeaderBar');
  11. Template.globalSearch.helpers({
  12. userId() {
  13. return Meteor.userId();
  14. },
  15. });
  16. BlazeComponent.extendComponent({
  17. events() {
  18. return [
  19. {
  20. 'click .js-due-cards-view-me'() {
  21. Utils.setDueCardsView('me');
  22. Popup.close();
  23. },
  24. 'click .js-due-cards-view-all'() {
  25. Utils.setDueCardsView('all');
  26. Popup.close();
  27. },
  28. },
  29. ];
  30. },
  31. }).register('globalSearchViewChangePopup');
  32. BlazeComponent.extendComponent({
  33. onCreated() {
  34. this.isPageReady = new ReactiveVar(true);
  35. this.searching = new ReactiveVar(false);
  36. this.hasResults = new ReactiveVar(false);
  37. this.query = new ReactiveVar('');
  38. this.queryParams = null;
  39. this.resultsCount = new ReactiveVar(0);
  40. this.totalHits = new ReactiveVar(0);
  41. this.queryErrors = new ReactiveVar(null);
  42. Meteor.subscribe('setting');
  43. },
  44. results() {
  45. if (this.queryParams) {
  46. const results = Cards.globalSearch(this.queryParams);
  47. const sessionData = SessionData.findOne({ userId: Meteor.userId() });
  48. // eslint-disable-next-line no-console
  49. // console.log('sessionData:', sessionData);
  50. // console.log('errors:', results.errors);
  51. this.totalHits.set(sessionData.totalHits);
  52. this.resultsCount.set(results.cards.count());
  53. this.queryErrors.set(results.errors);
  54. return results.cards;
  55. }
  56. this.resultsCount.set(0);
  57. return [];
  58. },
  59. errorMessages() {
  60. const errors = this.queryErrors.get();
  61. const messages = [];
  62. errors.notFound.boards.forEach(board => {
  63. messages.push({ tag: 'board-title-not-found', value: board });
  64. });
  65. errors.notFound.swimlanes.forEach(swim => {
  66. messages.push({ tag: 'swimlane-title-not-found', value: swim });
  67. });
  68. errors.notFound.lists.forEach(list => {
  69. messages.push({ tag: 'list-title-not-found', value: list });
  70. });
  71. errors.notFound.labels.forEach(label => {
  72. messages.push({ tag: 'label-not-found', value: label });
  73. });
  74. errors.notFound.users.forEach(user => {
  75. messages.push({ tag: 'user-username-not-found', value: user });
  76. });
  77. return messages;
  78. },
  79. resultsHeading() {
  80. if (this.resultsCount.get() === 0) {
  81. return TAPi18n.__('no-cards-found');
  82. } else if (this.resultsCount.get() === 1) {
  83. return TAPi18n.__('one-card-found');
  84. } else if (this.resultsCount.get() === this.totalHits.get()) {
  85. return TAPi18n.__('n-cards-found', this.resultsCount.get());
  86. }
  87. return TAPi18n.__('n-n-of-n-cards-found', {
  88. start: 1,
  89. end: this.resultsCount.get(),
  90. total: this.totalHits.get(),
  91. });
  92. },
  93. searchInstructions() {
  94. tags = {
  95. operator_board: TAPi18n.__('operator-board'),
  96. operator_list: TAPi18n.__('operator-list'),
  97. operator_swimlane: TAPi18n.__('operator-swimlane'),
  98. operator_label: TAPi18n.__('operator-label'),
  99. operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
  100. operator_user: TAPi18n.__('operator-user'),
  101. operator_user_abbrev: TAPi18n.__('operator-user-abbrev'),
  102. };
  103. text = `# ${TAPi18n.__('globalSearch-instructions-heading')}`;
  104. text += `\n${TAPi18n.__('globalSearch-instructions-description', tags)}`;
  105. text += `\n${TAPi18n.__('globalSearch-instructions-operators', tags)}`;
  106. text += `\n* ${TAPi18n.__(
  107. 'globalSearch-instructions-operator-board',
  108. tags,
  109. )}`;
  110. text += `\n* ${TAPi18n.__(
  111. 'globalSearch-instructions-operator-list',
  112. tags,
  113. )}`;
  114. text += `\n* ${TAPi18n.__(
  115. 'globalSearch-instructions-operator-swimlane',
  116. tags,
  117. )}`;
  118. text += `\n* ${TAPi18n.__(
  119. 'globalSearch-instructions-operator-label',
  120. tags,
  121. )}`;
  122. text += `\n* ${TAPi18n.__(
  123. 'globalSearch-instructions-operator-hash',
  124. tags,
  125. )}`;
  126. text += `\n* ${TAPi18n.__(
  127. 'globalSearch-instructions-operator-user',
  128. tags,
  129. )}`;
  130. text += `\n* ${TAPi18n.__('globalSearch-instructions-operator-at', tags)}`;
  131. text += `\n## ${TAPi18n.__('heading-notes')}`;
  132. text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-1', tags)}`;
  133. text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-2', tags)}`;
  134. text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-3', tags)}`;
  135. text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-4', tags)}`;
  136. text += `\n* ${TAPi18n.__('globalSearch-instructions-notes-5', tags)}`;
  137. return text;
  138. },
  139. events() {
  140. return [
  141. {
  142. 'submit .js-search-query-form'(evt) {
  143. evt.preventDefault();
  144. this.query.set(evt.target.searchQuery.value);
  145. this.queryErrors.set(null);
  146. if (!this.query.get()) {
  147. this.searching.set(false);
  148. this.hasResults.set(false);
  149. return;
  150. }
  151. this.searching.set(true);
  152. this.hasResults.set(false);
  153. let query = this.query.get();
  154. // eslint-disable-next-line no-console
  155. // console.log('query:', query);
  156. const reOperator1 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<value>\w+)(\s+|$)/;
  157. const reOperator2 = /^((?<operator>\w+):|(?<abbrev>[#@]))(?<quote>["']*)(?<value>.*?)\k<quote>(\s+|$)/;
  158. const reText = /^(?<text>\S+)(\s+|$)/;
  159. const reQuotedText = /^(?<quote>["'])(?<text>\w+)\k<quote>(\s+|$)/;
  160. const operatorMap = {};
  161. operatorMap[TAPi18n.__('operator-board')] = 'boards';
  162. operatorMap[TAPi18n.__('operator-board-abbrev')] = 'boards';
  163. operatorMap[TAPi18n.__('operator-swimlane')] = 'swimlanes';
  164. operatorMap[TAPi18n.__('operator-swimlane-abbrev')] = 'swimlanes';
  165. operatorMap[TAPi18n.__('operator-list')] = 'lists';
  166. operatorMap[TAPi18n.__('operator-list-abbrev')] = 'lists';
  167. operatorMap[TAPi18n.__('operator-label')] = 'labels';
  168. operatorMap[TAPi18n.__('operator-label-abbrev')] = 'labels';
  169. operatorMap[TAPi18n.__('operator-user')] = 'users';
  170. operatorMap[TAPi18n.__('operator-user-abbrev')] = 'users';
  171. operatorMap[TAPi18n.__('operator-is')] = 'is';
  172. // eslint-disable-next-line no-console
  173. // console.log('operatorMap:', operatorMap);
  174. const params = {
  175. boards: [],
  176. swimlanes: [],
  177. lists: [],
  178. users: [],
  179. labels: [],
  180. is: [],
  181. };
  182. let text = '';
  183. while (query) {
  184. m = query.match(reOperator1);
  185. if (!m) {
  186. m = query.match(reOperator2);
  187. if (m) {
  188. query = query.replace(reOperator2, '');
  189. }
  190. } else {
  191. query = query.replace(reOperator1, '');
  192. }
  193. if (m) {
  194. let op;
  195. if (m.groups.operator) {
  196. op = m.groups.operator.toLowerCase();
  197. } else {
  198. op = m.groups.abbrev;
  199. }
  200. if (op in operatorMap) {
  201. params[operatorMap[op]].push(m.groups.value);
  202. }
  203. continue;
  204. }
  205. m = query.match(reQuotedText);
  206. if (!m) {
  207. m = query.match(reText);
  208. if (m) {
  209. query = query.replace(reText, '');
  210. }
  211. } else {
  212. query = query.replace(reQuotedText, '');
  213. }
  214. if (m) {
  215. text += (text ? ' ' : '') + m.groups.text;
  216. }
  217. }
  218. // eslint-disable-next-line no-console
  219. // console.log('text:', text);
  220. params.text = text;
  221. // eslint-disable-next-line no-console
  222. // console.log('params:', params);
  223. this.queryParams = params;
  224. this.autorun(() => {
  225. const handle = subManager.subscribe('globalSearch', params);
  226. Tracker.nonreactive(() => {
  227. Tracker.autorun(() => {
  228. // eslint-disable-next-line no-console
  229. // console.log('ready:', handle.ready());
  230. if (handle.ready()) {
  231. this.searching.set(false);
  232. this.hasResults.set(true);
  233. }
  234. });
  235. });
  236. });
  237. },
  238. },
  239. ];
  240. },
  241. }).register('globalSearch');