| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 | import { ReactiveCache } from '/imports/reactiveCache';Utils = {  setBackgroundImage(url) {    const currentBoard = Utils.getCurrentBoard();    if (currentBoard.backgroundImageURL !== undefined) {      $(".board-wrapper,.board-wrapper .board-canvas").css({"background":"url(" + currentBoard.backgroundImageURL + ")","background-size":"cover"});      $(".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body").css({"background-color":"transparent"});    } else if (currentBoard.color !== undefined) {      currentBoard.setColor(currentBoard.color);    }  },  /** returns the current board id   * <li> returns the current board id or the board id of the popup card if set   */  getCurrentBoardId() {    let popupCardBoardId = Session.get('popupCardBoardId');    let currentBoard = Session.get('currentBoard');    let ret = currentBoard;    if (popupCardBoardId) {      ret = popupCardBoardId;    }    return ret;  },  getCurrentCardId(ignorePopupCard) {    let ret = Session.get('currentCard');    if (!ret && !ignorePopupCard) {      ret = Utils.getPopupCardId();    }    return ret;  },  getPopupCardId() {    const ret = Session.get('popupCardId');    return ret;  },  getCurrentListId() {    const ret = Session.get('currentList');    return ret;  },  /** returns the current board   * <li> returns the current board or the board of the popup card if set   */  getCurrentBoard() {    const boardId = Utils.getCurrentBoardId();    const ret = ReactiveCache.getBoard(boardId);    return ret;  },  getCurrentCard(ignorePopupCard) {    const cardId = Utils.getCurrentCardId(ignorePopupCard);    const ret = ReactiveCache.getCard(cardId);    return ret;  },  getCurrentList() {    const listId = this.getCurrentListId();    let ret = null;    if (listId) {      ret = ReactiveCache.getList(listId);    }    return ret;  },  getCurrentSetting() {    const ret = ReactiveCache.getCurrentSetting();    return ret;  },  getPopupCard() {    const cardId = Utils.getPopupCardId();    const ret = ReactiveCache.getCard(cardId);    return ret;  },  canModifyCard() {    const currentUser = ReactiveCache.getCurrentUser();    const ret = (      currentUser &&      currentUser.isBoardMember() &&      !currentUser.isCommentOnly() &&      !currentUser.isWorker()    );    return ret;  },  canModifyBoard() {    const currentUser = ReactiveCache.getCurrentUser();    const ret = (      currentUser &&      currentUser.isBoardMember() &&      !currentUser.isCommentOnly()    );    return ret;  },  reload() {    // we move all window.location.reload calls into this function    // so we can disable it when running tests.    // This is because we are not allowed to override location.reload but    // we can override Utils.reload to prevent reload during tests.    window.location.reload();  },  setBoardView(view) {    currentUser = ReactiveCache.getCurrentUser();    if (currentUser) {      ReactiveCache.getCurrentUser().setBoardView(view);    } else if (view === 'board-view-swimlanes') {      window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true      Utils.reload();    } else if (view === 'board-view-lists') {      window.localStorage.setItem('boardView', 'board-view-lists'); //true      Utils.reload();    } else if (view === 'board-view-cal') {      window.localStorage.setItem('boardView', 'board-view-cal'); //true      Utils.reload();    } else {      window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true      Utils.reload();    }  },  unsetBoardView() {    window.localStorage.removeItem('boardView');    window.localStorage.removeItem('collapseSwimlane');  },  boardView() {    currentUser = ReactiveCache.getCurrentUser();    if (currentUser) {      return (currentUser.profile || {}).boardView;    } else if (      window.localStorage.getItem('boardView') === 'board-view-swimlanes'    ) {      return 'board-view-swimlanes';    } else if (      window.localStorage.getItem('boardView') === 'board-view-lists'    ) {      return 'board-view-lists';    } else if (window.localStorage.getItem('boardView') === 'board-view-cal') {      return 'board-view-cal';    } else {      window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true      Utils.reload();      return 'board-view-swimlanes';    }  },  myCardsSort() {    let sort = window.localStorage.getItem('myCardsSort');    if (!sort || !['board', 'dueAt'].includes(sort)) {      sort = 'board';    }    return sort;  },  myCardsSortToggle() {    if (this.myCardsSort() === 'board') {      this.setMyCardsSort('dueAt');    } else {      this.setMyCardsSort('board');    }  },  setMyCardsSort(sort) {    window.localStorage.setItem('myCardsSort', sort);    Utils.reload();  },  archivedBoardIds() {    const ret = ReactiveCache.getBoards({ archived: false }).map(board => board._id);    return ret;  },  dueCardsView() {    let view = window.localStorage.getItem('dueCardsView');    if (!view || !['me', 'all'].includes(view)) {      view = 'me';    }    return view;  },  setDueCardsView(view) {    window.localStorage.setItem('dueCardsView', view);    Utils.reload();  },  myCardsView() {    let view = window.localStorage.getItem('myCardsView');    if (!view || !['boards', 'table'].includes(view)) {      view = 'boards';    }    return view;  },  setMyCardsView(view) {    window.localStorage.setItem('myCardsView', view);    Utils.reload();  },  // XXX We should remove these two methods  goBoardId(_id) {    const board = ReactiveCache.getBoard(_id);    return (      board &&      FlowRouter.go('board', {        id: board._id,        slug: board.slug,      })    );  },  goCardId(_id) {    const card = ReactiveCache.getCard(_id);    const board = ReactiveCache.getBoard(card.boardId);    return (      board &&      FlowRouter.go('card', {        cardId: card._id,        boardId: board._id,        slug: board.slug,      })    );  },  getCommonAttachmentMetaFrom(card) {    const meta = {};    if (card.isLinkedCard()) {      meta.boardId = ReactiveCache.getCard(card.linkedId).boardId;      meta.cardId = card.linkedId;    } else {      meta.boardId = card.boardId;      meta.swimlaneId = card.swimlaneId;      meta.listId = card.listId;      meta.cardId = card._id;    }    return meta;  },  MAX_IMAGE_PIXEL: Meteor.settings.public.MAX_IMAGE_PIXEL,  COMPRESS_RATIO: Meteor.settings.public.IMAGE_COMPRESS_RATIO,  shrinkImage(options) {    // shrink image to certain size    const dataurl = options.dataurl,      callback = options.callback,      toBlob = options.toBlob;    let canvas = document.createElement('canvas'),      image = document.createElement('img');    const maxSize = options.maxSize || 1024;    const ratio = options.ratio || 1.0;    const next = function (result) {      image = null;      canvas = null;      if (typeof callback === 'function') {        callback(result);      }    };    image.onload = function () {      let width = this.width,        height = this.height;      let changed = false;      if (width > height) {        if (width > maxSize) {          height *= maxSize / width;          width = maxSize;          changed = true;        }      } else if (height > maxSize) {        width *= maxSize / height;        height = maxSize;        changed = true;      }      canvas.width = width;      canvas.height = height;      canvas.getContext('2d').drawImage(this, 0, 0, width, height);      if (changed === true) {        const type = 'image/jpeg';        if (toBlob) {          canvas.toBlob(next, type, ratio);        } else {          next(canvas.toDataURL(type, ratio));        }      } else {        next(changed);      }    };    image.onerror = function () {      next(false);    };    image.src = dataurl;  },  capitalize(string) {    return string.charAt(0).toUpperCase() + string.slice(1);  },  windowResizeDep: new Tracker.Dependency(),  // in fact, what we really care is screen size  // large mobile device like iPad or android Pad has a big screen, it should also behave like a desktop  // in a small window (even on desktop), Wekan run in compact mode.  // we can easily debug with a small window of desktop browser. :-)  isMiniScreen() {    // OLD WINDOW WIDTH DETECTION:    this.windowResizeDep.depend();    return $(window).width() <= 800;  },  isTouchScreen() {    // NEW TOUCH DEVICE DETECTION:    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent    var hasTouchScreen = false;    if ("maxTouchPoints" in navigator) {      hasTouchScreen = navigator.maxTouchPoints > 0;    } else if ("msMaxTouchPoints" in navigator) {      hasTouchScreen = navigator.msMaxTouchPoints > 0;    } else {      var mQ = window.matchMedia && matchMedia("(pointer:coarse)");      if (mQ && mQ.media === "(pointer:coarse)") {        hasTouchScreen = !!mQ.matches;      } else if ('orientation' in window) {        hasTouchScreen = true; // deprecated, but good fallback      } else {        // Only as a last resort, fall back to user agent sniffing        var UA = navigator.userAgent;        hasTouchScreen = (          /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||          /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)        );      }    }    return hasTouchScreen;  },  // returns if desktop drag handles are enabled  isShowDesktopDragHandles() {    //const currentUser = ReactiveCache.getCurrentUser();    //if (currentUser) {    //  return (currentUser.profile || {}).showDesktopDragHandles;    //} else if (window.localStorage.getItem('showDesktopDragHandles')) {    if (window.localStorage.getItem('showDesktopDragHandles')) {      return true;    } else {      return false;    }  },  // returns if mini screen or desktop drag handles  isTouchScreenOrShowDesktopDragHandles() {    //return this.isTouchScreen() || this.isShowDesktopDragHandles();    return this.isShowDesktopDragHandles();  },  calculateIndexData(prevData, nextData, nItems = 1) {    let base, increment;    // If we drop the card to an empty column    if (!prevData && !nextData) {      base = 0;      increment = 1;      // If we drop the card in the first position    } else if (!prevData) {      const nextSortIndex = nextData.sort;      const ceil = Math.ceil(nextSortIndex - 1);      if (ceil < nextSortIndex) {        increment = nextSortIndex - ceil;        base = nextSortIndex - increment;      } else {        base = nextData.sort - 1;        increment = -1;      }      // If we drop the card in the last position    } else if (!nextData) {      const prevSortIndex = prevData.sort;      const floor = Math.floor(prevSortIndex + 1);      if (floor > prevSortIndex) {        increment = prevSortIndex - floor;        base = prevSortIndex - increment;      } else {        base = prevData.sort + 1;        increment = 1;      }    }    // In the general case take the average of the previous and next element    // sort indexes.    else {      const prevSortIndex = prevData.sort;      const nextSortIndex = nextData.sort;      if (nItems == 1 ) {        if (prevSortIndex < 0 ) {          const ceil = Math.ceil(nextSortIndex - 1);          if (ceil < nextSortIndex && ceil > prevSortIndex) {            increment = ceil - prevSortIndex;          }        } else {          const floor = Math.floor(nextSortIndex - 1);          if (floor < nextSortIndex && floor > prevSortIndex) {            increment = floor - prevSortIndex;          }        }      }      if (!increment) {        increment = (nextSortIndex - prevSortIndex) / (nItems + 1);      }      if (!base) {        base = prevSortIndex + increment;      }    }    // XXX Return a generator that yield values instead of a base with a    // increment number.    return {      base,      increment,    };  },  // Determine the new sort index  calculateIndex(prevCardDomElement, nextCardDomElement, nCards = 1) {    let prevData = null;    let nextData = null;    if (prevCardDomElement) {      prevData = Blaze.getData(prevCardDomElement)    }    if (nextCardDomElement) {      nextData = Blaze.getData(nextCardDomElement);    }    const ret = Utils.calculateIndexData(prevData, nextData, nCards);    return ret;  },  manageCustomUI() {    Meteor.call('getCustomUI', (err, data) => {      if (err && err.error[0] === 'var-not-exist') {        Session.set('customUI', false); // siteId || address server not defined      }      if (!err) {        Utils.setCustomUI(data);      }    });  },  setCustomUI(data) {    const currentBoard = Utils.getCurrentBoard();    if (currentBoard) {      DocHead.setTitle(`${currentBoard.title} - ${data.productName}`);    } else {      DocHead.setTitle(`${data.productName}`);    }  },  setMatomo(data) {    window._paq = window._paq || [];    window._paq.push(['setDoNotTrack', data.doNotTrack]);    if (data.withUserName) {      window._paq.push(['setUserId', ReactiveCache.getCurrentUser().username]);    }    window._paq.push(['trackPageView']);    window._paq.push(['enableLinkTracking']);    (function () {      window._paq.push(['setTrackerUrl', `${data.address}piwik.php`]);      window._paq.push(['setSiteId', data.siteId]);      const script = document.createElement('script');      Object.assign(script, {        id: 'scriptMatomo',        type: 'text/javascript',        async: 'true',        defer: 'true',        src: `${data.address}piwik.js`,      });      const s = document.getElementsByTagName('script')[0];      s.parentNode.insertBefore(script, s);    })();    Session.set('matomo', true);  },  manageMatomo() {    const matomo = Session.get('matomo');    if (matomo === undefined) {      Meteor.call('getMatomoConf', (err, data) => {        if (err && err.error[0] === 'var-not-exist') {          Session.set('matomo', false); // siteId || address server not defined        }        if (!err) {          Utils.setMatomo(data);        }      });    } else if (matomo) {      window._paq.push(['trackPageView']);    }  },  getTriggerActionDesc(event, tempInstance) {    const jqueryEl = tempInstance.$(event.currentTarget.parentNode);    const triggerEls = jqueryEl.find('.trigger-content').children();    let finalString = '';    for (let i = 0; i < triggerEls.length; i++) {      const element = tempInstance.$(triggerEls[i]);      if (element.hasClass('trigger-text')) {        finalString += element.text().toLowerCase();      } else if (element.hasClass('user-details')) {        let username = element.find('input').val();        if (username === undefined || username === '') {          username = '*';        }        finalString += `${element          .find('.trigger-text')          .text()          .toLowerCase()} ${username}`;      } else if (element.find('select').length > 0) {        finalString += element          .find('select option:selected')          .text()          .toLowerCase();      } else if (element.find('input').length > 0) {        let inputvalue = element.find('input').val();        if (inputvalue === undefined || inputvalue === '') {          inputvalue = '*';        }        finalString += inputvalue;      }      // Add space      if (i !== length - 1) {        finalString += ' ';      }    }    return finalString;  },  fallbackCopyTextToClipboard(text) {    var textArea = document.createElement("textarea");    textArea.value = text;    // Avoid scrolling to bottom    textArea.style.top = "0";    textArea.style.left = "0";    textArea.style.position = "fixed";    document.body.appendChild(textArea);    textArea.focus();    textArea.select();    try {      document.execCommand('copy');      return Promise.resolve(true);    } catch (e) {      return Promise.reject(false);    } finally {      document.body.removeChild(textArea);    }  },  /** copy the text to the clipboard   * @see https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript/30810322#30810322   * @param string copy this text to the clipboard   * @return Promise   */  copyTextToClipboard(text) {    let ret;    if (navigator.clipboard) {      ret = navigator.clipboard.writeText(text).then(function () {      }, function (err) {        console.error('Async: Could not copy text: ', err);      });    } else {      ret = Utils.fallbackCopyTextToClipboard(text);    }    return ret;  },  /** show the "copied!" message   * @param promise the promise of Utils.copyTextToClipboard   * @param $tooltip jQuery tooltip element   */  showCopied(promise, $tooltip) {    if (promise) {      promise.then(() => {        $tooltip.show(100);        setTimeout(() => $tooltip.hide(100), 1000);      }, (err) => {        console.error("error: ", err);      });    }  },};// A simple tracker dependency that we invalidate every time the window is// resized. This is used to reactively re-calculate the popup position in case// of a window resize. This is the equivalent of a "Signal" in some other// programming environments (eg, elm).$(window).on('resize', () => Utils.windowResizeDep.changed());
 |