subtasks.js 5.2 KB

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