sidebar.js 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  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. },
  813. ];
  814. },
  815. }).register('boardCardSettingsPopup');
  816. BlazeComponent.extendComponent({
  817. onCreated() {
  818. this.error = new ReactiveVar('');
  819. this.loading = new ReactiveVar(false);
  820. },
  821. onRendered() {
  822. this.find('.js-search-member input').focus();
  823. this.setLoading(false);
  824. },
  825. isBoardMember() {
  826. const userId = this.currentData()._id;
  827. const user = Users.findOne(userId);
  828. return user && user.isBoardMember();
  829. },
  830. isValidEmail(email) {
  831. return SimpleSchema.RegEx.Email.test(email);
  832. },
  833. setError(error) {
  834. this.error.set(error);
  835. },
  836. setLoading(w) {
  837. this.loading.set(w);
  838. },
  839. isLoading() {
  840. return this.loading.get();
  841. },
  842. inviteUser(idNameEmail) {
  843. const boardId = Session.get('currentBoard');
  844. this.setLoading(true);
  845. const self = this;
  846. Meteor.call('inviteUserToBoard', idNameEmail, boardId, (err, ret) => {
  847. self.setLoading(false);
  848. if (err) self.setError(err.error);
  849. else if (ret.email) self.setError('email-sent');
  850. else Popup.close();
  851. });
  852. },
  853. events() {
  854. return [
  855. {
  856. 'keyup input'() {
  857. this.setError('');
  858. },
  859. 'click .js-select-member'() {
  860. const userId = this.currentData()._id;
  861. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  862. if (!currentBoard.hasMember(userId)) {
  863. this.inviteUser(userId);
  864. }
  865. },
  866. 'click .js-email-invite'() {
  867. const idNameEmail = $('.js-search-member input').val();
  868. if (idNameEmail.indexOf('@') < 0 || this.isValidEmail(idNameEmail)) {
  869. this.inviteUser(idNameEmail);
  870. } else this.setError('email-invalid');
  871. },
  872. },
  873. ];
  874. },
  875. }).register('addMemberPopup');
  876. Template.changePermissionsPopup.events({
  877. 'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
  878. event,
  879. ) {
  880. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  881. const memberId = this.userId;
  882. const isAdmin = $(event.currentTarget).hasClass('js-set-admin');
  883. const isCommentOnly = $(event.currentTarget).hasClass(
  884. 'js-set-comment-only',
  885. );
  886. const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
  887. const isWorker = $(event.currentTarget).hasClass('js-set-worker');
  888. currentBoard.setMemberPermission(
  889. memberId,
  890. isAdmin,
  891. isNoComments,
  892. isCommentOnly,
  893. isWorker,
  894. );
  895. Popup.back(1);
  896. },
  897. });
  898. Template.changePermissionsPopup.helpers({
  899. isAdmin() {
  900. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  901. return currentBoard.hasAdmin(this.userId);
  902. },
  903. isNormal() {
  904. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  905. return (
  906. !currentBoard.hasAdmin(this.userId) &&
  907. !currentBoard.hasNoComments(this.userId) &&
  908. !currentBoard.hasCommentOnly(this.userId) &&
  909. !currentBoard.hasWorker(this.userId)
  910. );
  911. },
  912. isNoComments() {
  913. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  914. return (
  915. !currentBoard.hasAdmin(this.userId) &&
  916. currentBoard.hasNoComments(this.userId)
  917. );
  918. },
  919. isCommentOnly() {
  920. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  921. return (
  922. !currentBoard.hasAdmin(this.userId) &&
  923. currentBoard.hasCommentOnly(this.userId)
  924. );
  925. },
  926. isWorker() {
  927. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  928. return (
  929. !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
  930. );
  931. },
  932. isLastAdmin() {
  933. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  934. return (
  935. currentBoard.hasAdmin(this.userId) && currentBoard.activeAdmins() === 1
  936. );
  937. },
  938. });