123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- import { ReactiveCache } from '/imports/reactiveCache';
- Utils = {
- setBackgroundImage(url) {
- const currentBoard = Utils.getCurrentBoard();
- if (currentBoard.backgroundImageURL !== undefined) {
- $(".board-wrapper").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"});
- $(".minicard").css({"opacity": "0.9"});
- } else if (currentBoard["background-color"]) {
- currentBoard.setColor(currentBoard["background-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;
- },
- 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());
|