forked from rebillar/site-accueil-insa
837 lines
27 KiB
JavaScript
837 lines
27 KiB
JavaScript
/*!
|
|
* Matomo - free/libre analytics platform
|
|
*
|
|
* @link https://matomo.org
|
|
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
|
*/
|
|
|
|
function _pk_translate(translationStringId, values) {
|
|
if (typeof(piwik_translations) !== 'undefined'
|
|
&& typeof(piwik_translations[translationStringId]) != 'undefined'
|
|
) {
|
|
var translation = piwik_translations[translationStringId];
|
|
if (typeof values != 'undefined' && values && values.length) {
|
|
values.unshift(translation);
|
|
return sprintf.apply(null, values);
|
|
} else {
|
|
translation = translation.replaceAll('%%', '%');
|
|
}
|
|
|
|
return translation;
|
|
}
|
|
|
|
return "The string "+translationStringId+" was not loaded in javascript. Make sure it is added in the Translate.getClientSideTranslationKeys hook.";
|
|
}
|
|
|
|
window.piwikHelper = {
|
|
|
|
htmlDecode: function(value)
|
|
{
|
|
var textArea = document.createElement('textarea');
|
|
textArea.innerHTML = value;
|
|
return textArea.value;
|
|
},
|
|
|
|
sendContentAsDownload: function (filename, content, mimeType) {
|
|
if (!mimeType) {
|
|
mimeType = 'text/plain';
|
|
}
|
|
function downloadFile(content)
|
|
{
|
|
var node = document.createElement('a');
|
|
node.style.display = 'none';
|
|
if ('string' === typeof content) {
|
|
node.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(content));
|
|
} else {
|
|
node.href = window.URL.createObjectURL(blob);
|
|
}
|
|
node.setAttribute('download', filename);
|
|
document.body.appendChild(node);
|
|
node.click();
|
|
document.body.removeChild(node);
|
|
}
|
|
|
|
var node;
|
|
if ('function' === typeof Blob) {
|
|
// browser supports blob
|
|
try {
|
|
var blob = new Blob([content], {type: mimeType});
|
|
if (window.navigator.msSaveOrOpenBlob) {
|
|
window.navigator.msSaveBlob(blob, filename);
|
|
return;
|
|
} else {
|
|
downloadFile(blob);
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
downloadFile(content);
|
|
}
|
|
}
|
|
downloadFile(content);
|
|
},
|
|
|
|
/**
|
|
* a nice cross-browser logging function
|
|
*/
|
|
log: function() {
|
|
try {
|
|
console.log.apply(console, arguments); // Firefox, Chrome
|
|
} catch (e) {
|
|
try {
|
|
opera.postError.apply(opera, arguments); // Opera
|
|
} catch (f) {
|
|
// don't alert as log is not considered to be important enough
|
|
// (as opposed to piwikHelper.error)
|
|
//alert(Array.prototype.join.call(arguments, ' ')); // MSIE
|
|
}
|
|
}
|
|
},
|
|
|
|
error: function() {
|
|
try {
|
|
console.error.apply(console, arguments); // Firefox, Chrome
|
|
} catch (e) {
|
|
try {
|
|
opera.postError.apply(opera, arguments); // Opera
|
|
} catch (f) {
|
|
alert(Array.prototype.join.call(arguments, ' ')); // MSIE
|
|
}
|
|
}
|
|
},
|
|
|
|
htmlEntities: function(value)
|
|
{
|
|
if (!value) {
|
|
return value;
|
|
}
|
|
var findReplace = [[/&/g, "&"], [/</g, "<"], [/>/g, ">"], [/"/g, """], [/{{/g, '{⁣{']];
|
|
for(var item in findReplace) {
|
|
value = value.replace(findReplace[item][0], findReplace[item][1]);
|
|
}
|
|
return value;
|
|
},
|
|
|
|
/**
|
|
* @deprecated use window.vueSanitize instead
|
|
*/
|
|
escape: function (value)
|
|
{
|
|
var escape = angular.element(document).injector().get('$sanitize');
|
|
|
|
return escape(value);
|
|
},
|
|
|
|
/**
|
|
* Add break points to a string so that it can be displayed more compactly
|
|
*/
|
|
addBreakpoints: function(text, breakpointMarkup)
|
|
{
|
|
return text.replace(/([\/&=?\.%#:_-])/g, '$1' +
|
|
(typeof breakpointMarkup == 'undefined' ? '<wbr>​' : breakpointMarkup));
|
|
// ​ is for internet explorer
|
|
},
|
|
|
|
/**
|
|
* Add breakpoints to a URL
|
|
* urldecodes and encodes htmlentities to display utf8 urls without XSS vulnerabilities
|
|
*/
|
|
addBreakpointsToUrl: function(url)
|
|
{
|
|
try {
|
|
url = decodeURIComponent(url);
|
|
} catch (e) {
|
|
// might throw "URI malformed"
|
|
}
|
|
url = piwikHelper.addBreakpoints(url, '|||');
|
|
url = $(document.createElement('p')).text(url).html();
|
|
url = url.replace(/\|\|\|/g, '<wbr />​'); // ​ is for internet explorer
|
|
return url;
|
|
},
|
|
|
|
getAngularDependency: function (dependency) {
|
|
return angular.element(document).injector().get(dependency);
|
|
},
|
|
|
|
// initial call for 'body' later in this file
|
|
compileVueEntryComponents: function (selector, extraProps) {
|
|
function toCamelCase(arg) {
|
|
return arg[0] + arg.substring(1)
|
|
.replace(/-[a-z]/g, function (s) { return s[1].toUpperCase(); });
|
|
}
|
|
|
|
function toKebabCase(arg) {
|
|
return arg[0].toLowerCase() + arg.substring(1)
|
|
.replace(/[A-Z]/g, function (s) { return '-' + s[0].toLowerCase(); });
|
|
}
|
|
|
|
// process vue-entry attributes
|
|
$('[vue-entry]', selector).add($(selector).filter('[vue-entry]')).each(function () {
|
|
if ($(this).closest('[vue-entry-ignore]').length) {
|
|
return;
|
|
}
|
|
|
|
var entry = $(this).attr('vue-entry');
|
|
var componentsToRegister = ($(this).attr('vue-components') || '').split(/\s+/).filter(function (s) {
|
|
return !!s.length;
|
|
});
|
|
|
|
var parts = entry.split('.');
|
|
if (parts.length !== 2) {
|
|
throw new Error('Expects vue-entry to have format Plugin.Component, where Component is exported Vue component. Got: ' + entry);
|
|
}
|
|
|
|
var useExternalPluginComponent = CoreHome.useExternalPluginComponent;
|
|
var createVueApp = CoreHome.createVueApp;
|
|
var plugin = window[parts[0]];
|
|
if (!plugin) {
|
|
throw new Error('Unknown plugin in vue-entry: ' + entry);
|
|
}
|
|
|
|
var component = plugin[parts[1]];
|
|
if (!component) {
|
|
throw new Error('Unknown component in vue-entry: ' + entry);
|
|
}
|
|
|
|
$(this).attr('ng-non-bindable', '');
|
|
|
|
var paramsStr = '';
|
|
var componentParams = {};
|
|
|
|
function handleProperty(name, value) {
|
|
if (name === 'vue-entry' || name === 'class' || name === 'style') {
|
|
return;
|
|
}
|
|
|
|
// append with underscore so reserved javascripy keywords aren't accidentally used
|
|
var camelName = toCamelCase(name) + '_';
|
|
paramsStr += ':' + name + '=' + JSON.stringify(camelName) + ' ';
|
|
|
|
try {
|
|
value = JSON.parse(value);
|
|
} catch (e) {
|
|
// pass
|
|
}
|
|
|
|
componentParams[camelName] = value;
|
|
}
|
|
|
|
$.each(this.attributes, function () {
|
|
handleProperty(this.name, this.value);
|
|
});
|
|
Object.entries(extraProps || {}).forEach(([name, value]) => {
|
|
handleProperty(name, value);
|
|
});
|
|
|
|
// NOTE: we could just do createVueApp(component, componentParams), but Vue will not allow
|
|
// slots to be in the vue-entry element this way. So instead, we create a quick
|
|
// template that references the root component and wraps the vue-entry component's html.
|
|
// this allows using slots in twig.
|
|
var app = createVueApp({
|
|
template: '<root ' + paramsStr + '>' + this.innerHTML + '</root>',
|
|
data: function () {
|
|
return componentParams;
|
|
}
|
|
});
|
|
app.component('root', component);
|
|
|
|
componentsToRegister.forEach(function (componentRef) {
|
|
var parts = componentRef.split('.');
|
|
var pluginName = parts[0];
|
|
var componentName = parts[1];
|
|
|
|
var component = useExternalPluginComponent(pluginName, componentName);
|
|
|
|
// the component is made available via kebab case, since casing is lost in HTML,
|
|
// and tag names will appear all lower case when vue processes them
|
|
app.component(toKebabCase(componentName), component);
|
|
});
|
|
|
|
app.mount(this);
|
|
|
|
this.addEventListener('matomoVueDestroy', function () {
|
|
app.unmount();
|
|
});
|
|
});
|
|
|
|
// process vue-directive attributes (only uses .mounted/.unmounted hooks)
|
|
piwikHelper.compileVueDirectives(selector);
|
|
},
|
|
|
|
compileVueDirectives: function (selector) {
|
|
$('[vue-directive]', selector).add($(selector).filter('[vue-directive]')).each(function () {
|
|
var vueDirectiveName = $(this).attr('vue-directive');
|
|
if (!vueDirectiveName) {
|
|
return;
|
|
}
|
|
|
|
var parts = vueDirectiveName.split('.');
|
|
if (parts.length !== 2) {
|
|
throw new Error('Expects vue-entry to have format Plugin.Component, where Component is exported Vue component. Got: ' + vueDirectiveName);
|
|
}
|
|
|
|
var plugin = window[parts[0]];
|
|
if (!plugin) {
|
|
throw new Error('Unknown plugin in vue-entry: ' + vueDirectiveName);
|
|
}
|
|
|
|
var directive = plugin[parts[1]];
|
|
if (!directive) {
|
|
throw new Error('Unknown component in vue-entry: ' + vueDirectiveName);
|
|
}
|
|
|
|
var directiveArgument = $(this).attr('vue-directive-value');
|
|
|
|
var value;
|
|
try {
|
|
value = JSON.parse(directiveArgument || '{}');
|
|
} catch (e) {
|
|
console.log('failed to parse directive value ' + value + ': ' + directiveArgument);
|
|
return;
|
|
}
|
|
|
|
var binding = { value: value };
|
|
|
|
if (directive.mounted) {
|
|
directive.mounted(this, binding);
|
|
}
|
|
|
|
this.addEventListener('matomoVueDestroy', function () {
|
|
if (directive.unmounted) {
|
|
directive.unmounted(this, binding);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
destroyVueComponent: function (selector) {
|
|
$('[vue-entry]', selector).each(function () {
|
|
this.dispatchEvent(new CustomEvent('matomoVueDestroy'));
|
|
});
|
|
},
|
|
|
|
/**
|
|
* As we still have a lot of old jQuery code and copy html from node to node we sometimes have to trigger the
|
|
* compiling of angular components manually.
|
|
*
|
|
* @param selector
|
|
* @param {object} options
|
|
* @param {object} options.scope if supplied, the given scope will be used when compiling the template. Shouldn't
|
|
* be a plain object but an actual angular scope.
|
|
* @param {object} options.params if supplied, the properties in this object are
|
|
* added to the new scope.
|
|
*/
|
|
compileAngularComponents: function (selector, options) {
|
|
options = options || {};
|
|
|
|
var $element = $(selector);
|
|
|
|
if (!$element.length) {
|
|
return;
|
|
}
|
|
|
|
angular.element(document).injector().invoke(function($compile, $rootScope) {
|
|
var scope = null;
|
|
if (options.scope) {
|
|
scope = options.scope;
|
|
} else if (!options.forceNewScope) { // TODO: docs
|
|
scope = angular.element($element).scope();
|
|
}
|
|
if (!scope) {
|
|
scope = $rootScope.$new(true);
|
|
}
|
|
|
|
if (options.params) {
|
|
$.extend(scope, options.params);
|
|
}
|
|
|
|
$compile($element)(scope);
|
|
|
|
setTimeout(function () {
|
|
piwikHelper.processDynamicHtml($element);
|
|
});
|
|
});
|
|
},
|
|
|
|
processDynamicHtml: function ($element) {
|
|
piwik.postEvent('Matomo.processDynamicHtml', $element);
|
|
},
|
|
|
|
/**
|
|
* Detection works currently only for directives defining an isolated scope. Functionality might need to be
|
|
* extended if needed. Under circumstances you might call this method before calling compileAngularComponents()
|
|
* to avoid compiling the same element twice.
|
|
* @param selector
|
|
*/
|
|
isAlreadyCompiledAngularComponent: function (selector) {
|
|
var $element = $(selector);
|
|
|
|
return ($element.length && $element.hasClass('ng-isolate-scope'));
|
|
},
|
|
|
|
/**
|
|
* Detects whether angular is rendering the page. If so, the page will be reloaded automatically
|
|
* via angular as soon as it detects a $locationChange
|
|
*
|
|
* @returns {number|jQuery}
|
|
* @deprecated use isReportingPage() instead
|
|
*/
|
|
isAngularRenderingThePage: function ()
|
|
{
|
|
return this.isReportingPage();
|
|
},
|
|
|
|
/**
|
|
* Detects whether the current page is a reporting page or not.
|
|
*
|
|
* @returns {number|jQuery|*}
|
|
*/
|
|
isReportingPage: function ()
|
|
{
|
|
return $('.reporting-page').length;
|
|
},
|
|
|
|
/**
|
|
* Moves an element further to the left by changing the left margin to make sure as much as possible of an element
|
|
* is visible in the current viewport. The top position keeps unchanged.
|
|
* @param elementToPosition
|
|
*/
|
|
setMarginLeftToBeInViewport: function (elementToPosition) {
|
|
var availableWidth = $(window).width();
|
|
$(elementToPosition).css('marginLeft', '0px');
|
|
var offset = $(elementToPosition).offset();
|
|
if (!offset) {
|
|
return;
|
|
}
|
|
var leftPos = offset.left;
|
|
if (leftPos < 0) {
|
|
leftPos = 0;
|
|
}
|
|
var widthSegmentForm = $(elementToPosition).outerWidth();
|
|
if (leftPos + widthSegmentForm > availableWidth) {
|
|
var extraSpaceForMoreBeauty = 16;
|
|
var newLeft = availableWidth - widthSegmentForm - extraSpaceForMoreBeauty;
|
|
if (newLeft < extraSpaceForMoreBeauty) {
|
|
newLeft = extraSpaceForMoreBeauty;
|
|
}
|
|
var marginLeft = Math.abs(leftPos - newLeft);
|
|
if (marginLeft > extraSpaceForMoreBeauty) {
|
|
// we only move it further to the left if it is actually more than 16px to the left.
|
|
// otherwise it is not really worth it and doesn't look as good.
|
|
$(elementToPosition).css('marginLeft', (parseInt(marginLeft, 10) * -1) + 'px');
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Displays a Modal dialog. Text will be taken from the DOM node domSelector.
|
|
* Given callback handles will be mapped to the buttons having a role attribute
|
|
*
|
|
* Dialog will be closed when a button is clicked and callback handle will be
|
|
* called, if one was given for the clicked role
|
|
*
|
|
* @param {string} domSelector domSelector for modal window
|
|
* @param {object} handles callback functions for available roles
|
|
* @param {object} options options for modal
|
|
* @return {void}
|
|
*/
|
|
modalConfirm: function(domSelector, handles, options)
|
|
{
|
|
if (!options) {
|
|
options = {};
|
|
}
|
|
|
|
var domElem = $(domSelector);
|
|
var buttons = [];
|
|
|
|
var content = '<div class="modal"><div class="modal-content"></div>';
|
|
content += '<div class="modal-footer"></div></div>';
|
|
|
|
var $content = $(content).hide();
|
|
var $footer = $content.find('.modal-footer');
|
|
|
|
$('[role]', domElem).each(function(){
|
|
var $button = $(this);
|
|
|
|
// skip this button if it's part of another modal, the current modal can launch
|
|
// (which is true if there are more than one parent elements contained in domElem,
|
|
// w/ css class ui-confirm)
|
|
var uiConfirm = $button.parents('.ui-confirm,[ui-confirm]').filter(function () {
|
|
return domElem[0] === this || $.contains(domElem[0], this);
|
|
});
|
|
if (uiConfirm.length > 1) {
|
|
return;
|
|
}
|
|
|
|
var role = $button.attr('role');
|
|
var title = $button.attr('title');
|
|
var text = $button.val();
|
|
$button.hide();
|
|
|
|
var button = $('<a href="javascript:;" class="modal-action modal-close waves-effect waves-light btn-flat "></a>');
|
|
|
|
if(role === 'validation'){
|
|
button = $('<a href="javascript:;" class="modal-action waves-effect waves-light btn"></a>');
|
|
}
|
|
|
|
button.text(text);
|
|
if (title) {
|
|
button.attr('title', title);
|
|
}
|
|
|
|
if (typeof handles !== 'undefined' && typeof handles[role] == 'function') {
|
|
button.on('click', function(){
|
|
handles[role].apply()
|
|
});
|
|
}
|
|
if (typeof $button.data('href') !== 'undefined') {
|
|
button.on('click', function () {
|
|
window.location.href = $button.data('href');
|
|
})
|
|
}
|
|
|
|
$footer.append(button);
|
|
});
|
|
|
|
$('body').append($content);
|
|
$content.find('.modal-content').append(domElem);
|
|
|
|
if (options && options.fixedFooter) {
|
|
$content.addClass('modal-fixed-footer');
|
|
delete options.fixedFooter;
|
|
}
|
|
|
|
if (options && options.extraWide) {
|
|
// if given, the modal will be shown larger than usual and almost consume the full width.
|
|
$content.addClass('modal-extra-wide');
|
|
delete options.extraWide;
|
|
}
|
|
|
|
if (options && !options.onOpenEnd) {
|
|
options.onOpenEnd = function () {
|
|
$(".modal.open a").focus();
|
|
var modalContent = $(".modal.open");
|
|
if (modalContent && modalContent[0]) {
|
|
// make sure to scroll to the top of the content
|
|
modalContent[0].scrollTop = 0;
|
|
}
|
|
};
|
|
}
|
|
|
|
domElem.show();
|
|
$content.modal(options).modal('open');
|
|
},
|
|
|
|
getQueryStringWithParametersModified: function (queryString, newParameters) {
|
|
if (queryString != '') {
|
|
var r, i, keyvalue, keysvalues = newParameters.split('&');
|
|
var appendUrl = '';
|
|
for (i = 0; i < keysvalues.length; i++) {
|
|
keyvalue = keysvalues[i].split('=');
|
|
r = new RegExp('(^|[?&])' + keyvalue[0] + '=[^&]*');
|
|
queryString = queryString.replace(r, '');
|
|
|
|
// empty value, eg. &segment=, we remove the parameter from URL entirely
|
|
if (keyvalue[1].length == 0) {
|
|
continue;
|
|
}
|
|
appendUrl += '&' + keyvalue[0] + '=' + keyvalue[1];
|
|
}
|
|
queryString += appendUrl;
|
|
if (queryString[0] == '&') {
|
|
queryString = '?' + queryString.substring(1);
|
|
}
|
|
} else {
|
|
queryString = '?' + newParameters;
|
|
}
|
|
|
|
return queryString;
|
|
},
|
|
|
|
/**
|
|
* Returns the current query string with the given parameters modified
|
|
* @param {String} newparams parameters to be modified
|
|
* @return {String}
|
|
*/
|
|
getCurrentQueryStringWithParametersModified: function(newparams)
|
|
{
|
|
var queryString = String(window.location.search);
|
|
if (newparams) {
|
|
queryString = this.getQueryStringWithParametersModified(queryString, newparams);
|
|
}
|
|
return String(window.location.pathname) + queryString;
|
|
},
|
|
|
|
/**
|
|
* Given param1=v1¶m2=k2
|
|
* returns: { "param1": "v1", "param2": "v2" }
|
|
*
|
|
* @param query string
|
|
* @return {Object}
|
|
*/
|
|
getArrayFromQueryString: function (query) {
|
|
var params = {};
|
|
var vars = query.split("&");
|
|
for (var i=0;i<vars.length;i++) {
|
|
var keyValue = vars[i].split("=");
|
|
// Jquery will urlencode these, but we wish to keep the current raw value
|
|
// use case: &segment=visitorId%3D%3Dabc...
|
|
params[keyValue[0]] = decodeURIComponent(keyValue[1]);
|
|
}
|
|
return params;
|
|
},
|
|
|
|
/**
|
|
* Returns query string for an object of key,values
|
|
* Note: we don't use $.param from jquery as it doesn't return array values the PHP way (returns a=v1&a=v2 instead of a[]=v1&a[]=v2)
|
|
* Example:
|
|
* piwikHelper.getQueryStringFromParameters({"a":"va","b":["vb","vc"],"c":1})
|
|
* Returns:
|
|
* a=va&b[]=vb&b[]=vc&c=1
|
|
* @param {object} parameters
|
|
* @return {string}
|
|
*/
|
|
getQueryStringFromParameters: function(parameters)
|
|
{
|
|
var queryString = '';
|
|
if(!parameters || parameters.length==0) {
|
|
return queryString;
|
|
}
|
|
for(var name in parameters) {
|
|
var value = parameters[name];
|
|
if(typeof value == 'object') {
|
|
for(var i in value) {
|
|
queryString += name + '[]=' + value[i] + '&';
|
|
}
|
|
} else {
|
|
queryString += name + '=' + value + '&';
|
|
}
|
|
}
|
|
return queryString.substring(0, queryString.length-1);
|
|
},
|
|
|
|
/**
|
|
* Displays the given ajax error message within the given id element
|
|
* @param {string} message error message
|
|
* @param {string} errorDivID id of the domNode (defaults to ajaxError)
|
|
* @return {void}
|
|
*/
|
|
showAjaxError: function( message, errorDivID )
|
|
{
|
|
errorDivID = errorDivID || 'ajaxError';
|
|
$('#'+errorDivID).html(message).show();
|
|
},
|
|
|
|
/**
|
|
* Hides the error message with the given id
|
|
* @param {string} [errorDivID] id of the domNode (defaults to ajaxError)
|
|
* @return {void}
|
|
*/
|
|
hideAjaxError: function(errorDivID)
|
|
{
|
|
errorDivID = errorDivID || 'ajaxError';
|
|
$('#'+errorDivID).hide();
|
|
},
|
|
|
|
/**
|
|
* Shows the loading message with the given Id
|
|
* @param {string} [loadingDivID] id of the domNode (defaults to ajaxLoading)
|
|
* @return {void}
|
|
*/
|
|
showAjaxLoading: function(loadingDivID)
|
|
{
|
|
loadingDivID = loadingDivID || 'ajaxLoadingDiv';
|
|
$('#'+loadingDivID).show();
|
|
},
|
|
|
|
/**
|
|
* Hides the loading message with the given id
|
|
* @param {string} [loadingDivID] id of the domNode (defaults to ajaxLoading)
|
|
* @return {void}
|
|
*/
|
|
hideAjaxLoading: function(loadingDivID)
|
|
{
|
|
loadingDivID = loadingDivID || 'ajaxLoadingDiv';
|
|
$('#'+loadingDivID).hide();
|
|
},
|
|
|
|
/**
|
|
* Reloads the page after the given period
|
|
* @param {int} timeoutPeriod
|
|
* @return void
|
|
*/
|
|
refreshAfter: function(timeoutPeriod)
|
|
{
|
|
if(timeoutPeriod == 0) {
|
|
location.reload();
|
|
} else {
|
|
setTimeout("location.reload();",timeoutPeriod);
|
|
}
|
|
},
|
|
|
|
redirect: function (params) {
|
|
// add updated=X to the URL so that a "Your changes have been saved" message is displayed
|
|
if (typeof params == 'object') {
|
|
params = this.getQueryStringFromParameters(params);
|
|
}
|
|
var urlToRedirect = this.getCurrentQueryStringWithParametersModified(params);
|
|
var updatedUrl = new RegExp('&updated=([0-9]+)');
|
|
var updatedCounter = updatedUrl.exec(urlToRedirect);
|
|
if (!updatedCounter) {
|
|
urlToRedirect += '&updated=1';
|
|
} else {
|
|
updatedCounter = 1 + parseInt(updatedCounter[1]);
|
|
urlToRedirect = urlToRedirect.replace(new RegExp('(&updated=[0-9]+)'), '&updated=' + updatedCounter);
|
|
}
|
|
var currentHashStr = window.location.hash;
|
|
if(currentHashStr.length > 0) {
|
|
urlToRedirect += currentHashStr;
|
|
}
|
|
this.redirectToUrl(urlToRedirect);
|
|
},
|
|
|
|
/**
|
|
* Redirect to the given url
|
|
* @param {string} url
|
|
*/
|
|
redirectToUrl: function(url)
|
|
{
|
|
window.location = url;
|
|
},
|
|
|
|
lazyScrollToContent: function () {
|
|
this.lazyScrollTo('#content', 250);
|
|
},
|
|
|
|
/**
|
|
* Scrolls the window to the jquery element 'elem'
|
|
* if the top of the element is not currently visible on screen
|
|
* @param {string} elem Selector for the DOM node to scroll to, eg. '#myDiv'
|
|
* @param {int} [time] Specifies the duration of the animation in ms
|
|
* @param {boolean} [forceScroll] Whether to force scroll to an element.
|
|
* @return {void}
|
|
*/
|
|
lazyScrollTo: function(elem, time, forceScroll)
|
|
{
|
|
var $elem = $(elem);
|
|
if (!$elem.length) {
|
|
return;
|
|
}
|
|
|
|
var elemTop = $elem.offset().top;
|
|
// only scroll the page if the graph is not visible
|
|
if (elemTop < $(window).scrollTop()
|
|
|| elemTop > $(window).scrollTop()+$(window).height()
|
|
|| forceScroll
|
|
) {
|
|
// scroll the page smoothly to the graph
|
|
$.scrollTo(elem, time);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the filtered/converted content of a textarea to be used for api requests
|
|
* @param {string} textareaContent
|
|
* @return {string}
|
|
*/
|
|
getApiFormatTextarea: function (textareaContent) {
|
|
if (typeof textareaContent == 'undefined') {
|
|
return '';
|
|
}
|
|
return textareaContent.trim().split("\n").join(',');
|
|
},
|
|
|
|
shortcuts: {},
|
|
|
|
/**
|
|
* Register a shortcut
|
|
*
|
|
* @param {string} key key-stroke to be registered for this shortcut
|
|
* @param {string } description description to be shown in summary
|
|
* @param callback method called when pressing the key
|
|
*/
|
|
registerShortcut: function(key, description, callback) {
|
|
|
|
piwikHelper.shortcuts[key] = description;
|
|
|
|
Mousetrap.bind(key, callback);
|
|
},
|
|
|
|
calculateEvolution: function (currentValue, pastValue) {
|
|
var dividend = currentValue - pastValue;
|
|
var divisor = pastValue;
|
|
|
|
if (dividend == 0) {
|
|
return 0;
|
|
} else if (divisor == 0) {
|
|
return 1;
|
|
} else {
|
|
return Math.round((dividend / divisor) * 1000) / 1000;
|
|
}
|
|
},
|
|
|
|
showVisitorProfilePopup: function (visitorId, idSite) {
|
|
require('piwik/UI').VisitorProfileControl.showPopover(visitorId, idSite);
|
|
},
|
|
};
|
|
if (typeof String.prototype.trim !== 'function') {
|
|
String.prototype.trim = function() {
|
|
return this.replace(/^\s+|\s+$/g,"");
|
|
};
|
|
}
|
|
/**
|
|
* Returns true if the event keypress passed in parameter is the ENTER key
|
|
* @param {Event} e current window event
|
|
* @return {boolean}
|
|
*/
|
|
function isEnterKey(e)
|
|
{
|
|
return (window.event?window.event.keyCode:e.which)==13;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the event keypress passed in parameter is the ESCAPE key
|
|
* @param {Event} e current window event
|
|
* @return {boolean}
|
|
*/
|
|
function isEscapeKey(e)
|
|
{
|
|
return (window.event?window.event.keyCode:e.which)==27;
|
|
}
|
|
|
|
// workarounds
|
|
(function($){
|
|
try {
|
|
// this code is not vital, so we make sure any errors are ignored
|
|
|
|
//--------------------------------------
|
|
//
|
|
// monkey patch that works around bug in arc function of some browsers where
|
|
// nothing gets drawn if angles are 2 * PI apart and in counter-clockwise direction.
|
|
// affects some versions of chrome & IE 8
|
|
//
|
|
//--------------------------------------
|
|
var oldArc = CanvasRenderingContext2D.prototype.arc;
|
|
CanvasRenderingContext2D.prototype.arc = function(x, y, r, sAngle, eAngle, clockwise) {
|
|
if (Math.abs(eAngle - sAngle - Math.PI * 2) < 0.000001 && !clockwise)
|
|
eAngle -= 0.000001;
|
|
oldArc.call(this, x, y, r, sAngle, eAngle, clockwise);
|
|
};
|
|
|
|
// Fix jQuery UI dialogs scrolling when click on links with tooltips
|
|
jQuery.ui.dialog.prototype._focusTabbable = $.noop;
|
|
|
|
// Fix jQuery UI tooltip displaying when dialog is closed by Esc key
|
|
jQuery(document).keyup(function(e) {
|
|
if (e.keyCode == 27) {
|
|
$('.ui-tooltip').hide();
|
|
}
|
|
});
|
|
|
|
} catch (e) {}
|
|
}(jQuery));
|
|
|
|
(function ($) {
|
|
$(function () {
|
|
piwikHelper.compileVueEntryComponents('body');
|
|
});
|
|
}(jQuery))
|