subtasks.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. BlazeComponent.extendComponent({
  2. canModifyCard() {
  3. return (
  4. Meteor.user() &&
  5. Meteor.user().isBoardMember() &&
  6. !Meteor.user().isCommentOnly() &&
  7. !Meteor.user().isWorker()
  8. );
  9. },
  10. }).register('subtaskDetail');
  11. BlazeComponent.extendComponent({
  12. addSubtask(event) {
  13. event.preventDefault();
  14. const textarea = this.find('textarea.js-add-subtask-item');
  15. const title = textarea.value.trim();
  16. const cardId = this.currentData().cardId;
  17. const card = Cards.findOne(cardId);
  18. const sortIndex = -1;
  19. const crtBoard = Boards.findOne(card.boardId);
  20. const targetBoard = crtBoard.getDefaultSubtasksBoard();
  21. const listId = targetBoard.getDefaultSubtasksListId();
  22. //Get the full swimlane data for the parent task.
  23. const parentSwimlane = Swimlanes.findOne({boardId: crtBoard._id, _id: card.swimlaneId});
  24. //find the swimlane of the same name in the target board.
  25. const targetSwimlane = Swimlanes.findOne({boardId: targetBoard._id, title: parentSwimlane.title});
  26. //If no swimlane with a matching title exists in the target board, fall back to the default swimlane.
  27. const swimlaneId = targetSwimlane === undefined ? targetBoard.getDefaultSwimline()._id : targetSwimlane._id;
  28. if (title) {
  29. const _id = Cards.insert({
  30. title,
  31. parentId: cardId,
  32. members: [],
  33. labelIds: [],
  34. customFields: [],
  35. listId,
  36. boardId: targetBoard._id,
  37. sort: sortIndex,
  38. swimlaneId,
  39. type: 'cardType-card',
  40. });
  41. // In case the filter is active we need to add the newly inserted card in
  42. // the list of exceptions -- cards that are not filtered. Otherwise the
  43. // card will disappear instantly.
  44. // See https://github.com/wekan/wekan/issues/80
  45. Filter.addException(_id);
  46. setTimeout(() => {
  47. this.$('.add-subtask-item')
  48. .last()
  49. .click();
  50. }, 100);
  51. }
  52. textarea.value = '';
  53. textarea.focus();
  54. },
  55. canModifyCard() {
  56. return (
  57. Meteor.user() &&
  58. Meteor.user().isBoardMember() &&
  59. !Meteor.user().isCommentOnly() &&
  60. !Meteor.user().isWorker()
  61. );
  62. },
  63. deleteSubtask() {
  64. const subtask = this.currentData().subtask;
  65. if (subtask && subtask._id) {
  66. subtask.archive();
  67. this.toggleDeleteDialog.set(false);
  68. }
  69. },
  70. editSubtask(event) {
  71. event.preventDefault();
  72. const textarea = this.find('textarea.js-edit-subtask-item');
  73. const title = textarea.value.trim();
  74. const subtask = this.currentData().subtask;
  75. subtask.setTitle(title);
  76. },
  77. onCreated() {
  78. this.toggleDeleteDialog = new ReactiveVar(false);
  79. this.subtaskToDelete = null; //Store data context to pass to subtaskDeleteDialog template
  80. },
  81. pressKey(event) {
  82. //If user press enter key inside a form, submit it
  83. //Unless the user is also holding down the 'shift' key
  84. if (event.keyCode === 13 && !event.shiftKey) {
  85. event.preventDefault();
  86. const $form = $(event.currentTarget).closest('form');
  87. $form.find('button[type=submit]').click();
  88. }
  89. },
  90. events() {
  91. const events = {
  92. 'click .toggle-delete-subtask-dialog'(event) {
  93. if ($(event.target).hasClass('js-delete-subtask')) {
  94. this.subtaskToDelete = this.currentData().subtask; //Store data context
  95. }
  96. this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
  97. },
  98. 'click .js-view-subtask'(event) {
  99. if ($(event.target).hasClass('js-view-subtask')) {
  100. const subtask = this.currentData().subtask;
  101. const board = subtask.board();
  102. FlowRouter.go('card', {
  103. boardId: board._id,
  104. slug: board.slug,
  105. cardId: subtask._id,
  106. });
  107. }
  108. },
  109. };
  110. return [
  111. {
  112. ...events,
  113. 'submit .js-add-subtask': this.addSubtask,
  114. 'submit .js-edit-subtask-title': this.editSubtask,
  115. 'click .confirm-subtask-delete': this.deleteSubtask,
  116. keydown: this.pressKey,
  117. },
  118. ];
  119. },
  120. }).register('subtasks');
  121. Template.subtaskDeleteDialog.onCreated(() => {
  122. const $cardDetails = this.$('.card-details');
  123. this.scrollState = {
  124. position: $cardDetails.scrollTop(), //save current scroll position
  125. top: false, //required for smooth scroll animation
  126. };
  127. //Callback's purpose is to only prevent scrolling after animation is complete
  128. $cardDetails.animate({ scrollTop: 0 }, 500, () => {
  129. this.scrollState.top = true;
  130. });
  131. //Prevent scrolling while dialog is open
  132. $cardDetails.on('scroll', () => {
  133. if (this.scrollState.top) {
  134. //If it's already in position, keep it there. Otherwise let animation scroll
  135. $cardDetails.scrollTop(0);
  136. }
  137. });
  138. });
  139. Template.subtaskDeleteDialog.onDestroyed(() => {
  140. const $cardDetails = this.$('.card-details');
  141. $cardDetails.off('scroll'); //Reactivate scrolling
  142. $cardDetails.animate({ scrollTop: this.scrollState.position });
  143. });
  144. Template.subtaskItemDetail.helpers({
  145. canModifyCard() {
  146. return (
  147. Meteor.user() &&
  148. Meteor.user().isBoardMember() &&
  149. !Meteor.user().isCommentOnly() &&
  150. !Meteor.user().isWorker()
  151. );
  152. },
  153. });
  154. BlazeComponent.extendComponent({
  155. // ...
  156. }).register('subtaskItemDetail');