diff options
author | dizzy <diosmosis@users.noreply.github.com> | 2021-11-05 08:39:33 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-05 08:39:33 +0300 |
commit | 2b94200db7e08389466859377c1b4732207673a2 (patch) | |
tree | e2f8ed241b13b1792e4f7e18d80622c77859dea8 /plugins/CoreHome/vue | |
parent | 0917a39fc686ac2d5e349b3bcc5266598ce59ee7 (diff) |
[Vue] migrate comparisons service + component (#18193)
* migrating RateFeature and ReviewLinks + adding AjaxHelper.fetch utility method (all untested)
* get ratefeature component to work, modify matomodialog component to use v-model, add event parameters to createAngularAdapter, allow translate to use variadic args or one string array + rebuild
* remove ratefeature angularjs files
* rebuild + make vue mapping property optional in createANgularJsAdapter
* migrate enrichedheadline and get to work
* fix test
* fix translate
* fix another translate issue & migrate contentblock directive
* fix anchor links, not including the "/" causes angularjs to fail (also on 4.x-dev)
* update expected screenshots
* fix ui test
* fix some test failures
* fix nested transclude issue
* remove content block files
* fix icon spacing that occurs due to angularjs inserting empty comments in between nodes while vue 3 does not
* update some screenshots
* update screenshot (actually fixes an alignment issue)
* update screenshot
* first pass at converting comparisons service/component
* get new code to build and load without error in the UI
* debugging
* getting basic functionaltiy to work
* fix UI test failure + URL encoding/angularjs issue causing back button to not work
* using ref in setup() is not needed to access this.$refs
* Convert comparisons service angularjs tests to comparison store typescript tests.
* built vue files
* rewrite URL handling to use computed properties in a URL store + do the same for other dependent data in the comparison store to allow vues to subscribe to the properties for changes to global state
* fix some tests
* some more fixes
* more fixes + disallow modifications to MatomoUrl state
* get angularjs unit tests to pass + fix a couple more issues
* another fix
* fix bad merge
* self review + fixes
* remove old fix as it may not be needed anymore
* empty string is not a valid date + do not report invalid date exception just rethrow
* update screenshots and try to fix random failure
* use jquery $destroy event instead of scope one since the scope one is broadcasted
* update expected screenshot
Diffstat (limited to 'plugins/CoreHome/vue')
26 files changed, 2610 insertions, 962 deletions
diff --git a/plugins/CoreHome/vue/dist/CoreHome.umd.js b/plugins/CoreHome/vue/dist/CoreHome.umd.js index b7c0b0f0db..6dc2a61e5d 100644 --- a/plugins/CoreHome/vue/dist/CoreHome.umd.js +++ b/plugins/CoreHome/vue/dist/CoreHome.umd.js @@ -134,7 +134,7 @@ __webpack_require__.d(__webpack_exports__, "ActivityIndicator", function() { ret __webpack_require__.d(__webpack_exports__, "translate", function() { return /* reexport */ translate; }); __webpack_require__.d(__webpack_exports__, "alertAdapter", function() { return /* reexport */ Alert_adapter; }); __webpack_require__.d(__webpack_exports__, "AjaxHelper", function() { return /* reexport */ AjaxHelper_AjaxHelper; }); -__webpack_require__.d(__webpack_exports__, "MatomoUrl", function() { return /* reexport */ MatomoUrl_MatomoUrl; }); +__webpack_require__.d(__webpack_exports__, "MatomoUrl", function() { return /* reexport */ src_MatomoUrl_MatomoUrl; }); __webpack_require__.d(__webpack_exports__, "Matomo", function() { return /* reexport */ Matomo_Matomo; }); __webpack_require__.d(__webpack_exports__, "Periods", function() { return /* reexport */ Periods_Periods; }); __webpack_require__.d(__webpack_exports__, "Day", function() { return /* reexport */ Day_DayPeriod; }); @@ -149,6 +149,7 @@ __webpack_require__.d(__webpack_exports__, "todayIsInRange", function() { return __webpack_require__.d(__webpack_exports__, "MatomoDialog", function() { return /* reexport */ MatomoDialog; }); __webpack_require__.d(__webpack_exports__, "EnrichedHeadline", function() { return /* reexport */ EnrichedHeadline; }); __webpack_require__.d(__webpack_exports__, "ContentBlock", function() { return /* reexport */ ContentBlock; }); +__webpack_require__.d(__webpack_exports__, "Comparisons", function() { return /* reexport */ Comparisons; }); // CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js // This file is imported into lib/wc client bundles. @@ -166,54 +167,12 @@ if (typeof window !== 'undefined') { // Indicate to webpack that this file can be concatenated /* harmony default export */ var setPublicPath = (null); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -/** - * Similar to angulars $location but works around some limitation. Use it if you need to access - * search params - */ -const MatomoUrl = { - getSearchParam(paramName) { - const hash = window.location.href.split('#'); - const regex = new RegExp(`${paramName}(\\[]|=)`); - - if (hash && hash[1] && regex.test(decodeURIComponent(hash[1]))) { - const valueFromHash = window.broadcast.getValueFromHash(paramName, window.location.href); // for date, period and idsite fall back to parameter from url, if non in hash was provided - - if (valueFromHash || paramName !== 'date' && paramName !== 'period' && paramName !== 'idSite') { - return valueFromHash; - } - } - - return window.broadcast.getValueFromUrl(paramName, window.location.search); - } - -}; -/* harmony default export */ var MatomoUrl_MatomoUrl = (MatomoUrl); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.adapter.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - +// EXTERNAL MODULE: ./plugins/CoreHome/vue/src/noAdblockFlag.ts +var noAdblockFlag = __webpack_require__("2342"); -function piwikUrl() { - const model = { - getSearchParam: MatomoUrl_MatomoUrl.getSearchParam.bind(MatomoUrl_MatomoUrl) - }; - return model; -} +// EXTERNAL MODULE: external {"commonjs":"vue","commonjs2":"vue","root":"Vue"} +var external_commonjs_vue_commonjs2_vue_root_Vue_ = __webpack_require__("8bbf"); -piwikUrl.$inject = []; -angular.module('piwikApp.service').service('piwikUrl', piwikUrl); // CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/Periods.ts function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -301,6 +260,91 @@ class Periods { } /* harmony default export */ var Periods_Periods = (new Periods()); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Matomo/Matomo.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +let originalTitle; +const { + piwik: Matomo_piwik, + broadcast: Matomo_broadcast, + piwikHelper: Matomo_piwikHelper +} = window; +Matomo_piwik.helper = Matomo_piwikHelper; +Matomo_piwik.broadcast = Matomo_broadcast; + +Matomo_piwik.updateDateInTitle = function updateDateInTitle(date, period) { + if (!$('.top_controls #periodString').length) { + return; + } // Cache server-rendered page title + + + originalTitle = originalTitle || document.title; + + if (originalTitle.indexOf(Matomo_piwik.siteName) === 0) { + const dateString = ` - ${Periods_Periods.parse(period, date).getPrettyString()} `; + document.title = `${Matomo_piwik.siteName}${dateString}${originalTitle.substr(Matomo_piwik.siteName.length)}`; + } +}; + +Matomo_piwik.hasUserCapability = function hasUserCapability(capability) { + return window.angular.isArray(Matomo_piwik.userCapabilities) && Matomo_piwik.userCapabilities.indexOf(capability) !== -1; +}; + +Matomo_piwik.on = function addMatomoEventListener(eventName, listener) { + function listenerWrapper(evt) { + listener(...evt.detail); // eslint-disable-line + } + + listener.wrapper = listenerWrapper; + window.addEventListener(eventName, listenerWrapper); +}; + +Matomo_piwik.off = function removeMatomoEventListener(eventName, listener) { + if (listener.wrapper) { + window.removeEventListener(eventName, listener.wrapper); + } +}; + +Matomo_piwik.postEventNoEmit = function postEventNoEmit(eventName, ...args // eslint-disable-line +) { + const event = new CustomEvent(eventName, { + detail: args + }); + window.dispatchEvent(event); +}; + +Matomo_piwik.postEvent = function postMatomoEvent(eventName, ...args // eslint-disable-line +) { + Matomo_piwik.postEventNoEmit(eventName, ...args); // required until angularjs is removed + + const $rootScope = Matomo_piwik.helper.getAngularDependency('$rootScope'); // eslint-disable-line + + return $rootScope.$oldEmit(eventName, ...args); +}; + +const Matomo = Matomo_piwik; +/* harmony default export */ var Matomo_Matomo = (Matomo); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/translate.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +function translate(translationStringId, ...values) { + let pkArgs = values; // handle variadic args AND single array of values (to match _pk_translate signature) + + if (values.length === 1 && values[0] && values[0] instanceof Array) { + [pkArgs] = values; + } + + return window._pk_translate(translationStringId, pkArgs); // eslint-disable-line +} // CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/utilities.ts /*! * Matomo - free/libre analytics platform @@ -329,7 +373,11 @@ function parseDate(date) { return date; } - const strDate = decodeURIComponent(date); + const strDate = decodeURIComponent(date).trim(); + + if (strDate === '') { + throw new Error('Invalid date, empty string.'); + } if (strDate === 'today' || strDate === 'now') { return getToday(); @@ -361,13 +409,7 @@ function parseDate(date) { return lastYear; } - try { - return $.datepicker.parseDate('yy-mm-dd', strDate); - } catch (err) { - // angular swallows this error, so manual console log here - console.error(err.message || err); - throw err; - } + return $.datepicker.parseDate('yy-mm-dd', strDate); } function todayIsInRange(dateRange) { if (dateRange.length !== 2) { @@ -380,7 +422,9 @@ function todayIsInRange(dateRange) { return false; } -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Matomo/Matomo.ts +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/Range.ts +function Range_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + /*! * Matomo - free/libre analytics platform * @@ -390,112 +434,204 @@ function todayIsInRange(dateRange) { -let originalTitle; -const { - piwik, - broadcast: Matomo_broadcast, - piwikHelper: Matomo_piwikHelper -} = window; -piwik.helper = Matomo_piwikHelper; -piwik.broadcast = Matomo_broadcast; +class Range_RangePeriod { + constructor(startDate, endDate, childPeriodType) { + Range_defineProperty(this, "startDate", void 0); -function isValidPeriod(periodStr, dateStr) { - try { - Periods_Periods.parse(periodStr, dateStr); - return true; - } catch (e) { - return false; - } -} + Range_defineProperty(this, "endDate", void 0); -piwik.updatePeriodParamsFromUrl = function updatePeriodParamsFromUrl() { - let date = MatomoUrl_MatomoUrl.getSearchParam('date'); - const period = MatomoUrl_MatomoUrl.getSearchParam('period'); + Range_defineProperty(this, "childPeriodType", void 0); - if (!isValidPeriod(period, date)) { - // invalid data in URL - return; + this.startDate = startDate; + this.endDate = endDate; + this.childPeriodType = childPeriodType; } + /** + * Returns a range representing the last N childPeriodType periods, including the current one. + */ - if (piwik.period === period && piwik.currentDateString === date) { - // this period / date is already loaded - return; - } - piwik.period = period; - const dateRange = Periods_Periods.parse(period, date).getDateRange(); - piwik.startDateString = format(dateRange[0]); - piwik.endDateString = format(dateRange[1]); - piwik.updateDateInTitle(date, period); // do not set anything to previousN/lastN, as it's more useful to plugins - // to have the dates than previousN/lastN. + static getLastNRange(childPeriodType, strAmount, strEndDate) { + const nAmount = Math.max(parseInt(strAmount.toString(), 10) - 1, 0); + + if (Number.isNaN(nAmount)) { + throw new Error('Invalid range strAmount'); + } + + let endDate = strEndDate ? parseDate(strEndDate) : getToday(); + let startDate = new Date(endDate.getTime()); + + if (childPeriodType === 'day') { + startDate.setDate(startDate.getDate() - nAmount); + } else if (childPeriodType === 'week') { + startDate.setDate(startDate.getDate() - nAmount * 7); + } else if (childPeriodType === 'month') { + startDate.setDate(1); + startDate.setMonth(startDate.getMonth() - nAmount); + } else if (childPeriodType === 'year') { + startDate.setFullYear(startDate.getFullYear() - nAmount); + } else { + throw new Error(`Unknown period type '${childPeriodType}'.`); + } + + if (childPeriodType !== 'day') { + const startPeriod = Periods_Periods.periods[childPeriodType].parse(startDate); + const endPeriod = Periods_Periods.periods[childPeriodType].parse(endDate); + [startDate] = startPeriod.getDateRange(); + [, endDate] = endPeriod.getDateRange(); + } + + const firstWebsiteDate = new Date(1991, 7, 6); + + if (startDate.getTime() - firstWebsiteDate.getTime() < 0) { + switch (childPeriodType) { + case 'year': + startDate = new Date(1992, 0, 1); + break; + + case 'month': + startDate = new Date(1991, 8, 1); + break; - if (piwik.period === 'range') { - date = `${piwik.startDateString},${piwik.endDateString}`; + case 'week': + startDate = new Date(1991, 8, 12); + break; + + case 'day': + default: + startDate = firstWebsiteDate; + break; + } + } + + return new Range_RangePeriod(startDate, endDate, childPeriodType); } + /** + * Returns a range representing a specific child date range counted back from the end date + * + * @param childPeriodType Type of the period, eg. day, week, year + * @param rangeEndDate + * @param countBack Return only the child date range for this specific period number + * @returns {RangePeriod} + */ - piwik.currentDateString = date; -}; -piwik.updateDateInTitle = function updateDateInTitle(date, period) { - if (!$('.top_controls #periodString').length) { - return; - } // Cache server-rendered page title + static getLastNRangeChild(childPeriodType, rangeEndDate, countBack) { + const ed = rangeEndDate ? parseDate(rangeEndDate) : getToday(); + let startDate = new Date(ed.getTime()); + let endDate = new Date(ed.getTime()); + if (childPeriodType === 'day') { + startDate.setDate(startDate.getDate() - countBack); + endDate.setDate(endDate.getDate() - countBack); + } else if (childPeriodType === 'week') { + startDate.setDate(startDate.getDate() - countBack * 7); + endDate.setDate(endDate.getDate() - countBack * 7); + } else if (childPeriodType === 'month') { + startDate.setDate(1); + startDate.setMonth(startDate.getMonth() - countBack); + endDate.setDate(1); + endDate.setMonth(endDate.getMonth() - countBack); + } else if (childPeriodType === 'year') { + startDate.setFullYear(startDate.getFullYear() - countBack); + endDate.setFullYear(endDate.getFullYear() - countBack); + } else { + throw new Error(`Unknown period type '${childPeriodType}'.`); + } - originalTitle = originalTitle || document.title; + if (childPeriodType !== 'day') { + const startPeriod = Periods_Periods.periods[childPeriodType].parse(startDate); + const endPeriod = Periods_Periods.periods[childPeriodType].parse(endDate); + [startDate] = startPeriod.getDateRange(); + [, endDate] = endPeriod.getDateRange(); + } - if (originalTitle.indexOf(piwik.siteName) === 0) { - const dateString = ` - ${Periods_Periods.parse(period, date).getPrettyString()} `; - document.title = `${piwik.siteName}${dateString}${originalTitle.substr(piwik.siteName.length)}`; + const firstWebsiteDate = new Date(1991, 7, 6); + + if (startDate.getTime() - firstWebsiteDate.getTime() < 0) { + switch (childPeriodType) { + case 'year': + startDate = new Date(1992, 0, 1); + break; + + case 'month': + startDate = new Date(1991, 8, 1); + break; + + case 'week': + startDate = new Date(1991, 8, 12); + break; + + case 'day': + default: + startDate = firstWebsiteDate; + break; + } + } + + return new Range_RangePeriod(startDate, endDate, childPeriodType); } -}; -piwik.hasUserCapability = function hasUserCapability(capability) { - return window.angular.isArray(piwik.userCapabilities) && piwik.userCapabilities.indexOf(capability) !== -1; -}; + static parse(strDate, childPeriodType = 'day') { + if (/^previous/.test(strDate)) { + const endDate = Range_RangePeriod.getLastNRange(childPeriodType, '2').startDate; + return Range_RangePeriod.getLastNRange(childPeriodType, strDate.substring(8), endDate); + } -const Matomo = piwik; -/* harmony default export */ var Matomo_Matomo = (Matomo); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Matomo/Matomo.adapter.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ + if (/^last/.test(strDate)) { + return Range_RangePeriod.getLastNRange(childPeriodType, strDate.substring(4)); + } + const parts = decodeURIComponent(strDate).split(','); + return new Range_RangePeriod(parseDate(parts[0]), parseDate(parts[1]), childPeriodType); + } -function piwikService() { - return Matomo_Matomo; -} + static getDisplayText() { + return translate('General_DateRangeInPeriodList'); + } -angular.module('piwikApp.service').service('piwik', piwikService); + getPrettyString() { + const start = format(this.startDate); + const end = format(this.endDate); + return translate('General_DateRangeFromTo', [start, end]); + } -function initPiwikService(piwik, $rootScope) { - $rootScope.$on('$locationChangeSuccess', piwik.updatePeriodParamsFromUrl); -} + getDateRange() { + return [this.startDate, this.endDate]; + } -initPiwikService.$inject = ['piwik', '$rootScope']; -angular.module('piwikApp.service').run(initPiwikService); -// EXTERNAL MODULE: ./plugins/CoreHome/vue/src/noAdblockFlag.ts -var noAdblockFlag = __webpack_require__("2342"); + containsToday() { + return todayIsInRange(this.getDateRange()); + } -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/translate.ts +} +Periods_Periods.addCustomPeriod('range', Range_RangePeriod); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/Periods.adapter.ts /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -function translate(translationStringId, ...values) { - let pkArgs = values; // handle variadic args AND single array of values (to match _pk_translate signature) - if (values.length === 1 && values[0] && values[0] instanceof Array) { - [pkArgs] = values; - } - return window._pk_translate(translationStringId, pkArgs); // eslint-disable-line + +window.piwik.addCustomPeriod = Periods_Periods.addCustomPeriod.bind(Periods_Periods); + +function piwikPeriods() { + return { + getAllLabels: Periods_Periods.getAllLabels.bind(Periods_Periods), + isRecognizedPeriod: Periods_Periods.isRecognizedPeriod.bind(Periods_Periods), + get: Periods_Periods.get.bind(Periods_Periods), + parse: Periods_Periods.parse.bind(Periods_Periods), + parseDate: parseDate, + format: format, + RangePeriod: Range_RangePeriod, + todayIsInRange: todayIsInRange + }; } + +window.angular.module('piwikApp.service').factory('piwikPeriods', piwikPeriods); // CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/Day.ts function Day_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -681,9 +817,7 @@ class Year_YearPeriod { } Periods_Periods.addCustomPeriod('year', Year_YearPeriod); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/Range.ts -function Range_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/index.ts /*! * Matomo - free/libre analytics platform * @@ -693,179 +827,137 @@ function Range_defineProperty(obj, key, value) { if (key in obj) { Object.define -class Range_RangePeriod { - constructor(startDate, endDate, childPeriodType) { - Range_defineProperty(this, "startDate", void 0); - - Range_defineProperty(this, "endDate", void 0); - - Range_defineProperty(this, "childPeriodType", void 0); - this.startDate = startDate; - this.endDate = endDate; - this.childPeriodType = childPeriodType; - } - /** - * Returns a range representing the last N childPeriodType periods, including the current one. - */ - static getLastNRange(childPeriodType, strAmount, strEndDate) { - const nAmount = Math.max(parseInt(strAmount.toString(), 10) - 1, 0); - if (Number.isNaN(nAmount)) { - throw new Error('Invalid range strAmount'); - } - let endDate = strEndDate ? parseDate(strEndDate) : getToday(); - let startDate = new Date(endDate.getTime()); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.ts +function MatomoUrl_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - if (childPeriodType === 'day') { - startDate.setDate(startDate.getDate() - nAmount); - } else if (childPeriodType === 'week') { - startDate.setDate(startDate.getDate() - nAmount * 7); - } else if (childPeriodType === 'month') { - startDate.setDate(1); - startDate.setMonth(startDate.getMonth() - nAmount); - } else if (childPeriodType === 'year') { - startDate.setFullYear(startDate.getFullYear() - nAmount); - } else { - throw new Error(`Unknown period type '${childPeriodType}'.`); - } +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ - if (childPeriodType !== 'day') { - const startPeriod = Periods_Periods.periods[childPeriodType].parse(startDate); - const endPeriod = Periods_Periods.periods[childPeriodType].parse(endDate); - [startDate] = startPeriod.getDateRange(); - [, endDate] = endPeriod.getDateRange(); - } - const firstWebsiteDate = new Date(1991, 7, 6); + // important to load all periods here - if (startDate.getTime() - firstWebsiteDate.getTime() < 0) { - switch (childPeriodType) { - case 'year': - startDate = new Date(1992, 0, 1); - break; +const { + piwik: MatomoUrl_piwik, + broadcast: MatomoUrl_broadcast +} = window; - case 'month': - startDate = new Date(1991, 8, 1); - break; +function isValidPeriod(periodStr, dateStr) { + try { + Periods_Periods.parse(periodStr, dateStr); + return true; + } catch (e) { + return false; + } +} +/** + * URL store and helper functions. + */ - case 'week': - startDate = new Date(1991, 8, 12); - break; - case 'day': - default: - startDate = firstWebsiteDate; - break; - } - } +class MatomoUrl_MatomoUrl { + constructor() { + MatomoUrl_defineProperty(this, "urlQuery", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])('')); - return new Range_RangePeriod(startDate, endDate, childPeriodType); - } - /** - * Returns a range representing a specific child date range counted back from the end date - * - * @param childPeriodType Type of the period, eg. day, week, year - * @param rangeEndDate - * @param countBack Return only the child date range for this specific period number - * @returns {RangePeriod} - */ + MatomoUrl_defineProperty(this, "hashQuery", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])('')); + MatomoUrl_defineProperty(this, "urlParsed", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(MatomoUrl_broadcast.getValuesFromUrl(`?${this.urlQuery.value}`, true)))); - static getLastNRangeChild(childPeriodType, rangeEndDate, countBack) { - const ed = rangeEndDate ? parseDate(rangeEndDate) : getToday(); - let startDate = new Date(ed.getTime()); - let endDate = new Date(ed.getTime()); + MatomoUrl_defineProperty(this, "hashParsed", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(MatomoUrl_broadcast.getValuesFromUrl(`?${this.hashQuery.value}`, true)))); - if (childPeriodType === 'day') { - startDate.setDate(startDate.getDate() - countBack); - endDate.setDate(endDate.getDate() - countBack); - } else if (childPeriodType === 'week') { - startDate.setDate(startDate.getDate() - countBack * 7); - endDate.setDate(endDate.getDate() - countBack * 7); - } else if (childPeriodType === 'month') { - startDate.setDate(1); - startDate.setMonth(startDate.getMonth() - countBack); - endDate.setDate(1); - endDate.setMonth(endDate.getMonth() - countBack); - } else if (childPeriodType === 'year') { - startDate.setFullYear(startDate.getFullYear() - countBack); - endDate.setFullYear(endDate.getFullYear() - countBack); - } else { - throw new Error(`Unknown period type '${childPeriodType}'.`); - } + MatomoUrl_defineProperty(this, "parsed", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])({ ...this.urlParsed.value, + ...this.hashParsed.value + }))); - if (childPeriodType !== 'day') { - const startPeriod = Periods_Periods.periods[childPeriodType].parse(startDate); - const endPeriod = Periods_Periods.periods[childPeriodType].parse(endDate); - [startDate] = startPeriod.getDateRange(); - [, endDate] = endPeriod.getDateRange(); - } + this.setUrlQuery(window.location.search); + this.setHashQuery(window.location.hash); // $locationChangeSuccess is triggered before angularjs changes actual window the hash, so we + // have to hook into this method if we want our event handlers to execute before other angularjs + // handlers (like the reporting page one) - const firstWebsiteDate = new Date(1991, 7, 6); + Matomo_Matomo.on('$locationChangeSuccess', absUrl => { + const url = new URL(absUrl); + this.setUrlQuery(url.search.replace(/^\?/, '')); + this.setHashQuery(url.hash.replace(/^#/, '')); + }); + this.updatePeriodParamsFromUrl(); + } - if (startDate.getTime() - firstWebsiteDate.getTime() < 0) { - switch (childPeriodType) { - case 'year': - startDate = new Date(1992, 0, 1); - break; + updateHash(params) { + const serializedParams = typeof params !== 'string' ? this.stringify(params) : params; + const $location = Matomo_Matomo.helper.getAngularDependency('$location'); + $location.search(serializedParams); + } - case 'month': - startDate = new Date(1991, 8, 1); - break; + getSearchParam(paramName) { + const hash = window.location.href.split('#'); + const regex = new RegExp(`${paramName}(\\[]|=)`); - case 'week': - startDate = new Date(1991, 8, 12); - break; + if (hash && hash[1] && regex.test(decodeURIComponent(hash[1]))) { + const valueFromHash = window.broadcast.getValueFromHash(paramName, window.location.href); // for date, period and idsite fall back to parameter from url, if non in hash was provided - case 'day': - default: - startDate = firstWebsiteDate; - break; + if (valueFromHash || paramName !== 'date' && paramName !== 'period' && paramName !== 'idSite') { + return valueFromHash; } } - return new Range_RangePeriod(startDate, endDate, childPeriodType); + return window.broadcast.getValueFromUrl(paramName, window.location.search); } - static parse(strDate, childPeriodType = 'day') { - if (/^previous/.test(strDate)) { - const endDate = Range_RangePeriod.getLastNRange(childPeriodType, '2').startDate; - return Range_RangePeriod.getLastNRange(childPeriodType, strDate.substring(8), endDate); + stringify(search) { + // TODO: using $ since URLSearchParams does not handle array params the way Matomo uses them + return $.param(search).replace(/%5B%5D/g, '[]'); + } + + updatePeriodParamsFromUrl() { + let date = this.getSearchParam('date'); + const period = this.getSearchParam('period'); + + if (!isValidPeriod(period, date)) { + // invalid data in URL + return; } - if (/^last/.test(strDate)) { - return Range_RangePeriod.getLastNRange(childPeriodType, strDate.substring(4)); + if (MatomoUrl_piwik.period === period && MatomoUrl_piwik.currentDateString === date) { + // this period / date is already loaded + return; } - const parts = decodeURIComponent(strDate).split(','); - return new Range_RangePeriod(parseDate(parts[0]), parseDate(parts[1]), childPeriodType); - } + MatomoUrl_piwik.period = period; + const dateRange = Periods_Periods.parse(period, date).getDateRange(); + MatomoUrl_piwik.startDateString = format(dateRange[0]); + MatomoUrl_piwik.endDateString = format(dateRange[1]); + MatomoUrl_piwik.updateDateInTitle(date, period); // do not set anything to previousN/lastN, as it's more useful to plugins + // to have the dates than previousN/lastN. - static getDisplayText() { - return translate('General_DateRangeInPeriodList'); - } + if (MatomoUrl_piwik.period === 'range') { + date = `${MatomoUrl_piwik.startDateString},${MatomoUrl_piwik.endDateString}`; + } - getPrettyString() { - const start = format(this.startDate); - const end = format(this.endDate); - return translate('General_DateRangeFromTo', [start, end]); + MatomoUrl_piwik.currentDateString = date; } - getDateRange() { - return [this.startDate, this.endDate]; + setUrlQuery(search) { + this.urlQuery.value = search.replace(/^\?/, ''); } - containsToday() { - return todayIsInRange(this.getDateRange()); + setHashQuery(hash) { + this.hashQuery.value = hash.replace(/^[#/?]+/, ''); } } -Periods_Periods.addCustomPeriod('range', Range_RangePeriod); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/Periods.adapter.ts + +const instance = new MatomoUrl_MatomoUrl(); +/* harmony default export */ var src_MatomoUrl_MatomoUrl = (instance); +MatomoUrl_piwik.updatePeriodParamsFromUrl = instance.updatePeriodParamsFromUrl.bind(instance); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.adapter.ts /*! * Matomo - free/libre analytics platform * @@ -874,23 +966,50 @@ Periods_Periods.addCustomPeriod('range', Range_RangePeriod); */ +function piwikUrl() { + const model = { + getSearchParam: src_MatomoUrl_MatomoUrl.getSearchParam.bind(src_MatomoUrl_MatomoUrl) + }; + return model; +} -window.piwik.addCustomPeriod = Periods_Periods.addCustomPeriod.bind(Periods_Periods); +piwikUrl.$inject = []; +angular.module('piwikApp.service').service('piwikUrl', piwikUrl); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Matomo/Matomo.adapter.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ -function piwikPeriods() { - return { - getAllLabels: Periods_Periods.getAllLabels.bind(Periods_Periods), - isRecognizedPeriod: Periods_Periods.isRecognizedPeriod.bind(Periods_Periods), - get: Periods_Periods.get.bind(Periods_Periods), - parse: Periods_Periods.parse.bind(Periods_Periods), - parseDate: parseDate, - format: format, - RangePeriod: Range_RangePeriod, - todayIsInRange: todayIsInRange + +function piwikService() { + return Matomo_Matomo; +} + +window.angular.module('piwikApp.service').service('piwik', piwikService); + +function initPiwikService(piwik, $rootScope) { + // overwrite $rootScope so all events also go through Matomo.postEvent(...) too. + $rootScope.$oldEmit = $rootScope.$emit; // eslint-disable-line + + $rootScope.$emit = function emitWrapper(name, ...args) { + return Matomo_Matomo.postEvent(name, ...args); + }; + + $rootScope.$oldBroadcast = $rootScope.$broadcast; // eslint-disable-line + + $rootScope.$broadcast = function broadcastWrapper(name, ...args) { + Matomo_Matomo.postEventNoEmit(name, ...args); + return $rootScope.$oldBroadcast(name, ...args); // eslint-disable-line }; + + $rootScope.$on('$locationChangeSuccess', piwik.updatePeriodParamsFromUrl); } -angular.module('piwikApp.service').factory('piwikPeriods', piwikPeriods); +initPiwikService.$inject = ['piwik', '$rootScope']; +window.angular.module('piwikApp.service').run(initPiwikService); // CONCATENATED MODULE: ./plugins/CoreHome/vue/src/AjaxHelper/AjaxHelper.ts function AjaxHelper_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } @@ -939,6 +1058,12 @@ function defaultErrorCallback(deferred, status) { return; } + if (typeof Piwik_Popover === 'undefined') { + console.log(`Request failed: ${deferred.responseText}`); // mostly for tests + + return; + } + const loadingError = $('#loadingError'); if (Piwik_Popover.isOpen() && deferred && deferred.status === 500) { @@ -1062,18 +1187,14 @@ class AjaxHelper_AjaxHelper { * Adds params to the request. * If params are given more then once, the latest given value is used for the request * - * @param params + * @param initialParams * @param type type of given parameters (POST or GET) * @return {void} */ - addParams(params, type) { - if (typeof params === 'string') { - // TODO: add global types for broadcast (multiple uses below) - params = window['broadcast'].getValuesFromUrl(params); // eslint-disable-line - } - + addParams(initialParams, type) { + const params = typeof initialParams === 'string' ? window.broadcast.getValuesFromUrl(initialParams) : initialParams; const arrayParams = ['compareSegments', 'comparePeriods', 'compareDates']; Object.keys(params).forEach(key => { const value = params[key]; @@ -1108,7 +1229,7 @@ class AjaxHelper_AjaxHelper { setBulkRequests(...urls) { - const urlsProcessed = urls.map(u => $.param(u)); + const urlsProcessed = urls.map(u => typeof u === 'string' ? u : $.param(u)); this.addParams({ module: 'API', method: 'API.getBulkRequest', @@ -1261,8 +1382,15 @@ class AjaxHelper_AjaxHelper { } this.requestHandle = this.buildAjaxCall(); - globalAjaxQueue.push(this.requestHandle); - return this.requestHandle; + window.globalAjaxQueue.push(this.requestHandle); + return new Promise((resolve, reject) => { + this.requestHandle.then(resolve).fail(xhr => { + if (xhr.statusText !== 'abort') { + console.log(`Warning: the ${$.param(this.getParams)} request failed!`); + reject(xhr); + } + }); + }); } /** * Aborts the current request if it is (still) running @@ -1308,11 +1436,11 @@ class AjaxHelper_AjaxHelper { url, dataType: this.format || 'json', complete: this.completeCallback, - error: function errorCallback() { - globalAjaxQueue.active -= 1; + error: function errorCallback(...args) { + window.globalAjaxQueue.active -= 1; if (self.errorCallback) { - self.errorCallback.apply(this, arguments); // eslint-disable-line + self.errorCallback.apply(this, args); } }, success: (response, status, request) => { @@ -1346,7 +1474,7 @@ class AjaxHelper_AjaxHelper { this.callback(response, status, request); } - globalAjaxQueue.active -= 1; + window.globalAjaxQueue.active -= 1; if (Matomo_Matomo.ajaxRequestFinished) { Matomo_Matomo.ajaxRequestFinished(); @@ -1400,9 +1528,9 @@ class AjaxHelper_AjaxHelper { mixinDefaultGetParams(originalParams) { - const segment = MatomoUrl_MatomoUrl.getSearchParam('segment'); + const segment = src_MatomoUrl_MatomoUrl.getSearchParam('segment'); const defaultParams = { - idSite: Matomo_Matomo.idSite || broadcast.getValueFromUrl('idSite'), + idSite: Matomo_Matomo.idSite ? Matomo_Matomo.idSite.toString() : broadcast.getValueFromUrl('idSite'), period: Matomo_Matomo.period || broadcast.getValueFromUrl('period'), segment }; @@ -1436,155 +1564,7 @@ function ajaxQueue() { } angular.module('piwikApp.service').service('globalAjaxQueue', ajaxQueue); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -/** - * Similar to angulars $location but works around some limitation. Use it if you need to access - * search params - */ -const PiwikUrl = { - getSearchParam(paramName) { - const hash = window.location.href.split('#'); - const regex = new RegExp(`${paramName}(\\[]|=)`); - - if (hash && hash[1] && regex.test(decodeURIComponent(hash[1]))) { - const valueFromHash = window.broadcast.getValueFromHash(paramName, window.location.href); // for date, period and idsite fall back to parameter from url, if non in hash was provided - - if (valueFromHash || paramName !== 'date' && paramName !== 'period' && paramName !== 'idSite') { - return valueFromHash; - } - } - - return window.broadcast.getValueFromUrl(paramName, window.location.search); - } - -}; -/* harmony default export */ var PiwikUrl_PiwikUrl = (PiwikUrl); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.adapter.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - - -function PiwikUrl_adapter_piwikUrl() { - const model = { - getSearchParam: PiwikUrl_PiwikUrl.getSearchParam.bind(PiwikUrl_PiwikUrl) - }; - return model; -} - -PiwikUrl_adapter_piwikUrl.$inject = []; -angular.module('piwikApp.service').service('piwikUrl', PiwikUrl_adapter_piwikUrl); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Piwik/Piwik.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - - - -let Piwik_originalTitle; -const { - piwik: Piwik_piwik, - broadcast: Piwik_broadcast, - piwikHelper: Piwik_piwikHelper -} = window; -Piwik_piwik.helper = Piwik_piwikHelper; -Piwik_piwik.broadcast = Piwik_broadcast; - -function Piwik_isValidPeriod(periodStr, dateStr) { - try { - Periods_Periods.parse(periodStr, dateStr); - return true; - } catch (e) { - return false; - } -} - -Piwik_piwik.updatePeriodParamsFromUrl = function updatePeriodParamsFromUrl() { - let date = PiwikUrl_PiwikUrl.getSearchParam('date'); - const period = PiwikUrl_PiwikUrl.getSearchParam('period'); - - if (!Piwik_isValidPeriod(period, date)) { - // invalid data in URL - return; - } - - if (Piwik_piwik.period === period && Piwik_piwik.currentDateString === date) { - // this period / date is already loaded - return; - } - - Piwik_piwik.period = period; - const dateRange = Periods_Periods.parse(period, date).getDateRange(); - Piwik_piwik.startDateString = format(dateRange[0]); - Piwik_piwik.endDateString = format(dateRange[1]); - Piwik_piwik.updateDateInTitle(date, period); // do not set anything to previousN/lastN, as it's more useful to plugins - // to have the dates than previousN/lastN. - - if (Piwik_piwik.period === 'range') { - date = `${Piwik_piwik.startDateString},${Piwik_piwik.endDateString}`; - } - - Piwik_piwik.currentDateString = date; -}; - -Piwik_piwik.updateDateInTitle = function updateDateInTitle(date, period) { - if (!$('.top_controls #periodString').length) { - return; - } // Cache server-rendered page title - - - Piwik_originalTitle = Piwik_originalTitle || document.title; - - if (Piwik_originalTitle.indexOf(Piwik_piwik.siteName) === 0) { - const dateString = ` - ${Periods_Periods.parse(period, date).getPrettyString()} `; - document.title = `${Piwik_piwik.siteName}${dateString}${Piwik_originalTitle.substr(Piwik_piwik.siteName.length)}`; - } -}; - -Piwik_piwik.hasUserCapability = function hasUserCapability(capability) { - return window.angular.isArray(Piwik_piwik.userCapabilities) && Piwik_piwik.userCapabilities.indexOf(capability) !== -1; -}; - -const Piwik = Piwik_piwik; -/* harmony default export */ var Piwik_Piwik = (Piwik); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Piwik/Piwik.adapter.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - - -function Piwik_adapter_piwikService() { - return Piwik_Piwik; -} - -angular.module('piwikApp.service').service('piwik', Piwik_adapter_piwikService); - -function Piwik_adapter_initPiwikService(piwik, $rootScope) { - $rootScope.$on('$locationChangeSuccess', piwik.updatePeriodParamsFromUrl); -} - -Piwik_adapter_initPiwikService.$inject = ['piwik', '$rootScope']; -angular.module('piwikApp.service').run(Piwik_adapter_initPiwikService); -// EXTERNAL MODULE: external {"commonjs":"vue","commonjs2":"vue","root":"Vue"} -var external_commonjs_vue_commonjs2_vue_root_Vue_ = __webpack_require__("8bbf"); - -// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue?vue&type=template&id=50fda6d8 +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue?vue&type=template&id=15ad69b4 const _hoisted_1 = { ref: "root" @@ -1592,7 +1572,7 @@ const _hoisted_1 = { function render(_ctx, _cache, $props, $setup, $data, $options) { return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withDirectives"])((Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", _hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderSlot"])(_ctx.$slots, "default")], 512)), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.modelValue]]); } -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue?vue&type=template&id=50fda6d8 +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue?vue&type=template&id=15ad69b4 // CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/@vue/cli-plugin-typescript/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-3!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue?vue&type=script&lang=ts @@ -1621,13 +1601,6 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { }, emits: ['yes', 'no', 'closeEnd', 'close', 'update:modelValue'], - setup() { - const root = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null); - return { - root - }; - }, - activated() { this.$emit('update:modelValue', false); }, @@ -1691,7 +1664,8 @@ function createAngularJsAdapter(options) { transclude, mountPointFactory, postCreate, - noScope + noScope, + restrict = 'A' } = options; const currentTranscludeCounter = transcludeCounter; @@ -1712,7 +1686,7 @@ function createAngularJsAdapter(options) { function angularJsAdapter(...injectedServices) { const adapter = { - restrict: 'A', + restrict, scope: noScope ? undefined : angularJsScope, compile: function angularJsAdapterCompile() { return { @@ -1885,9 +1859,9 @@ function createAngularJsAdapter(options) { }, noScope: true })); -// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue?vue&type=template&id=b71c9b1c +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue?vue&type=template&id=5653b0bd -const EnrichedHeadlinevue_type_template_id_b71c9b1c_hoisted_1 = { +const EnrichedHeadlinevue_type_template_id_5653b0bd_hoisted_1 = { key: 0, class: "title", tabindex: "6" @@ -1918,7 +1892,7 @@ const _hoisted_11 = { }; const _hoisted_12 = ["innerHTML"]; const _hoisted_13 = ["href"]; -function EnrichedHeadlinevue_type_template_id_b71c9b1c_render(_ctx, _cache, $props, $setup, $data, $options) { +function EnrichedHeadlinevue_type_template_id_5653b0bd_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_RateFeature = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("RateFeature"); return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { @@ -1926,7 +1900,7 @@ function EnrichedHeadlinevue_type_template_id_b71c9b1c_render(_ctx, _cache, $pro onMouseenter: _cache[1] || (_cache[1] = $event => _ctx.showIcons = true), onMouseleave: _cache[2] || (_cache[2] = $event => _ctx.showIcons = false), ref: "root" - }, [!_ctx.editUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", EnrichedHeadlinevue_type_template_id_b71c9b1c_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderSlot"])(_ctx.$slots, "default")])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.editUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("a", { + }, [!_ctx.editUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", EnrichedHeadlinevue_type_template_id_5653b0bd_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderSlot"])(_ctx.$slots, "default")])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.editUrl ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("a", { key: 1, class: "title", href: _ctx.editUrl, @@ -1957,7 +1931,7 @@ function EnrichedHeadlinevue_type_template_id_b71c9b1c_render(_ctx, _cache, $pro href: _ctx.helpUrl }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_MoreDetails')), 9, _hoisted_13)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)], 512), [[external_commonjs_vue_commonjs2_vue_root_Vue_["vShow"], _ctx.showInlineHelp]])], 544); } -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue?vue&type=template&id=b71c9b1c +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue?vue&type=template&id=5653b0bd // CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/@vue/cli-plugin-typescript/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-3!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue?vue&type=script&lang=ts @@ -2032,13 +2006,6 @@ const RateFeature = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["define }; }, - setup() { - const root = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null); - return { - root - }; - }, - watch: { inlineHelp(newValue) { this.actualInlineHelp = newValue; @@ -2100,7 +2067,7 @@ const RateFeature = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["define -EnrichedHeadlinevue_type_script_lang_ts.render = EnrichedHeadlinevue_type_template_id_b71c9b1c_render +EnrichedHeadlinevue_type_script_lang_ts.render = EnrichedHeadlinevue_type_template_id_5653b0bd_render /* harmony default export */ var EnrichedHeadline = (EnrichedHeadlinevue_type_script_lang_ts); // CONCATENATED MODULE: ./plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.adapter.ts @@ -2134,39 +2101,39 @@ EnrichedHeadlinevue_type_script_lang_ts.render = EnrichedHeadlinevue_type_templa directiveName: 'piwikEnrichedHeadline', transclude: true })); -// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue?vue&type=template&id=5f6844c4 +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue?vue&type=template&id=09ef9e02 -const ContentBlockvue_type_template_id_5f6844c4_hoisted_1 = { +const ContentBlockvue_type_template_id_09ef9e02_hoisted_1 = { class: "card", ref: "root" }; -const ContentBlockvue_type_template_id_5f6844c4_hoisted_2 = { +const ContentBlockvue_type_template_id_09ef9e02_hoisted_2 = { class: "card-content" }; -const ContentBlockvue_type_template_id_5f6844c4_hoisted_3 = { +const ContentBlockvue_type_template_id_09ef9e02_hoisted_3 = { key: 0, class: "card-title" }; -const ContentBlockvue_type_template_id_5f6844c4_hoisted_4 = { +const ContentBlockvue_type_template_id_09ef9e02_hoisted_4 = { key: 1, class: "card-title" }; -const ContentBlockvue_type_template_id_5f6844c4_hoisted_5 = { +const ContentBlockvue_type_template_id_09ef9e02_hoisted_5 = { ref: "content" }; -function ContentBlockvue_type_template_id_5f6844c4_render(_ctx, _cache, $props, $setup, $data, $options) { +function ContentBlockvue_type_template_id_09ef9e02_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_EnrichedHeadline = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["resolveComponent"])("EnrichedHeadline"); - return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", ContentBlockvue_type_template_id_5f6844c4_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", ContentBlockvue_type_template_id_5f6844c4_hoisted_2, [_ctx.contentTitle && !_ctx.actualFeature && !_ctx.helpUrl && !_ctx.actualHelpText ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", ContentBlockvue_type_template_id_5f6844c4_hoisted_3, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.contentTitle), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.contentTitle && (_ctx.actualFeature || _ctx.helpUrl || _ctx.actualHelpText) ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", ContentBlockvue_type_template_id_5f6844c4_hoisted_4, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_EnrichedHeadline, { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", ContentBlockvue_type_template_id_09ef9e02_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", ContentBlockvue_type_template_id_09ef9e02_hoisted_2, [_ctx.contentTitle && !_ctx.actualFeature && !_ctx.helpUrl && !_ctx.actualHelpText ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", ContentBlockvue_type_template_id_09ef9e02_hoisted_3, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.contentTitle), 1)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), _ctx.contentTitle && (_ctx.actualFeature || _ctx.helpUrl || _ctx.actualHelpText) ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("h2", ContentBlockvue_type_template_id_09ef9e02_hoisted_4, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createVNode"])(_component_EnrichedHeadline, { "feature-name": _ctx.actualFeature, "help-url": _ctx.helpUrl, "inline-help": _ctx.actualHelpText }, { default: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["withCtx"])(() => [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.contentTitle), 1)]), _: 1 - }, 8, ["feature-name", "help-url", "inline-help"])])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", ContentBlockvue_type_template_id_5f6844c4_hoisted_5, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderSlot"])(_ctx.$slots, "default")], 512)])], 512); + }, 8, ["feature-name", "help-url", "inline-help"])])) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", ContentBlockvue_type_template_id_09ef9e02_hoisted_5, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderSlot"])(_ctx.$slots, "default")], 512)])], 512); } -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue?vue&type=template&id=5f6844c4 +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue?vue&type=template&id=09ef9e02 // CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/@vue/cli-plugin-typescript/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-3!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue?vue&type=script&lang=ts @@ -2191,15 +2158,6 @@ let adminContent = null; }; }, - setup() { - const root = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null); - const content = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["ref"])(null); - return { - root, - content - }; - }, - watch: { feature(newValue) { this.actualFeature = newValue; @@ -2269,7 +2227,7 @@ let adminContent = null; -ContentBlockvue_type_script_lang_ts.render = ContentBlockvue_type_template_id_5f6844c4_render +ContentBlockvue_type_script_lang_ts.render = ContentBlockvue_type_template_id_09ef9e02_render /* harmony default export */ var ContentBlock = (ContentBlockvue_type_script_lang_ts); // CONCATENATED MODULE: ./plugins/CoreHome/vue/src/ContentBlock/ContentBlock.adapter.ts @@ -2303,6 +2261,636 @@ ContentBlockvue_type_script_lang_ts.render = ContentBlockvue_type_template_id_5f directiveName: 'piwikContentBlock', transclude: true })); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Segmentation/Segments.store.ts +function Segments_store_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + + + +class Segments_store_SegmentsStore { + get state() { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(this.segmentState); + } + + constructor() { + Segments_store_defineProperty(this, "segmentState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({ + availableSegments: [] + })); + + Matomo_Matomo.on('piwikSegmentationInited', () => this.setSegmentState()); + } + + setSegmentState() { + try { + const uiControlObject = $('.segmentEditorPanel').data('uiControlObject'); + this.segmentState.availableSegments = uiControlObject.impl.availableSegments || []; + } catch (e) {// segment editor is not initialized yet + } + } + +} + +/* harmony default export */ var Segments_store = (new Segments_store_SegmentsStore()); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts +function Comparisons_store_defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + + + + + + + +const SERIES_COLOR_COUNT = 8; +const SERIES_SHADE_COUNT = 3; + +function wrapArray(values) { + if (!values) { + return []; + } + + return values instanceof Array ? values : [values]; +} + +class Comparisons_store_ComparisonsStore { + // for tests + constructor() { + Comparisons_store_defineProperty(this, "privateState", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["reactive"])({ + comparisonsDisabledFor: [] + })); + + Comparisons_store_defineProperty(this, "state", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["readonly"])(this.privateState)); + + Comparisons_store_defineProperty(this, "colors", {}); + + Comparisons_store_defineProperty(this, "segmentComparisons", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => this.parseSegmentComparisons())); + + Comparisons_store_defineProperty(this, "periodComparisons", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => this.parsePeriodComparisons())); + + Comparisons_store_defineProperty(this, "isEnabled", Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => this.checkEnabledForCurrentPage())); + + this.loadComparisonsDisabledFor(); + $(() => { + this.colors = this.getAllSeriesColors(); + }); + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["watch"])(() => this.getComparisons(), () => Matomo_Matomo.postEvent('piwikComparisonsChanged'), { + deep: true + }); + } + + getComparisons() { + return this.getSegmentComparisons().concat(this.getPeriodComparisons()); + } + + isComparing() { + return this.isComparisonEnabled() // first two in each array are for the currently selected segment/period + && (this.segmentComparisons.value.length > 1 || this.periodComparisons.value.length > 1); + } + + isComparingPeriods() { + return this.getPeriodComparisons().length > 1; // first is currently selected period + } + + getSegmentComparisons() { + if (!this.isComparisonEnabled()) { + return []; + } + + return this.segmentComparisons.value; + } + + getPeriodComparisons() { + if (!this.isComparisonEnabled()) { + return []; + } + + return this.periodComparisons.value; + } + + getSeriesColor(segmentComparison, periodComparison, metricIndex = 0) { + const seriesIndex = this.getComparisonSeriesIndex(periodComparison.index, segmentComparison.index) % SERIES_COLOR_COUNT; + + if (metricIndex === 0) { + return this.colors[`series${seriesIndex}`]; + } + + const shadeIndex = metricIndex % SERIES_SHADE_COUNT; + return this.colors[`series${seriesIndex}-shade${shadeIndex}`]; + } + + getSeriesColorName(seriesIndex, metricIndex) { + let colorName = `series${seriesIndex % SERIES_COLOR_COUNT}`; + + if (metricIndex > 0) { + colorName += `-shade${metricIndex % SERIES_SHADE_COUNT}`; + } + + return colorName; + } + + isComparisonEnabled() { + return this.isEnabled.value; + } + + getIndividualComparisonRowIndices(seriesIndex) { + const segmentCount = this.getSegmentComparisons().length; + const segmentIndex = seriesIndex % segmentCount; + const periodIndex = Math.floor(seriesIndex / segmentCount); + return { + segmentIndex, + periodIndex + }; + } + + getComparisonSeriesIndex(periodIndex, segmentIndex) { + const segmentCount = this.getSegmentComparisons().length; + return periodIndex * segmentCount + segmentIndex; + } + + getAllComparisonSeries() { + const seriesInfo = []; + let seriesIndex = 0; + this.getPeriodComparisons().forEach(periodComp => { + this.getSegmentComparisons().forEach(segmentComp => { + seriesInfo.push({ + index: seriesIndex, + params: { ...segmentComp.params, + ...periodComp.params + }, + color: this.colors[`series${seriesIndex}`] + }); + seriesIndex += 1; + }); + }); + return seriesInfo; + } + + removeSegmentComparison(index) { + if (!this.isComparisonEnabled()) { + throw new Error('Comparison disabled.'); + } + + const newComparisons = [...this.segmentComparisons.value]; + newComparisons.splice(index, 1); + const extraParams = {}; + + if (index === 0) { + extraParams.segment = newComparisons[0].params.segment; + } + + this.updateQueryParamsFromComparisons(newComparisons, this.periodComparisons.value, extraParams); + } + + addSegmentComparison(params) { + if (!this.isComparisonEnabled()) { + throw new Error('Comparison disabled.'); + } + + const newComparisons = this.segmentComparisons.value.concat([{ + params, + index: -1, + title: '' + }]); + this.updateQueryParamsFromComparisons(newComparisons, this.periodComparisons.value); + } + + updateQueryParamsFromComparisons(segmentComparisons, periodComparisons, extraParams = {}) { + // get unique segments/periods/dates from new Comparisons + const compareSegments = {}; + const comparePeriodDatePairs = {}; + let firstSegment = false; + let firstPeriod = false; + segmentComparisons.forEach(comparison => { + if (firstSegment) { + compareSegments[comparison.params.segment] = true; + } else { + firstSegment = true; + } + }); + periodComparisons.forEach(comparison => { + if (firstPeriod) { + comparePeriodDatePairs[`${comparison.params.period}|${comparison.params.date}`] = true; + } else { + firstPeriod = true; + } + }); + const comparePeriods = []; + const compareDates = []; + Object.keys(comparePeriodDatePairs).forEach(pair => { + const parts = pair.split('|'); + comparePeriods.push(parts[0]); + compareDates.push(parts[1]); + }); + const compareParams = { + compareSegments: Object.keys(compareSegments), + comparePeriods, + compareDates + }; // change the page w/ these new param values + + if (Matomo_Matomo.helper.isAngularRenderingThePage()) { + const search = src_MatomoUrl_MatomoUrl.hashParsed.value; + const newSearch = { ...search, + ...compareParams, + ...extraParams + }; + delete newSearch['compareSegments[]']; + delete newSearch['comparePeriods[]']; + delete newSearch['compareDates[]']; + + if (JSON.stringify(newSearch) !== JSON.stringify(search)) { + src_MatomoUrl_MatomoUrl.updateHash(newSearch); + } + + return; + } + + const paramsToRemove = []; + ['compareSegments', 'comparePeriods', 'compareDates'].forEach(name => { + if (!compareParams[name].length) { + paramsToRemove.push(name); + } + }); // angular is not rendering the page (ie, we are in the embedded dashboard) or we need to change + // the segment + + const url = src_MatomoUrl_MatomoUrl.stringify(extraParams); + const strHash = src_MatomoUrl_MatomoUrl.stringify(compareParams); + window.broadcast.propagateNewPage(url, undefined, strHash, paramsToRemove); + } + + getAllSeriesColors() { + const { + ColorManager + } = Matomo_Matomo; + const seriesColorNames = []; + + for (let i = 0; i < SERIES_COLOR_COUNT; i += 1) { + seriesColorNames.push(`series${i}`); + + for (let j = 0; j < SERIES_SHADE_COUNT; j += 1) { + seriesColorNames.push(`series${i}-shade${j}`); + } + } + + return ColorManager.getColors('comparison-series-color', seriesColorNames); + } + + loadComparisonsDisabledFor() { + AjaxHelper_AjaxHelper.fetch({ + module: 'API', + method: 'API.getPagesComparisonsDisabledFor' + }).then(result => { + this.privateState.comparisonsDisabledFor = result; + }); + } + + parseSegmentComparisons() { + const { + availableSegments + } = Segments_store.state; + const compareSegments = [...wrapArray(src_MatomoUrl_MatomoUrl.parsed.value.compareSegments)]; // add base comparisons + + compareSegments.unshift(src_MatomoUrl_MatomoUrl.parsed.value.segment || ''); + const newSegmentComparisons = []; + compareSegments.forEach((segment, idx) => { + let storedSegment; + availableSegments.forEach(s => { + if (s.definition === segment || s.definition === decodeURIComponent(segment) || decodeURIComponent(s.definition) === segment) { + storedSegment = s; + } + }); + let segmentTitle = storedSegment ? storedSegment.name : translate('General_Unknown'); + + if (segment.trim() === '') { + segmentTitle = translate('SegmentEditor_DefaultAllVisits'); + } + + newSegmentComparisons.push({ + params: { + segment + }, + title: Matomo_Matomo.helper.htmlDecode(segmentTitle), + index: idx + }); + }); + return newSegmentComparisons; + } + + parsePeriodComparisons() { + const comparePeriods = [...wrapArray(src_MatomoUrl_MatomoUrl.parsed.value.comparePeriods)]; + const compareDates = [...wrapArray(src_MatomoUrl_MatomoUrl.parsed.value.compareDates)]; + comparePeriods.unshift(src_MatomoUrl_MatomoUrl.parsed.value.period); + compareDates.unshift(src_MatomoUrl_MatomoUrl.parsed.value.date); + const newPeriodComparisons = []; + + for (let i = 0; i < Math.min(compareDates.length, comparePeriods.length); i += 1) { + let title; + + try { + title = Periods_Periods.parse(comparePeriods[i], compareDates[i]).getPrettyString(); + } catch (e) { + title = translate('General_Error'); + } + + newPeriodComparisons.push({ + params: { + date: compareDates[i], + period: comparePeriods[i] + }, + title, + index: i + }); + } + + return newPeriodComparisons; + } + + checkEnabledForCurrentPage() { + // category/subcategory is not included on top bar pages, so in that case we use module/action + const category = src_MatomoUrl_MatomoUrl.parsed.value.category || src_MatomoUrl_MatomoUrl.parsed.value.module; + const subcategory = src_MatomoUrl_MatomoUrl.parsed.value.subcategory || src_MatomoUrl_MatomoUrl.parsed.value.action; + const id = `${category}.${subcategory}`; + const isEnabled = this.privateState.comparisonsDisabledFor.indexOf(id) === -1 && this.privateState.comparisonsDisabledFor.indexOf(`${category}.*`) === -1; + document.documentElement.classList.toggle('comparisonsDisabled', !isEnabled); + return isEnabled; + } + +} +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Comparisons/Comparisons.store.instance.ts + +/* harmony default export */ var Comparisons_store_instance = (new Comparisons_store_ComparisonsStore()); +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/Comparisons/Comparisons.vue?vue&type=template&id=1b8ecdd2 + +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_1 = { + key: 0, + ref: "root", + class: "matomo-comparisons" +}; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_2 = { + class: "comparison-type" +}; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_3 = ["title"]; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_4 = ["href"]; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_5 = ["title"]; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_6 = { + class: "comparison-period-label" +}; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_7 = ["onClick"]; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_8 = ["title"]; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_9 = { + class: "loadingPiwik", + style: { + "display": "none" + } +}; +const Comparisonsvue_type_template_id_1b8ecdd2_hoisted_10 = ["alt"]; +function Comparisonsvue_type_template_id_1b8ecdd2_render(_ctx, _cache, $props, $setup, $data, $options) { + return _ctx.isComparing ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", Comparisonsvue_type_template_id_1b8ecdd2_hoisted_1, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("h3", null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Comparisons')), 1), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.segmentComparisons, (comparison, $index) => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + class: "comparison card", + key: comparison.index + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Comparisonsvue_type_template_id_1b8ecdd2_hoisted_2, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_Segment')), 1), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", { + class: "title", + title: comparison.title + '<br/>' + decodeURIComponent(comparison.params.segment) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("a", { + target: "_blank", + href: _ctx.getUrlToSegment(comparison.params.segment) + }, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(comparison.title), 9, Comparisonsvue_type_template_id_1b8ecdd2_hoisted_4)], 8, Comparisonsvue_type_template_id_1b8ecdd2_hoisted_3), (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(true), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])(external_commonjs_vue_commonjs2_vue_root_Vue_["Fragment"], null, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["renderList"])(_ctx.periodComparisons, periodComparison => { + return Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("div", { + class: "comparison-period", + key: periodComparison.index, + title: _ctx.getComparisonTooltip(comparison, periodComparison) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "comparison-dot", + style: Object(external_commonjs_vue_commonjs2_vue_root_Vue_["normalizeStyle"])({ + 'background-color': _ctx.getSeriesColor(comparison, periodComparison) + }) + }, null, 4), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", Comparisonsvue_type_template_id_1b8ecdd2_hoisted_6, Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(periodComparison.title) + " (" + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.getComparisonPeriodType(periodComparison)) + ") ", 1)], 8, Comparisonsvue_type_template_id_1b8ecdd2_hoisted_5); + }), 128)), _ctx.segmentComparisons.length > 1 ? (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["openBlock"])(), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementBlock"])("a", { + key: 0, + class: "remove-button", + onClick: $event => _ctx.removeSegmentComparison($index) + }, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("span", { + class: "icon icon-close", + title: _ctx.translate('General_ClickToRemoveComp') + }, null, 8, Comparisonsvue_type_template_id_1b8ecdd2_hoisted_8)], 8, Comparisonsvue_type_template_id_1b8ecdd2_hoisted_7)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true)]); + }), 128)), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("div", Comparisonsvue_type_template_id_1b8ecdd2_hoisted_9, [Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createElementVNode"])("img", { + src: "plugins/Morpheus/images/loading-blue.gif", + alt: _ctx.translate('General_LoadingData') + }, null, 8, Comparisonsvue_type_template_id_1b8ecdd2_hoisted_10), Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createTextVNode"])(" " + Object(external_commonjs_vue_commonjs2_vue_root_Vue_["toDisplayString"])(_ctx.translate('General_LoadingData')), 1)])], 512)) : Object(external_commonjs_vue_commonjs2_vue_root_Vue_["createCommentVNode"])("", true); +} +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Comparisons/Comparisons.vue?vue&type=template&id=1b8ecdd2 + +// CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-typescript/node_modules/cache-loader/dist/cjs.js??ref--14-0!./node_modules/@vue/cli-plugin-typescript/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-plugin-typescript/node_modules/ts-loader??ref--14-3!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/Comparisons/Comparisons.vue?vue&type=script&lang=ts + + + + + + +/* harmony default export */ var Comparisonsvue_type_script_lang_ts = (Object(external_commonjs_vue_commonjs2_vue_root_Vue_["defineComponent"])({ + props: {}, + + data() { + return { + comparisonTooltips: null + }; + }, + + setup() { + // accessing has to be done through a computed property so we can use the computed + // instance directly in the template. unfortunately, vue won't register to changes. + const isComparing = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Comparisons_store_instance.isComparing()); + const segmentComparisons = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Comparisons_store_instance.getSegmentComparisons()); + const periodComparisons = Object(external_commonjs_vue_commonjs2_vue_root_Vue_["computed"])(() => Comparisons_store_instance.getPeriodComparisons()); + const getSeriesColor = Comparisons_store_instance.getSeriesColor.bind(Comparisons_store_instance); + return { + isComparing, + segmentComparisons, + periodComparisons, + getSeriesColor + }; + }, + + methods: { + comparisonHasSegment(comparison) { + return typeof comparison.params.segment !== 'undefined'; + }, + + removeSegmentComparison(index) { + // otherwise the tooltip will be stuck on the screen + window.$(this.$refs.root).tooltip('destroy'); + Comparisons_store_instance.removeSegmentComparison(index); + }, + + getComparisonPeriodType(comparison) { + const { + period + } = comparison.params; + + if (period === 'range') { + return translate('CoreHome_PeriodRange'); + } + + const periodStr = translate(`Intl_Period${period.substring(0, 1).toUpperCase()}${period.substring(1)}`); + return periodStr.substring(0, 1).toUpperCase() + periodStr.substring(1); + }, + + getComparisonTooltip(segmentComparison, periodComparison) { + if (!this.comparisonTooltips || !Object.keys(this.comparisonTooltips).length) { + return undefined; + } + + return (this.comparisonTooltips[periodComparison.index] || {})[segmentComparison.index]; + }, + + getUrlToSegment(segment) { + const hash = { ...src_MatomoUrl_MatomoUrl.hashParsed.value + }; + delete hash.comparePeriods; + delete hash.compareDates; + delete hash.compareSegments; + hash.segment = segment; + return `${window.location.search}#?${src_MatomoUrl_MatomoUrl.stringify(hash)}`; + }, + + setUpTooltips() { + const { + $ + } = window; + $(this.$refs.root).tooltip({ + track: true, + content: function transformTooltipContent() { + const title = $(this).attr('title'); + return window.vueSanitize(title.replace(/\n/g, '<br />')); + }, + show: { + delay: 200, + duration: 200 + }, + hide: false + }); + }, + + onComparisonsChanged() { + this.comparisonTooltips = null; + + if (!Comparisons_store_instance.isComparing()) { + return; + } + + const periodComparisons = Comparisons_store_instance.getPeriodComparisons(); + const segmentComparisons = Comparisons_store_instance.getSegmentComparisons(); + AjaxHelper_AjaxHelper.fetch({ + method: 'API.getProcessedReport', + apiModule: 'VisitsSummary', + apiAction: 'get', + compare: '1', + compareSegments: src_MatomoUrl_MatomoUrl.getSearchParam('compareSegments'), + comparePeriods: src_MatomoUrl_MatomoUrl.getSearchParam('comparePeriods'), + compareDates: src_MatomoUrl_MatomoUrl.getSearchParam('compareDates'), + format_metrics: '1' + }).then(report => { + this.comparisonTooltips = {}; + periodComparisons.forEach(periodComp => { + this.comparisonTooltips[periodComp.index] = {}; + segmentComparisons.forEach(segmentComp => { + const tooltip = this.generateComparisonTooltip(report, periodComp, segmentComp); + this.comparisonTooltips[periodComp.index][segmentComp.index] = tooltip; + }); + }); + }); + }, + + generateComparisonTooltip(visitsSummary, periodComp, segmentComp) { + if (!visitsSummary.reportData.comparisons) { + // sanity check + return ''; + } + + const firstRowIndex = Comparisons_store_instance.getComparisonSeriesIndex(periodComp.index, 0); + const firstRow = visitsSummary.reportData.comparisons[firstRowIndex]; + const comparisonRowIndex = Comparisons_store_instance.getComparisonSeriesIndex(periodComp.index, segmentComp.index); + const comparisonRow = visitsSummary.reportData.comparisons[comparisonRowIndex]; + const firstPeriodRow = visitsSummary.reportData.comparisons[segmentComp.index]; + let tooltip = '<div class="comparison-card-tooltip">'; + let visitsPercent = (comparisonRow.nb_visits / firstRow.nb_visits * 100).toFixed(2); + visitsPercent = `${visitsPercent}%`; + tooltip += translate('General_ComparisonCardTooltip1', [`'${comparisonRow.compareSegmentPretty}'`, comparisonRow.comparePeriodPretty, visitsPercent, comparisonRow.nb_visits.toString(), firstRow.nb_visits.toString()]); + + if (periodComp.index > 0) { + tooltip += '<br/><br/>'; + tooltip += translate('General_ComparisonCardTooltip2', [comparisonRow.nb_visits_change.toString(), firstPeriodRow.compareSegmentPretty, firstPeriodRow.comparePeriodPretty]); + } + + tooltip += '</div>'; + return tooltip; + } + + }, + + updated() { + setTimeout(() => this.setUpTooltips()); + }, + + mounted() { + Matomo_Matomo.on('piwikComparisonsChanged', () => { + this.onComparisonsChanged(); + }); + this.onComparisonsChanged(); + setTimeout(() => this.setUpTooltips()); + }, + + beforeUnmount() { + try { + window.$(this.refs.root).tooltip('destroy'); + } catch (e) {// ignore + } + } + +})); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Comparisons/Comparisons.vue?vue&type=script&lang=ts + +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Comparisons/Comparisons.vue + + + +Comparisonsvue_type_script_lang_ts.render = Comparisonsvue_type_template_id_1b8ecdd2_render + +/* harmony default export */ var Comparisons = (Comparisonsvue_type_script_lang_ts); +// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Comparisons/Comparisons.adapter.ts +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + + + + +function ComparisonFactory() { + return Comparisons_store_instance; +} + +ComparisonFactory.$inject = []; +angular.module('piwikApp.service').factory('piwikComparisonsService', ComparisonFactory); +/* harmony default export */ var Comparisons_adapter = (createAngularJsAdapter({ + component: Comparisons, + directiveName: 'piwikComparisons', + restrict: 'E' +})); // CONCATENATED MODULE: ./node_modules/@vue/cli-plugin-babel/node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/@vue/cli-plugin-babel/node_modules/thread-loader/dist/cjs.js!./node_modules/babel-loader/lib!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist/templateLoader.js??ref--6!./node_modules/@vue/cli-service/node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/@vue/cli-service/node_modules/vue-loader-v16/dist??ref--0-1!./plugins/CoreHome/vue/src/ActivityIndicator/ActivityIndicator.vue?vue&type=template&id=6af4d064 const ActivityIndicatorvue_type_template_id_6af4d064_hoisted_1 = { @@ -2421,21 +3009,6 @@ Alertvue_type_script_lang_ts.render = Alertvue_type_template_id_c3863ae2_render directiveName: 'piwikAlert', transclude: true })); -// CONCATENATED MODULE: ./plugins/CoreHome/vue/src/Periods/index.ts -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - - - - - - - - // CONCATENATED MODULE: ./plugins/CoreHome/vue/src/index.ts /*! * Matomo - free/libre analytics platform diff --git a/plugins/CoreHome/vue/dist/CoreHome.umd.min.js b/plugins/CoreHome/vue/dist/CoreHome.umd.min.js index 0da652c656..a98ec10354 100644 --- a/plugins/CoreHome/vue/dist/CoreHome.umd.min.js +++ b/plugins/CoreHome/vue/dist/CoreHome.umd.min.js @@ -1,152 +1,152 @@ -(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("vue")):"function"===typeof define&&define.amd?define([],t):"object"===typeof exports?exports["CoreHome"]=t(require("vue")):e["CoreHome"]=t(e["Vue"])})("undefined"!==typeof self?self:this,(function(e){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="plugins/CoreHome/vue/dist/",n(n.s="fae3")}({2342:function(e,t,n){"use strict"; +(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("vue")):"function"===typeof define&&define.amd?define([],t):"object"===typeof exports?exports["CoreHome"]=t(require("vue")):e["CoreHome"]=t(e["Vue"])})("undefined"!==typeof self?self:this,(function(e){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var a in e)r.d(n,a,function(t){return e[t]}.bind(null,a));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="plugins/CoreHome/vue/dist/",r(r.s="fae3")}({2342:function(e,t,r){"use strict"; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */window.hasBlockedContent=!1},"8bbf":function(t,n){t.exports=e},fae3:function(e,t,n){"use strict";if(n.r(t),n.d(t,"createAngularJsAdapter",(function(){return ie})),n.d(t,"activityIndicatorAdapter",(function(){return Ne})),n.d(t,"ActivityIndicator",(function(){return Fe})),n.d(t,"translate",(function(){return k})),n.d(t,"alertAdapter",(function(){return Ve})),n.d(t,"AjaxHelper",(function(){return U})),n.d(t,"MatomoUrl",(function(){return o})),n.d(t,"Matomo",(function(){return P})),n.d(t,"Periods",(function(){return u})),n.d(t,"Day",(function(){return S})),n.d(t,"Week",(function(){return E})),n.d(t,"Month",(function(){return I})),n.d(t,"Year",(function(){return H})),n.d(t,"Range",(function(){return A})),n.d(t,"format",(function(){return d})),n.d(t,"getToday",(function(){return p})),n.d(t,"parseDate",(function(){return h})),n.d(t,"todayIsInRange",(function(){return m})),n.d(t,"MatomoDialog",(function(){return re})),n.d(t,"EnrichedHeadline",(function(){return ve})),n.d(t,"ContentBlock",(function(){return Ie})),"undefined"!==typeof window){var r=window.document.currentScript,a=r&&r.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);a&&(n.p=a[1])} + */window.hasBlockedContent=!1},"8bbf":function(t,r){t.exports=e},fae3:function(e,t,r){"use strict";if(r.r(t),r.d(t,"createAngularJsAdapter",(function(){return X})),r.d(t,"activityIndicatorAdapter",(function(){return Ke})),r.d(t,"ActivityIndicator",(function(){return Xe})),r.d(t,"translate",(function(){return g})),r.d(t,"alertAdapter",(function(){return rt})),r.d(t,"AjaxHelper",(function(){return G})),r.d(t,"MatomoUrl",(function(){return R})),r.d(t,"Matomo",(function(){return h})),r.d(t,"Periods",(function(){return l})),r.d(t,"Day",(function(){return O})),r.d(t,"Week",(function(){return S})),r.d(t,"Month",(function(){return E})),r.d(t,"Year",(function(){return x})),r.d(t,"Range",(function(){return C})),r.d(t,"format",(function(){return f})),r.d(t,"getToday",(function(){return b})),r.d(t,"parseDate",(function(){return w})),r.d(t,"todayIsInRange",(function(){return y})),r.d(t,"MatomoDialog",(function(){return Y})),r.d(t,"EnrichedHeadline",(function(){return he})),r.d(t,"ContentBlock",(function(){return je})),r.d(t,"Comparisons",(function(){return Le})),"undefined"!==typeof window){var n=window.document.currentScript,a=n&&n.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);a&&(r.p=a[1])}r("2342");var o=r("8bbf");function i(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ -const i={getSearchParam(e){const t=window.location.href.split("#"),n=new RegExp(e+"(\\[]|=)");if(t&&t[1]&&n.test(decodeURIComponent(t[1]))){const t=window.broadcast.getValueFromHash(e,window.location.href);if(t||"date"!==e&&"period"!==e&&"idSite"!==e)return t}return window.broadcast.getValueFromUrl(e,window.location.search)}};var o=i; + */class s{constructor(){i(this,"periods",{}),i(this,"periodOrder",[])}addCustomPeriod(e,t){if(this.periods[e])throw new Error(`The "${e}" period already exists! It cannot be overridden.`);this.periods[e]=t,this.periodOrder.push(e)}getAllLabels(){return Array().concat(this.periodOrder)}get(e){const t=this.periods[e];if(!t)throw new Error("Invalid period label: "+e);return t}parse(e,t){return this.get(e).parse(t)}isRecognizedPeriod(e){return!!this.periods[e]}}var l=new s; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function s(){const e={getSearchParam:o.getSearchParam.bind(o)};return e}function l(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e} + */let c;const{piwik:d,broadcast:u,piwikHelper:p}=window;d.helper=p,d.broadcast=u,d.updateDateInTitle=function(e,t){if($(".top_controls #periodString").length&&(c=c||document.title,0===c.indexOf(d.siteName))){const r=` - ${l.parse(t,e).getPrettyString()} `;document.title=`${d.siteName}${r}${c.substr(d.siteName.length)}`}},d.hasUserCapability=function(e){return window.angular.isArray(d.userCapabilities)&&-1!==d.userCapabilities.indexOf(e)},d.on=function(e,t){function r(e){t(...e.detail)}t.wrapper=r,window.addEventListener(e,r)},d.off=function(e,t){t.wrapper&&window.removeEventListener(e,t.wrapper)},d.postEventNoEmit=function(e,...t){const r=new CustomEvent(e,{detail:t});window.dispatchEvent(r)},d.postEvent=function(e,...t){d.postEventNoEmit(e,...t);const r=d.helper.getAngularDependency("$rootScope");return r.$oldEmit(e,...t)};const m=d;var h=m; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */s.$inject=[],angular.module("piwikApp.service").service("piwikUrl",s);class c{constructor(){l(this,"periods",{}),l(this,"periodOrder",[])}addCustomPeriod(e,t){if(this.periods[e])throw new Error(`The "${e}" period already exists! It cannot be overridden.`);this.periods[e]=t,this.periodOrder.push(e)}getAllLabels(){return Array().concat(this.periodOrder)}get(e){const t=this.periods[e];if(!t)throw new Error("Invalid period label: "+e);return t}parse(e,t){return this.get(e).parse(t)}isRecognizedPeriod(e){return!!this.periods[e]}}var u=new c; + */function g(e,...t){let r=t;return 1===t.length&&t[0]&&t[0]instanceof Array&&([r]=t),window._pk_translate(e,r)} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function d(e){return $.datepicker.formatDate("yy-mm-dd",e)}function p(){const e=new Date(Date.now());return e.setTime(e.getTime()+60*e.getTimezoneOffset()*1e3),e.setHours(e.getHours()+(window.piwik.timezoneOffset||0)/3600),e.setHours(0),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e}function h(e){if(e instanceof Date)return e;const t=decodeURIComponent(e);if("today"===t||"now"===t)return p();if("yesterday"===t||"yesterdaySameTime"===t){const e=p();return e.setDate(e.getDate()-1),e}if(t.match(/last[ -]?week/i)){const e=p();return e.setDate(e.getDate()-7),e}if(t.match(/last[ -]?month/i)){const e=p();return e.setDate(1),e.setMonth(e.getMonth()-1),e}if(t.match(/last[ -]?year/i)){const e=p();return e.setFullYear(e.getFullYear()-1),e}try{return $.datepicker.parseDate("yy-mm-dd",t)}catch(n){throw console.error(n.message||n),n}}function m(e){return 2===e.length&&(p()>=e[0]&&p()<=e[1])} + */function f(e){return $.datepicker.formatDate("yy-mm-dd",e)}function b(){const e=new Date(Date.now());return e.setTime(e.getTime()+60*e.getTimezoneOffset()*1e3),e.setHours(e.getHours()+(window.piwik.timezoneOffset||0)/3600),e.setHours(0),e.setMinutes(0),e.setSeconds(0),e.setMilliseconds(0),e}function w(e){if(e instanceof Date)return e;const t=decodeURIComponent(e).trim();if(""===t)throw new Error("Invalid date, empty string.");if("today"===t||"now"===t)return b();if("yesterday"===t||"yesterdaySameTime"===t){const e=b();return e.setDate(e.getDate()-1),e}if(t.match(/last[ -]?week/i)){const e=b();return e.setDate(e.getDate()-7),e}if(t.match(/last[ -]?month/i)){const e=b();return e.setDate(1),e.setMonth(e.getMonth()-1),e}if(t.match(/last[ -]?year/i)){const e=b();return e.setFullYear(e.getFullYear()-1),e}return $.datepicker.parseDate("yy-mm-dd",t)}function y(e){return 2===e.length&&(b()>=e[0]&&b()<=e[1])}function v(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */let g;const{piwik:f,broadcast:b,piwikHelper:w}=window;function y(e,t){try{return u.parse(e,t),!0}catch(n){return!1}}f.helper=w,f.broadcast=b,f.updatePeriodParamsFromUrl=function(){let e=o.getSearchParam("date");const t=o.getSearchParam("period");if(!y(t,e))return;if(f.period===t&&f.currentDateString===e)return;f.period=t;const n=u.parse(t,e).getDateRange();f.startDateString=d(n[0]),f.endDateString=d(n[1]),f.updateDateInTitle(e,t),"range"===f.period&&(e=`${f.startDateString},${f.endDateString}`),f.currentDateString=e},f.updateDateInTitle=function(e,t){if($(".top_controls #periodString").length&&(g=g||document.title,0===g.indexOf(f.siteName))){const n=` - ${u.parse(t,e).getPrettyString()} `;document.title=`${f.siteName}${n}${g.substr(f.siteName.length)}`}},f.hasUserCapability=function(e){return window.angular.isArray(f.userCapabilities)&&-1!==f.userCapabilities.indexOf(e)};const D=f;var P=D; + */class C{constructor(e,t,r){v(this,"startDate",void 0),v(this,"endDate",void 0),v(this,"childPeriodType",void 0),this.startDate=e,this.endDate=t,this.childPeriodType=r}static getLastNRange(e,t,r){const n=Math.max(parseInt(t.toString(),10)-1,0);if(Number.isNaN(n))throw new Error("Invalid range strAmount");let a=r?w(r):b(),o=new Date(a.getTime());if("day"===e)o.setDate(o.getDate()-n);else if("week"===e)o.setDate(o.getDate()-7*n);else if("month"===e)o.setDate(1),o.setMonth(o.getMonth()-n);else{if("year"!==e)throw new Error(`Unknown period type '${e}'.`);o.setFullYear(o.getFullYear()-n)}if("day"!==e){const t=l.periods[e].parse(o),r=l.periods[e].parse(a);[o]=t.getDateRange(),[,a]=r.getDateRange()}const i=new Date(1991,7,6);if(o.getTime()-i.getTime()<0)switch(e){case"year":o=new Date(1992,0,1);break;case"month":o=new Date(1991,8,1);break;case"week":o=new Date(1991,8,12);break;case"day":default:o=i;break}return new C(o,a,e)}static getLastNRangeChild(e,t,r){const n=t?w(t):b();let a=new Date(n.getTime()),o=new Date(n.getTime());if("day"===e)a.setDate(a.getDate()-r),o.setDate(o.getDate()-r);else if("week"===e)a.setDate(a.getDate()-7*r),o.setDate(o.getDate()-7*r);else if("month"===e)a.setDate(1),a.setMonth(a.getMonth()-r),o.setDate(1),o.setMonth(o.getMonth()-r);else{if("year"!==e)throw new Error(`Unknown period type '${e}'.`);a.setFullYear(a.getFullYear()-r),o.setFullYear(o.getFullYear()-r)}if("day"!==e){const t=l.periods[e].parse(a),r=l.periods[e].parse(o);[a]=t.getDateRange(),[,o]=r.getDateRange()}const i=new Date(1991,7,6);if(a.getTime()-i.getTime()<0)switch(e){case"year":a=new Date(1992,0,1);break;case"month":a=new Date(1991,8,1);break;case"week":a=new Date(1991,8,12);break;case"day":default:a=i;break}return new C(a,o,e)}static parse(e,t="day"){if(/^previous/.test(e)){const r=C.getLastNRange(t,"2").startDate;return C.getLastNRange(t,e.substring(8),r)}if(/^last/.test(e))return C.getLastNRange(t,e.substring(4));const r=decodeURIComponent(e).split(",");return new C(w(r[0]),w(r[1]),t)}static getDisplayText(){return g("General_DateRangeInPeriodList")}getPrettyString(){const e=f(this.startDate),t=f(this.endDate);return g("General_DateRangeFromTo",[e,t])}getDateRange(){return[this.startDate,this.endDate]}containsToday(){return y(this.getDateRange())}}function P(){return{getAllLabels:l.getAllLabels.bind(l),isRecognizedPeriod:l.isRecognizedPeriod.bind(l),get:l.get.bind(l),parse:l.parse.bind(l),parseDate:w,format:f,RangePeriod:C,todayIsInRange:y}}function j(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function v(){return P}function j(e,t){t.$on("$locationChangeSuccess",e.updatePeriodParamsFromUrl)}angular.module("piwikApp.service").service("piwik",v),j.$inject=["piwik","$rootScope"],angular.module("piwikApp.service").run(j);n("2342"); + */l.addCustomPeriod("range",C), /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function k(e,...t){let n=t;return 1===t.length&&t[0]&&t[0]instanceof Array&&([n]=t),window._pk_translate(e,n)}function O(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e} + */ +window.piwik.addCustomPeriod=l.addCustomPeriod.bind(l),window.angular.module("piwikApp.service").factory("piwikPeriods",P);class O{constructor(e){j(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new O(w(e))}static getDisplayText(){return g("Intl_PeriodDay")}getPrettyString(){return f(this.dateInPeriod)}getDateRange(){return[new Date(this.dateInPeriod.getTime()),new Date(this.dateInPeriod.getTime())]}containsToday(){return y(this.getDateRange())}}function D(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */class S{constructor(e){O(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new S(h(e))}static getDisplayText(){return k("Intl_PeriodDay")}getPrettyString(){return d(this.dateInPeriod)}getDateRange(){return[new Date(this.dateInPeriod.getTime()),new Date(this.dateInPeriod.getTime())]}containsToday(){return m(this.getDateRange())}}function T(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e} + */l.addCustomPeriod("day",O);class S{constructor(e){D(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new S(w(e))}static getDisplayText(){return g("Intl_PeriodWeek")}getPrettyString(){const e=this.getDateRange(),t=f(e[0]),r=f(e[1]);return g("General_DateRangeFromTo",[t,r])}getDateRange(){const e=(this.dateInPeriod.getDay()+6)%7,t=new Date(this.dateInPeriod.getTime());t.setDate(this.dateInPeriod.getDate()-e);const r=new Date(t.getTime());return r.setDate(t.getDate()+6),[t,r]}containsToday(){return y(this.getDateRange())}}function k(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */u.addCustomPeriod("day",S);class E{constructor(e){T(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new E(h(e))}static getDisplayText(){return k("Intl_PeriodWeek")}getPrettyString(){const e=this.getDateRange(),t=d(e[0]),n=d(e[1]);return k("General_DateRangeFromTo",[t,n])}getDateRange(){const e=(this.dateInPeriod.getDay()+6)%7,t=new Date(this.dateInPeriod.getTime());t.setDate(this.dateInPeriod.getDate()-e);const n=new Date(t.getTime());return n.setDate(t.getDate()+6),[t,n]}containsToday(){return m(this.getDateRange())}}function C(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e} + */l.addCustomPeriod("week",S);class E{constructor(e){k(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new E(w(e))}static getDisplayText(){return g("Intl_PeriodMonth")}getPrettyString(){const e=g("Intl_Month_Long_StandAlone_"+(this.dateInPeriod.getMonth()+1));return`${e} ${this.dateInPeriod.getFullYear()}`}getDateRange(){const e=new Date(this.dateInPeriod.getTime());e.setDate(1);const t=new Date(this.dateInPeriod.getTime());return t.setDate(1),t.setMonth(t.getMonth()+1),t.setDate(0),[e,t]}containsToday(){return y(this.getDateRange())}}function T(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */u.addCustomPeriod("week",E);class I{constructor(e){C(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new I(h(e))}static getDisplayText(){return k("Intl_PeriodMonth")}getPrettyString(){const e=k("Intl_Month_Long_StandAlone_"+(this.dateInPeriod.getMonth()+1));return`${e} ${this.dateInPeriod.getFullYear()}`}getDateRange(){const e=new Date(this.dateInPeriod.getTime());e.setDate(1);const t=new Date(this.dateInPeriod.getTime());return t.setDate(1),t.setMonth(t.getMonth()+1),t.setDate(0),[e,t]}containsToday(){return m(this.getDateRange())}}function x(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e} + */l.addCustomPeriod("month",E);class x{constructor(e){T(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new x(w(e))}static getDisplayText(){return g("Intl_PeriodYear")}getPrettyString(){return this.dateInPeriod.getFullYear().toString()}getDateRange(){const e=new Date(this.dateInPeriod.getTime());e.setMonth(0),e.setDate(1);const t=new Date(this.dateInPeriod.getTime());return t.setMonth(12),t.setDate(0),[e,t]}containsToday(){return y(this.getDateRange())}} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */u.addCustomPeriod("month",I);class H{constructor(e){x(this,"dateInPeriod",void 0),this.dateInPeriod=e}static parse(e){return new H(h(e))}static getDisplayText(){return k("Intl_PeriodYear")}getPrettyString(){return this.dateInPeriod.getFullYear().toString()}getDateRange(){const e=new Date(this.dateInPeriod.getTime());e.setMonth(0),e.setDate(1);const t=new Date(this.dateInPeriod.getTime());return t.setMonth(12),t.setDate(0),[e,t]}containsToday(){return m(this.getDateRange())}}function R(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e} + */ +function I(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */u.addCustomPeriod("year",H);class A{constructor(e,t,n){R(this,"startDate",void 0),R(this,"endDate",void 0),R(this,"childPeriodType",void 0),this.startDate=e,this.endDate=t,this.childPeriodType=n}static getLastNRange(e,t,n){const r=Math.max(parseInt(t.toString(),10)-1,0);if(Number.isNaN(r))throw new Error("Invalid range strAmount");let a=n?h(n):p(),i=new Date(a.getTime());if("day"===e)i.setDate(i.getDate()-r);else if("week"===e)i.setDate(i.getDate()-7*r);else if("month"===e)i.setDate(1),i.setMonth(i.getMonth()-r);else{if("year"!==e)throw new Error(`Unknown period type '${e}'.`);i.setFullYear(i.getFullYear()-r)}if("day"!==e){const t=u.periods[e].parse(i),n=u.periods[e].parse(a);[i]=t.getDateRange(),[,a]=n.getDateRange()}const o=new Date(1991,7,6);if(i.getTime()-o.getTime()<0)switch(e){case"year":i=new Date(1992,0,1);break;case"month":i=new Date(1991,8,1);break;case"week":i=new Date(1991,8,12);break;case"day":default:i=o;break}return new A(i,a,e)}static getLastNRangeChild(e,t,n){const r=t?h(t):p();let a=new Date(r.getTime()),i=new Date(r.getTime());if("day"===e)a.setDate(a.getDate()-n),i.setDate(i.getDate()-n);else if("week"===e)a.setDate(a.getDate()-7*n),i.setDate(i.getDate()-7*n);else if("month"===e)a.setDate(1),a.setMonth(a.getMonth()-n),i.setDate(1),i.setMonth(i.getMonth()-n);else{if("year"!==e)throw new Error(`Unknown period type '${e}'.`);a.setFullYear(a.getFullYear()-n),i.setFullYear(i.getFullYear()-n)}if("day"!==e){const t=u.periods[e].parse(a),n=u.periods[e].parse(i);[a]=t.getDateRange(),[,i]=n.getDateRange()}const o=new Date(1991,7,6);if(a.getTime()-o.getTime()<0)switch(e){case"year":a=new Date(1992,0,1);break;case"month":a=new Date(1991,8,1);break;case"week":a=new Date(1991,8,12);break;case"day":default:a=o;break}return new A(a,i,e)}static parse(e,t="day"){if(/^previous/.test(e)){const n=A.getLastNRange(t,"2").startDate;return A.getLastNRange(t,e.substring(8),n)}if(/^last/.test(e))return A.getLastNRange(t,e.substring(4));const n=decodeURIComponent(e).split(",");return new A(h(n[0]),h(n[1]),t)}static getDisplayText(){return k("General_DateRangeInPeriodList")}getPrettyString(){const e=d(this.startDate),t=d(this.endDate);return k("General_DateRangeFromTo",[e,t])}getDateRange(){return[this.startDate,this.endDate]}containsToday(){return m(this.getDateRange())}}function F(){return{getAllLabels:u.getAllLabels.bind(u),isRecognizedPeriod:u.isRecognizedPeriod.bind(u),get:u.get.bind(u),parse:u.parse.bind(u),parseDate:h,format:d,RangePeriod:A,todayIsInRange:m}}function N(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e} + */l.addCustomPeriod("year",x);const{piwik:H,broadcast:N}=window;function F(e,t){try{return l.parse(e,t),!0}catch(r){return!1}}class B{constructor(){I(this,"urlQuery",Object(o["ref"])("")),I(this,"hashQuery",Object(o["ref"])("")),I(this,"urlParsed",Object(o["computed"])(()=>Object(o["readonly"])(N.getValuesFromUrl("?"+this.urlQuery.value,!0)))),I(this,"hashParsed",Object(o["computed"])(()=>Object(o["readonly"])(N.getValuesFromUrl("?"+this.hashQuery.value,!0)))),I(this,"parsed",Object(o["computed"])(()=>Object(o["readonly"])({...this.urlParsed.value,...this.hashParsed.value}))),this.setUrlQuery(window.location.search),this.setHashQuery(window.location.hash),h.on("$locationChangeSuccess",e=>{const t=new URL(e);this.setUrlQuery(t.search.replace(/^\?/,"")),this.setHashQuery(t.hash.replace(/^#/,""))}),this.updatePeriodParamsFromUrl()}updateHash(e){const t="string"!==typeof e?this.stringify(e):e,r=h.helper.getAngularDependency("$location");r.search(t)}getSearchParam(e){const t=window.location.href.split("#"),r=new RegExp(e+"(\\[]|=)");if(t&&t[1]&&r.test(decodeURIComponent(t[1]))){const t=window.broadcast.getValueFromHash(e,window.location.href);if(t||"date"!==e&&"period"!==e&&"idSite"!==e)return t}return window.broadcast.getValueFromUrl(e,window.location.search)}stringify(e){return $.param(e).replace(/%5B%5D/g,"[]")}updatePeriodParamsFromUrl(){let e=this.getSearchParam("date");const t=this.getSearchParam("period");if(!F(t,e))return;if(H.period===t&&H.currentDateString===e)return;H.period=t;const r=l.parse(t,e).getDateRange();H.startDateString=f(r[0]),H.endDateString=f(r[1]),H.updateDateInTitle(e,t),"range"===H.period&&(e=`${H.startDateString},${H.endDateString}`),H.currentDateString=e}setUrlQuery(e){this.urlQuery.value=e.replace(/^\?/,"")}setHashQuery(e){this.hashQuery.value=e.replace(/^[#/?]+/,"")}}const A=new B;var R=A; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function B(e,t){if("abort"===t)return;const n=$("#loadingError");Piwik_Popover.isOpen()&&e&&500===e.status?e&&500===e.status&&$(document.body).html(piwikHelper.escape(e.responseText)):n.show()}u.addCustomPeriod("range",A), + */ +function U(){const e={getSearchParam:R.getSearchParam.bind(R)};return e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -window.piwik.addCustomPeriod=u.addCustomPeriod.bind(u),angular.module("piwikApp.service").factory("piwikPeriods",F),window.globalAjaxQueue=[],window.globalAjaxQueue.active=0,window.globalAjaxQueue.clean=function(){for(let e=this.length;e>=0;e-=1)this[e]&&4!==this[e].readyState||this.splice(e,1)},window.globalAjaxQueue.push=function(...e){return this.active+=e.length,this.clean(),Array.prototype.push.call(this,...e)},window.globalAjaxQueue.abort=function(){this.forEach(e=>e&&e.abort&&e.abort()),this.splice(0,this.length),this.active=0};class U{static fetch(e){const t=new U;return t.setFormat("json"),t.addParams({module:"API",format:"json",...e},"get"),t.send()}constructor(){N(this,"format","json"),N(this,"timeout",null),N(this,"callback",null),N(this,"useRegularCallbackInCaseOfError",!1),N(this,"errorCallback",void 0),N(this,"withToken",!1),N(this,"completeCallback",void 0),N(this,"getParams",{}),N(this,"getUrl","?"),N(this,"postParams",{}),N(this,"loadingElement",null),N(this,"errorElement","#ajaxError"),N(this,"requestHandle",null),N(this,"defaultParams",["idSite","period","date","segment"]),this.errorCallback=B}addParams(e,t){"string"===typeof e&&(e=window["broadcast"].getValuesFromUrl(e));const n=["compareSegments","comparePeriods","compareDates"];Object.keys(e).forEach(r=>{const a=e[r];(-1===n.indexOf(r)||a)&&("get"===t.toLowerCase()?this.getParams[r]=a:"post"===t.toLowerCase()&&(this.postParams[r]=a))})}withTokenInUrl(){this.withToken=!0}setUrl(e){this.addParams(broadcast.getValuesFromUrl(e),"GET")}setBulkRequests(...e){const t=e.map(e=>$.param(e));this.addParams({module:"API",method:"API.getBulkRequest",urls:t,format:"json"},"post")}setTimeout(e){this.timeout=e}setCallback(e){this.callback=e}useCallbackInCaseOfError(){this.useRegularCallbackInCaseOfError=!0}redirectOnSuccess(e){this.setCallback(()=>{piwikHelper.redirect(e)})}setErrorCallback(e){this.errorCallback=e}setCompleteCallback(e){this.completeCallback=e}setFormat(e){this.format=e}setLoadingElement(e){this.loadingElement=e||"#ajaxLoadingDiv"}setErrorElement(e){e&&(this.errorElement=e)}useGETDefaultParameter(e){if(e&&this.defaultParams)for(let t=0;t<this.defaultParams.length;t+=1)if(this.defaultParams[t]===e)return!0;return!1}removeDefaultParameter(e){if(e&&this.defaultParams)for(let t=0;t<this.defaultParams.length;t+=1)this.defaultParams[t]===e&&this.defaultParams.splice(t,1)}send(){return $(this.errorElement).length&&$(this.errorElement).hide(),this.loadingElement&&$(this.loadingElement).fadeIn(),this.requestHandle=this.buildAjaxCall(),globalAjaxQueue.push(this.requestHandle),this.requestHandle}abort(){this.requestHandle&&"function"===typeof this.requestHandle.abort&&(this.requestHandle.abort(),this.requestHandle=null)}buildAjaxCall(){const e=this,t=this.mixinDefaultGetParams(this.getParams);let n=this.getUrl;"?"!==n[n.length-1]&&(n+="&"),t.segment&&(n=`${n}segment=${t.segment}&`,delete t.segment),t.date&&(n=`${n}date=${decodeURIComponent(t.date.toString())}&`,delete t.date),n+=$.param(t);const r={type:"POST",async:!0,url:n,dataType:this.format||"json",complete:this.completeCallback,error:function(){globalAjaxQueue.active-=1,e.errorCallback&&e.errorCallback.apply(this,arguments)},success:(e,t,n)=>{if(this.loadingElement&&$(this.loadingElement).hide(),e&&"error"===e.result&&!this.useRegularCallbackInCaseOfError){let t=null,n="toast";if($(this.errorElement).length&&e.message&&($(this.errorElement).show(),t=this.errorElement,n=null),e.message){const r=window["require"]("piwik/UI"),a=new r.Notification;a.show(e.message,{placeat:t,context:"error",type:n,id:"ajaxHelper"}),a.scrollToNotification()}}else this.callback&&this.callback(e,t,n);globalAjaxQueue.active-=1,P.ajaxRequestFinished&&P.ajaxRequestFinished()},data:this.mixinDefaultPostParams(this.postParams),timeout:null!==this.timeout?this.timeout:void 0};return $.ajax(r)}isRequestToApiMethod(){return this.getParams&&"API"===this.getParams.module&&this.getParams.method||this.postParams&&"API"===this.postParams.module&&this.postParams.method}isWidgetizedRequest(){return"Widgetize"===broadcast.getValueFromUrl("module")}getDefaultPostParams(){return this.withToken||this.isRequestToApiMethod()||P.shouldPropagateTokenAuth?{token_auth:P.token_auth,force_api_session:broadcast.isWidgetizeRequestWithoutSession()?0:1}:{}}mixinDefaultPostParams(e){const t=this.getDefaultPostParams(),n={...t,...e};return n}mixinDefaultGetParams(e){const t=o.getSearchParam("segment"),n={idSite:P.idSite||broadcast.getValueFromUrl("idSite"),period:P.period||broadcast.getValueFromUrl("period"),segment:t},r=e;return r.token_auth&&(r.token_auth=null,delete r.token_auth),Object.keys(n).forEach(e=>{this.useGETDefaultParameter(e)&&!r[e]&&!this.postParams[e]&&n[e]&&(r[e]=n[e])}),!this.useGETDefaultParameter("date")||r.date||this.postParams.date||(r.date=P.currentDateString),r}}function M(){return globalAjaxQueue}window.ajaxHelper=U,angular.module("piwikApp.service").service("globalAjaxQueue",M); +function _(){return h}function V(e,t){t.$oldEmit=t.$emit,t.$emit=function(e,...t){return h.postEvent(e,...t)},t.$oldBroadcast=t.$broadcast,t.$broadcast=function(e,...r){return h.postEventNoEmit(e,...r),t.$oldBroadcast(e,...r)},t.$on("$locationChangeSuccess",e.updatePeriodParamsFromUrl)}function M(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ -const V={getSearchParam(e){const t=window.location.href.split("#"),n=new RegExp(e+"(\\[]|=)");if(t&&t[1]&&n.test(decodeURIComponent(t[1]))){const t=window.broadcast.getValueFromHash(e,window.location.href);if(t||"date"!==e&&"period"!==e&&"idSite"!==e)return t}return window.broadcast.getValueFromUrl(e,window.location.search)}};var _=V; + */function q(e,t){if("abort"===t)return;if("undefined"===typeof Piwik_Popover)return void console.log("Request failed: "+e.responseText);const r=$("#loadingError");Piwik_Popover.isOpen()&&e&&500===e.status?e&&500===e.status&&$(document.body).html(piwikHelper.escape(e.responseText)):r.show()}H.updatePeriodParamsFromUrl=A.updatePeriodParamsFromUrl.bind(A),U.$inject=[],angular.module("piwikApp.service").service("piwikUrl",U),window.angular.module("piwikApp.service").service("piwik",_),V.$inject=["piwik","$rootScope"],window.angular.module("piwikApp.service").run(V),window.globalAjaxQueue=[],window.globalAjaxQueue.active=0,window.globalAjaxQueue.clean=function(){for(let e=this.length;e>=0;e-=1)this[e]&&4!==this[e].readyState||this.splice(e,1)},window.globalAjaxQueue.push=function(...e){return this.active+=e.length,this.clean(),Array.prototype.push.call(this,...e)},window.globalAjaxQueue.abort=function(){this.forEach(e=>e&&e.abort&&e.abort()),this.splice(0,this.length),this.active=0};class G{static fetch(e){const t=new G;return t.setFormat("json"),t.addParams({module:"API",format:"json",...e},"get"),t.send()}constructor(){M(this,"format","json"),M(this,"timeout",null),M(this,"callback",null),M(this,"useRegularCallbackInCaseOfError",!1),M(this,"errorCallback",void 0),M(this,"withToken",!1),M(this,"completeCallback",void 0),M(this,"getParams",{}),M(this,"getUrl","?"),M(this,"postParams",{}),M(this,"loadingElement",null),M(this,"errorElement","#ajaxError"),M(this,"requestHandle",null),M(this,"defaultParams",["idSite","period","date","segment"]),this.errorCallback=q}addParams(e,t){const r="string"===typeof e?window.broadcast.getValuesFromUrl(e):e,n=["compareSegments","comparePeriods","compareDates"];Object.keys(r).forEach(e=>{const a=r[e];(-1===n.indexOf(e)||a)&&("get"===t.toLowerCase()?this.getParams[e]=a:"post"===t.toLowerCase()&&(this.postParams[e]=a))})}withTokenInUrl(){this.withToken=!0}setUrl(e){this.addParams(broadcast.getValuesFromUrl(e),"GET")}setBulkRequests(...e){const t=e.map(e=>"string"===typeof e?e:$.param(e));this.addParams({module:"API",method:"API.getBulkRequest",urls:t,format:"json"},"post")}setTimeout(e){this.timeout=e}setCallback(e){this.callback=e}useCallbackInCaseOfError(){this.useRegularCallbackInCaseOfError=!0}redirectOnSuccess(e){this.setCallback(()=>{piwikHelper.redirect(e)})}setErrorCallback(e){this.errorCallback=e}setCompleteCallback(e){this.completeCallback=e}setFormat(e){this.format=e}setLoadingElement(e){this.loadingElement=e||"#ajaxLoadingDiv"}setErrorElement(e){e&&(this.errorElement=e)}useGETDefaultParameter(e){if(e&&this.defaultParams)for(let t=0;t<this.defaultParams.length;t+=1)if(this.defaultParams[t]===e)return!0;return!1}removeDefaultParameter(e){if(e&&this.defaultParams)for(let t=0;t<this.defaultParams.length;t+=1)this.defaultParams[t]===e&&this.defaultParams.splice(t,1)}send(){return $(this.errorElement).length&&$(this.errorElement).hide(),this.loadingElement&&$(this.loadingElement).fadeIn(),this.requestHandle=this.buildAjaxCall(),window.globalAjaxQueue.push(this.requestHandle),new Promise((e,t)=>{this.requestHandle.then(e).fail(e=>{"abort"!==e.statusText&&(console.log(`Warning: the ${$.param(this.getParams)} request failed!`),t(e))})})}abort(){this.requestHandle&&"function"===typeof this.requestHandle.abort&&(this.requestHandle.abort(),this.requestHandle=null)}buildAjaxCall(){const e=this,t=this.mixinDefaultGetParams(this.getParams);let r=this.getUrl;"?"!==r[r.length-1]&&(r+="&"),t.segment&&(r=`${r}segment=${t.segment}&`,delete t.segment),t.date&&(r=`${r}date=${decodeURIComponent(t.date.toString())}&`,delete t.date),r+=$.param(t);const n={type:"POST",async:!0,url:r,dataType:this.format||"json",complete:this.completeCallback,error:function(...t){window.globalAjaxQueue.active-=1,e.errorCallback&&e.errorCallback.apply(this,t)},success:(e,t,r)=>{if(this.loadingElement&&$(this.loadingElement).hide(),e&&"error"===e.result&&!this.useRegularCallbackInCaseOfError){let t=null,r="toast";if($(this.errorElement).length&&e.message&&($(this.errorElement).show(),t=this.errorElement,r=null),e.message){const n=window["require"]("piwik/UI"),a=new n.Notification;a.show(e.message,{placeat:t,context:"error",type:r,id:"ajaxHelper"}),a.scrollToNotification()}}else this.callback&&this.callback(e,t,r);window.globalAjaxQueue.active-=1,h.ajaxRequestFinished&&h.ajaxRequestFinished()},data:this.mixinDefaultPostParams(this.postParams),timeout:null!==this.timeout?this.timeout:void 0};return $.ajax(n)}isRequestToApiMethod(){return this.getParams&&"API"===this.getParams.module&&this.getParams.method||this.postParams&&"API"===this.postParams.module&&this.postParams.method}isWidgetizedRequest(){return"Widgetize"===broadcast.getValueFromUrl("module")}getDefaultPostParams(){return this.withToken||this.isRequestToApiMethod()||h.shouldPropagateTokenAuth?{token_auth:h.token_auth,force_api_session:broadcast.isWidgetizeRequestWithoutSession()?0:1}:{}}mixinDefaultPostParams(e){const t=this.getDefaultPostParams(),r={...t,...e};return r}mixinDefaultGetParams(e){const t=R.getSearchParam("segment"),r={idSite:h.idSite?h.idSite.toString():broadcast.getValueFromUrl("idSite"),period:h.period||broadcast.getValueFromUrl("period"),segment:t},n=e;return n.token_auth&&(n.token_auth=null,delete n.token_auth),Object.keys(r).forEach(e=>{this.useGETDefaultParameter(e)&&!n[e]&&!this.postParams[e]&&r[e]&&(n[e]=r[e])}),!this.useGETDefaultParameter("date")||n.date||this.postParams.date||(n.date=h.currentDateString),n}}function L(){return globalAjaxQueue}window.ajaxHelper=G,angular.module("piwikApp.service").service("globalAjaxQueue",L);const Q={ref:"root"};function J(e,t,r,n,a,i){return Object(o["withDirectives"])((Object(o["openBlock"])(),Object(o["createElementBlock"])("div",Q,[Object(o["renderSlot"])(e.$slots,"default")],512)),[[o["vShow"],e.modelValue]])}var z=Object(o["defineComponent"])({props:{modelValue:{type:Boolean,required:!0},element:{type:HTMLElement,required:!1}},emits:["yes","no","closeEnd","close","update:modelValue"],activated(){this.$emit("update:modelValue",!1)},watch:{modelValue(e,t){if(e){const e=this.element||this.$refs.root.firstElementChild;h.helper.modalConfirm(e,{yes:()=>{this.$emit("yes")},no:()=>{this.$emit("no")}},{onCloseEnd:()=>{this.element||this.$refs.root.appendChild(e),this.$emit("update:modelValue",!1),this.$emit("closeEnd")}})}else!1===e&&!0===t&&this.$emit("close")}}});z.render=J;var Y=z; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function q(){const e={getSearchParam:_.getSearchParam.bind(_)};return e} + */let W=0;function X(e){const{component:t,scope:r={},events:n={},$inject:a,directiveName:i,transclude:s,mountPointFactory:l,postCreate:c,noScope:d,restrict:u="A"}=e,p=W;s&&(W+=1);const m={};function h(...e){const a={restrict:u,scope:d?void 0:m,compile:function(){return{post:function(a,i,d){const u=s?i.find(`[ng-transclude][counter=${p}]`):null;let m="<root-component";Object.entries(r).forEach(([,e])=>{m+=` :${e.vue}="${e.vue}"`}),Object.entries(n).forEach(e=>{const[t]=e;m+=` @${t}="onEventHandler('${t}', $event)"`}),m+=">",s&&(m+='<div ref="transcludeTarget"/>'),m+="</root-component>";const h=Object(o["createApp"])({template:m,data(){const t={};return Object.entries(r).forEach(([r,n])=>{let o=a[r];"undefined"===typeof o&&"undefined"!==typeof n.default&&(o=n.default instanceof Function?n.default(a,i,d,...e):n.default),t[n.vue]=o}),t},setup(){if(s){const e=Object(o["ref"])(null);return{transcludeTarget:e}}},methods:{onEventHandler(t,r){n[t]&&n[t](r,a,i,d,...e)}}});h.config.globalProperties.$sanitize=window.vueSanitize,h.config.globalProperties.translate=g,h.component("root-component",t);const f=l?l(a,i,d,...e):i[0],b=h.mount(f);Object.entries(r).forEach(([t,r])=>{r.angularJsBind&&a.$watch(t,n=>{"undefined"!==typeof r.default&&"undefined"===typeof n?b[t]=r.default instanceof Function?r.default(a,i,d,...e):r.default:b[t]=n})}),s&&$(b.transcludeTarget).append(u),c&&c(b,a,i,d,...e),i.on("$destroy",()=>{h.unmount()})}}}};return s&&(a.transclude=!0,a.template=`<div ng-transclude counter="${p}"/>`),a}return Object.entries(r).forEach(([e,t])=>{t.vue||(t.vue=e),t.angularJsBind&&(m[e]=t.angularJsBind)}),h.$inject=a||[],angular.module("piwikApp").directive(i,h),h} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ -let L;q.$inject=[],angular.module("piwikApp.service").service("piwikUrl",q);const{piwik:G,broadcast:J,piwikHelper:z}=window;function Y(e,t){try{return u.parse(e,t),!0}catch(n){return!1}}G.helper=z,G.broadcast=J,G.updatePeriodParamsFromUrl=function(){let e=_.getSearchParam("date");const t=_.getSearchParam("period");if(!Y(t,e))return;if(G.period===t&&G.currentDateString===e)return;G.period=t;const n=u.parse(t,e).getDateRange();G.startDateString=d(n[0]),G.endDateString=d(n[1]),G.updateDateInTitle(e,t),"range"===G.period&&(e=`${G.startDateString},${G.endDateString}`),G.currentDateString=e},G.updateDateInTitle=function(e,t){if($(".top_controls #periodString").length&&(L=L||document.title,0===L.indexOf(G.siteName))){const n=` - ${u.parse(t,e).getPrettyString()} `;document.title=`${G.siteName}${n}${L.substr(G.siteName.length)}`}},G.hasUserCapability=function(e){return window.angular.isArray(G.userCapabilities)&&-1!==G.userCapabilities.indexOf(e)};const Q=G;var W=Q; + */X({component:Y,scope:{show:{vue:"modelValue",default:!1},element:{default:(e,t)=>t[0]}},events:{yes:(e,t,r,n)=>{n.yes&&(t.$eval(n.yes),setTimeout(()=>{t.$apply()},0))},no:(e,t,r,n)=>{n.no&&(t.$eval(n.no),setTimeout(()=>{t.$apply()},0))},close:(e,t,r,n)=>{n.close&&(t.$eval(n.close),setTimeout(()=>{t.$apply()},0))},"update:modelValue":(e,t,r,n,a)=>{setTimeout(()=>{t.$apply(a(n.piwikDialog).assign(t,e))},0)}},$inject:["$parse"],directiveName:"piwikDialog",transclude:!0,mountPointFactory:(e,t)=>{const r=$('<div class="vue-placeholder"/>');return r.appendTo(t),r[0]},postCreate:(e,t,r,n)=>{t.$watch(n.piwikDialog,(t,r)=>{r!==t&&(e.modelValue=t||!1)})},noScope:!0});const K={key:0,class:"title",tabindex:"6"},Z=["href","title"],ee={class:"iconsBar"},te=["href","title"],re=Object(o["createElementVNode"])("span",{class:"icon-help"},null,-1),ne=[re],ae=["title"],oe=Object(o["createElementVNode"])("span",{class:"icon-info"},null,-1),ie=[oe],se={class:"ratingIcons"},le={class:"inlineHelp"},ce=["innerHTML"],de=["href"];function ue(e,t,r,n,a,i){const s=Object(o["resolveComponent"])("RateFeature");return Object(o["openBlock"])(),Object(o["createElementBlock"])("div",{class:"enrichedHeadline",onMouseenter:t[1]||(t[1]=t=>e.showIcons=!0),onMouseleave:t[2]||(t[2]=t=>e.showIcons=!1),ref:"root"},[e.editUrl?Object(o["createCommentVNode"])("",!0):(Object(o["openBlock"])(),Object(o["createElementBlock"])("div",K,[Object(o["renderSlot"])(e.$slots,"default")])),e.editUrl?(Object(o["openBlock"])(),Object(o["createElementBlock"])("a",{key:1,class:"title",href:e.editUrl,title:e.translate("CoreHome_ClickToEditX",e.$sanitize(e.actualFeatureName))},[Object(o["renderSlot"])(e.$slots,"default")],8,Z)):Object(o["createCommentVNode"])("",!0),Object(o["withDirectives"])(Object(o["createElementVNode"])("span",ee,[e.helpUrl&&!e.actualInlineHelp?(Object(o["openBlock"])(),Object(o["createElementBlock"])("a",{key:0,rel:"noreferrer noopener",target:"_blank",class:"helpIcon",href:e.helpUrl,title:e.translate("CoreHome_ExternalHelp")},ne,8,te)):Object(o["createCommentVNode"])("",!0),e.actualInlineHelp?(Object(o["openBlock"])(),Object(o["createElementBlock"])("a",{key:1,onClick:t[0]||(t[0]=t=>e.showInlineHelp=!e.showInlineHelp),class:Object(o["normalizeClass"])(["helpIcon",{active:e.showInlineHelp}]),title:e.translate(e.reportGenerated?"General_HelpReport":"General_Help")},ie,10,ae)):Object(o["createCommentVNode"])("",!0),Object(o["createElementVNode"])("div",se,[Object(o["createVNode"])(s,{title:e.actualFeatureName},null,8,["title"])])],512),[[o["vShow"],e.showIcons||e.showInlineHelp]]),Object(o["withDirectives"])(Object(o["createElementVNode"])("div",le,[Object(o["createElementVNode"])("div",{innerHTML:e.$sanitize(e.actualInlineHelp)},null,8,ce),e.helpUrl?(Object(o["openBlock"])(),Object(o["createElementBlock"])("a",{key:0,rel:"noreferrer noopener",target:"_blank",class:"readMore",href:e.helpUrl},Object(o["toDisplayString"])(e.translate("General_MoreDetails")),9,de)):Object(o["createCommentVNode"])("",!0)],512),[[o["vShow"],e.showInlineHelp]])],544)}const pe=Object(o["defineAsyncComponent"])(()=>new Promise(e=>{window.$(document).ready(()=>{e(window.Feedback.RateFeature)})}));var me=Object(o["defineComponent"])({props:{helpUrl:{type:String,default:""},editUrl:{type:String,default:""},reportGenerated:String,featureName:String,inlineHelp:String},components:{RateFeature:pe},data(){return{showIcons:!1,showInlineHelp:!1,actualFeatureName:this.featureName,actualInlineHelp:this.inlineHelp}},watch:{inlineHelp(e){this.actualInlineHelp=e},featureName(e){this.actualFeatureName=e}},mounted(){const{root:e}=this.$refs;setTimeout(()=>{if(!this.actualInlineHelp){let t=e.querySelector(".title .inlineHelp");if(!t&&e.parentElement.nextElementSibling&&(t=e.parentElement.nextElementSibling.querySelector(".reportDocumentation")),t){const e=t.getAttribute("data-content").trim();e.length&&(this.actualInlineHelp=`<p>${e}</p>`,setTimeout(()=>t.remove(),0))}}this.actualFeatureName||(this.actualFeatureName=e.querySelector(".title").textContent),this.reportGenerated&&l.parse(h.period,h.currentDateString).containsToday()&&window.$(e.querySelector(".report-generated")).tooltip({track:!0,content:this.reportGenerated,items:"div",show:!1,hide:!1})})}});me.render=ue;var he=me; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function X(){return W}function K(e,t){t.$on("$locationChangeSuccess",e.updatePeriodParamsFromUrl)}angular.module("piwikApp.service").service("piwik",X),K.$inject=["piwik","$rootScope"],angular.module("piwikApp.service").run(K);var Z=n("8bbf");const ee={ref:"root"};function te(e,t,n,r,a,i){return Object(Z["withDirectives"])((Object(Z["openBlock"])(),Object(Z["createElementBlock"])("div",ee,[Object(Z["renderSlot"])(e.$slots,"default")],512)),[[Z["vShow"],e.modelValue]])}var ne=Object(Z["defineComponent"])({props:{modelValue:{type:Boolean,required:!0},element:{type:HTMLElement,required:!1}},emits:["yes","no","closeEnd","close","update:modelValue"],setup(){const e=Object(Z["ref"])(null);return{root:e}},activated(){this.$emit("update:modelValue",!1)},watch:{modelValue(e,t){if(e){const e=this.element||this.$refs.root.firstElementChild;P.helper.modalConfirm(e,{yes:()=>{this.$emit("yes")},no:()=>{this.$emit("no")}},{onCloseEnd:()=>{this.element||this.$refs.root.appendChild(e),this.$emit("update:modelValue",!1),this.$emit("closeEnd")}})}else!1===e&&!0===t&&this.$emit("close")}}});ne.render=te;var re=ne; + */X({component:he,scope:{helpUrl:{angularJsBind:"@"},editUrl:{angularJsBind:"@"},reportGenerated:{angularJsBind:"@?"},featureName:{angularJsBind:"@"},inlineHelp:{angularJsBind:"@?"}},directiveName:"piwikEnrichedHeadline",transclude:!0});const ge={class:"card",ref:"root"},fe={class:"card-content"},be={key:0,class:"card-title"},we={key:1,class:"card-title"},ye={ref:"content"};function ve(e,t,r,n,a,i){const s=Object(o["resolveComponent"])("EnrichedHeadline");return Object(o["openBlock"])(),Object(o["createElementBlock"])("div",ge,[Object(o["createElementVNode"])("div",fe,[!e.contentTitle||e.actualFeature||e.helpUrl||e.actualHelpText?Object(o["createCommentVNode"])("",!0):(Object(o["openBlock"])(),Object(o["createElementBlock"])("h2",be,Object(o["toDisplayString"])(e.contentTitle),1)),e.contentTitle&&(e.actualFeature||e.helpUrl||e.actualHelpText)?(Object(o["openBlock"])(),Object(o["createElementBlock"])("h2",we,[Object(o["createVNode"])(s,{"feature-name":e.actualFeature,"help-url":e.helpUrl,"inline-help":e.actualHelpText},{default:Object(o["withCtx"])(()=>[Object(o["createTextVNode"])(Object(o["toDisplayString"])(e.contentTitle),1)]),_:1},8,["feature-name","help-url","inline-help"])])):Object(o["createCommentVNode"])("",!0),Object(o["createElementVNode"])("div",ye,[Object(o["renderSlot"])(e.$slots,"default")],512)])],512)}let Ce=null;var Pe=Object(o["defineComponent"])({props:{contentTitle:String,feature:String,helpUrl:String,helpText:String,anchor:String},components:{EnrichedHeadline:he},data(){return{actualFeature:this.feature,actualHelpText:this.helpText}},watch:{feature(e){this.actualFeature=e},helpText(e){this.actualHelpText=e}},mounted(){const{root:e,content:t}=this.$refs;if(this.anchor){const t=document.createElement("a");t.id=this.anchor,e.parentElement.prepend(t)}let r;if(setTimeout(()=>{const e=t.querySelector(".contentHelp");e&&(this.actualHelpText=e.innerHTML,e.remove())},0),!this.actualFeature||!0!==this.actualFeature&&"true"!==this.actualFeature||(this.actualFeature=this.contentTitle),null===Ce&&(Ce=document.querySelector("#content.admin")),Ce&&(r=Ce.offsetTop),r||0===r){const t=e.closest("[piwik-widget-loader]"),n=t?t.offsetTop:e.offsetTop;n-r<17&&(e.style.marginTop=0)}}});Pe.render=ve;var je=Pe; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */let ae=0;function ie(e){const{component:t,scope:n={},events:r={},$inject:a,directiveName:i,transclude:o,mountPointFactory:s,postCreate:l,noScope:c}=e,u=ae;o&&(ae+=1);const d={};function p(...e){const a={restrict:"A",scope:c?void 0:d,compile:function(){return{post:function(a,i,c){const d=o?i.find(`[ng-transclude][counter=${u}]`):null;let p="<root-component";Object.entries(n).forEach(([,e])=>{p+=` :${e.vue}="${e.vue}"`}),Object.entries(r).forEach(e=>{const[t]=e;p+=` @${t}="onEventHandler('${t}', $event)"`}),p+=">",o&&(p+='<div ref="transcludeTarget"/>'),p+="</root-component>";const h=Object(Z["createApp"])({template:p,data(){const t={};return Object.entries(n).forEach(([n,r])=>{let o=a[n];"undefined"===typeof o&&"undefined"!==typeof r.default&&(o=r.default instanceof Function?r.default(a,i,c,...e):r.default),t[r.vue]=o}),t},setup(){if(o){const e=Object(Z["ref"])(null);return{transcludeTarget:e}}},methods:{onEventHandler(t,n){r[t]&&r[t](n,a,i,c,...e)}}});h.config.globalProperties.$sanitize=window.vueSanitize,h.config.globalProperties.translate=k,h.component("root-component",t);const m=s?s(a,i,c,...e):i[0],g=h.mount(m);Object.entries(n).forEach(([t,n])=>{n.angularJsBind&&a.$watch(t,r=>{"undefined"!==typeof n.default&&"undefined"===typeof r?g[t]=n.default instanceof Function?n.default(a,i,c,...e):n.default:g[t]=r})}),o&&$(g.transcludeTarget).append(d),l&&l(g,a,i,c,...e),i.on("$destroy",()=>{h.unmount()})}}}};return o&&(a.transclude=!0,a.template=`<div ng-transclude counter="${u}"/>`),a}return Object.entries(n).forEach(([e,t])=>{t.vue||(t.vue=e),t.angularJsBind&&(d[e]=t.angularJsBind)}),p.$inject=a||[],angular.module("piwikApp").directive(i,p),p} + */X({component:je,scope:{contentTitle:{angularJsBind:"@"},feature:{angularJsBind:"@"},helpUrl:{angularJsBind:"@"},helpText:{angularJsBind:"@"},anchor:{angularJsBind:"@?"}},directiveName:"piwikContentBlock",transclude:!0});function Oe(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ie({component:re,scope:{show:{vue:"modelValue",default:!1},element:{default:(e,t)=>t[0]}},events:{yes:(e,t,n,r)=>{r.yes&&(t.$eval(r.yes),setTimeout(()=>{t.$apply()},0))},no:(e,t,n,r)=>{r.no&&(t.$eval(r.no),setTimeout(()=>{t.$apply()},0))},close:(e,t,n,r)=>{r.close&&(t.$eval(r.close),setTimeout(()=>{t.$apply()},0))},"update:modelValue":(e,t,n,r,a)=>{setTimeout(()=>{t.$apply(a(r.piwikDialog).assign(t,e))},0)}},$inject:["$parse"],directiveName:"piwikDialog",transclude:!0,mountPointFactory:(e,t)=>{const n=$('<div class="vue-placeholder"/>');return n.appendTo(t),n[0]},postCreate:(e,t,n,r)=>{t.$watch(r.piwikDialog,(t,n)=>{n!==t&&(e.modelValue=t||!1)})},noScope:!0});const oe={key:0,class:"title",tabindex:"6"},se=["href","title"],le={class:"iconsBar"},ce=["href","title"],ue=Object(Z["createElementVNode"])("span",{class:"icon-help"},null,-1),de=[ue],pe=["title"],he=Object(Z["createElementVNode"])("span",{class:"icon-info"},null,-1),me=[he],ge={class:"ratingIcons"},fe={class:"inlineHelp"},be=["innerHTML"],we=["href"];function ye(e,t,n,r,a,i){const o=Object(Z["resolveComponent"])("RateFeature");return Object(Z["openBlock"])(),Object(Z["createElementBlock"])("div",{class:"enrichedHeadline",onMouseenter:t[1]||(t[1]=t=>e.showIcons=!0),onMouseleave:t[2]||(t[2]=t=>e.showIcons=!1),ref:"root"},[e.editUrl?Object(Z["createCommentVNode"])("",!0):(Object(Z["openBlock"])(),Object(Z["createElementBlock"])("div",oe,[Object(Z["renderSlot"])(e.$slots,"default")])),e.editUrl?(Object(Z["openBlock"])(),Object(Z["createElementBlock"])("a",{key:1,class:"title",href:e.editUrl,title:e.translate("CoreHome_ClickToEditX",e.$sanitize(e.actualFeatureName))},[Object(Z["renderSlot"])(e.$slots,"default")],8,se)):Object(Z["createCommentVNode"])("",!0),Object(Z["withDirectives"])(Object(Z["createElementVNode"])("span",le,[e.helpUrl&&!e.actualInlineHelp?(Object(Z["openBlock"])(),Object(Z["createElementBlock"])("a",{key:0,rel:"noreferrer noopener",target:"_blank",class:"helpIcon",href:e.helpUrl,title:e.translate("CoreHome_ExternalHelp")},de,8,ce)):Object(Z["createCommentVNode"])("",!0),e.actualInlineHelp?(Object(Z["openBlock"])(),Object(Z["createElementBlock"])("a",{key:1,onClick:t[0]||(t[0]=t=>e.showInlineHelp=!e.showInlineHelp),class:Object(Z["normalizeClass"])(["helpIcon",{active:e.showInlineHelp}]),title:e.translate(e.reportGenerated?"General_HelpReport":"General_Help")},me,10,pe)):Object(Z["createCommentVNode"])("",!0),Object(Z["createElementVNode"])("div",ge,[Object(Z["createVNode"])(o,{title:e.actualFeatureName},null,8,["title"])])],512),[[Z["vShow"],e.showIcons||e.showInlineHelp]]),Object(Z["withDirectives"])(Object(Z["createElementVNode"])("div",fe,[Object(Z["createElementVNode"])("div",{innerHTML:e.$sanitize(e.actualInlineHelp)},null,8,be),e.helpUrl?(Object(Z["openBlock"])(),Object(Z["createElementBlock"])("a",{key:0,rel:"noreferrer noopener",target:"_blank",class:"readMore",href:e.helpUrl},Object(Z["toDisplayString"])(e.translate("General_MoreDetails")),9,we)):Object(Z["createCommentVNode"])("",!0)],512),[[Z["vShow"],e.showInlineHelp]])],544)}const De=Object(Z["defineAsyncComponent"])(()=>new Promise(e=>{window.$(document).ready(()=>{e(window.Feedback.RateFeature)})}));var Pe=Object(Z["defineComponent"])({props:{helpUrl:{type:String,default:""},editUrl:{type:String,default:""},reportGenerated:String,featureName:String,inlineHelp:String},components:{RateFeature:De},data(){return{showIcons:!1,showInlineHelp:!1,actualFeatureName:this.featureName,actualInlineHelp:this.inlineHelp}},setup(){const e=Object(Z["ref"])(null);return{root:e}},watch:{inlineHelp(e){this.actualInlineHelp=e},featureName(e){this.actualFeatureName=e}},mounted(){const{root:e}=this.$refs;setTimeout(()=>{if(!this.actualInlineHelp){let t=e.querySelector(".title .inlineHelp");if(!t&&e.parentElement.nextElementSibling&&(t=e.parentElement.nextElementSibling.querySelector(".reportDocumentation")),t){const e=t.getAttribute("data-content").trim();e.length&&(this.actualInlineHelp=`<p>${e}</p>`,setTimeout(()=>t.remove(),0))}}this.actualFeatureName||(this.actualFeatureName=e.querySelector(".title").textContent),this.reportGenerated&&u.parse(P.period,P.currentDateString).containsToday()&&window.$(e.querySelector(".report-generated")).tooltip({track:!0,content:this.reportGenerated,items:"div",show:!1,hide:!1})})}});Pe.render=ye;var ve=Pe; + */class De{get state(){return Object(o["readonly"])(this.segmentState)}constructor(){Oe(this,"segmentState",Object(o["reactive"])({availableSegments:[]})),h.on("piwikSegmentationInited",()=>this.setSegmentState())}setSegmentState(){try{const e=$(".segmentEditorPanel").data("uiControlObject");this.segmentState.availableSegments=e.impl.availableSegments||[]}catch(e){}}}var Se=new De;function ke(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e} /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ie({component:ve,scope:{helpUrl:{angularJsBind:"@"},editUrl:{angularJsBind:"@"},reportGenerated:{angularJsBind:"@?"},featureName:{angularJsBind:"@"},inlineHelp:{angularJsBind:"@?"}},directiveName:"piwikEnrichedHeadline",transclude:!0});const je={class:"card",ref:"root"},ke={class:"card-content"},Oe={key:0,class:"card-title"},Se={key:1,class:"card-title"},Te={ref:"content"};function $e(e,t,n,r,a,i){const o=Object(Z["resolveComponent"])("EnrichedHeadline");return Object(Z["openBlock"])(),Object(Z["createElementBlock"])("div",je,[Object(Z["createElementVNode"])("div",ke,[!e.contentTitle||e.actualFeature||e.helpUrl||e.actualHelpText?Object(Z["createCommentVNode"])("",!0):(Object(Z["openBlock"])(),Object(Z["createElementBlock"])("h2",Oe,Object(Z["toDisplayString"])(e.contentTitle),1)),e.contentTitle&&(e.actualFeature||e.helpUrl||e.actualHelpText)?(Object(Z["openBlock"])(),Object(Z["createElementBlock"])("h2",Se,[Object(Z["createVNode"])(o,{"feature-name":e.actualFeature,"help-url":e.helpUrl,"inline-help":e.actualHelpText},{default:Object(Z["withCtx"])(()=>[Object(Z["createTextVNode"])(Object(Z["toDisplayString"])(e.contentTitle),1)]),_:1},8,["feature-name","help-url","inline-help"])])):Object(Z["createCommentVNode"])("",!0),Object(Z["createElementVNode"])("div",Te,[Object(Z["renderSlot"])(e.$slots,"default")],512)])],512)}let Ee=null;var Ce=Object(Z["defineComponent"])({props:{contentTitle:String,feature:String,helpUrl:String,helpText:String,anchor:String},components:{EnrichedHeadline:ve},data(){return{actualFeature:this.feature,actualHelpText:this.helpText}},setup(){const e=Object(Z["ref"])(null),t=Object(Z["ref"])(null);return{root:e,content:t}},watch:{feature(e){this.actualFeature=e},helpText(e){this.actualHelpText=e}},mounted(){const{root:e,content:t}=this.$refs;if(this.anchor){const t=document.createElement("a");t.id=this.anchor,e.parentElement.prepend(t)}let n;if(setTimeout(()=>{const e=t.querySelector(".contentHelp");e&&(this.actualHelpText=e.innerHTML,e.remove())},0),!this.actualFeature||!0!==this.actualFeature&&"true"!==this.actualFeature||(this.actualFeature=this.contentTitle),null===Ee&&(Ee=document.querySelector("#content.admin")),Ee&&(n=Ee.offsetTop),n||0===n){const t=e.closest("[piwik-widget-loader]"),r=t?t.offsetTop:e.offsetTop;r-n<17&&(e.style.marginTop=0)}}});Ce.render=$e;var Ie=Ce; + */const Ee=8,Te=3;function $e(e){return e?e instanceof Array?e:[e]:[]}class xe{constructor(){ke(this,"privateState",Object(o["reactive"])({comparisonsDisabledFor:[]})),ke(this,"state",Object(o["readonly"])(this.privateState)),ke(this,"colors",{}),ke(this,"segmentComparisons",Object(o["computed"])(()=>this.parseSegmentComparisons())),ke(this,"periodComparisons",Object(o["computed"])(()=>this.parsePeriodComparisons())),ke(this,"isEnabled",Object(o["computed"])(()=>this.checkEnabledForCurrentPage())),this.loadComparisonsDisabledFor(),$(()=>{this.colors=this.getAllSeriesColors()}),Object(o["watch"])(()=>this.getComparisons(),()=>h.postEvent("piwikComparisonsChanged"),{deep:!0})}getComparisons(){return this.getSegmentComparisons().concat(this.getPeriodComparisons())}isComparing(){return this.isComparisonEnabled()&&(this.segmentComparisons.value.length>1||this.periodComparisons.value.length>1)}isComparingPeriods(){return this.getPeriodComparisons().length>1}getSegmentComparisons(){return this.isComparisonEnabled()?this.segmentComparisons.value:[]}getPeriodComparisons(){return this.isComparisonEnabled()?this.periodComparisons.value:[]}getSeriesColor(e,t,r=0){const n=this.getComparisonSeriesIndex(t.index,e.index)%Ee;if(0===r)return this.colors["series"+n];const a=r%Te;return this.colors[`series${n}-shade${a}`]}getSeriesColorName(e,t){let r="series"+e%Ee;return t>0&&(r+="-shade"+t%Te),r}isComparisonEnabled(){return this.isEnabled.value}getIndividualComparisonRowIndices(e){const t=this.getSegmentComparisons().length,r=e%t,n=Math.floor(e/t);return{segmentIndex:r,periodIndex:n}}getComparisonSeriesIndex(e,t){const r=this.getSegmentComparisons().length;return e*r+t}getAllComparisonSeries(){const e=[];let t=0;return this.getPeriodComparisons().forEach(r=>{this.getSegmentComparisons().forEach(n=>{e.push({index:t,params:{...n.params,...r.params},color:this.colors["series"+t]}),t+=1})}),e}removeSegmentComparison(e){if(!this.isComparisonEnabled())throw new Error("Comparison disabled.");const t=[...this.segmentComparisons.value];t.splice(e,1);const r={};0===e&&(r.segment=t[0].params.segment),this.updateQueryParamsFromComparisons(t,this.periodComparisons.value,r)}addSegmentComparison(e){if(!this.isComparisonEnabled())throw new Error("Comparison disabled.");const t=this.segmentComparisons.value.concat([{params:e,index:-1,title:""}]);this.updateQueryParamsFromComparisons(t,this.periodComparisons.value)}updateQueryParamsFromComparisons(e,t,r={}){const n={},a={};let o=!1,i=!1;e.forEach(e=>{o?n[e.params.segment]=!0:o=!0}),t.forEach(e=>{i?a[`${e.params.period}|${e.params.date}`]=!0:i=!0});const s=[],l=[];Object.keys(a).forEach(e=>{const t=e.split("|");s.push(t[0]),l.push(t[1])});const c={compareSegments:Object.keys(n),comparePeriods:s,compareDates:l};if(h.helper.isAngularRenderingThePage()){const e=R.hashParsed.value,t={...e,...c,...r};return delete t["compareSegments[]"],delete t["comparePeriods[]"],delete t["compareDates[]"],void(JSON.stringify(t)!==JSON.stringify(e)&&R.updateHash(t))}const d=[];["compareSegments","comparePeriods","compareDates"].forEach(e=>{c[e].length||d.push(e)});const u=R.stringify(r),p=R.stringify(c);window.broadcast.propagateNewPage(u,void 0,p,d)}getAllSeriesColors(){const{ColorManager:e}=h,t=[];for(let r=0;r<Ee;r+=1){t.push("series"+r);for(let e=0;e<Te;e+=1)t.push(`series${r}-shade${e}`)}return e.getColors("comparison-series-color",t)}loadComparisonsDisabledFor(){G.fetch({module:"API",method:"API.getPagesComparisonsDisabledFor"}).then(e=>{this.privateState.comparisonsDisabledFor=e})}parseSegmentComparisons(){const{availableSegments:e}=Se.state,t=[...$e(R.parsed.value.compareSegments)];t.unshift(R.parsed.value.segment||"");const r=[];return t.forEach((t,n)=>{let a;e.forEach(e=>{e.definition!==t&&e.definition!==decodeURIComponent(t)&&decodeURIComponent(e.definition)!==t||(a=e)});let o=a?a.name:g("General_Unknown");""===t.trim()&&(o=g("SegmentEditor_DefaultAllVisits")),r.push({params:{segment:t},title:h.helper.htmlDecode(o),index:n})}),r}parsePeriodComparisons(){const e=[...$e(R.parsed.value.comparePeriods)],t=[...$e(R.parsed.value.compareDates)];e.unshift(R.parsed.value.period),t.unshift(R.parsed.value.date);const r=[];for(let a=0;a<Math.min(t.length,e.length);a+=1){let o;try{o=l.parse(e[a],t[a]).getPrettyString()}catch(n){o=g("General_Error")}r.push({params:{date:t[a],period:e[a]},title:o,index:a})}return r}checkEnabledForCurrentPage(){const e=R.parsed.value.category||R.parsed.value.module,t=R.parsed.value.subcategory||R.parsed.value.action,r=`${e}.${t}`,n=-1===this.privateState.comparisonsDisabledFor.indexOf(r)&&-1===this.privateState.comparisonsDisabledFor.indexOf(e+".*");return document.documentElement.classList.toggle("comparisonsDisabled",!n),n}}var Ie=new xe;const He={key:0,ref:"root",class:"matomo-comparisons"},Ne={class:"comparison-type"},Fe=["title"],Be=["href"],Ae=["title"],Re={class:"comparison-period-label"},Ue=["onClick"],_e=["title"],Ve={class:"loadingPiwik",style:{display:"none"}},Me=["alt"];function qe(e,t,r,n,a,i){return e.isComparing?(Object(o["openBlock"])(),Object(o["createElementBlock"])("div",He,[Object(o["createElementVNode"])("h3",null,Object(o["toDisplayString"])(e.translate("General_Comparisons")),1),(Object(o["openBlock"])(!0),Object(o["createElementBlock"])(o["Fragment"],null,Object(o["renderList"])(e.segmentComparisons,(t,r)=>(Object(o["openBlock"])(),Object(o["createElementBlock"])("div",{class:"comparison card",key:t.index},[Object(o["createElementVNode"])("div",Ne,Object(o["toDisplayString"])(e.translate("General_Segment")),1),Object(o["createElementVNode"])("div",{class:"title",title:t.title+"<br/>"+decodeURIComponent(t.params.segment)},[Object(o["createElementVNode"])("a",{target:"_blank",href:e.getUrlToSegment(t.params.segment)},Object(o["toDisplayString"])(t.title),9,Be)],8,Fe),(Object(o["openBlock"])(!0),Object(o["createElementBlock"])(o["Fragment"],null,Object(o["renderList"])(e.periodComparisons,r=>(Object(o["openBlock"])(),Object(o["createElementBlock"])("div",{class:"comparison-period",key:r.index,title:e.getComparisonTooltip(t,r)},[Object(o["createElementVNode"])("span",{class:"comparison-dot",style:Object(o["normalizeStyle"])({"background-color":e.getSeriesColor(t,r)})},null,4),Object(o["createElementVNode"])("span",Re,Object(o["toDisplayString"])(r.title)+" ("+Object(o["toDisplayString"])(e.getComparisonPeriodType(r))+") ",1)],8,Ae))),128)),e.segmentComparisons.length>1?(Object(o["openBlock"])(),Object(o["createElementBlock"])("a",{key:0,class:"remove-button",onClick:t=>e.removeSegmentComparison(r)},[Object(o["createElementVNode"])("span",{class:"icon icon-close",title:e.translate("General_ClickToRemoveComp")},null,8,_e)],8,Ue)):Object(o["createCommentVNode"])("",!0)]))),128)),Object(o["createElementVNode"])("div",Ve,[Object(o["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif",alt:e.translate("General_LoadingData")},null,8,Me),Object(o["createTextVNode"])(" "+Object(o["toDisplayString"])(e.translate("General_LoadingData")),1)])],512)):Object(o["createCommentVNode"])("",!0)}var Ge=Object(o["defineComponent"])({props:{},data(){return{comparisonTooltips:null}},setup(){const e=Object(o["computed"])(()=>Ie.isComparing()),t=Object(o["computed"])(()=>Ie.getSegmentComparisons()),r=Object(o["computed"])(()=>Ie.getPeriodComparisons()),n=Ie.getSeriesColor.bind(Ie);return{isComparing:e,segmentComparisons:t,periodComparisons:r,getSeriesColor:n}},methods:{comparisonHasSegment(e){return"undefined"!==typeof e.params.segment},removeSegmentComparison(e){window.$(this.$refs.root).tooltip("destroy"),Ie.removeSegmentComparison(e)},getComparisonPeriodType(e){const{period:t}=e.params;if("range"===t)return g("CoreHome_PeriodRange");const r=g(`Intl_Period${t.substring(0,1).toUpperCase()}${t.substring(1)}`);return r.substring(0,1).toUpperCase()+r.substring(1)},getComparisonTooltip(e,t){if(this.comparisonTooltips&&Object.keys(this.comparisonTooltips).length)return(this.comparisonTooltips[t.index]||{})[e.index]},getUrlToSegment(e){const t={...R.hashParsed.value};return delete t.comparePeriods,delete t.compareDates,delete t.compareSegments,t.segment=e,`${window.location.search}#?${R.stringify(t)}`},setUpTooltips(){const{$:e}=window;e(this.$refs.root).tooltip({track:!0,content:function(){const t=e(this).attr("title");return window.vueSanitize(t.replace(/\n/g,"<br />"))},show:{delay:200,duration:200},hide:!1})},onComparisonsChanged(){if(this.comparisonTooltips=null,!Ie.isComparing())return;const e=Ie.getPeriodComparisons(),t=Ie.getSegmentComparisons();G.fetch({method:"API.getProcessedReport",apiModule:"VisitsSummary",apiAction:"get",compare:"1",compareSegments:R.getSearchParam("compareSegments"),comparePeriods:R.getSearchParam("comparePeriods"),compareDates:R.getSearchParam("compareDates"),format_metrics:"1"}).then(r=>{this.comparisonTooltips={},e.forEach(e=>{this.comparisonTooltips[e.index]={},t.forEach(t=>{const n=this.generateComparisonTooltip(r,e,t);this.comparisonTooltips[e.index][t.index]=n})})})},generateComparisonTooltip(e,t,r){if(!e.reportData.comparisons)return"";const n=Ie.getComparisonSeriesIndex(t.index,0),a=e.reportData.comparisons[n],o=Ie.getComparisonSeriesIndex(t.index,r.index),i=e.reportData.comparisons[o],s=e.reportData.comparisons[r.index];let l='<div class="comparison-card-tooltip">',c=(i.nb_visits/a.nb_visits*100).toFixed(2);return c+="%",l+=g("General_ComparisonCardTooltip1",[`'${i.compareSegmentPretty}'`,i.comparePeriodPretty,c,i.nb_visits.toString(),a.nb_visits.toString()]),t.index>0&&(l+="<br/><br/>",l+=g("General_ComparisonCardTooltip2",[i.nb_visits_change.toString(),s.compareSegmentPretty,s.comparePeriodPretty])),l+="</div>",l}},updated(){setTimeout(()=>this.setUpTooltips())},mounted(){h.on("piwikComparisonsChanged",()=>{this.onComparisonsChanged()}),this.onComparisonsChanged(),setTimeout(()=>this.setUpTooltips())},beforeUnmount(){try{window.$(this.refs.root).tooltip("destroy")}catch(e){}}});Ge.render=qe;var Le=Ge; /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ie({component:Ie,scope:{contentTitle:{angularJsBind:"@"},feature:{angularJsBind:"@"},helpUrl:{angularJsBind:"@"},helpText:{angularJsBind:"@"},anchor:{angularJsBind:"@?"}},directiveName:"piwikContentBlock",transclude:!0});const xe={class:"loadingPiwik"},He=Object(Z["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif",alt:""},null,-1);function Re(e,t,n,r,a,i){return Object(Z["withDirectives"])((Object(Z["openBlock"])(),Object(Z["createElementBlock"])("div",xe,[He,Object(Z["createElementVNode"])("span",null,Object(Z["toDisplayString"])(e.loadingMessage),1)],512)),[[Z["vShow"],e.loading]])}var Ae=Object(Z["defineComponent"])({props:{loading:{type:Boolean,required:!0,default:!1},loadingMessage:{type:String,required:!1,default:k("General_LoadingData")}}});Ae.render=Re;var Fe=Ae,Ne=ie({component:Fe,scope:{loading:{vue:"loading",angularJsBind:"<"},loadingMessage:{vue:"loadingMessage",angularJsBind:"<",default:()=>k("General_LoadingData")}},$inject:[],directiveName:"piwikActivityIndicator"}); + */function Qe(){return Ie}Qe.$inject=[],angular.module("piwikApp.service").factory("piwikComparisonsService",Qe);X({component:Le,directiveName:"piwikComparisons",restrict:"E"});const Je={class:"loadingPiwik"},ze=Object(o["createElementVNode"])("img",{src:"plugins/Morpheus/images/loading-blue.gif",alt:""},null,-1);function Ye(e,t,r,n,a,i){return Object(o["withDirectives"])((Object(o["openBlock"])(),Object(o["createElementBlock"])("div",Je,[ze,Object(o["createElementVNode"])("span",null,Object(o["toDisplayString"])(e.loadingMessage),1)],512)),[[o["vShow"],e.loading]])}var We=Object(o["defineComponent"])({props:{loading:{type:Boolean,required:!0,default:!1},loadingMessage:{type:String,required:!1,default:g("General_LoadingData")}}});We.render=Ye;var Xe=We,Ke=X({component:Xe,scope:{loading:{vue:"loading",angularJsBind:"<"},loadingMessage:{vue:"loadingMessage",angularJsBind:"<",default:()=>g("General_LoadingData")}},$inject:[],directiveName:"piwikActivityIndicator"}); /*! * Matomo - free/libre analytics platform * * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */function Be(e,t,n,r,a,i){return Object(Z["openBlock"])(),Object(Z["createElementBlock"])("div",{class:Object(Z["normalizeClass"])(["alert",{["alert-"+e.severity]:!0}])},[Object(Z["renderSlot"])(e.$slots,"default")],2)}var Ue=Object(Z["defineComponent"])({props:{severity:{type:String,required:!0}}});Ue.render=Be;var Me=Ue,Ve=ie({component:Me,scope:{severity:{vue:"severity",angularJsBind:"@piwikAlert"}},directiveName:"piwikAlert",transclude:!0}); + */function Ze(e,t,r,n,a,i){return Object(o["openBlock"])(),Object(o["createElementBlock"])("div",{class:Object(o["normalizeClass"])(["alert",{["alert-"+e.severity]:!0}])},[Object(o["renderSlot"])(e.$slots,"default")],2)}var et=Object(o["defineComponent"])({props:{severity:{type:String,required:!0}}});et.render=Ze;var tt=et,rt=X({component:tt,scope:{severity:{vue:"severity",angularJsBind:"@piwikAlert"}},directiveName:"piwikAlert",transclude:!0}); /*! * Matomo - free/libre analytics platform * diff --git a/plugins/CoreHome/vue/src/AjaxHelper/AjaxHelper.ts b/plugins/CoreHome/vue/src/AjaxHelper/AjaxHelper.ts index 09eeaac874..b1bf30ca7f 100644 --- a/plugins/CoreHome/vue/src/AjaxHelper/AjaxHelper.ts +++ b/plugins/CoreHome/vue/src/AjaxHelper/AjaxHelper.ts @@ -5,15 +5,16 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ +import jqXHR = JQuery.jqXHR; import MatomoUrl from '../MatomoUrl/MatomoUrl'; import Matomo from '../Matomo/Matomo'; -window.globalAjaxQueue = [] as GlobalAjaxQueue; +window.globalAjaxQueue = [] as unknown as GlobalAjaxQueue; window.globalAjaxQueue.active = 0; window.globalAjaxQueue.clean = function globalAjaxQueueClean() { for (let i = this.length; i >= 0; i -= 1) { - if (!this[i] || this[i].readyState === 4) { + if (!this[i] || this[i]!.readyState === 4) { this.splice(i, 1); } } @@ -39,8 +40,6 @@ window.globalAjaxQueue.abort = function globalAjaxQueueAbort() { this.active = 0; }; -type ParameterValue = string | number | null | undefined | ParameterValue[]; -type Parameters = {[name: string]: ParameterValue | Parameters}; type AnyFunction = (...params:any[]) => any; // eslint-disable-line /** @@ -52,6 +51,11 @@ function defaultErrorCallback(deferred: XMLHttpRequest, status: string): void { return; } + if (typeof Piwik_Popover === 'undefined') { + console.log(`Request failed: ${deferred.responseText}`); // mostly for tests + return; + } + const loadingError = $('#loadingError'); if (Piwik_Popover.isOpen() && deferred && deferred.status === 500) { if (deferred && deferred.status === 500) { @@ -65,7 +69,7 @@ function defaultErrorCallback(deferred: XMLHttpRequest, status: string): void { /** * Global ajax helper to handle requests within Matomo */ -export default class AjaxHelper { +export default class AjaxHelper<T = any> { // eslint-disable-line /** * Format of response */ @@ -74,12 +78,12 @@ export default class AjaxHelper { /** * A timeout for the request which will override any global timeout */ - timeout = null; + timeout: number|null = null; /** * Callback function to be executed on success */ - callback: AnyFunction = null; + callback: AnyFunction|null = null; /** * Use this.callback if an error is returned @@ -100,13 +104,13 @@ export default class AjaxHelper { * * @deprecated use the jquery promise API */ - completeCallback: AnyFunction; + completeCallback?: AnyFunction; /** * Params to be passed as GET params * @see ajaxHelper.mixinDefaultGetParams */ - getParams: Parameters = {}; + getParams: QueryParameters = {}; /** * Base URL used in the AJAX request. Can be set by setUrl. @@ -124,7 +128,7 @@ export default class AjaxHelper { * Params to be passed as GET params * @see ajaxHelper.mixinDefaultPostParams */ - postParams: Parameters = {}; + postParams: QueryParameters = {}; /** * Element to be displayed while loading @@ -139,13 +143,13 @@ export default class AjaxHelper { /** * Handle for current request */ - requestHandle: XMLHttpRequest|JQuery.jqXHR|null = null; + requestHandle: JQuery.jqXHR|null = null; defaultParams = ['idSite', 'period', 'date', 'segment']; // helper method entry point - static fetch(params: Parameters): JQuery.jqXHR { - const helper = new AjaxHelper(); + static fetch<R = any>(params: QueryParameters): Promise<R> { // eslint-disable-line + const helper = new AjaxHelper<R>(); helper.setFormat('json'); helper.addParams({ module: 'API', format: 'json', ...params }, 'get'); return helper.send(); @@ -159,15 +163,13 @@ export default class AjaxHelper { * Adds params to the request. * If params are given more then once, the latest given value is used for the request * - * @param params + * @param initialParams * @param type type of given parameters (POST or GET) * @return {void} */ - addParams(params: Parameters|string, type: string): void { - if (typeof params === 'string') { - // TODO: add global types for broadcast (multiple uses below) - params = window['broadcast'].getValuesFromUrl(params); // eslint-disable-line - } + addParams(initialParams: QueryParameters|string, type: string): void { + const params: QueryParameters = typeof initialParams === 'string' + ? window.broadcast.getValuesFromUrl(initialParams) : initialParams; const arrayParams = ['compareSegments', 'comparePeriods', 'compareDates']; Object.keys(params).forEach((key) => { @@ -201,8 +203,8 @@ export default class AjaxHelper { * Gets this helper instance ready to send a bulk request. Each argument to this * function is a single request to use. */ - setBulkRequests(...urls: string[]): void { - const urlsProcessed = urls.map((u) => $.param(u)); + setBulkRequests(...urls: Array<string|QueryParameters>): void { + const urlsProcessed = urls.map((u) => (typeof u === 'string' ? u : $.param(u))); this.addParams({ module: 'API', @@ -246,7 +248,7 @@ export default class AjaxHelper { * @param [params] to modify in redirect url * @return {void} */ - redirectOnSuccess(params: Parameters): void { + redirectOnSuccess(params: QueryParameters): void { this.setCallback(() => { piwikHelper.redirect(params); }); @@ -333,7 +335,7 @@ export default class AjaxHelper { /** * Send the request */ - send(): JQuery.jqXHR { + send(): Promise<T> { if ($(this.errorElement).length) { $(this.errorElement).hide(); } @@ -343,9 +345,17 @@ export default class AjaxHelper { } this.requestHandle = this.buildAjaxCall(); - globalAjaxQueue.push(this.requestHandle); + window.globalAjaxQueue.push(this.requestHandle); - return this.requestHandle; + return new Promise<T>((resolve, reject) => { + this.requestHandle!.then(resolve).fail((xhr: jqXHR) => { + if (xhr.statusText !== 'abort') { + console.log(`Warning: the ${$.param(this.getParams)} request failed!`); + + reject(xhr); + } + }); + }); } /** @@ -387,21 +397,21 @@ export default class AjaxHelper { url, dataType: this.format || 'json', complete: this.completeCallback, - error: function errorCallback() { - globalAjaxQueue.active -= 1; + error: function errorCallback(...args: any[]) { // eslint-disable-line + window.globalAjaxQueue.active -= 1; if (self.errorCallback) { - self.errorCallback.apply(this, arguments); // eslint-disable-line + self.errorCallback.apply(this, args); } }, - success: (response, status, request) => { + success: (response: any, status: string, request: jqXHR) => { // eslint-disable-line if (this.loadingElement) { $(this.loadingElement).hide(); } if (response && response.result === 'error' && !this.useRegularCallbackInCaseOfError) { let placeAt = null; - let type = 'toast'; + let type: string|null = 'toast'; if ($(this.errorElement).length && response.message) { $(this.errorElement).show(); placeAt = this.errorElement; @@ -423,7 +433,7 @@ export default class AjaxHelper { this.callback(response, status, request); } - globalAjaxQueue.active -= 1; + window.globalAjaxQueue.active -= 1; if (Matomo.ajaxRequestFinished) { Matomo.ajaxRequestFinished(); } @@ -462,7 +472,7 @@ export default class AjaxHelper { * * @param params parameter object */ - private mixinDefaultPostParams(params): Parameters { + private mixinDefaultPostParams(params: QueryParameters): QueryParameters { const defaultParams = this.getDefaultPostParams(); const mergedParams = { @@ -478,11 +488,11 @@ export default class AjaxHelper { * * @param params parameter object */ - private mixinDefaultGetParams(originalParams): Parameters { + private mixinDefaultGetParams(originalParams: QueryParameters): QueryParameters { const segment = MatomoUrl.getSearchParam('segment'); - const defaultParams = { - idSite: Matomo.idSite || broadcast.getValueFromUrl('idSite'), + const defaultParams: Record<string, string> = { + idSite: Matomo.idSite ? Matomo.idSite.toString() : broadcast.getValueFromUrl('idSite'), period: Matomo.period || broadcast.getValueFromUrl('period'), segment, }; diff --git a/plugins/CoreHome/vue/src/Comparisons/Comparisons.adapter.ts b/plugins/CoreHome/vue/src/Comparisons/Comparisons.adapter.ts new file mode 100644 index 0000000000..845c965081 --- /dev/null +++ b/plugins/CoreHome/vue/src/Comparisons/Comparisons.adapter.ts @@ -0,0 +1,24 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import createAngularJsAdapter from '../createAngularJsAdapter'; +import ComparisonsStoreInstance from './Comparisons.store.instance'; +import Comparisons from './Comparisons.vue'; + +function ComparisonFactory() { + return ComparisonsStoreInstance; +} + +ComparisonFactory.$inject = []; + +angular.module('piwikApp.service').factory('piwikComparisonsService', ComparisonFactory); + +export default createAngularJsAdapter({ + component: Comparisons, + directiveName: 'piwikComparisons', + restrict: 'E', +}); diff --git a/plugins/CoreHome/vue/src/Comparisons/Comparisons.less b/plugins/CoreHome/vue/src/Comparisons/Comparisons.less new file mode 100644 index 0000000000..6b73255b5f --- /dev/null +++ b/plugins/CoreHome/vue/src/Comparisons/Comparisons.less @@ -0,0 +1,78 @@ +.matomo-comparisons { + h3 { + margin-top: 0; + margin-bottom: 0; + } + + .comparison { + display: inline-block; + min-width: 164px; + margin-right: 16px; + padding: 14px; + background-color: white; + margin-bottom: 16px; + position: relative; + } + + .comparison-type { + font-weight: bold; + text-transform: uppercase; + font-size: .7em; + color: #999; + } + + .remove-button { + font-size: .6em; + color: #666; + position: absolute; + right: 12px; + top: 12px; + } + + .title { + font-size: 1.1rem; + margin-top: 4px; + margin-bottom: 12px; + + overflow-x: hidden; + max-width: 250px; + text-overflow: ellipsis; + white-space: nowrap; + + a { + color: #333; + } + } + + .comparison-period { + margin-top: 4px; + + > span { + display: inline-block; + } + + .comparison-dot { + width: 1em; + height: 1em; + display: inline-block; + border-radius: .5em; + } + + .comparison-period-label { + position: relative; + top: -3px; + margin-left: 6.5px; + display: inline-block; + font-size: .85rem; + font-style: italic; + } + } +} + +.comparison-card-tooltip { + p { + font-size: 1.1em; + line-height: 1.3em; + color: #fff; + } +}
\ No newline at end of file diff --git a/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.instance.ts b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.instance.ts new file mode 100644 index 0000000000..4e1a2b38ab --- /dev/null +++ b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.instance.ts @@ -0,0 +1,3 @@ +import ComparisonsStore from './Comparisons.store'; + +export default new ComparisonsStore(); diff --git a/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.spec.ts b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.spec.ts new file mode 100644 index 0000000000..97e1ad3f22 --- /dev/null +++ b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.spec.ts @@ -0,0 +1,457 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ +import nock from 'nock'; +import '../Periods/Day'; +import '../Periods/Week'; +import '../Periods/Month'; +import '../Periods/Year'; +import '../Periods/Range'; +import '../Matomo/Matomo.adapter'; // for $rootScope.$oldEmit +import ComparisonsStore from './Comparisons.store'; +import MatomoUrl from '../MatomoUrl/MatomoUrl'; + +describe('CoreHome/Comparisons.store', () => { + const DISABLED_PAGES = [ + 'MyModule1.disabledPage', + 'MyModule2.disabledPage2', + 'MyModule3.*', + ]; + let piwikComparisonsService: ComparisonsStore; + let oldWindowHash: string; + let nockScope: nock.Scope; + let oldColorManager: ColorManagerService; + + function wait() { + return new Promise(resolve => setTimeout(resolve, 0)); + } + + function angularApply() { + window.angular.element(document).injector().get('$rootScope').$apply(); + } + + async function setHash(search: string) { + MatomoUrl.updateHash(search); + angularApply(); + await wait(); + + // more than one required for all callbacks to finish + while (!piwikComparisonsService.state.comparisonsDisabledFor.length) { + await wait(); + } + } + + beforeAll(() => { + nockScope = nock('http://localhost') + .persist() + .post('/') + .query((query) => { + return query.method === 'API.getPagesComparisonsDisabledFor'; + }) + .reply(200, JSON.stringify(DISABLED_PAGES)); + }); + beforeAll(() => { + // so piwikHelper.isAngularRenderingThePage will return true + document.body.innerHTML = document.body.innerHTML + '<div piwik-reporting-page />'; + }); + beforeAll(async () => { + await new Promise<void>((resolve) => { + window.angular.element(() => { + window.angular.bootstrap(document, ['piwikApp']); + resolve(); + }); + }); + }); + + beforeEach(() => { + oldWindowHash = window.location.hash; + }); + beforeEach(() => { + oldColorManager = window.piwik.ColorManager; + window.piwik.ColorManager = { + getColors(ns: string, colors: string[]) { + const result: {[key: string]: string} = {}; + colors.forEach((name: string) => { + result[name] = `${ns}.${name}`; + }); + return result; + } + } as unknown as ColorManagerService; + }); + beforeEach(() => { + angularApply(); // necessary for some reason... doesn't work in beforeAll(), just beforeEach() + }); + beforeEach(() => { + piwikComparisonsService = new ComparisonsStore(); + }); + afterEach(() => { + window.piwik.ColorManager = oldColorManager; + window.location.hash = oldWindowHash; + }); + + afterAll(() => { + nockScope.done(); + }); + + describe('#getComparisons()', () => { + it('should return all comparisons in URL', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getComparisons()).toEqual([ + { + params: { + segment: 'abcdefg', + }, + title: 'General_Unknown', + index: 0, + }, + { + params: { + segment: 'comparedsegment', + }, + title: 'General_Unknown', + index: 1, + }, + { + params: { + segment: '', + }, + title: 'SegmentEditor_DefaultAllVisits', + index: 2, + }, + { + params: { + date: '2018-01-02', + period: 'day' + }, + title: '2018-01-02', + index: 0, + }, + { + params: { + date: '2018-03-04', + period: 'week' + }, + title: 'General_DateRangeFromTo', + index: 1, + }, + ]); + }); + + it('should return base params if there are no comparisons', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg'); + + expect(piwikComparisonsService.getComparisons()).toEqual([ + { + params: { + segment: 'abcdefg' + }, + title: 'General_Unknown', + index: 0, + }, + { + params: { + date: '2018-01-02', + period: 'day' + }, + title: '2018-01-02', + index: 0, + }, + ]); + }); + it('should return nothing if comparison is not enabled for the page', async () => { + await setHash('category=MyModule1&subcategory=disabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getComparisons()).toEqual([]); + }); + }); + + describe('#removeSegmentComparison()', () => { + it('should remove an existing segment comparison from the URL', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + piwikComparisonsService.removeSegmentComparison(1); + angularApply(); + await wait(); + + expect(window.location.href).toEqual('http://localhost/#?category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates%5B%5D=2018-03-04&comparePeriods%5B%5D=week&compareSegments%5B%5D='); + }); + + it('should change the base comparison if the first segment is removed', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + piwikComparisonsService.removeSegmentComparison(0); + angularApply(); + await wait(); + + expect(window.location.href).toEqual('http://localhost/#?category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=comparedsegment&compareDates%5B%5D=2018-03-04&comparePeriods%5B%5D=week&compareSegments%5B%5D='); + }); + }); + + describe('#addSegmentComparison()', () => { + it('should add a new segment comparison to the URL', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment'); + + piwikComparisonsService.addSegmentComparison({ + segment: 'newsegment', + }); + angularApply(); + await wait(); + + expect(piwikComparisonsService.getComparisons()).toEqual([ + {"params":{"segment":""},"title":"SegmentEditor_DefaultAllVisits","index":0}, + {"params":{"segment":"comparedsegment"},"title":"General_Unknown","index":1}, + {"params":{"segment":"newsegment"},"title":"General_Unknown","index":2}, + {"params":{"date":"2018-01-02","period":"day"},"title":"2018-01-02","index":0}, + {"params":{"date":"2018-03-04","period":"week"},"title":"General_DateRangeFromTo","index":1}, + ]); + }); + + it('should add the all visits segment to the URL', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment'); + + piwikComparisonsService.addSegmentComparison({ + segment: '', + }); + angularApply(); + await wait(); + + expect(piwikComparisonsService.getComparisons()).toEqual([ + {"params":{"segment":"abcdefg"},"title":"General_Unknown","index":0}, + {"params":{"segment":"comparedsegment"},"title":"General_Unknown","index":1}, + {"params":{"segment":""},"title":"SegmentEditor_DefaultAllVisits","index":2}, + {"params":{"date":"2018-01-02","period":"day"},"title":"2018-01-02","index":0}, + {"params":{"date":"2018-03-04","period":"week"},"title":"General_DateRangeFromTo","index":1} + ]); + }); + }); + + describe('#isComparisonEnabled()', () => { + it('should return true if comparison is enabled for the page', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment'); + + expect(piwikComparisonsService.isComparisonEnabled()).toBe(true); + }); + + it('should return false if comparison is disabled for the page', async () => { + await setHash('category=MyModule2&subcategory=disabledPage2&date=2018-01-02&period=day&segment=&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment'); + + expect(piwikComparisonsService.isComparisonEnabled()).toBe(false); + }); + + it('should return false if comparison is disabled for the entire category', async () => { + await setHash('category=MyModule3&subcategory=enabledPage&date=2018-01-02&period=day&segment=&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment'); + + expect(piwikComparisonsService.isComparisonEnabled()).toBe(false); + }); + }); + + describe('#getSegmentComparisons()', () => { + it('should return the segment comparisons only', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getSegmentComparisons()).toEqual([ + {"params":{"segment":"abcdefg"},"title":"General_Unknown","index":0}, + {"params":{"segment":"comparedsegment"},"title":"General_Unknown","index":1}, + {"params":{"segment":""},"title":"SegmentEditor_DefaultAllVisits","index":2} + ]); + }); + + it('should return nothing if comparison is not enabled', async () => { + await setHash('category=MyModule1&subcategory=disabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getSegmentComparisons()).toEqual([]); + }); + }); + + describe('#getPeriodComparisons()', () => { + it('should return the period comparisons only', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getPeriodComparisons()).toEqual([ + { + params: { + date: '2018-01-02', + period: 'day', + }, + title: '2018-01-02', + index: 0, + }, + { + params: { + date: '2018-03-04', + period: 'week', + }, + title: 'General_DateRangeFromTo', + index: 1, + }, + ]); + }); + + it('should return nothing if comparison is not enabled', async () => { + await setHash('category=MyModule1&subcategory=disabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getPeriodComparisons()).toEqual([]); + }); + }); + + describe('#getAllComparisonSeries()', () => { + it('should return all individual comparison serieses', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getAllComparisonSeries()).toEqual([ + { + index: 0, + params: { + segment: 'abcdefg', + date: '2018-01-02', + period: 'day', + }, + color: 'comparison-series-color.series0', + }, + { + "index":1, + "params": { + "segment":"comparedsegment", + "date":"2018-01-02", + "period":"day" + }, + color: 'comparison-series-color.series1', + }, + { + "index":2, + "params": { + "segment":"", + "date":"2018-01-02", + "period":"day" + }, + color: 'comparison-series-color.series2', + }, + { + "index":3, + "params": { + "segment":"abcdefg", + "date":"2018-03-04", + "period":"week" + }, + color: 'comparison-series-color.series3', + }, + { + "index":4, + "params": { + "segment":"comparedsegment", + "date":"2018-03-04", + "period":"week" + }, + color: 'comparison-series-color.series4', + }, + { + "index":5, + "params": { + "segment":"", + "date":"2018-03-04", + "period":"week" + }, + color: 'comparison-series-color.series5', + }, + ]); + }); + + it('should return nothing if comparison is not enabled', async () => { + await setHash('category=MyModule1&subcategory=disabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getAllComparisonSeries()).toEqual([]); + }); + }); + + describe('#isComparing()', () => { + it('should return true if there are comparison parameters present', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.isComparing()).toBe(true); + }); + + it('should return true if there are segment comparisons but no period comparisons', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.isComparing()).toBe(true); + }); + + it('should return true if there are period comparisons but no segment comparisons', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week'); + + expect(piwikComparisonsService.isComparing()).toBe(true); + }); + + it('should return false if there are no comparison parameters present', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg'); + + expect(piwikComparisonsService.isComparing()).toBe(false); + + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day'); + + expect(piwikComparisonsService.isComparing()).toBe(false); + }); + + it('should return false if comparison is not enabled', async () => { + await setHash('category=MyModule1&subcategory=disabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.isComparing()).toBe(false); + }); + }); + + describe('#isComparingPeriods()', () => { + it('should return true if there are periods being compared', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.isComparingPeriods()).toBe(true); + }); + + it('should return false if there are no periods being compared, just segments', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.isComparingPeriods()).toBe(false); + }); + + it('should return false if there is nothing being compared', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg'); + + expect(piwikComparisonsService.isComparingPeriods()).toBe(false); + }); + + it('should return false if comparing is not enabled', async () => { + await setHash('category=MyModule1&subcategory=disabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.isComparingPeriods()).toBe(false); + }); + }); + + describe('#getIndividualComparisonRowIndices()', () => { + it('should calculate the segment/period index from the given series index', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getIndividualComparisonRowIndices(3)).toEqual({ + segmentIndex: 0, + periodIndex: 1, + }); + + expect(piwikComparisonsService.getIndividualComparisonRowIndices(0)).toEqual({ + segmentIndex: 0, + periodIndex: 0, + }); + }); + }); + + describe('#getComparisonSeriesIndex()', () => { + it('should return the comparison series index from the given segment & period indices', async () => { + await setHash('category=MyModule1&subcategory=enabledPage&date=2018-01-02&period=day&segment=abcdefg&compareDates[]=2018-03-04&comparePeriods[]=week&compareSegments[]=comparedsegment&compareSegments[]='); + + expect(piwikComparisonsService.getComparisonSeriesIndex(1, 1)).toEqual(4); + + expect(piwikComparisonsService.getComparisonSeriesIndex(0, 1)).toEqual(1); + }); + }); +}); diff --git a/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts new file mode 100644 index 0000000000..f3204f163a --- /dev/null +++ b/plugins/CoreHome/vue/src/Comparisons/Comparisons.store.ts @@ -0,0 +1,411 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { + reactive, + watch, + computed, + readonly, +} from 'vue'; +import MatomoUrl from '../MatomoUrl/MatomoUrl'; +import Matomo from '../Matomo/Matomo'; +import translate from '../translate'; +import Periods from '../Periods/Periods'; +import AjaxHelper from '../AjaxHelper/AjaxHelper'; +import SegmentsStore from '../Segmentation/Segments.store'; + +const SERIES_COLOR_COUNT = 8; +const SERIES_SHADE_COUNT = 3; + +export interface SegmentComparison { + params: { + segment: string, + }, + title: string, + index: number, +} + +export interface PeriodComparison { + params: { + period: string, + date: string, + }, + title: string, + index: number, +} + +export interface AnyComparison { + params: { [name: string]: string }, + title: string, + index: number, +} + +export interface ComparisonsStoreState { + comparisonsDisabledFor: string[]; +} + +export interface ComparisonSeriesInfo { + index: number; + params: { [key: string]: string }; + color: string; +} + +function wrapArray<T>(values: T | T[]): T[] { + if (!values) { + return []; + } + return values instanceof Array ? values : [values]; +} + +export default class ComparisonsStore { + private privateState = reactive<ComparisonsStoreState>({ + comparisonsDisabledFor: [], + }); + + readonly state = readonly(this.privateState); // for tests + + private colors: { [key: string]: string } = {}; + + readonly segmentComparisons = computed(() => this.parseSegmentComparisons()); + + readonly periodComparisons = computed(() => this.parsePeriodComparisons()); + + readonly isEnabled = computed(() => this.checkEnabledForCurrentPage()); + + constructor() { + this.loadComparisonsDisabledFor(); + + $(() => { + this.colors = this.getAllSeriesColors() as { [key: string]: string }; + }); + + watch( + () => this.getComparisons(), + () => Matomo.postEvent('piwikComparisonsChanged'), + { deep: true }, + ); + } + + getComparisons(): AnyComparison[] { + return (this.getSegmentComparisons() as AnyComparison[]) + .concat(this.getPeriodComparisons() as AnyComparison[]); + } + + isComparing(): boolean { + return this.isComparisonEnabled() + // first two in each array are for the currently selected segment/period + && (this.segmentComparisons.value.length > 1 + || this.periodComparisons.value.length > 1); + } + + isComparingPeriods(): boolean { + return this.getPeriodComparisons().length > 1; // first is currently selected period + } + + getSegmentComparisons(): SegmentComparison[] { + if (!this.isComparisonEnabled()) { + return []; + } + + return this.segmentComparisons.value; + } + + getPeriodComparisons(): PeriodComparison[] { + if (!this.isComparisonEnabled()) { + return []; + } + + return this.periodComparisons.value; + } + + getSeriesColor( + segmentComparison: SegmentComparison, + periodComparison: PeriodComparison, + metricIndex = 0, + ): string { + const seriesIndex = this.getComparisonSeriesIndex( + periodComparison.index, + segmentComparison.index, + ) % SERIES_COLOR_COUNT; + + if (metricIndex === 0) { + return this.colors[`series${seriesIndex}`]; + } + + const shadeIndex = metricIndex % SERIES_SHADE_COUNT; + return this.colors[`series${seriesIndex}-shade${shadeIndex}`]; + } + + getSeriesColorName(seriesIndex: number, metricIndex: number): string { + let colorName = `series${(seriesIndex % SERIES_COLOR_COUNT)}`; + if (metricIndex > 0) { + colorName += `-shade${(metricIndex % SERIES_SHADE_COUNT)}`; + } + return colorName; + } + + isComparisonEnabled(): boolean { + return this.isEnabled.value; + } + + getIndividualComparisonRowIndices(seriesIndex: number): { + segmentIndex: number, + periodIndex: number, + } { + const segmentCount = this.getSegmentComparisons().length; + const segmentIndex = seriesIndex % segmentCount; + const periodIndex = Math.floor(seriesIndex / segmentCount); + + return { + segmentIndex, + periodIndex, + }; + } + + getComparisonSeriesIndex(periodIndex: number, segmentIndex: number): number { + const segmentCount = this.getSegmentComparisons().length; + return periodIndex * segmentCount + segmentIndex; + } + + getAllComparisonSeries(): ComparisonSeriesInfo[] { + const seriesInfo: ComparisonSeriesInfo[] = []; + + let seriesIndex = 0; + this.getPeriodComparisons().forEach((periodComp) => { + this.getSegmentComparisons().forEach((segmentComp) => { + seriesInfo.push({ + index: seriesIndex, + params: { ...segmentComp.params, ...periodComp.params }, + color: this.colors[`series${seriesIndex}`], + }); + seriesIndex += 1; + }); + }); + + return seriesInfo; + } + + removeSegmentComparison(index: number): void { + if (!this.isComparisonEnabled()) { + throw new Error('Comparison disabled.'); + } + + const newComparisons: SegmentComparison[] = [...this.segmentComparisons.value]; + newComparisons.splice(index, 1); + + const extraParams: {[key: string]: string} = {}; + if (index === 0) { + extraParams.segment = newComparisons[0].params.segment; + } + + this.updateQueryParamsFromComparisons( + newComparisons, + this.periodComparisons.value, + extraParams, + ); + } + + addSegmentComparison(params: { [name: string]: string }): void { + if (!this.isComparisonEnabled()) { + throw new Error('Comparison disabled.'); + } + + const newComparisons = this.segmentComparisons.value + .concat([{ params, index: -1, title: '' } as SegmentComparison]); + this.updateQueryParamsFromComparisons(newComparisons, this.periodComparisons.value); + } + + private updateQueryParamsFromComparisons( + segmentComparisons: SegmentComparison[], + periodComparisons: PeriodComparison[], + extraParams = {}, + ) { + // get unique segments/periods/dates from new Comparisons + const compareSegments: {[key: string]: boolean} = {}; + const comparePeriodDatePairs: {[key: string]: boolean} = {}; + + let firstSegment = false; + let firstPeriod = false; + + segmentComparisons.forEach((comparison) => { + if (firstSegment) { + compareSegments[comparison.params.segment] = true; + } else { + firstSegment = true; + } + }); + + periodComparisons.forEach((comparison) => { + if (firstPeriod) { + comparePeriodDatePairs[`${comparison.params.period}|${comparison.params.date}`] = true; + } else { + firstPeriod = true; + } + }); + + const comparePeriods: string[] = []; + const compareDates: string[] = []; + Object.keys(comparePeriodDatePairs).forEach((pair) => { + const parts = pair.split('|'); + comparePeriods.push(parts[0]); + compareDates.push(parts[1]); + }); + + const compareParams: {[key: string]: string[]} = { + compareSegments: Object.keys(compareSegments), + comparePeriods, + compareDates, + }; + + // change the page w/ these new param values + if (Matomo.helper.isAngularRenderingThePage()) { + const search = MatomoUrl.hashParsed.value; + + const newSearch: {[key: string]: string|string[]} = { + ...search, + ...compareParams, + ...extraParams, + }; + + delete newSearch['compareSegments[]']; + delete newSearch['comparePeriods[]']; + delete newSearch['compareDates[]']; + + if (JSON.stringify(newSearch) !== JSON.stringify(search)) { + MatomoUrl.updateHash(newSearch); + } + + return; + } + + const paramsToRemove: string[] = []; + ['compareSegments', 'comparePeriods', 'compareDates'].forEach((name) => { + if (!compareParams[name].length) { + paramsToRemove.push(name); + } + }); + + // angular is not rendering the page (ie, we are in the embedded dashboard) or we need to change + // the segment + const url = MatomoUrl.stringify(extraParams); + const strHash = MatomoUrl.stringify(compareParams); + + window.broadcast.propagateNewPage(url, undefined, strHash, paramsToRemove); + } + + private getAllSeriesColors() { + const { ColorManager } = Matomo; + const seriesColorNames = []; + + for (let i = 0; i < SERIES_COLOR_COUNT; i += 1) { + seriesColorNames.push(`series${i}`); + for (let j = 0; j < SERIES_SHADE_COUNT; j += 1) { + seriesColorNames.push(`series${i}-shade${j}`); + } + } + + return ColorManager.getColors('comparison-series-color', seriesColorNames); + } + + private loadComparisonsDisabledFor() { + AjaxHelper.fetch({ + module: 'API', + method: 'API.getPagesComparisonsDisabledFor', + }).then((result) => { + this.privateState.comparisonsDisabledFor = result; + }); + } + + private parseSegmentComparisons(): SegmentComparison[] { + const { availableSegments } = SegmentsStore.state; + + const compareSegments: string[] = [ + ...wrapArray(MatomoUrl.parsed.value.compareSegments as string[]), + ]; + + // add base comparisons + compareSegments.unshift(MatomoUrl.parsed.value.segment as string || ''); + + const newSegmentComparisons: SegmentComparison[] = []; + compareSegments.forEach((segment, idx) => { + let storedSegment!: { definition: string, name: string }; + + availableSegments.forEach((s) => { + if (s.definition === segment + || s.definition === decodeURIComponent(segment) + || decodeURIComponent(s.definition) === segment + ) { + storedSegment = s; + } + }); + + let segmentTitle = storedSegment ? storedSegment.name : translate('General_Unknown'); + if (segment.trim() === '') { + segmentTitle = translate('SegmentEditor_DefaultAllVisits'); + } + + newSegmentComparisons.push({ + params: { + segment, + }, + title: Matomo.helper.htmlDecode(segmentTitle), + index: idx, + }); + }); + + return newSegmentComparisons; + } + + private parsePeriodComparisons(): PeriodComparison[] { + const comparePeriods: string[] = [ + ...wrapArray(MatomoUrl.parsed.value.comparePeriods as string[]), + ]; + + const compareDates: string[] = [ + ...wrapArray(MatomoUrl.parsed.value.compareDates as string[]), + ]; + + comparePeriods.unshift(MatomoUrl.parsed.value.period as string); + compareDates.unshift(MatomoUrl.parsed.value.date as string); + + const newPeriodComparisons: PeriodComparison[] = []; + for (let i = 0; i < Math.min(compareDates.length, comparePeriods.length); i += 1) { + let title; + try { + title = Periods.parse(comparePeriods[i], compareDates[i]).getPrettyString(); + } catch (e) { + title = translate('General_Error'); + } + + newPeriodComparisons.push({ + params: { + date: compareDates[i], + period: comparePeriods[i], + }, + title, + index: i, + }); + } + + return newPeriodComparisons; + } + + private checkEnabledForCurrentPage() { + // category/subcategory is not included on top bar pages, so in that case we use module/action + const category = MatomoUrl.parsed.value.category || MatomoUrl.parsed.value.module; + const subcategory = MatomoUrl.parsed.value.subcategory + || MatomoUrl.parsed.value.action; + + const id = `${category}.${subcategory}`; + const isEnabled = this.privateState.comparisonsDisabledFor.indexOf(id) === -1 + && this.privateState.comparisonsDisabledFor.indexOf(`${category}.*`) === -1; + + document.documentElement.classList.toggle('comparisonsDisabled', !isEnabled); + + return isEnabled; + } +} diff --git a/plugins/CoreHome/vue/src/Comparisons/Comparisons.vue b/plugins/CoreHome/vue/src/Comparisons/Comparisons.vue new file mode 100644 index 0000000000..7639c2e620 --- /dev/null +++ b/plugins/CoreHome/vue/src/Comparisons/Comparisons.vue @@ -0,0 +1,264 @@ +<!-- + Matomo - free/libre analytics platform + @link https://matomo.org + @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later +--> + +<template> + <div v-if="isComparing" ref="root" class="matomo-comparisons"> + <h3>{{ translate('General_Comparisons') }}</h3> + <div + class="comparison card" + v-for="(comparison, $index) in segmentComparisons" + :key="comparison.index" + > + <div class="comparison-type">{{ translate('General_Segment') }}</div> + <div + class="title" + :title="comparison.title + '<br/>' + decodeURIComponent(comparison.params.segment)" + > + <a + target="_blank" + :href="getUrlToSegment(comparison.params.segment)" + > + {{ comparison.title }} + </a> + </div> + <div + class="comparison-period" + v-for="periodComparison in periodComparisons" + :key="periodComparison.index" + :title="getComparisonTooltip(comparison, periodComparison)" + > + <span + class="comparison-dot" + :style="{ + 'background-color': getSeriesColor(comparison, periodComparison) + }" + /> + <span class="comparison-period-label"> + {{ periodComparison.title }} ({{ getComparisonPeriodType(periodComparison) }}) + </span> + </div> + <a + class="remove-button" + v-on:click="removeSegmentComparison($index)" + v-if="segmentComparisons.length > 1" + > + <span + class="icon icon-close" + :title="translate('General_ClickToRemoveComp')" + /> + </a> + </div> + <div + class="loadingPiwik" + style="display:none;" + > + <img + src="plugins/Morpheus/images/loading-blue.gif" + :alt="translate('General_LoadingData')" + /> + {{ translate('General_LoadingData') }} + </div> + </div> +</template> + +<script lang="ts"> +import { defineComponent, computed } from 'vue'; +import { AnyComparison } from './Comparisons.store'; +import ComparisonsStoreInstance from './Comparisons.store.instance'; +import Matomo from '../Matomo/Matomo'; +import MatomoUrl from '../MatomoUrl/MatomoUrl'; +import AjaxHelper from '../AjaxHelper/AjaxHelper'; +import translate from '../translate'; + +interface ProcessedReportComparison { + compareSegmentPretty: string; + comparePeriodPretty: string; + nb_visits: number; + nb_visits_change: number; +} + +interface ProcessedReportData { + comparisons?: ProcessedReportComparison[]; +} + +interface ProcessedReportResponse { + reportData: ProcessedReportData; +} + +export default defineComponent({ + props: { + }, + data() { + return { + comparisonTooltips: null, + }; + }, + setup() { + // accessing has to be done through a computed property so we can use the computed + // instance directly in the template. unfortunately, vue won't register to changes. + const isComparing = computed(() => ComparisonsStoreInstance.isComparing()); + const segmentComparisons = computed(() => ComparisonsStoreInstance.getSegmentComparisons()); + const periodComparisons = computed(() => ComparisonsStoreInstance.getPeriodComparisons()); + const getSeriesColor = ComparisonsStoreInstance.getSeriesColor.bind(ComparisonsStoreInstance); + return { + isComparing, + segmentComparisons, + periodComparisons, + getSeriesColor, + }; + }, + methods: { + comparisonHasSegment(comparison: AnyComparison) { + return typeof comparison.params.segment !== 'undefined'; + }, + removeSegmentComparison(index: number) { + // otherwise the tooltip will be stuck on the screen + window.$(this.$refs.root).tooltip('destroy'); + ComparisonsStoreInstance.removeSegmentComparison(index); + }, + getComparisonPeriodType(comparison: AnyComparison) { + const { period } = comparison.params; + if (period === 'range') { + return translate('CoreHome_PeriodRange'); + } + const periodStr = translate( + `Intl_Period${period.substring(0, 1).toUpperCase()}${period.substring(1)}`, + ); + return periodStr.substring(0, 1).toUpperCase() + periodStr.substring(1); + }, + getComparisonTooltip( + segmentComparison: AnyComparison, + periodComparison: AnyComparison, + ): string|undefined { + if (!this.comparisonTooltips + || !Object.keys(this.comparisonTooltips).length + ) { + return undefined; + } + + return (this.comparisonTooltips[periodComparison.index] || {})[segmentComparison.index]; + }, + getUrlToSegment(segment: string) { + const hash = { ...MatomoUrl.hashParsed.value }; + delete hash.comparePeriods; + delete hash.compareDates; + delete hash.compareSegments; + hash.segment = segment; + return `${window.location.search}#?${MatomoUrl.stringify(hash)}`; + }, + setUpTooltips() { + const { $ } = window; + $(this.$refs.root).tooltip({ + track: true, + content: function transformTooltipContent() { + const title = $(this).attr('title'); + return window.vueSanitize(title.replace(/\n/g, '<br />')); + }, + show: { delay: 200, duration: 200 }, + hide: false, + }); + }, + onComparisonsChanged() { + this.comparisonTooltips = null; + + if (!ComparisonsStoreInstance.isComparing()) { + return; + } + + const periodComparisons = ComparisonsStoreInstance.getPeriodComparisons(); + const segmentComparisons = ComparisonsStoreInstance.getSegmentComparisons(); + AjaxHelper.fetch({ + method: 'API.getProcessedReport', + apiModule: 'VisitsSummary', + apiAction: 'get', + compare: '1', + compareSegments: MatomoUrl.getSearchParam('compareSegments'), + comparePeriods: MatomoUrl.getSearchParam('comparePeriods'), + compareDates: MatomoUrl.getSearchParam('compareDates'), + format_metrics: '1', + }).then((report) => { + this.comparisonTooltips = {}; + periodComparisons.forEach((periodComp) => { + this.comparisonTooltips[periodComp.index] = {}; + + segmentComparisons.forEach((segmentComp) => { + const tooltip = this.generateComparisonTooltip(report, periodComp, segmentComp); + this.comparisonTooltips[periodComp.index][segmentComp.index] = tooltip; + }); + }); + }); + }, + generateComparisonTooltip( + visitsSummary: ProcessedReportResponse, + periodComp: AnyComparison, + segmentComp: AnyComparison, + ): string { + if (!visitsSummary.reportData.comparisons) { // sanity check + return ''; + } + + const firstRowIndex = ComparisonsStoreInstance.getComparisonSeriesIndex( + periodComp.index, + 0, + ); + + const firstRow = visitsSummary.reportData.comparisons[firstRowIndex]; + + const comparisonRowIndex = ComparisonsStoreInstance.getComparisonSeriesIndex( + periodComp.index, + segmentComp.index, + ); + const comparisonRow = visitsSummary.reportData.comparisons[comparisonRowIndex]; + + const firstPeriodRow = visitsSummary.reportData.comparisons[segmentComp.index]; + + let tooltip = '<div class="comparison-card-tooltip">'; + + let visitsPercent = ((comparisonRow.nb_visits / firstRow.nb_visits) * 100) + .toFixed(2); + visitsPercent = `${visitsPercent}%`; + + tooltip += translate('General_ComparisonCardTooltip1', [ + `'${comparisonRow.compareSegmentPretty}'`, + comparisonRow.comparePeriodPretty, + visitsPercent, + comparisonRow.nb_visits.toString(), + firstRow.nb_visits.toString(), + ]); + if (periodComp.index > 0) { + tooltip += '<br/><br/>'; + tooltip += translate('General_ComparisonCardTooltip2', [ + comparisonRow.nb_visits_change.toString(), + firstPeriodRow.compareSegmentPretty, + firstPeriodRow.comparePeriodPretty, + ]); + } + + tooltip += '</div>'; + return tooltip; + }, + }, + updated() { + setTimeout(() => this.setUpTooltips()); + }, + mounted() { + Matomo.on('piwikComparisonsChanged', () => { + this.onComparisonsChanged(); + }); + + this.onComparisonsChanged(); + + setTimeout(() => this.setUpTooltips()); + }, + beforeUnmount() { + try { + window.$(this.refs.root).tooltip('destroy'); + } catch (e) { + // ignore + } + }, +}); +</script> diff --git a/plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue b/plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue index 47774a4f39..12a89d770b 100644 --- a/plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue +++ b/plugins/CoreHome/vue/src/ContentBlock/ContentBlock.vue @@ -31,7 +31,7 @@ </template> <script lang="ts"> -import { defineComponent, ref } from 'vue'; +import { defineComponent } from 'vue'; import EnrichedHeadline from '../EnrichedHeadline/EnrichedHeadline.vue'; let adminContent: HTMLElement|null = null; @@ -53,15 +53,6 @@ export default defineComponent({ actualHelpText: this.helpText, }; }, - setup() { - const root = ref<HTMLElement>(null); - const content = ref<HTMLElement>(null); - - return { - root, - content, - }; - }, watch: { feature(newValue: string) { this.actualFeature = newValue; diff --git a/plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue b/plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue index 4adb7a43d3..f7d8cd7912 100644 --- a/plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue +++ b/plugins/CoreHome/vue/src/EnrichedHeadline/EnrichedHeadline.vue @@ -71,7 +71,6 @@ import { defineComponent, defineAsyncComponent, - ref, } from 'vue'; import Matomo from '../Matomo/Matomo'; import Periods from '../Periods/Periods'; @@ -143,13 +142,6 @@ export default defineComponent({ actualInlineHelp: this.inlineHelp, }; }, - setup() { - const root = ref<HTMLElement>(null); - - return { - root, - }; - }, watch: { inlineHelp(newValue: string) { this.actualInlineHelp = newValue; diff --git a/plugins/CoreHome/vue/src/Matomo/Matomo.adapter.ts b/plugins/CoreHome/vue/src/Matomo/Matomo.adapter.ts index e76adb190b..8ea591fca7 100644 --- a/plugins/CoreHome/vue/src/Matomo/Matomo.adapter.ts +++ b/plugins/CoreHome/vue/src/Matomo/Matomo.adapter.ts @@ -5,18 +5,31 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ +import { IAngularEvent, IRootScopeService } from 'angular'; import Matomo from './Matomo'; function piwikService() { return Matomo; } -angular.module('piwikApp.service').service('piwik', piwikService); +window.angular.module('piwikApp.service').service('piwik', piwikService); + +function initPiwikService(piwik: PiwikGlobal, $rootScope: IRootScopeService) { + // overwrite $rootScope so all events also go through Matomo.postEvent(...) too. + ($rootScope as any).$oldEmit = $rootScope.$emit; // eslint-disable-line + $rootScope.$emit = function emitWrapper(name: string, ...args: any[]): IAngularEvent { // eslint-disable-line + return Matomo.postEvent(name, ...args); + }; + + ($rootScope as any).$oldBroadcast = $rootScope.$broadcast; // eslint-disable-line + $rootScope.$broadcast = function broadcastWrapper(name: string, ...args: any[]): IAngularEvent { // eslint-disable-line + Matomo.postEventNoEmit(name, ...args); + return ($rootScope as any).$oldBroadcast(name, ...args); // eslint-disable-line + }; -function initPiwikService(piwik, $rootScope) { $rootScope.$on('$locationChangeSuccess', piwik.updatePeriodParamsFromUrl); } initPiwikService.$inject = ['piwik', '$rootScope']; -angular.module('piwikApp.service').run(initPiwikService); +window.angular.module('piwikApp.service').run(initPiwikService); diff --git a/plugins/CoreHome/vue/src/Matomo/Matomo.ts b/plugins/CoreHome/vue/src/Matomo/Matomo.ts index 2a705f665e..85d74dfb3b 100644 --- a/plugins/CoreHome/vue/src/Matomo/Matomo.ts +++ b/plugins/CoreHome/vue/src/Matomo/Matomo.ts @@ -5,9 +5,8 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -import MatomoUrl from '../MatomoUrl/MatomoUrl'; +import { IAngularEvent } from 'angular'; import Periods from '../Periods/Periods'; -import { format } from '../Periods/utilities'; let originalTitle: string; @@ -16,45 +15,6 @@ const { piwik, broadcast, piwikHelper } = window; piwik.helper = piwikHelper; piwik.broadcast = broadcast; -function isValidPeriod(periodStr: string, dateStr: string) { - try { - Periods.parse(periodStr, dateStr); - return true; - } catch (e) { - return false; - } -} - -piwik.updatePeriodParamsFromUrl = function updatePeriodParamsFromUrl() { - let date = MatomoUrl.getSearchParam('date'); - const period = MatomoUrl.getSearchParam('period'); - if (!isValidPeriod(period, date)) { - // invalid data in URL - return; - } - - if (piwik.period === period && piwik.currentDateString === date) { - // this period / date is already loaded - return; - } - - piwik.period = period; - - const dateRange = Periods.parse(period, date).getDateRange(); - piwik.startDateString = format(dateRange[0]); - piwik.endDateString = format(dateRange[1]); - - piwik.updateDateInTitle(date, period); - - // do not set anything to previousN/lastN, as it's more useful to plugins - // to have the dates than previousN/lastN. - if (piwik.period === 'range') { - date = `${piwik.startDateString},${piwik.endDateString}`; - } - - piwik.currentDateString = date; -}; - piwik.updateDateInTitle = function updateDateInTitle(date: string, period: string) { if (!$('.top_controls #periodString').length) { return; @@ -74,5 +34,40 @@ piwik.hasUserCapability = function hasUserCapability(capability: string) { && piwik.userCapabilities.indexOf(capability) !== -1; }; +piwik.on = function addMatomoEventListener(eventName: string, listener: WrappedEventListener) { + function listenerWrapper(evt: Event) { + listener(...(evt as CustomEvent<any[]>).detail); // eslint-disable-line + } + + listener.wrapper = listenerWrapper; + + window.addEventListener(eventName, listenerWrapper); +}; + +piwik.off = function removeMatomoEventListener(eventName: string, listener: WrappedEventListener) { + if (listener.wrapper) { + window.removeEventListener(eventName, listener.wrapper); + } +}; + +piwik.postEventNoEmit = function postEventNoEmit( + eventName: string, + ...args: any[] // eslint-disable-line +): void { + const event = new CustomEvent(eventName, { detail: args }); + window.dispatchEvent(event); +}; + +piwik.postEvent = function postMatomoEvent( + eventName: string, + ...args: any[] // eslint-disable-line +): IAngularEvent { + piwik.postEventNoEmit(eventName, ...args); + + // required until angularjs is removed + const $rootScope = piwik.helper.getAngularDependency('$rootScope') as any; // eslint-disable-line + return $rootScope.$oldEmit(eventName, ...args); +}; + const Matomo = piwik; export default Matomo; diff --git a/plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue b/plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue index 779b0c155c..81a232f4ba 100644 --- a/plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue +++ b/plugins/CoreHome/vue/src/MatomoDialog/MatomoDialog.vue @@ -10,7 +10,7 @@ </div> </template> <script lang="ts"> -import { defineComponent, ref } from 'vue'; +import { defineComponent } from 'vue'; import Matomo from '../Matomo/Matomo'; export default defineComponent({ @@ -36,13 +36,6 @@ export default defineComponent({ }, }, emits: ['yes', 'no', 'closeEnd', 'close', 'update:modelValue'], - setup() { - const root = ref(null); - - return { - root, - }; - }, activated() { this.$emit('update:modelValue', false); }, diff --git a/plugins/CoreHome/vue/src/Matomo/Matomo.spec.ts b/plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.spec.ts index 49ab7f5b4a..a6eea35483 100644 --- a/plugins/CoreHome/vue/src/Matomo/Matomo.spec.ts +++ b/plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.spec.ts @@ -4,14 +4,15 @@ * @link https://matomo.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -import Matomo from './Matomo'; +import Matomo from '../Matomo/Matomo'; +import MatomoUrl from './MatomoUrl'; import '../Periods/Day'; import '../Periods/Week'; import '../Periods/Month'; import '../Periods/Year'; import '../Periods/Range'; -describe('CoreHome/Matomo', () => { +describe('CoreHome/MatomoUrl', () => { describe('#updatePeriodParamsFromUrl()', () => { const DATE_PERIODS_TO_TEST = [ { @@ -110,7 +111,7 @@ describe('CoreHome/Matomo', () => { history.pushState(null, '', '?date=' + date + '&period=' + period); - Matomo.updatePeriodParamsFromUrl(); + MatomoUrl.updatePeriodParamsFromUrl(); expect(Matomo.currentDateString).toEqual(expected.currentDateString); expect(Matomo.period).toEqual(expected.period); @@ -126,7 +127,7 @@ describe('CoreHome/Matomo', () => { history.pushState(null, '', '?someparam=somevalue#?date=' + date + '&period=' + period); - Matomo.updatePeriodParamsFromUrl(); + MatomoUrl.updatePeriodParamsFromUrl(); expect(Matomo.currentDateString).toEqual(expected.currentDateString); expect(Matomo.period).toEqual(expected.period); @@ -143,7 +144,7 @@ describe('CoreHome/Matomo', () => { history.pushState(null, '', '?someparam=somevalue#?date=' + Matomo.currentDateString + '&period=' + Matomo.period); - Matomo.updatePeriodParamsFromUrl(); + MatomoUrl.updatePeriodParamsFromUrl(); expect(Matomo.startDateString).toEqual('shouldnotchange'); expect(Matomo.endDateString).toEqual('shouldnotchangeeither'); diff --git a/plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.ts b/plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.ts index 8c69e2834f..11eec228a8 100644 --- a/plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.ts +++ b/plugins/CoreHome/vue/src/MatomoUrl/MatomoUrl.ts @@ -5,11 +5,69 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ +import { ILocationService } from 'angular'; +import { computed, ref, readonly } from 'vue'; +import Matomo from '../Matomo/Matomo'; +import { Periods, format } from '../Periods'; // important to load all periods here + +const { piwik, broadcast } = window; + +function isValidPeriod(periodStr: string, dateStr: string) { + try { + Periods.parse(periodStr, dateStr); + return true; + } catch (e) { + return false; + } +} + +// using unknown since readonly does not work well with recursive types like QueryParameters +type ParsedQueryParameters = Record<string, unknown>; + /** - * Similar to angulars $location but works around some limitation. Use it if you need to access - * search params + * URL store and helper functions. */ -const MatomoUrl = { +class MatomoUrl { + private urlQuery = ref(''); + + private hashQuery = ref(''); + + readonly urlParsed = computed(() => readonly( + broadcast.getValuesFromUrl(`?${this.urlQuery.value}`, true) as ParsedQueryParameters, + )); + + readonly hashParsed = computed(() => readonly( + broadcast.getValuesFromUrl(`?${this.hashQuery.value}`, true) as ParsedQueryParameters, + )); + + readonly parsed = computed(() => readonly({ + ...this.urlParsed.value, + ...this.hashParsed.value, + } as ParsedQueryParameters)); + + constructor() { + this.setUrlQuery(window.location.search); + this.setHashQuery(window.location.hash); + + // $locationChangeSuccess is triggered before angularjs changes actual window the hash, so we + // have to hook into this method if we want our event handlers to execute before other angularjs + // handlers (like the reporting page one) + Matomo.on('$locationChangeSuccess', (absUrl: string) => { + const url = new URL(absUrl); + this.setUrlQuery(url.search.replace(/^\?/, '')); + this.setHashQuery(url.hash.replace(/^#/, '')); + }); + + this.updatePeriodParamsFromUrl(); + } + + updateHash(params: QueryParameters|string) { + const serializedParams: string = typeof params !== 'string' ? this.stringify(params) : params; + + const $location: ILocationService = Matomo.helper.getAngularDependency('$location'); + $location.search(serializedParams); + } + getSearchParam(paramName: string): string { const hash = window.location.href.split('#'); @@ -26,7 +84,53 @@ const MatomoUrl = { } return window.broadcast.getValueFromUrl(paramName, window.location.search); - }, -}; + } + + stringify(search: QueryParameters): string { + // TODO: using $ since URLSearchParams does not handle array params the way Matomo uses them + return $.param(search).replace(/%5B%5D/g, '[]'); + } + + updatePeriodParamsFromUrl(): void { + let date = this.getSearchParam('date'); + const period = this.getSearchParam('period'); + if (!isValidPeriod(period, date)) { + // invalid data in URL + return; + } + + if (piwik.period === period && piwik.currentDateString === date) { + // this period / date is already loaded + return; + } + + piwik.period = period; + + const dateRange = Periods.parse(period, date).getDateRange(); + piwik.startDateString = format(dateRange[0]); + piwik.endDateString = format(dateRange[1]); + + piwik.updateDateInTitle(date, period); + + // do not set anything to previousN/lastN, as it's more useful to plugins + // to have the dates than previousN/lastN. + if (piwik.period === 'range') { + date = `${piwik.startDateString},${piwik.endDateString}`; + } + + piwik.currentDateString = date; + } + + private setUrlQuery(search: string) { + this.urlQuery.value = search.replace(/^\?/, ''); + } + + private setHashQuery(hash: string) { + this.hashQuery.value = hash.replace(/^[#/?]+/, ''); + } +} + +const instance = new MatomoUrl(); +export default instance; -export default MatomoUrl; +piwik.updatePeriodParamsFromUrl = instance.updatePeriodParamsFromUrl.bind(instance); diff --git a/plugins/CoreHome/vue/src/Periods/Periods.adapter.ts b/plugins/CoreHome/vue/src/Periods/Periods.adapter.ts index 4d9e6820f9..aff5d6c4f1 100644 --- a/plugins/CoreHome/vue/src/Periods/Periods.adapter.ts +++ b/plugins/CoreHome/vue/src/Periods/Periods.adapter.ts @@ -24,4 +24,4 @@ function piwikPeriods() { }; } -angular.module('piwikApp.service').factory('piwikPeriods', piwikPeriods); +window.angular.module('piwikApp.service').factory('piwikPeriods', piwikPeriods); diff --git a/plugins/CoreHome/vue/src/Periods/utilities.ts b/plugins/CoreHome/vue/src/Periods/utilities.ts index b5488b7c01..a183b4ed68 100644 --- a/plugins/CoreHome/vue/src/Periods/utilities.ts +++ b/plugins/CoreHome/vue/src/Periods/utilities.ts @@ -31,7 +31,10 @@ export function parseDate(date: string|Date): Date { return date; } - const strDate = decodeURIComponent(date); + const strDate = decodeURIComponent(date).trim(); + if (strDate === '') { + throw new Error('Invalid date, empty string.'); + } if (strDate === 'today' || strDate === 'now' @@ -67,13 +70,7 @@ export function parseDate(date: string|Date): Date { return lastYear; } - try { - return $.datepicker.parseDate('yy-mm-dd', strDate); - } catch (err) { - // angular swallows this error, so manual console log here - console.error(err.message || err); - throw err; - } + return $.datepicker.parseDate('yy-mm-dd', strDate); } export function todayIsInRange(dateRange: Date[]): boolean { diff --git a/plugins/CoreHome/vue/src/Piwik/Piwik.adapter.ts b/plugins/CoreHome/vue/src/Piwik/Piwik.adapter.ts deleted file mode 100644 index 856916a248..0000000000 --- a/plugins/CoreHome/vue/src/Piwik/Piwik.adapter.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -import Piwik from './Piwik'; - -function piwikService() { - return Piwik; -} - -angular.module('piwikApp.service').service('piwik', piwikService); - -function initPiwikService(piwik, $rootScope) { - $rootScope.$on('$locationChangeSuccess', piwik.updatePeriodParamsFromUrl); -} - -initPiwikService.$inject = ['piwik', '$rootScope']; - -angular.module('piwikApp.service').run(initPiwikService); diff --git a/plugins/CoreHome/vue/src/Piwik/Piwik.spec.ts b/plugins/CoreHome/vue/src/Piwik/Piwik.spec.ts deleted file mode 100644 index a07c5d3490..0000000000 --- a/plugins/CoreHome/vue/src/Piwik/Piwik.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ -import Piwik from './Piwik'; -import '../Periods/Day'; -import '../Periods/Week'; -import '../Periods/Month'; -import '../Periods/Year'; -import '../Periods/Range'; - -describe('PiwikService', () => { - describe('#updatePeriodParamsFromUrl()', () => { - const DATE_PERIODS_TO_TEST = [ - { - date: '2012-01-02', - period: 'day', - expected: { - currentDateString: '2012-01-02', - period: 'day', - startDateString: '2012-01-02', - endDateString: '2012-01-02' - } - }, - { - date: '2012-01-02', - period: 'week', - expected: { - currentDateString: '2012-01-02', - period: 'week', - startDateString: '2012-01-02', - endDateString: '2012-01-08' - } - }, - { - date: '2012-01-02', - period: 'month', - expected: { - currentDateString: '2012-01-02', - period: 'month', - startDateString: '2012-01-01', - endDateString: '2012-01-31' - } - }, - { - date: '2012-01-02', - period: 'year', - expected: { - currentDateString: '2012-01-02', - period: 'year', - startDateString: '2012-01-01', - endDateString: '2012-12-31' - } - }, - { - date: '2012-01-02,2012-02-03', - period: 'range', - expected: { - currentDateString: '2012-01-02,2012-02-03', - period: 'range', - startDateString: '2012-01-02', - endDateString: '2012-02-03' - } - }, - // invalid - { - date: '2012-01-02', - period: 'range', - expected: { - currentDateString: undefined, - period: undefined, - startDateString: undefined, - endDateString: undefined - } - }, - { - date: 'sldfjkdslkfj', - period: 'month', - expected: { - currentDateString: undefined, - period: undefined, - startDateString: undefined, - endDateString: undefined - } - }, - { - date: '2012-01-02', - period: 'sflkjdslkfj', - expected: { - currentDateString: undefined, - period: undefined, - startDateString: undefined, - endDateString: undefined - } - } - ]; - - DATE_PERIODS_TO_TEST.forEach((test) => { - const date = test.date, - period = test.period, - expected = test.expected; - - it(`should parse the period in the URL correctly when date=${date} and period=${period}`, () => { - delete Piwik.currentDateString; - delete Piwik.period; - delete Piwik.startDateString; - delete Piwik.endDateString; - - history.pushState(null, '', '?date=' + date + '&period=' + period); - - Piwik.updatePeriodParamsFromUrl(); - - expect(Piwik.currentDateString).toEqual(expected.currentDateString); - expect(Piwik.period).toEqual(expected.period); - expect(Piwik.startDateString).toEqual(expected.startDateString); - expect(Piwik.endDateString).toEqual(expected.endDateString); - }); - - it('should parse the period in the URL hash correctly when date=' + date + ' and period=' + period, () => { - delete Piwik.currentDateString; - delete Piwik.period; - delete Piwik.startDateString; - delete Piwik.endDateString; - - history.pushState(null, '', '?someparam=somevalue#?date=' + date + '&period=' + period); - - Piwik.updatePeriodParamsFromUrl(); - - expect(Piwik.currentDateString).toEqual(expected.currentDateString); - expect(Piwik.period).toEqual(expected.period); - expect(Piwik.startDateString).toEqual(expected.startDateString); - expect(Piwik.endDateString).toEqual(expected.endDateString); - }); - }); - - it('should not change object values if the current date/period is the same as the URL date/period', () => { - Piwik.period = 'range'; - Piwik.currentDateString = '2012-01-01,2012-01-02'; - Piwik.startDateString = 'shouldnotchange'; - Piwik.endDateString = 'shouldnotchangeeither'; - - history.pushState(null, '', '?someparam=somevalue#?date=' + Piwik.currentDateString + '&period=' + Piwik.period); - - Piwik.updatePeriodParamsFromUrl(); - - expect(Piwik.startDateString).toEqual('shouldnotchange'); - expect(Piwik.endDateString).toEqual('shouldnotchangeeither'); - }); - }); -}); diff --git a/plugins/CoreHome/vue/src/Piwik/Piwik.ts b/plugins/CoreHome/vue/src/Piwik/Piwik.ts deleted file mode 100644 index 084dd53a1d..0000000000 --- a/plugins/CoreHome/vue/src/Piwik/Piwik.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -import PiwikUrl from '../PiwikUrl/PiwikUrl'; -import Periods from '../Periods/Periods'; -import { format } from '../Periods/utilities'; - -let originalTitle: string; - -const { piwik, broadcast, piwikHelper } = window; - -piwik.helper = piwikHelper; -piwik.broadcast = broadcast; - -function isValidPeriod(periodStr: string, dateStr: string) { - try { - Periods.parse(periodStr, dateStr); - return true; - } catch (e) { - return false; - } -} - -piwik.updatePeriodParamsFromUrl = function updatePeriodParamsFromUrl() { - let date = PiwikUrl.getSearchParam('date'); - const period = PiwikUrl.getSearchParam('period'); - if (!isValidPeriod(period, date)) { - // invalid data in URL - return; - } - - if (piwik.period === period && piwik.currentDateString === date) { - // this period / date is already loaded - return; - } - - piwik.period = period; - - const dateRange = Periods.parse(period, date).getDateRange(); - piwik.startDateString = format(dateRange[0]); - piwik.endDateString = format(dateRange[1]); - - piwik.updateDateInTitle(date, period); - - // do not set anything to previousN/lastN, as it's more useful to plugins - // to have the dates than previousN/lastN. - if (piwik.period === 'range') { - date = `${piwik.startDateString},${piwik.endDateString}`; - } - - piwik.currentDateString = date; -}; - -piwik.updateDateInTitle = function updateDateInTitle(date: string, period: string) { - if (!$('.top_controls #periodString').length) { - return; - } - - // Cache server-rendered page title - originalTitle = originalTitle || document.title; - - if (originalTitle.indexOf(piwik.siteName) === 0) { - const dateString = ` - ${Periods.parse(period, date).getPrettyString()} `; - document.title = `${piwik.siteName}${dateString}${originalTitle.substr(piwik.siteName.length)}`; - } -}; - -piwik.hasUserCapability = function hasUserCapability(capability: string) { - return window.angular.isArray(piwik.userCapabilities) - && piwik.userCapabilities.indexOf(capability) !== -1; -}; - -const Piwik = piwik; -export default Piwik; diff --git a/plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.adapter.ts b/plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.adapter.ts deleted file mode 100644 index abf76cbcb5..0000000000 --- a/plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.adapter.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ -import PiwikUrl from './PiwikUrl'; - -function piwikUrl() { - const model = { - getSearchParam: PiwikUrl.getSearchParam.bind(PiwikUrl), - }; - - return model; -} - -piwikUrl.$inject = []; - -angular.module('piwikApp.service').service('piwikUrl', piwikUrl); diff --git a/plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.ts b/plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.ts deleted file mode 100644 index 83ab1f68b2..0000000000 --- a/plugins/CoreHome/vue/src/PiwikUrl/PiwikUrl.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Matomo - free/libre analytics platform - * - * @link https://matomo.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -/** - * Similar to angulars $location but works around some limitation. Use it if you need to access - * search params - */ -const PiwikUrl = { - getSearchParam(paramName: string): string { - const hash = window.location.href.split('#'); - - const regex = new RegExp(`${paramName}(\\[]|=)`); - if (hash && hash[1] && regex.test(decodeURIComponent(hash[1]))) { - const valueFromHash = window.broadcast.getValueFromHash(paramName, window.location.href); - - // for date, period and idsite fall back to parameter from url, if non in hash was provided - if (valueFromHash - || (paramName !== 'date' && paramName !== 'period' && paramName !== 'idSite') - ) { - return valueFromHash; - } - } - - return window.broadcast.getValueFromUrl(paramName, window.location.search); - }, -}; - -export default PiwikUrl; diff --git a/plugins/CoreHome/vue/src/Segmentation/Segments.store.ts b/plugins/CoreHome/vue/src/Segmentation/Segments.store.ts new file mode 100644 index 0000000000..0ab754079a --- /dev/null +++ b/plugins/CoreHome/vue/src/Segmentation/Segments.store.ts @@ -0,0 +1,43 @@ +/*! + * Matomo - free/libre analytics platform + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +import { reactive, readonly, DeepReadonly } from 'vue'; +import Matomo from '../Matomo/Matomo'; + +interface SegmentInfo { + definition: string; + name: string; +} + +interface SegmentsStoreData { + availableSegments: SegmentInfo[]; +} + +class SegmentsStore { + private segmentState = reactive<SegmentsStoreData>({ + availableSegments: [], + }); + + get state(): DeepReadonly<SegmentsStoreData> { + return readonly(this.segmentState); + } + + constructor() { + Matomo.on('piwikSegmentationInited', () => this.setSegmentState()); + } + + private setSegmentState() { + try { + const uiControlObject = $('.segmentEditorPanel').data('uiControlObject'); + this.segmentState.availableSegments = uiControlObject.impl.availableSegments || []; + } catch (e) { + // segment editor is not initialized yet + } + } +} + +export default new SegmentsStore(); diff --git a/plugins/CoreHome/vue/src/createAngularJsAdapter.ts b/plugins/CoreHome/vue/src/createAngularJsAdapter.ts index 209fa98b15..32bd1e17b4 100644 --- a/plugins/CoreHome/vue/src/createAngularJsAdapter.ts +++ b/plugins/CoreHome/vue/src/createAngularJsAdapter.ts @@ -60,6 +60,7 @@ export default function createAngularJsAdapter<InjectTypes = []>(options: { mountPointFactory?: AdapterFunction<InjectTypes, HTMLElement>, postCreate?: PostCreateFunction<InjectTypes>, noScope?: boolean, + restrict?: string, }): ng.IDirectiveFactory { const { component, @@ -71,6 +72,7 @@ export default function createAngularJsAdapter<InjectTypes = []>(options: { mountPointFactory, postCreate, noScope, + restrict = 'A', } = options; const currentTranscludeCounter = transcludeCounter; @@ -90,7 +92,7 @@ export default function createAngularJsAdapter<InjectTypes = []>(options: { function angularJsAdapter(...injectedServices: InjectTypes) { const adapter: ng.IDirective = { - restrict: 'A', + restrict, scope: noScope ? undefined : angularJsScope, compile: function angularJsAdapterCompile() { return { diff --git a/plugins/CoreHome/vue/src/index.ts b/plugins/CoreHome/vue/src/index.ts index fdc9743f57..1808a8e32c 100644 --- a/plugins/CoreHome/vue/src/index.ts +++ b/plugins/CoreHome/vue/src/index.ts @@ -5,9 +5,9 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ +import './noAdblockFlag'; import './MatomoUrl/MatomoUrl.adapter'; import './Matomo/Matomo.adapter'; -import './noAdblockFlag'; import './Periods/Day'; import './Periods/Week'; import './Periods/Month'; @@ -15,11 +15,10 @@ import './Periods/Year'; import './Periods/Range'; import './Periods/Periods.adapter'; import './AjaxHelper/AjaxHelper.adapter'; -import './PiwikUrl/PiwikUrl.adapter'; -import './Piwik/Piwik.adapter'; import './MatomoDialog/MatomoDialog.adapter'; import './EnrichedHeadline/EnrichedHeadline.adapter'; import './ContentBlock/ContentBlock.adapter'; +import './Comparisons/Comparisons.adapter'; export { default as createAngularJsAdapter } from './createAngularJsAdapter'; export { default as activityIndicatorAdapter } from './ActivityIndicator/ActivityIndicator.adapter'; @@ -33,3 +32,4 @@ export * from './Periods'; export { default as MatomoDialog } from './MatomoDialog/MatomoDialog.vue'; export { default as EnrichedHeadline } from './EnrichedHeadline/EnrichedHeadline.vue'; export { default as ContentBlock } from './ContentBlock/ContentBlock.vue'; +export { default as Comparisons } from './Comparisons/Comparisons.vue'; |