sidebar.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  1. import { Cookies } from 'meteor/ostrio:cookies';
  2. const cookies = new Cookies();
  3. Sidebar = null;
  4. const defaultView = 'home';
  5. const MCB = '.materialCheckBox';
  6. const CKCLS = 'is-checked';
  7. const viewTitles = {
  8. filter: 'filter-cards',
  9. search: 'search-cards',
  10. multiselection: 'multi-selection',
  11. customFields: 'custom-fields',
  12. archives: 'archives',
  13. };
  14. BlazeComponent.extendComponent({
  15. mixins() {
  16. return [Mixins.InfiniteScrolling, Mixins.PerfectScrollbar];
  17. },
  18. onCreated() {
  19. this._isOpen = new ReactiveVar(false);
  20. this._view = new ReactiveVar(defaultView);
  21. Sidebar = this;
  22. },
  23. onDestroyed() {
  24. Sidebar = null;
  25. },
  26. isOpen() {
  27. return this._isOpen.get();
  28. },
  29. open() {
  30. if (!this._isOpen.get()) {
  31. this._isOpen.set(true);
  32. EscapeActions.executeUpTo('detailsPane');
  33. }
  34. },
  35. hide() {
  36. if (this._isOpen.get()) {
  37. this._isOpen.set(false);
  38. }
  39. },
  40. toggle() {
  41. this._isOpen.set(!this._isOpen.get());
  42. },
  43. calculateNextPeak() {
  44. const sidebarElement = this.find('.js-board-sidebar-content');
  45. if (sidebarElement) {
  46. const altitude = sidebarElement.scrollHeight;
  47. this.callFirstWith(this, 'setNextPeak', altitude);
  48. }
  49. },
  50. reachNextPeak() {
  51. const activitiesComponent = this.childComponents('activities')[0];
  52. activitiesComponent.loadNextPage();
  53. },
  54. isTongueHidden() {
  55. return this.isOpen() && this.getView() !== defaultView;
  56. },
  57. scrollTop() {
  58. this.$('.js-board-sidebar-content').scrollTop(0);
  59. },
  60. getView() {
  61. return this._view.get();
  62. },
  63. setView(view) {
  64. view = _.isString(view) ? view : defaultView;
  65. if (this._view.get() !== view) {
  66. this._view.set(view);
  67. this.scrollTop();
  68. EscapeActions.executeUpTo('detailsPane');
  69. }
  70. this.open();
  71. },
  72. isDefaultView() {
  73. return this.getView() === defaultView;
  74. },
  75. getViewTemplate() {
  76. return `${this.getView()}Sidebar`;
  77. },
  78. getViewTitle() {
  79. return TAPi18n.__(viewTitles[this.getView()]);
  80. },
  81. showTongueTitle() {
  82. if (this.isOpen()) return `${TAPi18n.__('sidebar-close')}`;
  83. else return `${TAPi18n.__('sidebar-open')}`;
  84. },
  85. events() {
  86. return [
  87. {
  88. 'click .js-hide-sidebar': this.hide,
  89. 'click .js-toggle-sidebar': this.toggle,
  90. 'click .js-back-home': this.setView,
  91. 'click .js-toggle-minicard-label-text'() {
  92. currentUser = Meteor.user();
  93. if (currentUser) {
  94. Meteor.call('toggleMinicardLabelText');
  95. } else if (cookies.has('hiddenMinicardLabelText')) {
  96. cookies.remove('hiddenMinicardLabelText');
  97. } else {
  98. cookies.set('hiddenMinicardLabelText', 'true');
  99. }
  100. },
  101. 'click .js-shortcuts'() {
  102. FlowRouter.go('shortcuts');
  103. },
  104. },
  105. ];
  106. },
  107. }).register('sidebar');
  108. Blaze.registerHelper('Sidebar', () => Sidebar);
  109. Template.homeSidebar.helpers({
  110. hiddenMinicardLabelText() {
  111. currentUser = Meteor.user();
  112. if (currentUser) {
  113. return (currentUser.profile || {}).hiddenMinicardLabelText;
  114. } else if (cookies.has('hiddenMinicardLabelText')) {
  115. return true;
  116. } else {
  117. return false;
  118. }
  119. },
  120. });
  121. EscapeActions.register(
  122. 'sidebarView',
  123. () => {
  124. Sidebar.setView(defaultView);
  125. },
  126. () => {
  127. return Sidebar && Sidebar.getView() !== defaultView;
  128. },
  129. );
  130. Template.memberPopup.helpers({
  131. user() {
  132. return Users.findOne(this.userId);
  133. },
  134. memberType() {
  135. const type = Users.findOne(this.userId).isBoardAdmin() ? 'admin' : 'normal';
  136. if (type === 'normal') {
  137. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  138. const commentOnly = currentBoard.hasCommentOnly(this.userId);
  139. const noComments = currentBoard.hasNoComments(this.userId);
  140. const worker = currentBoard.hasWorker(this.userId);
  141. if (commentOnly) {
  142. return TAPi18n.__('comment-only').toLowerCase();
  143. } else if (noComments) {
  144. return TAPi18n.__('no-comments').toLowerCase();
  145. } else if (worker) {
  146. return TAPi18n.__('worker').toLowerCase();
  147. } else {
  148. return TAPi18n.__(type).toLowerCase();
  149. }
  150. } else {
  151. return TAPi18n.__(type).toLowerCase();
  152. }
  153. },
  154. isInvited() {
  155. return Users.findOne(this.userId).isInvitedTo(Session.get('currentBoard'));
  156. },
  157. });
  158. Template.boardMenuPopup.events({
  159. 'click .js-rename-board': Popup.open('boardChangeTitle'),
  160. 'click .js-custom-fields'() {
  161. Sidebar.setView('customFields');
  162. Popup.close();
  163. },
  164. 'click .js-open-archives'() {
  165. Sidebar.setView('archives');
  166. Popup.close();
  167. },
  168. 'click .js-change-board-color': Popup.open('boardChangeColor'),
  169. 'click .js-change-language': Popup.open('changeLanguage'),
  170. 'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() {
  171. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  172. currentBoard.archive();
  173. // XXX We should have some kind of notification on top of the page to
  174. // confirm that the board was successfully archived.
  175. FlowRouter.go('home');
  176. }),
  177. 'click .js-delete-board': Popup.afterConfirm('deleteBoard', function() {
  178. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  179. Popup.close();
  180. Boards.remove(currentBoard._id);
  181. FlowRouter.go('home');
  182. }),
  183. 'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
  184. 'click .js-import-board': Popup.open('chooseBoardSource'),
  185. 'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
  186. 'click .js-card-settings': Popup.open('boardCardSettings'),
  187. });
  188. Template.boardMenuPopup.helpers({
  189. exportUrl() {
  190. const params = {
  191. boardId: Session.get('currentBoard'),
  192. };
  193. const queryParams = {
  194. authToken: Accounts._storedLoginToken(),
  195. };
  196. return FlowRouter.path('/api/boards/:boardId/export', params, queryParams);
  197. },
  198. exportFilename() {
  199. const boardId = Session.get('currentBoard');
  200. return `wekan-export-board-${boardId}.json`;
  201. },
  202. });
  203. Template.memberPopup.events({
  204. 'click .js-filter-member'() {
  205. Filter.members.toggle(this.userId);
  206. Popup.close();
  207. },
  208. 'click .js-change-role': Popup.open('changePermissions'),
  209. 'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
  210. const boardId = Session.get('currentBoard');
  211. const memberId = this.userId;
  212. Cards.find({ boardId, members: memberId }).forEach(card => {
  213. card.unassignMember(memberId);
  214. });
  215. Boards.findOne(boardId).removeMember(memberId);
  216. Popup.close();
  217. }),
  218. 'click .js-leave-member': Popup.afterConfirm('leaveBoard', () => {
  219. const boardId = Session.get('currentBoard');
  220. Meteor.call('quitBoard', boardId, () => {
  221. Popup.close();
  222. FlowRouter.go('home');
  223. });
  224. }),
  225. });
  226. Template.removeMemberPopup.helpers({
  227. user() {
  228. return Users.findOne(this.userId);
  229. },
  230. board() {
  231. return Boards.findOne(Session.get('currentBoard'));
  232. },
  233. });
  234. Template.leaveBoardPopup.helpers({
  235. board() {
  236. return Boards.findOne(Session.get('currentBoard'));
  237. },
  238. });
  239. Template.membersWidget.helpers({
  240. isInvited() {
  241. const user = Meteor.user();
  242. return user && user.isInvitedTo(Session.get('currentBoard'));
  243. },
  244. isWorker() {
  245. const user = Meteor.user();
  246. if (user) {
  247. return Meteor.call(Boards.hasWorker(user.memberId));
  248. } else {
  249. return false;
  250. }
  251. },
  252. });
  253. Template.membersWidget.events({
  254. 'click .js-member': Popup.open('member'),
  255. 'click .js-open-board-menu': Popup.open('boardMenu'),
  256. 'click .js-manage-board-members': Popup.open('addMember'),
  257. 'click .js-import': Popup.open('boardImportBoard'),
  258. submit: this.onSubmit,
  259. 'click .js-import-board': Popup.open('chooseBoardSource'),
  260. 'click .js-open-archived-board'() {
  261. Modal.open('archivedBoards');
  262. },
  263. 'click .sandstorm-powerbox-request-identity'() {
  264. window.sandstormRequestIdentity();
  265. },
  266. 'click .js-member-invite-accept'() {
  267. const boardId = Session.get('currentBoard');
  268. Meteor.user().removeInvite(boardId);
  269. },
  270. 'click .js-member-invite-decline'() {
  271. const boardId = Session.get('currentBoard');
  272. Meteor.call('quitBoard', boardId, (err, ret) => {
  273. if (!err && ret) {
  274. Meteor.user().removeInvite(boardId);
  275. FlowRouter.go('home');
  276. }
  277. });
  278. },
  279. });
  280. BlazeComponent.extendComponent({
  281. boardId() {
  282. return Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
  283. },
  284. integrations() {
  285. const boardId = this.boardId();
  286. return Integrations.find({ boardId: `${boardId}` }).fetch();
  287. },
  288. types() {
  289. return Integrations.Const.WEBHOOK_TYPES;
  290. },
  291. integration(cond) {
  292. const boardId = this.boardId();
  293. const condition = { boardId, ...cond };
  294. for (const k in condition) {
  295. if (!condition[k]) delete condition[k];
  296. }
  297. return Integrations.findOne(condition);
  298. },
  299. onCreated() {
  300. this.disabled = new ReactiveVar(false);
  301. },
  302. events() {
  303. return [
  304. {
  305. 'click a.flex'(evt) {
  306. this.disabled.set(!this.disabled.get());
  307. $(evt.target).toggleClass(CKCLS, this.disabled.get());
  308. },
  309. submit(evt) {
  310. evt.preventDefault();
  311. const url = evt.target.url.value;
  312. const boardId = this.boardId();
  313. let id = null;
  314. let integration = null;
  315. const title = evt.target.title.value;
  316. const token = evt.target.token.value;
  317. const type = evt.target.type.value;
  318. const enabled = !this.disabled.get();
  319. let remove = false;
  320. const values = {
  321. url,
  322. type,
  323. token,
  324. title,
  325. enabled,
  326. };
  327. if (evt.target.id) {
  328. id = evt.target.id.value;
  329. integration = this.integration({ _id: id });
  330. remove = !url;
  331. } else if (url) {
  332. integration = this.integration({ url, token });
  333. }
  334. if (remove) {
  335. Integrations.remove(integration._id);
  336. } else if (integration && integration._id) {
  337. Integrations.update(integration._id, {
  338. $set: values,
  339. });
  340. } else if (url) {
  341. Integrations.insert({
  342. ...values,
  343. userId: Meteor.userId(),
  344. enabled: true,
  345. boardId,
  346. activities: ['all'],
  347. });
  348. }
  349. Popup.close();
  350. },
  351. },
  352. ];
  353. },
  354. }).register('outgoingWebhooksPopup');
  355. BlazeComponent.extendComponent({
  356. template() {
  357. return 'chooseBoardSource';
  358. },
  359. }).register('chooseBoardSourcePopup');
  360. Template.labelsWidget.events({
  361. 'click .js-label': Popup.open('editLabel'),
  362. 'click .js-add-label': Popup.open('createLabel'),
  363. });
  364. // Board members can assign people or labels by drag-dropping elements from the
  365. // sidebar to the cards on the board. In order to re-initialize the jquery-ui
  366. // plugin any time a draggable member or label is modified or removed we use a
  367. // autorun function and register a dependency on the both members and labels
  368. // fields of the current board document.
  369. function draggableMembersLabelsWidgets() {
  370. this.autorun(() => {
  371. const currentBoardId = Tracker.nonreactive(() => {
  372. return Session.get('currentBoard');
  373. });
  374. Boards.findOne(currentBoardId, {
  375. fields: {
  376. members: 1,
  377. labels: 1,
  378. },
  379. });
  380. Tracker.afterFlush(() => {
  381. const $draggables = this.$('.js-member,.js-label');
  382. $draggables.draggable({
  383. appendTo: 'body',
  384. helper: 'clone',
  385. revert: 'invalid',
  386. revertDuration: 150,
  387. snap: false,
  388. snapMode: 'both',
  389. start() {
  390. EscapeActions.executeUpTo('popup-back');
  391. },
  392. });
  393. function userIsMember() {
  394. return Meteor.user() && Meteor.user().isBoardMember();
  395. }
  396. this.autorun(() => {
  397. $draggables.draggable('option', 'disabled', !userIsMember());
  398. });
  399. });
  400. });
  401. }
  402. Template.membersWidget.onRendered(draggableMembersLabelsWidgets);
  403. Template.labelsWidget.onRendered(draggableMembersLabelsWidgets);
  404. BlazeComponent.extendComponent({
  405. backgroundColors() {
  406. return Boards.simpleSchema()._schema.color.allowedValues;
  407. },
  408. isSelected() {
  409. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  410. return currentBoard.color === this.currentData().toString();
  411. },
  412. events() {
  413. return [
  414. {
  415. 'click .js-select-background'(evt) {
  416. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  417. const newColor = this.currentData().toString();
  418. currentBoard.setColor(newColor);
  419. evt.preventDefault();
  420. },
  421. },
  422. ];
  423. },
  424. }).register('boardChangeColorPopup');
  425. BlazeComponent.extendComponent({
  426. onCreated() {
  427. this.currentBoard = Boards.findOne(Session.get('currentBoard'));
  428. },
  429. allowsSubtasks() {
  430. return this.currentBoard.allowsSubtasks;
  431. },
  432. allowsReceivedDate() {
  433. return this.currentBoard.allowsReceivedDate;
  434. },
  435. isBoardSelected() {
  436. return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
  437. },
  438. isNullBoardSelected() {
  439. return (
  440. this.currentBoard.subtasksDefaultBoardId === null ||
  441. this.currentBoard.subtasksDefaultBoardId === undefined
  442. );
  443. },
  444. boards() {
  445. return Boards.find(
  446. {
  447. archived: false,
  448. 'members.userId': Meteor.userId(),
  449. },
  450. {
  451. sort: ['title'],
  452. },
  453. );
  454. },
  455. lists() {
  456. return Lists.find(
  457. {
  458. boardId: this.currentBoard._id,
  459. archived: false,
  460. },
  461. {
  462. sort: ['title'],
  463. },
  464. );
  465. },
  466. hasLists() {
  467. return this.lists().count() > 0;
  468. },
  469. isListSelected() {
  470. return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
  471. },
  472. presentParentTask() {
  473. let result = this.currentBoard.presentParentTask;
  474. if (result === null || result === undefined) {
  475. result = 'no-parent';
  476. }
  477. return result;
  478. },
  479. events() {
  480. return [
  481. {
  482. 'click .js-field-has-subtasks'(evt) {
  483. evt.preventDefault();
  484. this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
  485. this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
  486. $(`.js-field-has-subtasks ${MCB}`).toggleClass(
  487. CKCLS,
  488. this.currentBoard.allowsSubtasks,
  489. );
  490. $('.js-field-has-subtasks').toggleClass(
  491. CKCLS,
  492. this.currentBoard.allowsSubtasks,
  493. );
  494. $('.js-field-deposit-board').prop(
  495. 'disabled',
  496. !this.currentBoard.allowsSubtasks,
  497. );
  498. },
  499. 'change .js-field-deposit-board'(evt) {
  500. let value = evt.target.value;
  501. if (value === 'null') {
  502. value = null;
  503. }
  504. this.currentBoard.setSubtasksDefaultBoardId(value);
  505. evt.preventDefault();
  506. },
  507. 'change .js-field-deposit-list'(evt) {
  508. this.currentBoard.setSubtasksDefaultListId(evt.target.value);
  509. evt.preventDefault();
  510. },
  511. 'click .js-field-show-parent-in-minicard'(evt) {
  512. const value =
  513. evt.target.id ||
  514. $(evt.target).parent()[0].id ||
  515. $(evt.target)
  516. .parent()[0]
  517. .parent()[0].id;
  518. const options = [
  519. 'prefix-with-full-path',
  520. 'prefix-with-parent',
  521. 'subtext-with-full-path',
  522. 'subtext-with-parent',
  523. 'no-parent',
  524. ];
  525. options.forEach(function(element) {
  526. if (element !== value) {
  527. $(`#${element} ${MCB}`).toggleClass(CKCLS, false);
  528. $(`#${element}`).toggleClass(CKCLS, false);
  529. }
  530. });
  531. $(`#${value} ${MCB}`).toggleClass(CKCLS, true);
  532. $(`#${value}`).toggleClass(CKCLS, true);
  533. this.currentBoard.setPresentParentTask(value);
  534. evt.preventDefault();
  535. },
  536. },
  537. ];
  538. },
  539. }).register('boardSubtaskSettingsPopup');
  540. BlazeComponent.extendComponent({
  541. onCreated() {
  542. this.currentBoard = Boards.findOne(Session.get('currentBoard'));
  543. },
  544. allowsReceivedDate() {
  545. return this.currentBoard.allowsReceivedDate;
  546. },
  547. allowsStartDate() {
  548. return this.currentBoard.allowsStartDate;
  549. },
  550. allowsDueDate() {
  551. return this.currentBoard.allowsDueDate;
  552. },
  553. allowsEndDate() {
  554. return this.currentBoard.allowsEndDate;
  555. },
  556. allowsSubtasks() {
  557. return this.currentBoard.allowsSubtasks;
  558. },
  559. allowsMembers() {
  560. return this.currentBoard.allowsMembers;
  561. },
  562. allowsAssignee() {
  563. return this.currentBoard.allowsAssignee;
  564. },
  565. allowsAssignedBy() {
  566. return this.currentBoard.allowsAssignedBy;
  567. },
  568. allowsRequestedBy() {
  569. return this.currentBoard.allowsRequestedBy;
  570. },
  571. allowsLabels() {
  572. return this.currentBoard.allowsLabels;
  573. },
  574. allowsChecklists() {
  575. return this.currentBoard.allowsChecklists;
  576. },
  577. allowsAttachments() {
  578. return this.currentBoard.allowsAttachments;
  579. },
  580. allowsComments() {
  581. return this.currentBoard.allowsComments;
  582. },
  583. isBoardSelected() {
  584. return this.currentBoard.dateSettingsDefaultBoardID;
  585. },
  586. isNullBoardSelected() {
  587. return (
  588. this.currentBoard.dateSettingsDefaultBoardId === null ||
  589. this.currentBoard.dateSettingsDefaultBoardId === undefined
  590. );
  591. },
  592. boards() {
  593. return Boards.find(
  594. {
  595. archived: false,
  596. 'members.userId': Meteor.userId(),
  597. },
  598. {
  599. sort: ['title'],
  600. },
  601. );
  602. },
  603. lists() {
  604. return Lists.find(
  605. {
  606. boardId: this.currentBoard._id,
  607. archived: false,
  608. },
  609. {
  610. sort: ['title'],
  611. },
  612. );
  613. },
  614. hasLists() {
  615. return this.lists().count() > 0;
  616. },
  617. isListSelected() {
  618. return (
  619. this.currentBoard.dateSettingsDefaultBoardId === this.currentData()._id
  620. );
  621. },
  622. events() {
  623. return [
  624. {
  625. 'click .js-field-has-receiveddate'(evt) {
  626. evt.preventDefault();
  627. this.currentBoard.allowsReceivedDate = !this.currentBoard
  628. .allowsReceivedDate;
  629. this.currentBoard.setAllowsReceivedDate(
  630. this.currentBoard.allowsReceivedDate,
  631. );
  632. $(`.js-field-has-receiveddate ${MCB}`).toggleClass(
  633. CKCLS,
  634. this.currentBoard.allowsReceivedDate,
  635. );
  636. $('.js-field-has-receiveddate').toggleClass(
  637. CKCLS,
  638. this.currentBoard.allowsReceivedDate,
  639. );
  640. },
  641. 'click .js-field-has-startdate'(evt) {
  642. evt.preventDefault();
  643. this.currentBoard.allowsStartDate = !this.currentBoard
  644. .allowsStartDate;
  645. this.currentBoard.setAllowsStartDate(
  646. this.currentBoard.allowsStartDate,
  647. );
  648. $(`.js-field-has-startdate ${MCB}`).toggleClass(
  649. CKCLS,
  650. this.currentBoard.allowsStartDate,
  651. );
  652. $('.js-field-has-startdate').toggleClass(
  653. CKCLS,
  654. this.currentBoard.allowsStartDate,
  655. );
  656. },
  657. 'click .js-field-has-enddate'(evt) {
  658. evt.preventDefault();
  659. this.currentBoard.allowsEndDate = !this.currentBoard.allowsEndDate;
  660. this.currentBoard.setAllowsEndDate(this.currentBoard.allowsEndDate);
  661. $(`.js-field-has-enddate ${MCB}`).toggleClass(
  662. CKCLS,
  663. this.currentBoard.allowsEndDate,
  664. );
  665. $('.js-field-has-enddate').toggleClass(
  666. CKCLS,
  667. this.currentBoard.allowsEndDate,
  668. );
  669. },
  670. 'click .js-field-has-duedate'(evt) {
  671. evt.preventDefault();
  672. this.currentBoard.allowsDueDate = !this.currentBoard.allowsDueDate;
  673. this.currentBoard.setAllowsDueDate(this.currentBoard.allowsDueDate);
  674. $(`.js-field-has-duedate ${MCB}`).toggleClass(
  675. CKCLS,
  676. this.currentBoard.allowsDueDate,
  677. );
  678. $('.js-field-has-duedate').toggleClass(
  679. CKCLS,
  680. this.currentBoard.allowsDueDate,
  681. );
  682. },
  683. 'click .js-field-has-subtasks'(evt) {
  684. evt.preventDefault();
  685. this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
  686. this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
  687. $(`.js-field-has-subtasks ${MCB}`).toggleClass(
  688. CKCLS,
  689. this.currentBoard.allowsSubtasks,
  690. );
  691. $('.js-field-has-subtasks').toggleClass(
  692. CKCLS,
  693. this.currentBoard.allowsSubtasks,
  694. );
  695. },
  696. 'click .js-field-has-members'(evt) {
  697. evt.preventDefault();
  698. this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers;
  699. this.currentBoard.setAllowsMembers(this.currentBoard.allowsMembers);
  700. $(`.js-field-has-members ${MCB}`).toggleClass(
  701. CKCLS,
  702. this.currentBoard.allowsMembers,
  703. );
  704. $('.js-field-has-members').toggleClass(
  705. CKCLS,
  706. this.currentBoard.allowsMembers,
  707. );
  708. },
  709. 'click .js-field-has-assignee'(evt) {
  710. evt.preventDefault();
  711. this.currentBoard.allowsAssignee = !this.currentBoard.allowsAssignee;
  712. this.currentBoard.setAllowsAssignee(this.currentBoard.allowsAssignee);
  713. $(`.js-field-has-assignee ${MCB}`).toggleClass(
  714. CKCLS,
  715. this.currentBoard.allowsAssignee,
  716. );
  717. $('.js-field-has-assignee').toggleClass(
  718. CKCLS,
  719. this.currentBoard.allowsAssignee,
  720. );
  721. },
  722. 'click .js-field-has-assigned-by'(evt) {
  723. evt.preventDefault();
  724. this.currentBoard.allowsAssignedBy = !this.currentBoard
  725. .allowsAssignedBy;
  726. this.currentBoard.setAllowsAssignedBy(
  727. this.currentBoard.allowsAssignedBy,
  728. );
  729. $(`.js-field-has-assigned-by ${MCB}`).toggleClass(
  730. CKCLS,
  731. this.currentBoard.allowsAssignedBy,
  732. );
  733. $('.js-field-has-assigned-by').toggleClass(
  734. CKCLS,
  735. this.currentBoard.allowsAssignedBy,
  736. );
  737. },
  738. 'click .js-field-has-requested-by'(evt) {
  739. evt.preventDefault();
  740. this.currentBoard.allowsRequestedBy = !this.currentBoard
  741. .allowsRequestedBy;
  742. this.currentBoard.setAllowsRequestedBy(
  743. this.currentBoard.allowsRequestedBy,
  744. );
  745. $(`.js-field-has-requested-by ${MCB}`).toggleClass(
  746. CKCLS,
  747. this.currentBoard.allowsRequestedBy,
  748. );
  749. $('.js-field-has-requested-by').toggleClass(
  750. CKCLS,
  751. this.currentBoard.allowsRequestedBy,
  752. );
  753. },
  754. 'click .js-field-has-labels'(evt) {
  755. evt.preventDefault();
  756. this.currentBoard.allowsLabels = !this.currentBoard.allowsLabels;
  757. this.currentBoard.setAllowsLabels(this.currentBoard.allowsLabels);
  758. $(`.js-field-has-labels ${MCB}`).toggleClass(
  759. CKCLS,
  760. this.currentBoard.allowsAssignee,
  761. );
  762. $('.js-field-has-labels').toggleClass(
  763. CKCLS,
  764. this.currentBoard.allowsLabels,
  765. );
  766. },
  767. 'click .js-field-has-checklists'(evt) {
  768. evt.preventDefault();
  769. this.currentBoard.allowsChecklists = !this.currentBoard
  770. .allowsChecklists;
  771. this.currentBoard.setAllowsChecklists(
  772. this.currentBoard.allowsChecklists,
  773. );
  774. $(`.js-field-has-checklists ${MCB}`).toggleClass(
  775. CKCLS,
  776. this.currentBoard.allowsChecklists,
  777. );
  778. $('.js-field-has-checklists').toggleClass(
  779. CKCLS,
  780. this.currentBoard.allowsChecklists,
  781. );
  782. },
  783. 'click .js-field-has-attachments'(evt) {
  784. evt.preventDefault();
  785. this.currentBoard.allowsAttachments = !this.currentBoard
  786. .allowsAttachments;
  787. this.currentBoard.setAllowsAttachments(
  788. this.currentBoard.allowsAttachments,
  789. );
  790. $(`.js-field-has-attachments ${MCB}`).toggleClass(
  791. CKCLS,
  792. this.currentBoard.allowsAttachments,
  793. );
  794. $('.js-field-has-attachments').toggleClass(
  795. CKCLS,
  796. this.currentBoard.allowsAttachments,
  797. );
  798. },
  799. 'click .js-field-has-comments'(evt) {
  800. evt.preventDefault();
  801. this.currentBoard.allowsComments = !this.currentBoard.allowsComments;
  802. this.currentBoard.setAllowsComments(this.currentBoard.allowsComments);
  803. $(`.js-field-has-comments ${MCB}`).toggleClass(
  804. CKCLS,
  805. this.currentBoard.allowsComments,
  806. );
  807. $('.js-field-has-comments').toggleClass(
  808. CKCLS,
  809. this.currentBoard.allowsComments,
  810. );
  811. },
  812. 'click .js-field-has-activities'(evt) {
  813. evt.preventDefault();
  814. this.currentBoard.allowsActivities = !this.currentBoard
  815. .allowsActivities;
  816. this.currentBoard.setAllowsActivities(
  817. this.currentBoard.allowsActivities,
  818. );
  819. $(`.js-field-has-activities ${MCB}`).toggleClass(
  820. CKCLS,
  821. this.currentBoard.allowsActivities,
  822. );
  823. $('.js-field-has-activities').toggleClass(
  824. CKCLS,
  825. this.currentBoard.allowsActivities,
  826. );
  827. },
  828. },
  829. ];
  830. },
  831. }).register('boardCardSettingsPopup');
  832. BlazeComponent.extendComponent({
  833. onCreated() {
  834. this.error = new ReactiveVar('');
  835. this.loading = new ReactiveVar(false);
  836. },
  837. onRendered() {
  838. this.find('.js-search-member input').focus();
  839. this.setLoading(false);
  840. },
  841. isBoardMember() {
  842. const userId = this.currentData()._id;
  843. const user = Users.findOne(userId);
  844. return user && user.isBoardMember();
  845. },
  846. isValidEmail(email) {
  847. return SimpleSchema.RegEx.Email.test(email);
  848. },
  849. setError(error) {
  850. this.error.set(error);
  851. },
  852. setLoading(w) {
  853. this.loading.set(w);
  854. },
  855. isLoading() {
  856. return this.loading.get();
  857. },
  858. inviteUser(idNameEmail) {
  859. const boardId = Session.get('currentBoard');
  860. this.setLoading(true);
  861. const self = this;
  862. Meteor.call('inviteUserToBoard', idNameEmail, boardId, (err, ret) => {
  863. self.setLoading(false);
  864. if (err) self.setError(err.error);
  865. else if (ret.email) self.setError('email-sent');
  866. else Popup.close();
  867. });
  868. },
  869. events() {
  870. return [
  871. {
  872. 'keyup input'() {
  873. this.setError('');
  874. },
  875. 'click .js-select-member'() {
  876. const userId = this.currentData()._id;
  877. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  878. if (!currentBoard.hasMember(userId)) {
  879. this.inviteUser(userId);
  880. }
  881. },
  882. 'click .js-email-invite'() {
  883. const idNameEmail = $('.js-search-member input').val();
  884. if (idNameEmail.indexOf('@') < 0 || this.isValidEmail(idNameEmail)) {
  885. this.inviteUser(idNameEmail);
  886. } else this.setError('email-invalid');
  887. },
  888. },
  889. ];
  890. },
  891. }).register('addMemberPopup');
  892. Template.changePermissionsPopup.events({
  893. 'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
  894. event,
  895. ) {
  896. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  897. const memberId = this.userId;
  898. const isAdmin = $(event.currentTarget).hasClass('js-set-admin');
  899. const isCommentOnly = $(event.currentTarget).hasClass(
  900. 'js-set-comment-only',
  901. );
  902. const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
  903. const isWorker = $(event.currentTarget).hasClass('js-set-worker');
  904. currentBoard.setMemberPermission(
  905. memberId,
  906. isAdmin,
  907. isNoComments,
  908. isCommentOnly,
  909. isWorker,
  910. );
  911. Popup.back(1);
  912. },
  913. });
  914. Template.changePermissionsPopup.helpers({
  915. isAdmin() {
  916. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  917. return currentBoard.hasAdmin(this.userId);
  918. },
  919. isNormal() {
  920. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  921. return (
  922. !currentBoard.hasAdmin(this.userId) &&
  923. !currentBoard.hasNoComments(this.userId) &&
  924. !currentBoard.hasCommentOnly(this.userId) &&
  925. !currentBoard.hasWorker(this.userId)
  926. );
  927. },
  928. isNoComments() {
  929. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  930. return (
  931. !currentBoard.hasAdmin(this.userId) &&
  932. currentBoard.hasNoComments(this.userId)
  933. );
  934. },
  935. isCommentOnly() {
  936. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  937. return (
  938. !currentBoard.hasAdmin(this.userId) &&
  939. currentBoard.hasCommentOnly(this.userId)
  940. );
  941. },
  942. isWorker() {
  943. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  944. return (
  945. !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
  946. );
  947. },
  948. isLastAdmin() {
  949. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  950. return (
  951. currentBoard.hasAdmin(this.userId) && currentBoard.activeAdmins() === 1
  952. );
  953. },
  954. });