checklists.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. const { calculateIndexData, enableClickOnTouch } = 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. // ugly touch event hotfix
  37. enableClickOnTouch('.js-checklist-item:not(.placeholder)');
  38. }
  39. BlazeComponent.extendComponent({
  40. onRendered() {
  41. const self = this;
  42. self.itemsDom = this.$('.js-checklist-items');
  43. initSorting(self.itemsDom);
  44. self.itemsDom.mousedown(function(evt) {
  45. evt.stopPropagation();
  46. });
  47. function userIsMember() {
  48. return Meteor.user() && Meteor.user().isBoardMember();
  49. }
  50. // Disable sorting if the current user is not a board member
  51. self.autorun(() => {
  52. const $itemsDom = $(self.itemsDom);
  53. if ($itemsDom.data('sortable')) {
  54. $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
  55. }
  56. if(Utils.isMiniScreen()) {
  57. this.$('.js-checklists').sortable({
  58. handle: '.checklist-handle',
  59. });
  60. this.$('.js-checklist-item').sortable({
  61. handle: '.checklist-item-handle',
  62. });
  63. } else {
  64. if (Meteor.user().hasShowDesktopDragHandles()) {
  65. this.$('.js-checklists').sortable({
  66. handle: '.checklist-handle',
  67. });
  68. this.$('.js-checklist-item').sortable({
  69. handle: '.checklist-item-handle',
  70. });
  71. } else {
  72. this.$('.js-checklists').sortable({
  73. handle: '.checklist-title',
  74. });
  75. this.$('.js-checklist-item').sortable({
  76. handle: '.checklist-item',
  77. });
  78. }
  79. }
  80. });
  81. },
  82. canModifyCard() {
  83. return (
  84. Meteor.user() &&
  85. Meteor.user().isBoardMember() &&
  86. !Meteor.user().isCommentOnly()
  87. );
  88. },
  89. }).register('checklistDetail');
  90. Template.checklistDetail.helpers({
  91. showDesktopDragHandles() {
  92. return Meteor.user().hasShowDesktopDragHandles();
  93. },
  94. });
  95. BlazeComponent.extendComponent({
  96. addChecklist(event) {
  97. event.preventDefault();
  98. const textarea = this.find('textarea.js-add-checklist-item');
  99. const title = textarea.value.trim();
  100. let cardId = this.currentData().cardId;
  101. const card = Cards.findOne(cardId);
  102. if (card.isLinked()) cardId = card.linkedId;
  103. if (title) {
  104. Checklists.insert({
  105. cardId,
  106. title,
  107. sort: card.checklists().count(),
  108. });
  109. setTimeout(() => {
  110. this.$('.add-checklist-item')
  111. .last()
  112. .click();
  113. }, 100);
  114. }
  115. textarea.value = '';
  116. textarea.focus();
  117. },
  118. addChecklistItem(event) {
  119. event.preventDefault();
  120. const textarea = this.find('textarea.js-add-checklist-item');
  121. const title = textarea.value.trim();
  122. const checklist = this.currentData().checklist;
  123. if (title) {
  124. ChecklistItems.insert({
  125. title,
  126. checklistId: checklist._id,
  127. cardId: checklist.cardId,
  128. sort: checklist.itemCount(),
  129. });
  130. }
  131. // We keep the form opened, empty it.
  132. textarea.value = '';
  133. textarea.focus();
  134. },
  135. canModifyCard() {
  136. return (
  137. Meteor.user() &&
  138. Meteor.user().isBoardMember() &&
  139. !Meteor.user().isCommentOnly()
  140. );
  141. },
  142. deleteChecklist() {
  143. const checklist = this.currentData().checklist;
  144. if (checklist && checklist._id) {
  145. Checklists.remove(checklist._id);
  146. this.toggleDeleteDialog.set(false);
  147. }
  148. },
  149. deleteItem() {
  150. const checklist = this.currentData().checklist;
  151. const item = this.currentData().item;
  152. if (checklist && item && item._id) {
  153. ChecklistItems.remove(item._id);
  154. }
  155. },
  156. editChecklist(event) {
  157. event.preventDefault();
  158. const textarea = this.find('textarea.js-edit-checklist-item');
  159. const title = textarea.value.trim();
  160. const checklist = this.currentData().checklist;
  161. checklist.setTitle(title);
  162. },
  163. editChecklistItem(event) {
  164. event.preventDefault();
  165. const textarea = this.find('textarea.js-edit-checklist-item');
  166. const title = textarea.value.trim();
  167. const item = this.currentData().item;
  168. item.setTitle(title);
  169. },
  170. onCreated() {
  171. this.toggleDeleteDialog = new ReactiveVar(false);
  172. this.checklistToDelete = null; //Store data context to pass to checklistDeleteDialog template
  173. },
  174. pressKey(event) {
  175. //If user press enter key inside a form, submit it
  176. //Unless the user is also holding down the 'shift' key
  177. if (event.keyCode === 13 && !event.shiftKey) {
  178. event.preventDefault();
  179. const $form = $(event.currentTarget).closest('form');
  180. $form.find('button[type=submit]').click();
  181. }
  182. },
  183. events() {
  184. const events = {
  185. 'click .toggle-delete-checklist-dialog'(event) {
  186. if ($(event.target).hasClass('js-delete-checklist')) {
  187. this.checklistToDelete = this.currentData().checklist; //Store data context
  188. }
  189. this.toggleDeleteDialog.set(!this.toggleDeleteDialog.get());
  190. },
  191. };
  192. return [
  193. {
  194. ...events,
  195. 'submit .js-add-checklist': this.addChecklist,
  196. 'submit .js-edit-checklist-title': this.editChecklist,
  197. 'submit .js-add-checklist-item': this.addChecklistItem,
  198. 'submit .js-edit-checklist-item': this.editChecklistItem,
  199. 'click .js-delete-checklist-item': this.deleteItem,
  200. 'click .confirm-checklist-delete': this.deleteChecklist,
  201. keydown: this.pressKey,
  202. },
  203. ];
  204. },
  205. }).register('checklists');
  206. Template.checklists.helpers({
  207. showDesktopDragHandles() {
  208. return Meteor.user().hasShowDesktopDragHandles();
  209. },
  210. });
  211. Template.checklistDeleteDialog.onCreated(() => {
  212. const $cardDetails = this.$('.card-details');
  213. this.scrollState = {
  214. position: $cardDetails.scrollTop(), //save current scroll position
  215. top: false, //required for smooth scroll animation
  216. };
  217. //Callback's purpose is to only prevent scrolling after animation is complete
  218. $cardDetails.animate({ scrollTop: 0 }, 500, () => {
  219. this.scrollState.top = true;
  220. });
  221. //Prevent scrolling while dialog is open
  222. $cardDetails.on('scroll', () => {
  223. if (this.scrollState.top) {
  224. //If it's already in position, keep it there. Otherwise let animation scroll
  225. $cardDetails.scrollTop(0);
  226. }
  227. });
  228. });
  229. Template.checklistDeleteDialog.onDestroyed(() => {
  230. const $cardDetails = this.$('.card-details');
  231. $cardDetails.off('scroll'); //Reactivate scrolling
  232. $cardDetails.animate({ scrollTop: this.scrollState.position });
  233. });
  234. Template.checklistItemDetail.helpers({
  235. canModifyCard() {
  236. return (
  237. Meteor.user() &&
  238. Meteor.user().isBoardMember() &&
  239. !Meteor.user().isCommentOnly()
  240. );
  241. },
  242. showDesktopDragHandles() {
  243. return Meteor.user().hasShowDesktopDragHandles();
  244. },
  245. });
  246. BlazeComponent.extendComponent({
  247. toggleItem() {
  248. const checklist = this.currentData().checklist;
  249. const item = this.currentData().item;
  250. if (checklist && item && item._id) {
  251. item.toggleItem();
  252. }
  253. },
  254. events() {
  255. return [
  256. {
  257. 'click .js-checklist-item .check-box': this.toggleItem,
  258. },
  259. ];
  260. },
  261. }).register('checklistItemDetail');