UNPKG

@codegouvfr/react-dsfr

Version:

French State Design System React integration library

1,985 lines (1,646 loc) 121 kB
/*! DSFR v1.13.2 | SPDX-License-Identifier: MIT | License-Filename: LICENSE.md | restricted use (see terms and conditions) */ const config = { prefix: 'fr', namespace: 'dsfr', organisation: '@gouvfr', version: '1.13.2' }; const api = window[config.namespace]; const patch = { namespace: 'a4e35ba2a938ba9d007689dbf3f46acbb9807869' }; const Collection = { MANUAL: 'manual', LOAD: 'load', FULL: 'full', HASH: 'hash' }; const key = '_EA_'; const DISABLED = `${key}disabled`; const TOGGLE = `${key}toggle`; class Opt { constructor () { this._configure(); } _configure () { const scope = this; window[DISABLED] = () => scope.isDisabled; window[TOGGLE] = this.toggle.bind(this); } get isDisabled () { return localStorage.getItem(key); } toggle () { if (this.isDisabled) this.enable(); else this.disable(); } enable () { if (localStorage.getItem(key)) { localStorage.removeItem(key); } } disable () { localStorage.setItem(key, '1'); } } const opt = new Opt(); const PUSH = 'EA_push'; class Init { constructor (domain) { this._domain = domain; this._isLoaded = false; this._promise = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); } get id () { return this._id; } get store () { return this._store; } configure () { this.init(); return this._promise; } init () { let bit = 5381; for (let i = this._domain.length - 1; i > 0; i--) bit = (bit * 33) ^ this._domain.charCodeAt(i); bit >>>= 0; this._id = `_EA_${bit}`; this._store = []; this._store.eah = this._domain; window[this._id] = this._store; if (!window[PUSH]) window[PUSH] = (...args) => this.store.push(args); if (opt.isDisabled) { api.inspector.warn('User opted out, eulerian is disabled'); this._reject('User opted out, eulerian is disabled'); } else this.load(); } load () { const stamp = new Date() / 1E7 | 0; const offset = stamp % 26; const key = String.fromCharCode(97 + offset, 122 - offset, 65 + offset) + (stamp % 1E3); this._script = document.createElement('script'); this._script.ea = this.id; this._script.async = true; this._script.addEventListener('load', this.loaded.bind(this)); this._script.addEventListener('error', this.error.bind(this)); this._script.src = `//${this._domain}/${key}.js?2`; const node = document.getElementsByTagName('script')[0]; node.parentNode.insertBefore(this._script, node); } error () { api.inspector.error('unable to load Eulerian script file. the domain declared in your configuration must match the domain provided by the Eulerian interface (tag creation)'); this._reject('eulerian script loading error'); } loaded () { if (this._isLoaded) return; this._isLoaded = true; this._resolve(); } } /* (function(e, a) { var i = e.length, y = 5381, k = 'script', s = window, v = document, o = v.createElement(k); for (; i;) { i -= 1; y = (y * 33) ^ e.charCodeAt(i) } y = '_EA_' + (y >>>= 0); (function(e, a, s, y) { s[a] = s[a] || function() { (s[y] = s[y] || []).push(arguments); s[y].eah = e; }; }(e, a, s, y)); i = new Date / 1E7 | 0; o.ea = y; y = i % 26; o.async = 1; o.src = '//' + e + '/' + String.fromCharCode(97 + y, 122 - y, 65 + y) + (i % 1E3) + '.js?2'; s = v.getElementsByTagName(k)[0]; s.parentNode.insertBefore(o, s); }) ('mon.domainedetracking.com', 'EA_push'); */ /* (function(e, a) { var i = e.length, y = 5381, k = 'script', z = '_EA_', zd = z + 'disabled', s = window, v = document, o = v.createElement(k), l = s.localStorage; for (; i;) { i -= 1; y = (y * 33) ^ e.charCodeAt(i) } y = z + (y >>>= 0); (function(e, a, s, y, z, zd, l) { s[a] = s[a] || function() { (s[y] = s[y] || []).push(arguments); s[y].eah = e; }; s[zd] = function() { return l.getItem(z); }; s[z + 'toggle'] = function() { (s[zd]()) ? l.removeItem(z): l.setItem(z, 1); } }(e, a, s, y, z, zd, l)); if (!s[zd]()) { i = new Date / 1E7 | 0; o.ea = y; y = i % 26; o.async = 1; o.src = '//' + e + '/' + String.fromCharCode(97 + y, 122 - y, 65 + y) + (i % 1E3) + '.js?2'; s = v.getElementsByTagName(k)[0]; s.parentNode.insertBefore(o, s); } })('mon.domainedetracking.com', 'EA_push'); */ const State = { UNKNOWN: -1, CONFIGURING: 0, CONFIGURED: 1, INITIATED: 2, READY: 3 }; class TarteAuCitronIntegration { constructor (config) { this._config = config; this._state = State.UNKNOWN; this._promise = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); } configure () { if (this._state >= State.CONFIGURED) return this._promise; if (this._state === State.UNKNOWN) { api.inspector.info('analytics configures tarteaucitron'); this._state = State.CONFIGURING; } const tarteaucitron = window.tarteaucitron; if (!tarteaucitron || !tarteaucitron.services) { window.requestAnimationFrame(this.configure.bind(this)); return; } this._state = State.CONFIGURED; const init = this.init.bind(this); const data = { key: 'eulerian', type: 'analytic', name: 'Eulerian Analytics', needConsent: true, cookies: ['etuix'], uri: 'https://eulerian.com/vie-privee', js: init, fallback: () => { tarteaucitron.services.eulerian.js(); } }; tarteaucitron.services.eulerian = data; if (!tarteaucitron.job) tarteaucitron.job = []; tarteaucitron.job.push('eulerian'); return this._promise; } init () { if (this._state >= State.INITIATED) return; this._state = State.INITIATED; window.__eaGenericCmpApi = this.integrate.bind(this); const update = this.update.bind(this); window.addEventListener('tac.close_alert', update); window.addEventListener('tac.close_panel', update); } integrate (cmpApi) { if (this._state >= State.READY) return; this._state = State.READY; this._cmpApi = cmpApi; api.inspector.info('analytics has integrated tarteaucitron'); this._resolve(); this.update(); } update () { if (this._state < State.READY) return; this._cmpApi('tac', window.tarteaucitron, 1); } } class ConsentManagerPlatform { constructor (config) { this._config = config; if (config) { switch (config.id) { case 'tarteaucitron': this.integrateTarteAuCitron(); break; } } } integrateTarteAuCitron () { this._tac = new TarteAuCitronIntegration(this._config); return this._tac.configure(); } optin () { } } const push = (type, layer) => { if (typeof window.EA_push !== 'function') { api.inspector.warn('Analytics datalayer not sent, Eulerian API isn\'t yet avalaible'); return; } api.inspector.info('analytics', type, layer); window.EA_push(type, layer); }; const PushType = { COLLECTOR: 'collector', ACTION: 'action', ACTION_PARAMETER: 'actionparam' }; class Renderer { constructor () { this._renderables = []; this._rendering = this.render.bind(this); requestAnimationFrame(this._rendering); } add (renderable) { const index = this._renderables.indexOf(renderable); if (index === -1) this._renderables.push(renderable); } remove (renderable) { const index = this._renderables.indexOf(renderable); if (index > -1) this._renderables.splice(index, 1); } render () { this._renderables.forEach(renderable => renderable.render()); requestAnimationFrame(this._rendering); } } const renderer = new Renderer(); const ActionRegulation = { ENFORCE: 'enforce', PREVENT: 'prevent', NONE: 'none' }; const SLICE = 80; class Queue { constructor () { this._startingActions = []; this._endingActions = []; this._handlingVisibilityChange = this._handleVisibilityChange.bind(this); this._handlingEnd = this._handleEnd.bind(this); this._isStarted = false; this._isListening = false; this.reset(); } setCollector (collector) { this._collector = collector; } reset (ending = false) { this._type = PushType.ACTION; if (!ending) this._startingActions.length = 0; this._endingActions.length = 0; this._count = 0; this._delay = -1; this._isRequested = false; this._unlisten(); } start () { if (this._isStarted) return; this._isStarted = true; renderer.add(this); } collect () { this._type = PushType.COLLECTOR; this._request(); } regulate (action, queue) { if (!action) return false; if (queue.some(queued => queued.test(action))) { api.inspector.log('action exists in queue', action); return false; } switch (action.regulation) { case ActionRegulation.PREVENT: return false; case ActionRegulation.ENFORCE: return true; default: return this._collector.isActionEnabled === true; } } appendStartingAction (action, data) { if (!this.regulate(action, this._startingActions)) return; const queued = new QueuedAction(action, data); this._startingActions.push(queued); this._request(); } appendEndingAction (action, data) { if (!this.regulate(action, this._endingActions)) return; const queued = new QueuedAction(action, data); this._endingActions.push(queued); this._request(); } _request () { this._listen(); this._isRequested = true; this._delay = 4; } _listen () { if (this._isListening) return; this._isListening = true; document.addEventListener('visibilitychange', this._handlingVisibilityChange); document.addEventListener('unload', this._handlingEnd); document.addEventListener('beforeunload', this._handlingEnd); document.addEventListener('pagehide', this._handlingEnd); } _unlisten () { if (!this._isListening) return; this._isListening = false; document.removeEventListener('visibilitychange', this._handlingVisibilityChange); document.removeEventListener('unload', this._handlingEnd); document.removeEventListener('beforeunload', this._handlingEnd); document.removeEventListener('pagehide', this._handlingEnd); } _handleVisibilityChange (e) { if (document.visibilityState === 'hidden') this.send(); } _handleEnd () { this.send(); } render () { if (this._delay <= -1) return; this._delay--; this._count++; switch (true) { case this._count > 20: case this._delay === 0: this.send(); break; } } send (ending = false) { if (!this._isRequested) return; const actionLayers = []; if (!ending) actionLayers.push(...this._startingActions.map(queued => queued.start()).filter(layer => layer.length > 0)); actionLayers.push(...this._endingActions.map(queued => queued.end()).filter(layer => layer.length > 0)); const length = ((actionLayers.length / SLICE) + 1) | 0; const slices = []; for (let i = 0; i < length; i++) { const slice = actionLayers.slice(i * SLICE, (i + 1) * SLICE); slices.push(slice.flat()); } if (this._type === PushType.COLLECTOR && this._collector.isCollecting) { const layer = this._collector.layer; if (slices.length > 0) { const slice = slices.splice(0, 1)[0]; if (slice.length > 0) layer.push.apply(layer, slice); } layer.flat(); if (layer.length > 0) push(PushType.COLLECTOR, layer); } if (slices.length > 0) { for (let i = 0; i < slices.length; i++) { const slice = slices[i]; if (slice.length > 0) push(PushType.ACTION, slice); } } this.reset(ending); } } class QueuedAction { constructor (action, data) { this._action = action; this._data = data; } test (action) { return this._action === action; } start () { return this._action.start(this._data); } end () { return this._action.end(this._data); } } const queue = new Queue(); class Debug { get debugger () { return window._oEa; } get isActive () { if (!this.debugger) return false; return this.debugger._dbg === '1'; } set isActive (value) { if (!this.debugger || this.isActive === value) return; this.debugger.debug(value ? 1 : 0); } } const debug = new Debug(); const Status = { CONNECTED: { id: 'connected', value: 'connecté', isConnected: true, isDefault: true }, ANONYMOUS: { id: 'anonymous', value: 'anonyme', isConnected: false, isDefault: true }, GUEST: { id: 'guest', value: 'invité', isConnected: false } }; const Type$2 = { INDIVIDUAL: { id: 'individual', value: 'part' }, PROFESSIONNAL: { id: 'professionnal', value: 'pro' } }; /* '["\'<>*$&~`|\\\\?^~]'; */ var RESTRICTED = { '0x0022': '"', '0x0024': '$', '0x0026': '&', '0x0027': ''', '0x002a': '*', '0x002c': ',', '0x003c': '<', '0x003e': '>', '0x003f': '?', '0x005c': '\', '0x005e': '^', '0x0060': '`', '0x007c': '|', '0x007e': '~' }; // import TABLE from './unicode-table'; const charCodeHex = (char) => { const code = char.charCodeAt(0).toString(16); return '0x0000'.slice(0, -code.length) + code; }; const normalize = (text) => { if (!text) return text; // text = [...text].map(char => TABLE[charCodeHex(char)] || char).join(''); text = [...text].map(char => RESTRICTED[charCodeHex(char)] || char).join(''); text = text.replace(/\s+/g, ' ').replace(/\s/g, '_'); text = text.toLowerCase(); return text; }; const validateString = (value, name, allowNull = true) => { switch (true) { case typeof value === 'number': return `${value}`; case typeof value === 'string': return value; case value === undefined && allowNull: case value === null && allowNull: return ''; } api.inspector.warn(`unexpected value '${value}' set at analytics.${name}. Expecting a String`); return null; }; const validateNumber = (value, name, allowNull = true) => { switch (true) { case !isNaN(value): return value; case typeof value === 'string' && !isNaN(Number(value)): return Number(value); case value === undefined && allowNull: case value === null && allowNull: return -1; } api.inspector.warn(`unexpected value '${value}' set at analytics.${name}. Expecting a Number`); return null; }; const validateBoolean = (value, name) => { switch (true) { case typeof value === 'boolean': return value; case typeof value === 'string' && value.toLowerCase() === 'true': case value === '1': case value === 1: return true; case typeof value === 'string' && value.toLowerCase() === 'false': case value === '0': case value === 0: return false; case value === undefined: case value === null: return value; } api.inspector.warn(`unexpected value '${value}' set at analytics.${name}. Expecting a Boolean`); return null; }; const validateLang = (value, name, allowNull = true) => { switch (true) { case typeof value === 'string' && /^[A-Za-z]{2}$|^[A-Za-z]{2}[-_]/.test(value): return value.split(/[-_]/)[0].toLowerCase(); case value === undefined && allowNull: case value === null && allowNull: return ''; } api.inspector.warn(`unexpected value '${value}' set at analytics.${name}. Expecting language as a String following ISO 639-1 format`); return null; }; const validateGeography = (value, name, allowNull = true) => { switch (true) { case typeof value === 'string': if (!/^FR-[A-Z0-9]{2,3}$/.test(value)) api.inspector.warn(`value '${value}' set at analytics.${name} with wrong format. Geographic location should be a String following ISO 3166-2:FR format`); return value; case value === undefined && allowNull: case value === null && allowNull: return ''; } api.inspector.warn(`unexpected value '${value}' set at analytics.${name}. Expecting geographic location as a String following ISO 3166-2:FR format`); return null; }; const normaliseISODate = (date) => date.toISOString().split('T')[0]; const validateDate = (value, name, allowNull = true) => { switch (true) { case value instanceof Date: return normaliseISODate(value); case typeof value === 'string': { const date = new Date(value); if (date.toString() !== 'Invalid Date') return normaliseISODate(date); break; } case value === undefined && allowNull: case value === null && allowNull: return null; } api.inspector.warn(`unexpected value '${value}' set at analytics.${name}. Expecting a Date`); return null; }; class User { constructor (config) { this._config = config || {}; } reset (clear = false) { this._isConnected = false; this.status = Status.ANONYMOUS; if (!clear && this._config.connect) this.connect(this._config.connect.uid, this._config.connect.email, this._config.connect.isNew); else { this._uid = undefined; this._email = undefined; this._isNew = false; } this.profile = clear ? undefined : this._config.profile; this.language = clear ? undefined : this._config.language; this.type = clear ? undefined : this._config.type; } connect (uid, email, isNew = false) { this._uid = validateString(uid, 'user.uid'); if (/^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]{2,}@[a-zA-Z0-9-]{2,}\.[a-zA-Z]{2,}$/.test(email)) api.inspector.warn('Please check analytics.user.email is properly encrypted '); this._email = validateString(email, 'user.email'); this._isNew = validateBoolean(isNew); this._isConnected = true; this.status = Status.CONNECTED; } get uid () { return this._uid; } get email () { return this._email; } get isNew () { return this._isNew; } set status (id) { const stati = Object.values(Status).filter(status => status.isConnected === this._isConnected); this._status = stati.filter(status => status.id === id || status.value === id)[0] || stati.filter(status => status.isDefault)[0]; } get status () { return this._status.id; } set profile (value) { const valid = validateString(value, 'user.profile'); if (valid !== null) this._profile = valid; } get profile () { return this._profile.id; } set language (value) { const valid = validateLang(value, 'user.language'); if (valid !== null) this._language = valid; } get language () { return this._language || navigator.language; } set type (id) { this._type = Object.values(Type$2).filter(type => type.id === id || type.value === id)[0]; } get type () { return this._type.id; } get layer () { const layer = []; if (this.uid) layer.push('uid', normalize(this.uid)); if (this.email) layer.push('email', normalize(this.email)); if (this.isNew) layer.push('newcustomer', '1'); if (this.language) layer.push('user_language', this.language); layer.push('user_login_status', this._status.value); if (this._profile) layer.push('profile', this._profile); if (this._type) layer.push('user_type', this._type.value); return layer; } } User.Status = Status; User.Type = Type$2; const Environment = { DEVELOPMENT: { id: 'development', value: 'dev' }, STAGE: { id: 'stage', value: 'stage' }, PRODUCTION: { id: 'production', value: 'prod' } }; class Site { constructor (config) { this._config = config || {}; } reset (clear = false) { this.environment = clear ? Environment.DEVELOPMENT.id : this._config.environment; this.entity = clear ? undefined : this._config.entity; this.language = clear ? undefined : this._config.language; this.target = clear ? undefined : this._config.target; this.type = clear ? undefined : this._config.type; this.region = clear ? undefined : this._config.region; this.department = clear ? undefined : this._config.department; this.version = clear ? undefined : this._config.version; this._api = api.version; } set environment (value) { switch (value) { case Environment.PRODUCTION.id: case Environment.PRODUCTION.value: this._environment = Environment.PRODUCTION; break; case Environment.STAGE.id: case Environment.STAGE.value: this._environment = Environment.STAGE; break; case Environment.DEVELOPMENT.id: case Environment.DEVELOPMENT.value: this._environment = Environment.DEVELOPMENT; break; default: this._environment = Environment.DEVELOPMENT; } } get environment () { return this._environment ? this._environment.id : Environment.DEVELOPMENT.id; } set entity (value) { const valid = validateString(value, 'site.entity'); if (valid !== null) this._entity = valid; } get entity () { return this._entity; } set language (value) { const valid = validateLang(value, 'site.language'); if (valid !== null) this._language = valid; } get language () { return this._language || document.documentElement.lang; } set target (value) { const valid = validateString(value, 'site.target'); if (valid !== null) this._target = valid; } get target () { return this._target; } set type (value) { const valid = validateString(value, 'site.type'); if (valid !== null) this._type = valid; } get type () { return this._type; } set region (value) { const valid = validateGeography(value, 'site.region'); if (valid !== null) this._region = valid; } get region () { return this._region; } set department (value) { const valid = validateGeography(value, 'site.department'); if (valid !== null) this._department = valid; } get department () { return this._department; } set version (value) { const valid = validateString(value, 'site.version'); if (valid !== null) this._version = valid; } get version () { return this._version; } get api () { return this._api; } get layer () { const layer = []; layer.push('site_environment', this._environment.value); if (this.entity) layer.push('site_entity', normalize(this.entity)); else api.inspector.warn('entity is required in analytics.site'); if (this.language) layer.push('site_language', this.language); if (this.target) layer.push('site_target', normalize(this.target)); if (this.type) layer.push('site_type', normalize(this.type)); if (this.region) layer.push('site_region', this.region); if (this.department) layer.push('site_department', this.department); if (this.version) layer.push('site_version', this.version); if (this.api) layer.push('api_version', this.api); return layer; } } Site.Environment = Environment; const Inventory = { accordion: api.internals.ns.selector('accordion'), alert: api.internals.ns.selector('alert'), badge: api.internals.ns.selector('badge'), breadcrumb: api.internals.ns.selector('breadcrumb'), button: api.internals.ns.selector('btn'), callout: api.internals.ns.selector('callout'), card: api.internals.ns.selector('card'), checkbox: api.internals.ns.selector('checkbox-group'), connect: api.internals.ns.selector('connect'), consent: api.internals.ns.selector('consent-banner'), content: api.internals.ns.selector('content-media'), download: api.internals.ns.selector('download'), follow: api.internals.ns.selector('follow'), footer: api.internals.ns.selector('footer'), header: api.internals.ns.selector('header'), highlight: api.internals.ns.selector('highlight'), input: api.internals.ns.selector('input-group'), link: api.internals.ns.selector('link'), modal: api.internals.ns.selector('modal'), navigation: api.internals.ns.selector('nav'), notice: api.internals.ns.selector('notice'), pagination: api.internals.ns.selector('pagination'), quote: api.internals.ns.selector('quote'), radio: api.internals.ns.selector('radio-group'), search: api.internals.ns.selector('search-bar'), select: api.internals.ns.selector('select'), share: api.internals.ns.selector('share'), sidemenu: api.internals.ns.selector('sidemenu'), stepper: api.internals.ns.selector('stepper'), summary: api.internals.ns.selector('summary'), tab: api.internals.ns.selector('tabs'), table: api.internals.ns.selector('table'), tag: api.internals.ns.selector('tag'), tile: api.internals.ns.selector('tile'), toggle: api.internals.ns.selector('toggle'), tooltip: api.internals.ns.selector('tooltip'), transcription: api.internals.ns.selector('transcription'), translate: api.internals.ns.selector('translate'), upload: api.internals.ns.selector('upload-group') }; const CollectionState = { COLLECTABLE: 'collectable', COLLECTING: 'collecting', COLLECTED: 'collected' }; class Page { constructor (config) { this._config = config || {}; this._state = CollectionState.COLLECTABLE; } reset (clear = false) { this.path = clear ? '' : this._config.path; this.referrer = clear ? '' : this._config.referrer; this.title = clear ? '' : this._config.title; this.name = clear ? '' : this._config.name; this.id = clear ? '' : this._config.id; this.author = clear ? '' : this._config.author; this.date = clear ? '' : this._config.date; this._labels = clear || !this._config.labels ? ['', '', '', '', ''] : this._config.labels; this._labels.length = 5; this._tags = clear || !this._config.tags ? [] : this._config.tags; this._categories = clear || !this._config.categories ? ['', '', ''] : this._config.categories; this.isError = !clear && this._config.isError; this.template = clear ? '' : this._config.template; this.group = clear ? '' : this._config.group; this.segment = clear ? '' : this._config.segment; this.subtemplate = clear ? '' : this._config.subtemplate; this.theme = clear ? '' : this._config.theme; this.subtheme = clear ? '' : this._config.subtheme; this.related = clear ? '' : this._config.related; this.depth = clear || isNaN(this._config.depth) ? 0 : this._config.depth; this.current = clear || isNaN(this._config.current) ? -1 : this._config.current; this.total = clear || isNaN(this._config.total) ? -1 : this._config.total; this._filters = clear || !this._config.filters ? [] : this._config.filters; } collecting () { if (this._state !== CollectionState.COLLECTABLE) { api.inspector.warn(`current path '${this.path}' was already collected`); return false; } this._state = CollectionState.COLLECTING; return true; } get isCollecting () { return this._state === CollectionState.COLLECTING; } set path (value) { const valid = validateString(value, 'page.path'); if (valid !== null) { this._path = valid; this._state = CollectionState.COLLECTABLE; } } get path () { return this._path || `${document.location.pathname}${document.location.search}`; } set referrer (value) { const valid = validateString(value, 'page.referrer'); if (valid !== null) this._referrer = valid; } get referrer () { return this._referrer; } set title (value) { const valid = validateString(value, 'page.title'); if (valid !== null) this._title = valid; } get title () { return this._title || document.title; } set id (value) { const valid = validateString(value, 'page.id'); if (valid !== null) this._id = valid; } get id () { return this._id; } set author (value) { const valid = validateString(value, 'page.author'); if (valid !== null) this._author = valid; } get author () { return this._author; } set date (value) { const valid = validateDate(value, 'page.date'); if (valid !== null) this._date = valid; } get date () { return this._date; } get tags () { return this._tags; } set name (value) { const valid = validateString(value, 'page.name'); if (valid !== null) this._name = valid; } get name () { return this._name || this.title; } get labels () { return this._labels; } get categories () { return this._categories; } set isError (value) { const valid = validateBoolean(value, 'page.isError'); if (valid !== null) this._isError = valid; } get isError () { return this._isError; } set template (value) { const valid = validateString(value, 'page.template'); if (valid !== null) this._template = valid; } get template () { return this._template || 'autres'; } set segment (value) { const valid = validateString(value, 'page.segment'); if (valid !== null) this._segment = valid; } get segment () { return this._segment || this.template; } set group (value) { const valid = validateString(value, 'page.group'); if (valid !== null) this._group = valid; } get group () { return this._group || this.template; } set subtemplate (value) { const valid = validateString(value, 'page.subtemplate'); if (valid !== null) this._subtemplate = valid; } get subtemplate () { return this._subtemplate; } set theme (value) { const valid = validateString(value, 'page.theme'); if (valid !== null) this._theme = valid; } get theme () { return this._theme; } set subtheme (value) { const valid = validateString(value, 'page.subtheme'); if (valid !== null) this._subtheme = valid; } get subtheme () { return this._subtheme; } set related (value) { const valid = validateString(value, 'page.related'); if (valid !== null) this._related = valid; } get related () { return this._related; } set depth (value) { const valid = validateNumber(value, 'page.depth'); if (valid !== null) this._depth = valid; } get depth () { return this._depth; } set current (value) { const valid = validateNumber(value, 'page.current'); if (valid !== null) this._current = valid; } get current () { return this._current; } set total (value) { const valid = validateNumber(value, 'page.total'); if (valid !== null) this._total = valid; } get total () { return this._total; } get filters () { return this._filters; } get layer () { this._state = CollectionState.COLLECTED; const layer = []; if (this.path) layer.push('path', normalize(this.path)); if (this.referrer) layer.push('referrer', normalize(this.referrer)); if (this.title) layer.push('page_title', normalize(this.title)); if (this.name) layer.push('page_name', normalize(this.name)); if (this.id) layer.push('page_id', normalize(this.id)); if (this.author) layer.push('page_author', normalize(this.author)); if (this.date) layer.push('page_date', normalize(this.date)); const components = Object.keys(Inventory).map(id => document.querySelector(Inventory[id]) !== null ? id : null).filter(id => id !== null).join(','); if (components) layer.push('page_components', components); const labels = this._labels.slice(0, 5); labels.length = 5; if (labels.some(label => label)) layer.push('pagelabel', labels.map(label => typeof label === 'string' ? normalize(label) : '').join(',')); const tags = this._tags; if (tags.some(tag => tag)) layer.push('pagetag', tags.map(tag => typeof tag === 'string' ? normalize(tag) : '').join(',')); this._categories.forEach((category, index) => { if (category) layer.push(`page_category${index + 1}`, category); }); if (this._isError) layer.push('error', '1'); layer.push('page_template', normalize(this.template)); layer.push('pagegroup', normalize(this.group)); layer.push('site-segment', normalize(this.segment)); if (this.subtemplate) layer.push('page_subtemplate', normalize(this.subtemplate)); if (this.theme) layer.push('page_theme', normalize(this.theme)); if (this.subtheme) layer.push('page_subtheme', normalize(this.subtheme)); if (this.related) layer.push('page_related', normalize(this.related)); if (!isNaN(this.depth)) layer.push('page_depth', this.depth); if (!isNaN(this.current) && this.current > -1) { let pagination = `${this.current}`; if (!isNaN(this.total) && this.total > -1) pagination += `/${this.total}`; layer.push('page_pagination', pagination); } if (this.filters.length && this.filters.some(label => label)) { const filters = this.filters.map(filter => typeof filter === 'string' ? normalize(filter) : ''); layer.push('page_filters', filters.join(',')); } return layer; } } const Method = { STANDARD: { id: 'standard', value: 'standard', isDefault: true }, AUTOCOMPLETE: { id: 'autocomplete', value: 'autocompletion' } }; class Search { constructor (config) { this._config = config || {}; } reset (clear = false) { this.engine = clear ? undefined : this._config.engine; this.results = clear || isNaN(this._config.results) ? -1 : this._config.results; this.terms = clear ? undefined : this._config.terms; this.category = clear ? undefined : this._config.category; this.theme = clear ? undefined : this._config.theme; this.type = clear ? undefined : this._config.type; this.method = clear ? undefined : this._config.method; } set engine (value) { const valid = validateString(value, 'search.engine'); if (valid !== null) this._engine = valid; } get engine () { return this._engine; } set results (value) { const valid = validateNumber(value, 'search.results'); if (valid !== null) this._results = valid; } get results () { return this._results; } set terms (value) { const valid = validateString(value, 'search.terms'); if (valid !== null) this._terms = valid; } get terms () { return this._terms; } set category (value) { const valid = validateString(value, 'search.category'); if (valid !== null) this._category = valid; } get category () { return this._category; } set theme (value) { const valid = validateString(value, 'search.theme'); if (valid !== null) this._theme = valid; } get theme () { return this._theme; } set type (value) { const valid = validateString(value, 'search.type'); if (valid !== null) this._type = valid; this._type = value; } get type () { return this._type; } set method (id) { const methods = Object.values(Method); this._method = methods.filter(method => method.id === id || method.value === id)[0] || methods.filter(method => method.isDefault)[0]; } get method () { return this._method; } get layer () { const layer = []; if (this.engine) layer.push('isearchengine', normalize(this.engine)); if (this.results > -1) layer.push('isearchresults', this.results); if (this.terms) layer.push('isearchkey', 'search_terms', 'isearchdata', normalize(this.terms)); if (this.category) layer.push('isearchkey', 'search_category', 'isearchdata', normalize(this.category)); if (this.theme) layer.push('isearchkey', 'search_theme', 'isearchdata', normalize(this.theme)); if (this.type) layer.push('isearchkey', 'search_type', 'isearchdata', normalize(this.type)); if (this._method && layer.length) layer.push('isearchkey', 'search_method', 'isearchdata', this._method.value); return layer; } } Search.Method = Method; class Funnel { constructor (config) { this._config = config || {}; } reset (clear = false) { this.id = clear ? undefined : this._config.id; this.type = clear ? undefined : this._config.type; this.name = clear ? undefined : this._config.name; this.step = clear ? undefined : this._config.step; this.current = clear || isNaN(this._config.current) ? -1 : this._config.current; this.total = clear || isNaN(this._config.total) ? -1 : this._config.total; this.objective = clear ? undefined : this._config.objective; this.error = clear ? undefined : this._config.error; } set id (value) { const valid = validateString(value, 'funnel.id'); if (valid !== null) this._id = valid; } get id () { return this._id; } set type (value) { const valid = validateString(value, 'funnel.type'); if (valid !== null) this._type = valid; } get type () { return this._type; } set name (value) { const valid = validateString(value, 'funnel.name'); if (valid !== null) this._name = valid; } get name () { return this._name; } set step (value) { const valid = validateString(value, 'funnel.step'); if (valid !== null) this._step = valid; } get step () { return this._step; } set current (value) { const valid = validateNumber(value, 'funnel.current'); if (valid !== null) this._current = valid; } get current () { return this._current; } set total (value) { const valid = validateNumber(value, 'funnel.total'); if (valid !== null) this._total = valid; } get total () { return this._total; } set objective (value) { const valid = validateString(value, 'funnel.objective'); if (valid !== null) this._objective = valid; this._objective = value; } get objective () { return this._objective; } set error (value) { const valid = validateString(value, 'funnel.error'); if (valid !== null) this._error = valid; this._error = value; } get error () { return this._error; } get layer () { const layer = []; if (this.id) layer.push('funnel_id', normalize(this.id)); if (this.type) layer.push('funnel_type', normalize(this.type)); if (this.name) layer.push('funnel_name', normalize(this.name)); if (this.step) layer.push('funnel_step_name', normalize(this.step)); if (!isNaN(this.current) && this.current > -1) layer.push('funnel_step_number', this.current); if (!isNaN(this.total) && this.total > -1) layer.push('funnel_step_max', this.total); if (this.objective) layer.push('funnel_objective', normalize(this.objective)); if (this.error) layer.push('funnel_error', normalize(this.error)); return layer; } } const ActionMode = { IN: 'in', OUT: 'out', NONE: 'none' }; const ActionStatus = { UNSTARTED: { id: 'unstarted', value: -1 }, STARTED: { id: 'started', value: 1 }, SINGULAR: { id: 'singular', value: 2 }, ENDED: { id: 'ended', value: 3 } }; const getParametersLayer = (data) => { return Object.entries(data).map(([key, value]) => ['actionpname', normalize(key), 'actionpvalue', normalize(value)]).flat(); }; class Action { constructor (name) { this._isMuted = false; this._regulation = ActionRegulation.NONE; this._name = name; this._status = ActionStatus.UNSTARTED; this._labels = []; this._parameters = {}; this._sentData = []; } get isMuted () { return this._isMuted; } set isMuted (value) { this._isMuted = value; } get regulation () { return this._regulation; } set regulation (value) { if (Object.values(ActionRegulation).includes(value)) this._regulation = value; } get isSingular () { return this._status === ActionStatus.SINGULAR; } get status () { return this._status; } get name () { return this._name; } get labels () { return this._labels; } get reference () { return this._reference; } get parameters () { return this._parameters; } get mode () { return this._mode; } singularize () { this._status = ActionStatus.SINGULAR; } rewind () { this._sentData = []; this._status = ActionStatus.UNSTARTED; } addParameter (key, value) { this._parameters[key] = value; } removeParameter (key) { delete this._parameters[key]; } set reference (value) { const valid = validateString(value, `action ${this._name}`); if (valid !== null) this._reference = valid; } get _base () { return ['actionname', this._name]; } _getLayer (data = {}) { if (this._isMuted) return []; if (this._mode !== ActionMode.IN) this._sentData.push(JSON.stringify(data)); const layer = this._base; switch (this._mode) { case ActionMode.IN: case ActionMode.OUT: layer.push('actionmode', this._mode); break; } const labels = this._labels.slice(0, 5); labels.length = 5; if (labels.some(label => label)) layer.push('actionlabel', labels.map(label => typeof label === 'string' ? normalize(label) : '').join(',')); if (this._reference) layer.push('actionref', this._reference); layer.push.apply(layer, getParametersLayer(Object.assign(this._parameters, data || {}))); return layer; } start (data) { switch (this._status) { case ActionStatus.UNSTARTED: this._mode = ActionMode.IN; this._status = ActionStatus.STARTED; break; case ActionStatus.SINGULAR: this._mode = ActionMode.NONE; this._status = ActionStatus.ENDED; break; default: api.inspector.error(`unexpected start on action ${this._name} with status ${this._status.id}`); return []; } return this._getLayer(data); } end (data) { switch (this._status) { case ActionStatus.STARTED: this._mode = ActionMode.OUT; this._status = ActionStatus.ENDED; break; case ActionStatus.UNSTARTED: this._mode = ActionMode.NONE; this._status = ActionStatus.ENDED; break; case ActionStatus.SINGULAR: this._mode = ActionMode.NONE; this._status = ActionStatus.ENDED; break; case ActionStatus.ENDED: if (this._sentData.includes(JSON.stringify(data))) return []; this._mode = ActionMode.NONE; this._status = ActionStatus.ENDED; break; default: return []; } return this._getLayer(data); } resume (data) { if (this._isMuted) return []; if (this._status.value >= ActionStatus.ENDED.value) { api.inspector.error(`unexpected resuming on action ${this._name} with status ${this._status.id}`); return []; } const layer = this._base; if (data) layer.push.apply(layer, getParametersLayer(data)); return layer; } } class Actions { constructor () { this._actions = []; } rewind () { this._actions.forEach(action => action.rewind()); } getAction (name) { let action = this._actions.filter(action => action.name === name)[0]; if (!action) { action = new Action(name); this._actions.push(action); } return action; } hasAction (name) { return this._actions.some(action => action.name === name); } remove (action) { const index = this._actions.indexOf(action); if (index === -1) return false; this._actions.splice(index, 1); return true; } } Actions.ActionMode = ActionMode; const actions = new Actions(); Actions.instance = actions; class Location { constructor (onRouteChange, isListeningHash = false) { this._onRouteChange = onRouteChange; this._isListeningHash = isListeningHash; this._update(); renderer.add(this); } _update () { this._pathname = document.location.pathname; this._search = document.location.search; this._hash = document.location.hash; this._path = `${this._pathname}${this._search}`; if (this._isListeningHash) this._path += this._hash; this._hasTitle = this._title === document.title; this._title = document.title; } render () { if (this._pathname !== document.location.pathname || this._search !== document.location.search) this.change(); if (this._isListeningHash && this._hash !== document.location.hash) this.change(); } change () { this._referrer = this._path; this._update(); this._onRouteChange(); } get path () { return this._path; } get hasTitle () { return this._hasTitle; } get title () { return this._title; } get referrer () { return this._referrer; } } const CollectorEvent = { COLLECT: api.internals.ns.event('collect') }; const ActioneeEmission = { REWIND: api.internals.ns.emission('analytics', 'rewind') }; const ActionEnable = { ENABLE: { entries: ['enable', 'enabled', 'true', 'yes', '1', true], value: true, output: true }, DISABLE: { entries: ['disable', 'disabled', 'false', 'no', '0', false], value: false, output: false }, REDUCE: { entries: ['reduce'], value: 'reduce', output: false } }; class Collector { constructor (config) { switch (config.collection) { case Collection.MANUAL: case Collection.LOAD: case Collection.FULL: case Collection.HASH: this._collection = config.collection; break; default: /* deprecated start */ if (config.mode) { switch (config.mode) { case 'manual': this._collection = config.collection; break; } } /* deprecated end */ switch (true) { /* deprecated */ case config.mode === 'manual': this._collection = Collection.MANUAL; break; case api.mode === api.Modes.ANGULAR: case api.mode === api.Modes.REACT: case api.mode === api.Modes.VUE: this._collection = Collection.FULL; break; default: this._collection = Collection.LOAD; } } this.isActionEnabled = config.isActionEnabled; this._user = new User(config.user); this._site = new Site(config.site); this._page = new Page(config.page); this._search = new Search(config.search); this._funnel = new Funnel(config.funnel); this._delay = -1; queue.setCollector(this); } get page () { return this._page; } get user () { return this._user; } get site () { return this._site; } get search () { return this._search; } get funnel () { return this._funnel; } start () { const handleRouteChange = this._handleRouteChange.bind(this); switch (this._collection) { case Collection.LOAD: this.collect(); break; case Collection.FULL: this.collect(); this._location = new Location(handleRouteChange); break; case Collection.HASH: this.collect(); this._location = new Location(handleRouteChange, true); break; } } _handleRouteChange () { queue.send(true); this._delay = 6; renderer.add(this); } render () { this._delay--; if (this._delay < 0) { renderer.remove(this); this._routeChanged(); } } _routeChanged () { actions.rewind(); this._page.referrer = this._location.referrer; if (this._location.hasTitle) this._page.title = this._location.title; this._page.path = this._location.path; const event = new CustomEvent(CollectorEvent.COLLECT); document.documentElement.dispatchEvent(event); this.collect(); if (api.internals && api.internals.stage && api.internals.stage.root) api.internals.stage.root.descend(ActioneeEmission.REWIND); } reset (clear = false) { this._user.reset(clear); this._site.reset(clear); this._page.reset(clear); this._search.reset(clear); this._funnel.reset(clear); } collect () { if (!this.page.collecting()) return; queue.collect(); } get collection () { return this._collection; } get isCollecting () { return this._page.isCollecting; } get isActionEnabled () { return this._isActionEnabled.value; } set isActionEnabled (value) { this._isActionEnabled = Object.values(ActionEnable).find(enable => enable.entries.includes(value)) || ActionEnable.DISABLE; } get isActionReduced () { return this._isActionEnabled === ActionEnable.REDUCE; } get layer () { return [ ...this._user.layer, ...this._site.layer, ...this._page.layer, ...this._search.layer, ...this._funnel.layer ]; } } class Analytics { constructor () { this._isReady = false; this._readiness = new Promise((resolve, reject) => { if (this._isReady) resolve(); else { this._resolve = resolve; this._reject = reject; } }); this._configure(); } _configure () { switch (true) { case window[patch.namespace] !== undefined: this._config = window[patch.namespace].configuration.analytics; window[patch.namespace].promise.then(this._build.bind(this), () => {}); break; case api.internals !== undefined && api.internals.configuration !== undefined && api.internals.configuration.analytics !== undefined && api.internals.configuration.analytics.domain !== undefined: this._config = api.internals.configuration.analytics; this._build(); break; case api.analytics !== undefined && api.analytics.domain !== undefined: this._config = api.analytics; this._build(); break; default: api.inspector.warn('analytics configuration is incorrect or missing (required : domain)'); } } _build () { this._init = new Init(this._config.domain); this._init.configure().then(this._start.bind(this), (reason) => this._reject(reason)); } get isReady () { return this._isReady; } get readiness () { return this._readiness; } _start () { if (this._isReady) return; this._cmp = new ConsentManagerPlatform(this._config.