sidebar.js 32 KB

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