utils.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. Utils = {
  2. // XXX We should remove these two methods
  3. goBoardId(_id) {
  4. const board = Boards.findOne(_id);
  5. return (
  6. board &&
  7. FlowRouter.go('board', {
  8. id: board._id,
  9. slug: board.slug,
  10. })
  11. );
  12. },
  13. goCardId(_id) {
  14. const card = Cards.findOne(_id);
  15. const board = Boards.findOne(card.boardId);
  16. return (
  17. board &&
  18. FlowRouter.go('card', {
  19. cardId: card._id,
  20. boardId: board._id,
  21. slug: board.slug,
  22. })
  23. );
  24. },
  25. capitalize(string) {
  26. return string.charAt(0).toUpperCase() + string.slice(1);
  27. },
  28. windowResizeDep: new Tracker.Dependency(),
  29. // in fact, what we really care is screen size
  30. // large mobile device like iPad or android Pad has a big screen, it should also behave like a desktop
  31. // in a small window (even on desktop), Wekan run in compact mode.
  32. // we can easily debug with a small window of desktop browser. :-)
  33. isMiniScreen() {
  34. this.windowResizeDep.depend();
  35. return $(window).width() <= 800;
  36. },
  37. calculateIndexData(prevData, nextData, nItems = 1) {
  38. let base, increment;
  39. // If we drop the card to an empty column
  40. if (!prevData && !nextData) {
  41. base = 0;
  42. increment = 1;
  43. // If we drop the card in the first position
  44. } else if (!prevData) {
  45. base = nextData.sort - 1;
  46. increment = -1;
  47. // If we drop the card in the last position
  48. } else if (!nextData) {
  49. base = prevData.sort + 1;
  50. increment = 1;
  51. }
  52. // In the general case take the average of the previous and next element
  53. // sort indexes.
  54. else {
  55. const prevSortIndex = prevData.sort;
  56. const nextSortIndex = nextData.sort;
  57. increment = (nextSortIndex - prevSortIndex) / (nItems + 1);
  58. base = prevSortIndex + increment;
  59. }
  60. // XXX Return a generator that yield values instead of a base with a
  61. // increment number.
  62. return {
  63. base,
  64. increment,
  65. };
  66. },
  67. // Determine the new sort index
  68. calculateIndex(prevCardDomElement, nextCardDomElement, nCards = 1) {
  69. let base, increment;
  70. // If we drop the card to an empty column
  71. if (!prevCardDomElement && !nextCardDomElement) {
  72. base = 0;
  73. increment = 1;
  74. // If we drop the card in the first position
  75. } else if (!prevCardDomElement) {
  76. base = Blaze.getData(nextCardDomElement).sort - 1;
  77. increment = -1;
  78. // If we drop the card in the last position
  79. } else if (!nextCardDomElement) {
  80. base = Blaze.getData(prevCardDomElement).sort + 1;
  81. increment = 1;
  82. }
  83. // In the general case take the average of the previous and next element
  84. // sort indexes.
  85. else {
  86. const prevSortIndex = Blaze.getData(prevCardDomElement).sort;
  87. const nextSortIndex = Blaze.getData(nextCardDomElement).sort;
  88. increment = (nextSortIndex - prevSortIndex) / (nCards + 1);
  89. base = prevSortIndex + increment;
  90. }
  91. // XXX Return a generator that yield values instead of a base with a
  92. // increment number.
  93. return {
  94. base,
  95. increment,
  96. };
  97. },
  98. // Detect touch device
  99. isTouchDevice() {
  100. const isTouchable = (() => {
  101. const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
  102. const mq = function(query) {
  103. return window.matchMedia(query).matches;
  104. };
  105. if (
  106. 'ontouchstart' in window ||
  107. (window.DocumentTouch && document instanceof window.DocumentTouch)
  108. ) {
  109. return true;
  110. }
  111. // include the 'heartz' as a way to have a non matching MQ to help terminate the join
  112. // https://git.io/vznFH
  113. const query = [
  114. '(',
  115. prefixes.join('touch-enabled),('),
  116. 'heartz',
  117. ')',
  118. ].join('');
  119. return mq(query);
  120. })();
  121. Utils.isTouchDevice = () => isTouchable;
  122. return isTouchable;
  123. },
  124. calculateTouchDistance(touchA, touchB) {
  125. return Math.sqrt(
  126. Math.pow(touchA.screenX - touchB.screenX, 2) +
  127. Math.pow(touchA.screenY - touchB.screenY, 2),
  128. );
  129. },
  130. enableClickOnTouch(selector) {
  131. let touchStart = null;
  132. let lastTouch = null;
  133. $(document).on('touchstart', selector, function(e) {
  134. touchStart = e.originalEvent.touches[0];
  135. });
  136. $(document).on('touchmove', selector, function(e) {
  137. const touches = e.originalEvent.touches;
  138. lastTouch = touches[touches.length - 1];
  139. });
  140. $(document).on('touchend', selector, function(e) {
  141. if (
  142. touchStart &&
  143. lastTouch &&
  144. Utils.calculateTouchDistance(touchStart, lastTouch) <= 20
  145. ) {
  146. e.preventDefault();
  147. const clickEvent = document.createEvent('MouseEvents');
  148. clickEvent.initEvent('click', true, true);
  149. e.target.dispatchEvent(clickEvent);
  150. }
  151. });
  152. },
  153. manageCustomUI() {
  154. Meteor.call('getCustomUI', (err, data) => {
  155. if (err && err.error[0] === 'var-not-exist') {
  156. Session.set('customUI', false); // siteId || address server not defined
  157. }
  158. if (!err) {
  159. Utils.setCustomUI(data);
  160. }
  161. });
  162. },
  163. setCustomUI(data) {
  164. const currentBoard = Boards.findOne(Session.get('currentBoard'));
  165. if (currentBoard) {
  166. DocHead.setTitle(`${currentBoard.title} - ${data.productName}`);
  167. } else {
  168. DocHead.setTitle(`${data.productName}`);
  169. }
  170. },
  171. setMatomo(data) {
  172. window._paq = window._paq || [];
  173. window._paq.push(['setDoNotTrack', data.doNotTrack]);
  174. if (data.withUserName) {
  175. window._paq.push(['setUserId', Meteor.user().username]);
  176. }
  177. window._paq.push(['trackPageView']);
  178. window._paq.push(['enableLinkTracking']);
  179. (function() {
  180. window._paq.push(['setTrackerUrl', `${data.address}piwik.php`]);
  181. window._paq.push(['setSiteId', data.siteId]);
  182. const script = document.createElement('script');
  183. Object.assign(script, {
  184. id: 'scriptMatomo',
  185. type: 'text/javascript',
  186. async: 'true',
  187. defer: 'true',
  188. src: `${data.address}piwik.js`,
  189. });
  190. const s = document.getElementsByTagName('script')[0];
  191. s.parentNode.insertBefore(script, s);
  192. })();
  193. Session.set('matomo', true);
  194. },
  195. manageMatomo() {
  196. const matomo = Session.get('matomo');
  197. if (matomo === undefined) {
  198. Meteor.call('getMatomoConf', (err, data) => {
  199. if (err && err.error[0] === 'var-not-exist') {
  200. Session.set('matomo', false); // siteId || address server not defined
  201. }
  202. if (!err) {
  203. Utils.setMatomo(data);
  204. }
  205. });
  206. } else if (matomo) {
  207. window._paq.push(['trackPageView']);
  208. }
  209. },
  210. getTriggerActionDesc(event, tempInstance) {
  211. const jqueryEl = tempInstance.$(event.currentTarget.parentNode);
  212. const triggerEls = jqueryEl.find('.trigger-content').children();
  213. let finalString = '';
  214. for (let i = 0; i < triggerEls.length; i++) {
  215. const element = tempInstance.$(triggerEls[i]);
  216. if (element.hasClass('trigger-text')) {
  217. finalString += element.text().toLowerCase();
  218. } else if (element.hasClass('user-details')) {
  219. let username = element.find('input').val();
  220. if (username === undefined || username === '') {
  221. username = '*';
  222. }
  223. finalString += `${element
  224. .find('.trigger-text')
  225. .text()
  226. .toLowerCase()} ${username}`;
  227. } else if (element.find('select').length > 0) {
  228. finalString += element
  229. .find('select option:selected')
  230. .text()
  231. .toLowerCase();
  232. } else if (element.find('input').length > 0) {
  233. let inputvalue = element.find('input').val();
  234. if (inputvalue === undefined || inputvalue === '') {
  235. inputvalue = '*';
  236. }
  237. finalString += inputvalue;
  238. }
  239. // Add space
  240. if (i !== length - 1) {
  241. finalString += ' ';
  242. }
  243. }
  244. return finalString;
  245. },
  246. };
  247. // A simple tracker dependency that we invalidate every time the window is
  248. // resized. This is used to reactively re-calculate the popup position in case
  249. // of a window resize. This is the equivalent of a "Signal" in some other
  250. // programming environments (eg, elm).
  251. $(window).on('resize', () => Utils.windowResizeDep.changed());