123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764 |
- 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;
- },
- // Zoom and mobile mode utilities
- getZoomLevel() {
- const user = ReactiveCache.getCurrentUser();
- if (user && user.profile && user.profile.zoomLevel !== undefined) {
- return user.profile.zoomLevel;
- }
- // For non-logged-in users, check localStorage
- const stored = localStorage.getItem('wekan-zoom-level');
- return stored ? parseFloat(stored) : 1.0;
- },
- setZoomLevel(level) {
- const user = ReactiveCache.getCurrentUser();
- if (user) {
- // Update user profile
- user.setZoomLevel(level);
- } else {
- // Store in localStorage for non-logged-in users
- localStorage.setItem('wekan-zoom-level', level.toString());
- }
- Utils.applyZoomLevel(level);
- // Trigger reactive updates for UI components
- Session.set('wekan-zoom-level', level);
- },
- getMobileMode() {
- const user = ReactiveCache.getCurrentUser();
- if (user && user.profile && user.profile.mobileMode !== undefined) {
- return user.profile.mobileMode;
- }
- // For non-logged-in users, check localStorage
- const stored = localStorage.getItem('wekan-mobile-mode');
- return stored ? stored === 'true' : false;
- },
- setMobileMode(enabled) {
- const user = ReactiveCache.getCurrentUser();
- if (user) {
- // Update user profile
- user.setMobileMode(enabled);
- } else {
- // Store in localStorage for non-logged-in users
- localStorage.setItem('wekan-mobile-mode', enabled.toString());
- }
- Utils.applyMobileMode(enabled);
- // Trigger reactive updates for UI components
- Session.set('wekan-mobile-mode', enabled);
- },
- applyZoomLevel(level) {
- const boardWrapper = document.querySelector('.board-wrapper');
- const body = document.body;
- const isMobileMode = body.classList.contains('mobile-mode');
- if (boardWrapper) {
- if (isMobileMode) {
- // On mobile mode, only apply zoom to text and icons, not the entire layout
- // Remove any existing transform from board-wrapper
- boardWrapper.style.transform = '';
- boardWrapper.style.transformOrigin = '';
- // Apply zoom to text and icon elements instead
- const textElements = boardWrapper.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, div, .minicard, .list-header-name, .board-header-btn, .fa, .icon');
- textElements.forEach(element => {
- element.style.transform = `scale(${level})`;
- element.style.transformOrigin = 'center';
- });
- // Reset board-canvas height
- const boardCanvas = document.querySelector('.board-canvas');
- if (boardCanvas) {
- boardCanvas.style.height = '';
- }
- } else {
- // Desktop mode: apply zoom to entire board-wrapper as before
- boardWrapper.style.transform = `scale(${level})`;
- boardWrapper.style.transformOrigin = 'top left';
- // If zoom is 50% or lower, make board wrapper full width like content
- if (level <= 0.5) {
- boardWrapper.style.width = '100%';
- boardWrapper.style.maxWidth = '100%';
- boardWrapper.style.margin = '0';
- } else {
- // Reset to normal width for higher zoom levels
- boardWrapper.style.width = '';
- boardWrapper.style.maxWidth = '';
- boardWrapper.style.margin = '';
- }
- // Adjust container height to prevent scroll issues
- const boardCanvas = document.querySelector('.board-canvas');
- if (boardCanvas) {
- boardCanvas.style.height = `${100 / level}%`;
- // For high zoom levels (200%+), enable both horizontal and vertical scrolling
- if (level >= 2.0) {
- boardCanvas.style.overflowX = 'auto';
- boardCanvas.style.overflowY = 'auto';
- // Ensure the content area can scroll both horizontally and vertically
- const content = document.querySelector('#content');
- if (content) {
- content.style.overflowX = 'auto';
- content.style.overflowY = 'auto';
- }
- } else {
- // Reset overflow for normal zoom levels
- boardCanvas.style.overflowX = '';
- boardCanvas.style.overflowY = '';
- const content = document.querySelector('#content');
- if (content) {
- content.style.overflowX = '';
- content.style.overflowY = '';
- }
- }
- }
- }
- }
- },
- applyMobileMode(enabled) {
- const body = document.body;
- if (enabled) {
- body.classList.add('mobile-mode');
- body.classList.remove('desktop-mode');
- } else {
- body.classList.add('desktop-mode');
- body.classList.remove('mobile-mode');
- }
- },
- initializeUserSettings() {
- // Apply saved settings on page load
- const zoomLevel = Utils.getZoomLevel();
- const mobileMode = Utils.getMobileMode();
- Utils.applyZoomLevel(zoomLevel);
- Utils.applyMobileMode(mobileMode);
- },
- 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() {
- this.windowResizeDep.depend();
- // Show mobile view when:
- // 1. Screen width is 800px or less (matches CSS media queries)
- // 2. Mobile phones in portrait mode
- // 3. iPad in very small screens (≤ 600px)
- const isSmallScreen = window.innerWidth <= 800;
- const isVerySmallScreen = window.innerWidth <= 600;
- const isPortrait = window.innerWidth < window.innerHeight || window.matchMedia("(orientation: portrait)").matches;
- const isMobilePhone = /Mobile|Android|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) && !/iPad/i.test(navigator.userAgent);
- const isIPhone = /iPhone|iPod/i.test(navigator.userAgent);
- const isIPad = /iPad/i.test(navigator.userAgent);
- const isUbuntuTouch = /Ubuntu/i.test(navigator.userAgent);
- // For iPhone: always show mobile view regardless of orientation
- // For other mobile phones: show mobile view in portrait, desktop view in landscape
- // For iPad: show mobile view only in very small screens (≤ 600px)
- // For Ubuntu Touch: smartphones behave like mobile phones, tablets like iPad
- // For desktop: show mobile view when screen width <= 800px
- if (isIPhone) {
- return true; // iPhone: always mobile view
- } else if (isMobilePhone) {
- return isPortrait; // Other mobile phones: portrait = mobile, landscape = desktop
- } else if (isIPad) {
- return isVerySmallScreen; // iPad: only very small screens get mobile view
- } else if (isUbuntuTouch) {
- // Ubuntu Touch: smartphones (≤ 600px) behave like mobile phones, tablets (> 600px) like iPad
- if (isVerySmallScreen) {
- return isPortrait; // Ubuntu Touch smartphone: portrait = mobile, landscape = desktop
- } else {
- return isVerySmallScreen; // Ubuntu Touch tablet: only very small screens get mobile view
- }
- } else {
- return isSmallScreen; // Desktop: based on 800px screen width
- }
- },
- 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() {
- if (this.isTouchScreen()) {
- return true;
- /*
- 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;
- */
- } else {
- return false;
- }
- },
- // returns if mini screen or desktop drag handles
- isTouchScreenOrShowDesktopDragHandles() {
- // Always enable drag handles for mobile screens (touch devices)
- return this.isTouchScreen() || this.isMiniScreen();
- //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());
|