sidebar.js 32 KB

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