checklists.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. const { calculateIndexData } = Utils;
  2. function initSorting(items) {
  3. items.sortable({
  4. tolerance: 'pointer',
  5. helper: 'clone',
  6. items: '.js-checklist-item:not(.placeholder)',
  7. connectWith: '.js-checklist-items',
  8. appendTo: '.board-canvas',
  9. distance: 7,
  10. placeholder: 'checklist-item placeholder',
  11. scroll: false,
  12. start(evt, ui) {
  13. ui.placeholder.height(ui.helper.height());
  14. EscapeActions.executeUpTo('popup-close');
  15. },
  16. stop(evt, ui) {
  17. const parent = ui.item.parents('.js-checklist-items');
  18. const checklistId = Blaze.getData(parent.get(0)).checklist._id;
  19. let prevItem = ui.item.prev('.js-checklist-item').get(0);
  20. if (prevItem) {
  21. prevItem = Blaze.getData(prevItem).item;
  22. }
  23. let nextItem = ui.item.next('.js-checklist-item').get(0);
  24. if (nextItem) {
  25. nextItem = Blaze.getData(nextItem).item;
  26. }
  27. const nItems = 1;
  28. const sortIndex = calculateIndexData(prevItem, nextItem, nItems);
  29. const checklistDomElement = ui.item.get(0);
  30. const checklistData = Blaze.getData(checklistDomElement);
  31. const checklistItem = checklistData.item;
  32. items.sortable('cancel');
  33. checklistItem.move(checklistId, sortIndex.base);
  34. },
  35. });
  36. }
  37. BlazeComponent.extendComponent({
  38. onRendered() {
  39. const self = this;
  40. self.itemsDom = this.$('.js-checklist-items');
  41. initSorting(self.itemsDom);
  42. self.itemsDom.mousedown(function(evt) {
  43. evt.stopPropagation();
  44. });
  45. function userIsMember() {
  46. return Meteor.user() && Meteor.user().isBoardMember();
  47. }
  48. // Disable sorting if the current user is not a board member
  49. self.autorun(() => {
  50. const $itemsDom = $(self.itemsDom);
  51. if ($itemsDom.data('sortable')) {
  52. $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
  53. }
  54. });
  55. },
  56. canModifyCard() {
  57. return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  58. },
  59. }).register('checklistDetail');
  60. BlazeComponent.extendComponent({
  61. addChecklist(event) {
  62. event.preventDefault();
  63. const textarea = this.find('textarea.js-add-checklist-item');
  64. const title = textarea.value.trim();
  65. const cardId = this.currentData().cardId;
  66. const card = Cards.findOne(cardId);
  67. if (title) {
  68. Checklists.insert({
  69. cardId,
  70. title,
  71. sort: card.checklists().count(),
  72. });
  73. setTimeout(() => {
  74. this.$('.add-checklist-item').last().click();
  75. }, 100);
  76. }
  77. textarea.value = '';
  78. textarea.focus();
  79. },
  80. addChecklistItem(event) {
  81. event.preventDefault();
  82. const textarea = this.find('textarea.js-add-checklist-item');
  83. const title = textarea.value.trim();
  84. const checklist = this.currentData().checklist;
  85. if (title) {
  86. ChecklistItems.insert({
  87. title,
  88. checklistId: checklist._id,
  89. cardId: checklist.cardId,
  90. sort: checklist.itemCount(),
  91. });
  92. }
  93. // We keep the form opened, empty it.
  94. textarea.value = '';
  95. textarea.focus();
  96. },
  97. canModifyCard() {
  98. return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  99. },
  100. deleteChecklist() {
  101. const checklist = this.currentData().checklist;
  102. if (checklist && checklist._id) {
  103. Checklists.remove(checklist._id);
  104. this.toggleDeleteDialog.set(false);
  105. }
  106. },
  107. deleteItem() {
  108. const checklist = this.currentData().checklist;
  109. const item = this.currentData().item;
  110. if (checklist && item && item._id) {
  111. ChecklistItems.remove(item._id);
  112. }
  113. },
  114. editChecklist(event) {
  115. event.preventDefault();
  116. const textarea = this.find('textarea.js-edit-checklist-item');
  117. const title = textarea.value.trim();
  118. const checklist = this.currentData().checklist;
  119. checklist.setTitle(title);
  120. },
  121. editChecklistItem(event) {
  122. event.preventDefault();
  123. const textarea = this.find('textarea.js-edit-checklist-item');
  124. const title = textarea.value.trim();
  125. const item = this.currentData().item;
  126. item.setTitle(title);
  127. },
  128. onCreated() {
  129. this.toggleDeleteDialog = new ReactiveVar(false);
  130. this.checklistToDelete = null; //Store data context to pass to checklistDeleteDialog template
  131. },
  132. pressKey(event) {
  133. //If user press enter key inside a form, submit it
  134. //Unless the user is also holding down the 'shift' key
  135. if (event.keyCode === 13 && !event.shiftKey) {
  136. event.preventDefault();
  137. const $form = $(event.currentTarget).closest('form');
  138. $form.find('button[type=submit]').click();
  139. }
  140. },
  141. events() {
  142. const events = {
  143. 'click .toggle-delete-checklist-dialog'(event) {
  144. if($(event.target).hasClass('js-delete-checklist')){
  145. this.checklistToDelete = this.currentData().checklist; //Store data context
  146. }
  147. this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
  148. },
  149. };
  150. return [{
  151. ...events,
  152. 'submit .js-add-checklist': this.addChecklist,
  153. 'submit .js-edit-checklist-title': this.editChecklist,
  154. 'submit .js-add-checklist-item': this.addChecklistItem,
  155. 'submit .js-edit-checklist-item': this.editChecklistItem,
  156. 'click .js-delete-checklist-item': this.deleteItem,
  157. 'click .confirm-checklist-delete': this.deleteChecklist,
  158. keydown: this.pressKey,
  159. }];
  160. },
  161. }).register('checklists');
  162. Template.checklistDeleteDialog.onCreated(() => {
  163. const $cardDetails = this.$('.card-details');
  164. this.scrollState = { position: $cardDetails.scrollTop(), //save current scroll position
  165. top: false, //required for smooth scroll animation
  166. };
  167. //Callback's purpose is to only prevent scrolling after animation is complete
  168. $cardDetails.animate({ scrollTop: 0 }, 500, () => { this.scrollState.top = true; });
  169. //Prevent scrolling while dialog is open
  170. $cardDetails.on('scroll', () => {
  171. if(this.scrollState.top) { //If it's already in position, keep it there. Otherwise let animation scroll
  172. $cardDetails.scrollTop(0);
  173. }
  174. });
  175. });
  176. Template.checklistDeleteDialog.onDestroyed(() => {
  177. const $cardDetails = this.$('.card-details');
  178. $cardDetails.off('scroll'); //Reactivate scrolling
  179. $cardDetails.animate( { scrollTop: this.scrollState.position });
  180. });
  181. Template.itemDetail.helpers({
  182. canModifyCard() {
  183. return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly();
  184. },
  185. });
  186. BlazeComponent.extendComponent({
  187. toggleItem() {
  188. const checklist = this.currentData().checklist;
  189. const item = this.currentData().item;
  190. if (checklist && item && item._id) {
  191. item.toggleItem();
  192. }
  193. },
  194. events() {
  195. return [{
  196. 'click .js-checklist-item .check-box': this.toggleItem,
  197. }];
  198. },
  199. }).register('itemDetail');