diff --git a/client/components/forms/datepicker.js b/client/components/forms/datepicker.js new file mode 100644 index 000000000..c1b60025c --- /dev/null +++ b/client/components/forms/datepicker.js @@ -0,0 +1,16 @@ +import { + setupDatePicker, + datePickerRendered, + datePickerHelpers, + datePickerEvents, +} from '/client/lib/datepicker'; + +Template.datepicker.onCreated(function () { + setupDatePicker(this); +}); + +Template.datepicker.onRendered(function () { + datePickerRendered(this); +}); + +Template.datepicker.helpers(datePickerHelpers()); diff --git a/client/components/mixins/infiniteScrolling.js b/client/components/mixins/infiniteScrolling.js deleted file mode 100644 index 722774c48..000000000 --- a/client/components/mixins/infiniteScrolling.js +++ /dev/null @@ -1,34 +0,0 @@ -const peakAnticipation = 200; - -Mixins.InfiniteScrolling = BlazeComponent.extendComponent({ - onCreated() { - this._nextPeak = Infinity; - }, - - setNextPeak(v) { - this._nextPeak = v; - }, - - getNextPeak() { - return this._nextPeak; - }, - - resetNextPeak() { - this._nextPeak = Infinity; - }, - - events() { - return [ - { - scroll(evt) { - const domElement = evt.currentTarget; - let altitude = domElement.scrollTop + domElement.offsetHeight; - altitude += peakAnticipation; - if (altitude >= this.callFirstWith(null, 'getNextPeak')) { - this.mixinParent().callFirstWith(null, 'reachNextPeak'); - } - }, - }, - ]; - }, -}); diff --git a/client/lib/currentCard.js b/client/lib/currentCard.js new file mode 100644 index 000000000..7b33d5096 --- /dev/null +++ b/client/lib/currentCard.js @@ -0,0 +1,117 @@ +import Cards from '/models/cards'; + +function getCardIdFromData(data) { + if (!data || !data._id) { + return null; + } + + if ( + data.boardId || + data.listId || + typeof data.absoluteUrl === 'function' || + typeof data.getTitle === 'function' + ) { + return data._id; + } + + return null; +} + +function getCardIdFromElement(element) { + if (!element || typeof element.closest !== 'function') { + return null; + } + + const cardDetails = element.closest('.js-card-details'); + if (!cardDetails) { + return null; + } + + return getCardIdFromData(Blaze.getData(cardDetails)); +} + +function getCardIdFromParentData(maxDepth = 8) { + for (let depth = 1; depth <= maxDepth; depth += 1) { + try { + const cardId = getCardIdFromData(Template.parentData(depth)); + if (cardId) { + return cardId; + } + } catch (error) { + break; + } + } + + return null; +} + +function getPopupStack() { + if (typeof Popup !== 'undefined' && typeof Popup._getTopStack === 'function') { + return Popup._getTopStack(); + } + + return null; +} + +export function getCurrentCardIdFromContext({ ignorePopupCard = false } = {}) { + let cardId; + + try { + cardId = getCardIdFromData(Template.currentData()); + if (cardId) { + return cardId; + } + } catch (error) { + // No active Blaze view. + } + + cardId = getCardIdFromParentData(); + if (cardId) { + return cardId; + } + + const popupStack = getPopupStack(); + + cardId = getCardIdFromData(popupStack?.dataContext); + if (cardId) { + return cardId; + } + + cardId = getCardIdFromElement(popupStack?.openerElement); + if (cardId) { + return cardId; + } + + cardId = getCardIdFromElement(document.activeElement); + if (cardId) { + return cardId; + } + + cardId = Session.get('currentCard'); + if (cardId) { + return cardId; + } + + if (!ignorePopupCard) { + cardId = Session.get('popupCardId'); + if (cardId) { + return cardId; + } + } + + const cardDetails = document.querySelectorAll('.js-card-details'); + if (cardDetails.length === 1) { + return getCardIdFromElement(cardDetails[0]); + } + + return null; +} + +export function getCurrentCardFromContext(options) { + const cardId = getCurrentCardIdFromContext(options); + if (!cardId) { + return null; + } + + return Cards.findOne(cardId); +} diff --git a/client/lib/infiniteScrolling.js b/client/lib/infiniteScrolling.js new file mode 100644 index 000000000..fd0aa1d39 --- /dev/null +++ b/client/lib/infiniteScrolling.js @@ -0,0 +1,47 @@ +const PEAK_ANTICIPATION = 200; + +/** + * Infinite scrolling utility to replace the BlazeComponent mixin. + * + * Usage in a Template: + * Template.myTemplate.onCreated(function () { + * this.infiniteScrolling = new InfiniteScrolling(); + * }); + * + * The scroll event must be wired in Template.events: + * 'scroll .my-container'(event, tpl) { + * tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => { + * tpl.loadNextPage(); + * }); + * }, + * + * Or for components that delegate to a child for loading: + * tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => { + * activitiesComponent.loadNextPage(); + * }); + */ +export class InfiniteScrolling { + constructor() { + this._nextPeak = Infinity; + } + + setNextPeak(v) { + this._nextPeak = v; + } + + getNextPeak() { + return this._nextPeak; + } + + resetNextPeak() { + this._nextPeak = Infinity; + } + + checkScrollPosition(domElement, reachNextPeakCallback) { + let altitude = domElement.scrollTop + domElement.offsetHeight; + altitude += PEAK_ANTICIPATION; + if (altitude >= this._nextPeak) { + reachNextPeakCallback(); + } + } +} diff --git a/client/lib/mixins.js b/client/lib/mixins.js deleted file mode 100644 index 8d16be539..000000000 --- a/client/lib/mixins.js +++ /dev/null @@ -1 +0,0 @@ -Mixins = {};