PDF rausgenommen
This commit is contained in:
@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shows a general loading message while [loading] is set to true.
|
||||
*
|
||||
* @param {Boolean} loading If true, the activity indicator is shown, otherwise the indicator is hidden.
|
||||
*
|
||||
* Example:
|
||||
* <div piwik-activity-indicator loading-message="'My custom message'" loading="true|false"></div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikActivityIndicator', piwikActivityIndicator);
|
||||
|
||||
piwikActivityIndicator.$inject = ['piwik'];
|
||||
|
||||
function piwikActivityIndicator(piwik){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
scope: {
|
||||
loading: '=',
|
||||
loadingMessage: '=?'
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/activity-indicator/activityindicator.html?cb=' + piwik.cacheBuster,
|
||||
compile: function (element, attrs) {
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
if (!scope.loadingMessage) {
|
||||
scope.loadingMessage = _pk_translate('General_LoadingData');
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,3 @@
|
||||
<div ng-show="loading" class="loadingPiwik">
|
||||
<img src="plugins/Morpheus/images/loading-blue.gif" alt=""/> <span>{{ loadingMessage }}</span>
|
||||
</div>
|
@ -0,0 +1,85 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('AjaxFormController', AjaxFormController);
|
||||
|
||||
AjaxFormController.$inject = ['piwikApi', '$filter'];
|
||||
|
||||
function AjaxFormController(piwikApi, $filter) {
|
||||
var vm = this;
|
||||
|
||||
/**
|
||||
* Set to non-null when a form submit request returns successfully. When successful, it will
|
||||
* be the entire JSON parsed response of the request.
|
||||
*
|
||||
* @type {null|string}
|
||||
*/
|
||||
vm.successfulPostResponse = null;
|
||||
|
||||
/**
|
||||
* Set to non-null when a form submit request results in an error. When an error occurs,
|
||||
* it will be set to the string error message.
|
||||
*
|
||||
* @type {null|string}
|
||||
*/
|
||||
vm.errorPostResponse = null;
|
||||
|
||||
/**
|
||||
* true if currently submitting a POST request, false if otherwise.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
vm.isSubmitting = false;
|
||||
|
||||
vm.submitForm = submitForm;
|
||||
|
||||
/**
|
||||
* Sends a POST to the configured API method.
|
||||
*/
|
||||
function submitForm() {
|
||||
var postParams;
|
||||
|
||||
vm.successfulPostResponse = null;
|
||||
vm.errorPostResponse = null;
|
||||
|
||||
if (vm.sendJsonPayload) {
|
||||
postParams = {data: JSON.stringify(vm.data)};
|
||||
} else {
|
||||
postParams = vm.data;
|
||||
}
|
||||
|
||||
vm.isSubmitting = true;
|
||||
piwikApi.post(
|
||||
{ // GET params
|
||||
module: 'API',
|
||||
method: vm.submitApiMethod
|
||||
},
|
||||
postParams,
|
||||
{ // request options
|
||||
createErrorNotification: !vm.noErrorNotification
|
||||
}
|
||||
).then(function (response) {
|
||||
vm.successResponse = response;
|
||||
|
||||
if (!vm.noSuccessNotification) {
|
||||
var UI = require('piwik/UI');
|
||||
var notification = new UI.Notification();
|
||||
notification.show($filter('translate')('General_YourChangesHaveBeenSaved'), {
|
||||
context: 'success',
|
||||
type: 'toast',
|
||||
id: 'ajaxHelper'
|
||||
});
|
||||
notification.scrollToNotification();
|
||||
}
|
||||
}).catch(function (errorMessage) {
|
||||
vm.errorPostResponse = errorMessage;
|
||||
}).finally(function () {
|
||||
vm.isSubmitting = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,144 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* AngularJS directive that manages an AJAX form.
|
||||
*
|
||||
* This directive will detect inputs & selects defined within an element and when a
|
||||
* submit button is clicked, will post data from the inputs & selects to a Piwik API method.
|
||||
*
|
||||
* When the POST request is finished the result will, by default, be displayed as a
|
||||
* notification.
|
||||
*
|
||||
* This directive accepts the following attributes:
|
||||
*
|
||||
* - **submit-api-method**: **required** The Piwik API method that handles the POST request.
|
||||
* - **send-json-payload**: Whether to send the data as a form encoded URL or to send it as JSON.
|
||||
* If sending as JSON, the payload will still be a form encoded value,
|
||||
* but will contain a JSON object like `{data: {...form data...}}`.
|
||||
*
|
||||
* This is for forms with lots of fields where having the same number
|
||||
* of parameters in an API method would not be desired.
|
||||
* - **no-error-notification**: If true, does not display an error notification if the AJAX post
|
||||
* fails.
|
||||
* - **no-success-notification**: If true, does not display an error notification if the AJAX
|
||||
* results in success.
|
||||
*
|
||||
* **Custom Success/Error Handling**
|
||||
*
|
||||
* On success/failure, the response will be stored in controller scope. Child elements of a
|
||||
* piwik-ajax-form element can access this data, and thus, can customize what happens when
|
||||
* a form submit succeeds/fails.
|
||||
*
|
||||
* See the ajax-form.controller.js file for more info.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* <div piwik-ajax-form
|
||||
* submit-api-method="'MyPlugin.myFormSaveMethod'"
|
||||
* send-json-payload="true"
|
||||
* ng-model="myFormData">
|
||||
*
|
||||
* <h2>My Form</h2>
|
||||
* <input name="myOption" value="myDefaultValue" type="text" />
|
||||
* <input name="myOtherOption" type="checkbox" checked="checked" />
|
||||
* <input type="submit" value="Submit" ng-disabled="ajaxForm.isSubmitting" />
|
||||
*
|
||||
* <div piwik-notification context='error' ng-show="errorPostResponse">ERROR!</div>
|
||||
* </div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikAjaxForm', piwikAjaxForm);
|
||||
|
||||
piwikAjaxForm.$inject = ['$parse'];
|
||||
|
||||
function piwikAjaxForm($parse) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
submitApiMethod: '=',
|
||||
sendJsonPayload: '=',
|
||||
noErrorNotification: '=',
|
||||
noSuccessNotification: '=',
|
||||
useCustomDataBinding: '='
|
||||
},
|
||||
require: '?ngModel',
|
||||
controller: 'AjaxFormController',
|
||||
controllerAs: 'ajaxForm',
|
||||
transclude: true,
|
||||
compile: function (element, attrs) {
|
||||
attrs.noErrorNotification = !! attrs.noErrorNotification;
|
||||
|
||||
return function (scope, element, attrs, ngModel, transclude) {
|
||||
if (!scope.submitApiMethod) {
|
||||
throw new Error("submitApiMethod is required");
|
||||
}
|
||||
|
||||
scope.ajaxForm.submitApiMethod = scope.submitApiMethod;
|
||||
scope.ajaxForm.sendJsonPayload = scope.sendJsonPayload;
|
||||
scope.ajaxForm.noErrorNotification = scope.noErrorNotification;
|
||||
scope.ajaxForm.noSuccessNotification = scope.noSuccessNotification;
|
||||
|
||||
scope.ajaxForm.data = {};
|
||||
|
||||
// if a model is supplied, initiate form data w/ model value
|
||||
if (ngModel) {
|
||||
var ngModelGetter = $parse(attrs.ngModel); // probably redundant, but I cannot find another way to
|
||||
// get the ng model value here
|
||||
scope.ajaxForm.data = ngModelGetter(scope.$parent);
|
||||
}
|
||||
|
||||
// on change of any input, change appropriate value in model, but only if requested
|
||||
if (!scope.useCustomDataBinding) {
|
||||
element.on('change', 'input,select', function () {
|
||||
setFormValueFromInput(this);
|
||||
});
|
||||
}
|
||||
|
||||
// on submit call controller submit method
|
||||
element.on('click', 'input[type=submit]', function () {
|
||||
scope.ajaxForm.submitForm();
|
||||
});
|
||||
|
||||
// make sure child elements can access this directive's scope
|
||||
transclude(scope, function(clone, scope) {
|
||||
if (!scope.useCustomDataBinding) {
|
||||
var $inputs = clone.find('input,select').not('[type=submit]');
|
||||
|
||||
// initialize form data to input values (include <select>s
|
||||
$inputs.each(function () {
|
||||
setFormValueFromInput(this, true);
|
||||
});
|
||||
}
|
||||
|
||||
element.append(clone);
|
||||
});
|
||||
|
||||
function setFormValueFromInput(inputElement, skipScopeApply) {
|
||||
var $ = angular.element,
|
||||
name = $(inputElement).attr('name'),
|
||||
val;
|
||||
|
||||
if ($(inputElement).attr('type') == 'checkbox') {
|
||||
val = $(inputElement).is(':checked');
|
||||
} else {
|
||||
val = $(inputElement).val();
|
||||
}
|
||||
|
||||
scope.ajaxForm.data[name] = val;
|
||||
|
||||
if (!skipScopeApply) {
|
||||
setTimeout(function () {
|
||||
scope.$apply();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,2 @@
|
||||
<div class="alert alert-{{severity}}" ng-transclude>
|
||||
</div>
|
@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-alert>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikAlert', piwikAlert);
|
||||
|
||||
piwikAlert.$inject = ['piwik'];
|
||||
|
||||
function piwikAlert(piwik){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
transclude: true,
|
||||
scope: {severity: '@piwikAlert'},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/alert/alert.directive.html?cb=' + piwik.cacheBuster
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,124 @@
|
||||
.alert-icon-center-vertically(@font-size) {
|
||||
@half-height: @font-size / 2;
|
||||
// IE8 doesn't support calc(): it's OK, the icon will just be aligned to the top
|
||||
top: calc(~'50% - @{half-height}');
|
||||
// phantomjs only supports -webkit-calc()
|
||||
top: -webkit-calc(~'50% - @{half-height}');
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 20px 20px 20px 60px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
&:before {
|
||||
font-family: "matomo";
|
||||
content: "\e625";
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
line-height: 100%; // line-height = font-size
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
body #content .alert-success p {
|
||||
color: @color-green-piwik;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
color: @color-green-piwik;
|
||||
border-color: @color-green-piwik;
|
||||
&:before {
|
||||
content: "\e63d";
|
||||
color: @color-green-piwik;
|
||||
}
|
||||
p {
|
||||
color: @color-green-piwik;
|
||||
}
|
||||
a {
|
||||
color: @color-green-piwik;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body #content .alert-info p {
|
||||
color: #838383;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
color: #838383;
|
||||
background-color: #F5F5F5;
|
||||
font-size: 14px;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
p {
|
||||
color: #838383;
|
||||
}
|
||||
&:before {
|
||||
color: #afafaf;
|
||||
font-size: 20px;
|
||||
}
|
||||
a {
|
||||
color: #838383;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body #content .alert-warning p {
|
||||
color: #fbf7f1;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
color: #f57c00;
|
||||
border-color: #f57c00;
|
||||
&:before {
|
||||
content: "\e621";
|
||||
color: #f57c00;
|
||||
}
|
||||
p {
|
||||
color: #f57c00;
|
||||
}
|
||||
a {
|
||||
color: #f57c00;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body #content .alert-danger p {
|
||||
color: @color-red-piwik;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
color: @color-red-piwik;
|
||||
border-color: @color-red-piwik;
|
||||
&:before {
|
||||
content: "\e616";
|
||||
color: @color-red-piwik;
|
||||
}
|
||||
p {
|
||||
color: @color-red-piwik;
|
||||
}
|
||||
a {
|
||||
color: @color-red-piwik;
|
||||
text-decoration: underline;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
115
msd2/tracking/piwik/plugins/CoreHome/angularjs/anchorLinkFix.js
vendored
Normal file
115
msd2/tracking/piwik/plugins/CoreHome/angularjs/anchorLinkFix.js
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* See https://github.com/piwik/piwik/issues/4795 "linking to #hash tag does not work after merging AngularJS"
|
||||
*/
|
||||
(function () {
|
||||
|
||||
function scrollToAnchorNode($node)
|
||||
{
|
||||
$.scrollTo($node, 20);
|
||||
}
|
||||
|
||||
function preventDefaultIfEventExists(event)
|
||||
{
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToAnchorIfPossible(hash, event)
|
||||
{
|
||||
if (!hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (-1 !== hash.indexOf('&')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var $node = $('#' + hash);
|
||||
} catch (err) {
|
||||
// on jquery syntax error, ignore so nothing is logged to the console
|
||||
return;
|
||||
}
|
||||
|
||||
if ($node && $node.length) {
|
||||
scrollToAnchorNode($node);
|
||||
preventDefaultIfEventExists(event);
|
||||
return;
|
||||
}
|
||||
|
||||
$node = $('a[name='+ hash + ']');
|
||||
|
||||
if ($node && $node.length) {
|
||||
scrollToAnchorNode($node);
|
||||
preventDefaultIfEventExists(event);
|
||||
}
|
||||
}
|
||||
|
||||
function isLinkWithinSamePage(location, newUrl)
|
||||
{
|
||||
if (location && location.origin && -1 === newUrl.indexOf(location.origin)) {
|
||||
// link to different domain
|
||||
return false;
|
||||
}
|
||||
|
||||
if (location && location.pathname && -1 === newUrl.indexOf(location.pathname)) {
|
||||
// link to different path
|
||||
return false;
|
||||
}
|
||||
|
||||
if (location && location.search && -1 === newUrl.indexOf(location.search)) {
|
||||
// link with different search
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleScrollToAnchorIfPresentOnPageLoad()
|
||||
{
|
||||
if (location.hash.substr(0, 2) == '#/') {
|
||||
var hash = location.hash.substr(2);
|
||||
scrollToAnchorIfPossible(hash, null);
|
||||
}
|
||||
}
|
||||
|
||||
function handleScrollToAnchorAfterPageLoad()
|
||||
{
|
||||
angular.module('piwikApp').run(['$rootScope', function ($rootScope) {
|
||||
|
||||
$rootScope.$on('$locationChangeStart', onLocationChangeStart);
|
||||
|
||||
function onLocationChangeStart (event, newUrl, oldUrl, $location) {
|
||||
|
||||
if (!newUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hashPos = newUrl.indexOf('#/');
|
||||
if (-1 === hashPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLinkWithinSamePage(this.location, newUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hash = newUrl.substr(hashPos + 2);
|
||||
|
||||
scrollToAnchorIfPossible(hash, event);
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
handleScrollToAnchorAfterPageLoad();
|
||||
$(handleScrollToAnchorIfPresentOnPageLoad);
|
||||
|
||||
})();
|
63
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/attributes.js
vendored
Normal file
63
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/attributes.js
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* If the given text or resolved expression matches any text within the element, the matching text will be wrapped
|
||||
* with a class.
|
||||
*
|
||||
* Example:
|
||||
* <div piwik-autocomplete-matched="'text'">My text</div> ==> <div>My <span class="autocompleteMatched">text</span></div>
|
||||
*
|
||||
* <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
|
||||
* <input type="text" ng-model="searchTerm">
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikAttributes', piwikAttributes);
|
||||
|
||||
piwikAttributes.$inject = ['$sanitize'];
|
||||
|
||||
function piwikAttributes(piwik, $sanitize) {
|
||||
|
||||
return {
|
||||
link: function (scope, element, attrs) {
|
||||
if (!attrs.piwikAttributes || !angular.isString(attrs.piwikAttributes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
function applyAttributes(attributes)
|
||||
{
|
||||
if (angular.isObject(attributes)) {
|
||||
angular.forEach(attributes, function (value, key) {
|
||||
if (angular.isObject(value)) {
|
||||
value = JSON.stringify(value);
|
||||
}
|
||||
|
||||
// replace line breaks in placeholder with big amount of spaces for safari,
|
||||
// as line breaks are not support there
|
||||
if (key === 'placeholder' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
|
||||
value = value.replace(/(?:\r\n|\r|\n)/g, (new Array(200)).join(' '));
|
||||
}
|
||||
|
||||
if (key === 'disabled') {
|
||||
element.prop(key, value);
|
||||
} else {
|
||||
element.attr(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
applyAttributes(JSON.parse(attrs.piwikAttributes));
|
||||
|
||||
attrs.$observe('piwikAttributes', function (newVal) {
|
||||
applyAttributes(JSON.parse(newVal));
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
53
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js
vendored
Normal file
53
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/autocomplete-matched.js
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* If the given text or resolved expression matches any text within the element, the matching text will be wrapped
|
||||
* with a class.
|
||||
*
|
||||
* Example:
|
||||
* <div piwik-autocomplete-matched="'text'">My text</div> ==> <div>My <span class="autocompleteMatched">text</span></div>
|
||||
*
|
||||
* <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
|
||||
* <input type="text" ng-model="searchTerm">
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', piwikAutocompleteMatched);
|
||||
|
||||
piwikAutocompleteMatched.$inject = ['piwik', '$sanitize'];
|
||||
|
||||
function piwikAutocompleteMatched(piwik, $sanitize) {
|
||||
|
||||
return {
|
||||
priority: 10, // makes sure to render after other directives, otherwise the content might be overwritten again see https://github.com/piwik/piwik/pull/8467
|
||||
link: function (scope, element, attrs) {
|
||||
var searchTerm;
|
||||
|
||||
scope.$watch(attrs.piwikAutocompleteMatched, function (value) {
|
||||
searchTerm = value;
|
||||
updateText();
|
||||
});
|
||||
|
||||
function updateText() {
|
||||
if (!searchTerm || !element) {
|
||||
return;
|
||||
}
|
||||
|
||||
var content = piwik.helper.htmlEntities(element.text());
|
||||
var startTerm = content.toLowerCase().indexOf(searchTerm.toLowerCase());
|
||||
|
||||
if (-1 !== startTerm) {
|
||||
var word = content.substr(startTerm, searchTerm.length);
|
||||
var escapedword = $sanitize(piwik.helper.htmlEntities(word));
|
||||
content = content.replace(word, '<span class="autocompleteMatched">' + escapedword + '</span>');
|
||||
element.html(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
50
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/dialog.js
vendored
Normal file
50
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/dialog.js
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-dialog="showDialog">...</div>
|
||||
* Will show dialog once showDialog evaluates to true.
|
||||
*
|
||||
* <div piwik-dialog="showDialog" yes="executeMyFunction();">
|
||||
* ... <input type="button" role="yes" value="button">
|
||||
* </div>
|
||||
* Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikDialog', piwikDialog);
|
||||
|
||||
piwikDialog.$inject = ['piwik', '$parse'];
|
||||
|
||||
function piwikDialog(piwik, $parse) {
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
element.css('display', 'none');
|
||||
|
||||
scope.$watch(attrs.piwikDialog, function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
piwik.helper.modalConfirm(element, {yes: function() {
|
||||
if (attrs.yes) {
|
||||
scope.$eval(attrs.yes);
|
||||
setTimeout(function () { scope.$apply(); }, 0);
|
||||
}
|
||||
}}, {
|
||||
complete: function () {
|
||||
setTimeout(function () {
|
||||
scope.$apply($parse(attrs.piwikDialog).assign(scope, false));
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive', []);
|
||||
})();
|
37
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/dropdown-button.js
vendored
Normal file
37
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/dropdown-button.js
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-dropdown-button>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('dropdownButton', piwikDropdownButton);
|
||||
|
||||
piwikDropdownButton.$inject = ['piwik'];
|
||||
|
||||
function piwikDropdownButton(piwik){
|
||||
|
||||
return {
|
||||
restrict: 'C',
|
||||
compile: function (element, attrs) {
|
||||
|
||||
$(element).dropdown({
|
||||
inDuration: 300,
|
||||
outDuration: 225,
|
||||
constrain_width: false, // Does not change width of dropdown to that of the activator
|
||||
// hover: true, // Activate on hover
|
||||
belowOrigin: true // Displays dropdown below the button
|
||||
});
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
88
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/field-condition.js
vendored
Normal file
88
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/field-condition.js
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-form-condition>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('fieldCondition', piwikFieldCondition);
|
||||
|
||||
piwikFieldCondition.$inject = ['piwik', '$timeout'];
|
||||
|
||||
function piwikFieldCondition(piwik, $timeout){
|
||||
|
||||
function evaluate(scope, condition, element)
|
||||
{
|
||||
if (scope.$eval(condition, scope.allValues)) {
|
||||
element.show();
|
||||
} else {
|
||||
element.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function getValueFromElement(element)
|
||||
{
|
||||
if (element.attr('type') === 'checkbox') {
|
||||
return element.is(':checked');
|
||||
} else if (element.attr('type') === 'radio') {
|
||||
return $('.form-group [name=' + element.attr('name') + ']:checked').val();
|
||||
} else if (element.prop('tagName').toLowerCase() === 'select') {
|
||||
var name = element.val();
|
||||
if (name.indexOf('string:') === 0) {
|
||||
return name.substr('string:'.length);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
return element.val();
|
||||
}
|
||||
|
||||
function evaluateConditionalExpression(scope, condition, element)
|
||||
{
|
||||
var fieldParts = condition.replace('!', '');
|
||||
fieldParts = fieldParts.split(' ');
|
||||
var fieldNames = [];
|
||||
fieldParts.forEach(function (name) {
|
||||
name = $.trim(name);
|
||||
if (name && name.length > 3) {
|
||||
fieldNames.push(name);
|
||||
}
|
||||
});
|
||||
|
||||
scope.allValues = {};
|
||||
angular.forEach(fieldNames, function (name) {
|
||||
var actualField = $('.form-group [name=' + name + ']').first();
|
||||
if (actualField.length) {
|
||||
scope.allValues[name] = getValueFromElement(actualField);
|
||||
actualField.on('change', function () {
|
||||
scope.allValues[name] = getValueFromElement($(this));
|
||||
evaluate(scope, condition, element);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
evaluate(scope, condition, element);
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
priority: 10, // makes sure to render after other directives, otherwise the content might be overwritten again see https://github.com/piwik/piwik/pull/8467
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
var condition = attrs.fieldCondition;
|
||||
if (condition) {
|
||||
$timeout(function (){
|
||||
evaluateConditionalExpression(scope, condition, element);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
})();
|
76
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js
vendored
Normal file
76
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/focus-anywhere-but-here.js
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* The given expression will be executed when the user presses either escape or presses something outside
|
||||
* of this element
|
||||
*
|
||||
* Example:
|
||||
* <div piwik-focus-anywhere-but-here="closeDialog()">my dialog</div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', piwikFocusAnywhereButHere);
|
||||
|
||||
piwikFocusAnywhereButHere.$inject = ['$document'];
|
||||
|
||||
function piwikFocusAnywhereButHere($document){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
|
||||
var isMouseDown = false;
|
||||
var hasScrolled = false;
|
||||
|
||||
function onClickOutsideElement (event) {
|
||||
var hadUsedScrollbar = isMouseDown && hasScrolled;
|
||||
isMouseDown = false;
|
||||
hasScrolled = false;
|
||||
|
||||
if (hadUsedScrollbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.has(event.target).length === 0) {
|
||||
setTimeout(function () {
|
||||
scope.$apply(attr.piwikFocusAnywhereButHere);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function onScroll (event) {
|
||||
hasScrolled = true;
|
||||
}
|
||||
|
||||
function onMouseDown (event) {
|
||||
isMouseDown = true;
|
||||
hasScrolled = false;
|
||||
}
|
||||
|
||||
function onEscapeHandler (event) {
|
||||
if (event.which === 27) {
|
||||
setTimeout(function () {
|
||||
isMouseDown = false;
|
||||
hasScrolled = false;
|
||||
scope.$apply(attr.piwikFocusAnywhereButHere);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
$document.on('keyup', onEscapeHandler);
|
||||
$document.on('mousedown', onMouseDown);
|
||||
$document.on('mouseup', onClickOutsideElement);
|
||||
$document.on('scroll', onScroll);
|
||||
scope.$on('$destroy', function() {
|
||||
$document.off('keyup', onEscapeHandler);
|
||||
$document.off('mousedown', onMouseDown);
|
||||
$document.off('mouseup', onClickOutsideElement);
|
||||
$document.off('scroll', onScroll);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
33
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/focusif.js
vendored
Normal file
33
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/focusif.js
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* If the given expression evaluates to true the element will be focussed
|
||||
*
|
||||
* Example:
|
||||
* <input type="text" piwik-focus-if="view.editName">
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikFocusIf', piwikFocusIf);
|
||||
|
||||
piwikFocusIf.$inject = ['$timeout'];
|
||||
|
||||
function piwikFocusIf($timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch(attrs.piwikFocusIf, function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
$timeout(function () {
|
||||
element[0].focus();
|
||||
}, 5);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
25
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/ignore-click.js
vendored
Normal file
25
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/ignore-click.js
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prevents the default behavior of the click. For instance useful if a link should only work in case the user
|
||||
* does a "right click open in new window".
|
||||
*
|
||||
* Example
|
||||
* <a piwik-ignore-click ng-click="doSomething()" href="/">my link</a>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikIgnoreClick', piwikIgnoreClick);
|
||||
|
||||
function piwikIgnoreClick() {
|
||||
return function(scope, element, attrs) {
|
||||
$(element).click(function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
31
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/onenter.js
vendored
Normal file
31
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/onenter.js
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allows you to define any expression to be executed in case the user presses enter
|
||||
*
|
||||
* Example
|
||||
* <div piwik-onenter="save()">
|
||||
* <div piwik-onenter="showList=false">
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikOnenter', piwikOnenter);
|
||||
|
||||
function piwikOnenter() {
|
||||
return function(scope, element, attrs) {
|
||||
element.bind("keydown keypress", function(event) {
|
||||
if(event.which === 13) {
|
||||
scope.$apply(function(){
|
||||
scope.$eval(attrs.piwikOnenter, {'event': event});
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
69
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/select-on-focus.js
vendored
Normal file
69
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/select-on-focus.js
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* On focus (click, tab) selects the text within the current element
|
||||
*
|
||||
* Example:
|
||||
* <div piwik-select-on-focus>my dialog</div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikSelectOnFocus', piwikSelectOnFocus);
|
||||
|
||||
function piwikSelectOnFocus(){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
|
||||
var focusedElement = null;
|
||||
|
||||
var tagName = (element.prop('tagName') + '').toLowerCase();
|
||||
var elementSupportsSelect = tagName === 'textarea';
|
||||
|
||||
function onFocusHandler(event) {
|
||||
if (focusedElement !== this) {
|
||||
focusedElement = this;
|
||||
angular.element(this).select();
|
||||
}
|
||||
}
|
||||
|
||||
function onClickHandler(event) {
|
||||
// .select() + focus and blur seems to not work on pre elements
|
||||
var range = document.createRange();
|
||||
range.selectNode(this);
|
||||
var selection = window.getSelection();
|
||||
if (selection && selection.rangeCount > 0) {
|
||||
selection.removeAllRanges();
|
||||
}
|
||||
if (selection) {
|
||||
selection.addRange(range);
|
||||
}
|
||||
}
|
||||
|
||||
function onBlurHandler(event) {
|
||||
focusedElement = null;
|
||||
}
|
||||
|
||||
if (elementSupportsSelect) {
|
||||
element.on('focus', onFocusHandler);
|
||||
element.on('blur', onBlurHandler);
|
||||
} else {
|
||||
element.on('click', onClickHandler);
|
||||
}
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
if (elementSupportsSelect) {
|
||||
element.off('focus', onFocusHandler);
|
||||
element.off('blur', onBlurHandler);
|
||||
} else {
|
||||
element.off('click', onClickHandler);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
59
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/show-sensitive-data.js
vendored
Normal file
59
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/show-sensitive-data.js
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handles visibility of sensitive data. By default data will be shown replaced with stars (*)
|
||||
* On click on the element the full data will be shown
|
||||
*
|
||||
* Configuration attributes:
|
||||
* data-show-characters number of characters to show in clear text (defaults to 6)
|
||||
* data-click-element-selector selector for element that will show the full data on click (defaults to element)
|
||||
*
|
||||
* Example:
|
||||
* <div piwik-show-sensitive-date="some text"></div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikShowSensitiveData', piwikShowSensitiveData);
|
||||
|
||||
function piwikShowSensitiveData(){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attr) {
|
||||
|
||||
var sensitiveData = attr.piwikShowSensitiveData || attr.text();
|
||||
var showCharacters = attr.showCharacters || 6;
|
||||
var clickElement = attr.clickElementSelector || element;
|
||||
|
||||
var protectedData = '';
|
||||
if (showCharacters > 0) {
|
||||
protectedData += sensitiveData.substr(0, showCharacters);
|
||||
}
|
||||
protectedData += sensitiveData.substr(showCharacters).replace(/./g, '*');
|
||||
element.html(protectedData);
|
||||
|
||||
function onClickHandler(event) {
|
||||
element.html(sensitiveData);
|
||||
$(clickElement).css({
|
||||
cursor: ''
|
||||
});
|
||||
$(clickElement).tooltip("destroy");
|
||||
}
|
||||
|
||||
$(clickElement).tooltip({
|
||||
content: _pk_translate('CoreHome_ClickToSeeFullInformation'),
|
||||
items: '*',
|
||||
track: true
|
||||
});
|
||||
|
||||
$(clickElement).one('click', onClickHandler);
|
||||
$(clickElement).css({
|
||||
cursor: 'pointer'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
49
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/side-nav.js
vendored
Normal file
49
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/side-nav.js
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Will activate the materialize side nav feature once rendered. We use this directive as it makes sure
|
||||
* the actual left menu is rendered at the time we init the side nav.
|
||||
*
|
||||
* Has to be set on a collaapsible element
|
||||
*
|
||||
* Example:
|
||||
* <div class="collapsible" piwik-side-nav="nav .activateLeftMenu">...</div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikSideNav', piwikSideNav);
|
||||
|
||||
piwikSideNav.$inject = ['$timeout'];
|
||||
var initialized = false;
|
||||
|
||||
function piwikSideNav($timeout){
|
||||
return {
|
||||
restrict: 'A',
|
||||
priority: 10,
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
|
||||
if (attr.piwikSideNav) {
|
||||
$timeout(function () {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
|
||||
var sideNavActivator = $(attr.piwikSideNav).show();
|
||||
|
||||
sideNavActivator.sideNav({
|
||||
closeOnClick: true
|
||||
});
|
||||
}
|
||||
|
||||
if (element.hasClass('collapsible')) {
|
||||
element.collapsible();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
28
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/string-to-number.js
vendored
Normal file
28
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/string-to-number.js
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts ngModel value to a number
|
||||
*
|
||||
* Example:
|
||||
* <input type="number" string-to-number>...</div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('stringToNumber', function() {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
ngModel.$parsers.push(function(value) {
|
||||
return '' + value;
|
||||
});
|
||||
ngModel.$formatters.push(function(value) {
|
||||
return parseFloat(value);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
36
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/translate.js
vendored
Normal file
36
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/directives/translate.js
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Directive for easy & safe complex internationalization. This directive allows
|
||||
* users to embed the sprintf arguments used in internationalization inside an HTML
|
||||
* element. Since the HTML will eventually be sanitized by AngularJS, HTML can be used
|
||||
* within the sprintf args. Using the filter, this is not possible w/o manually sanitizing
|
||||
* and creating trusted HTML, which is not as safe.
|
||||
*
|
||||
* Note: nesting this directive is not supported.
|
||||
*
|
||||
* Usage:
|
||||
* <span piwik-translate="Plugin_TranslationToken">
|
||||
* first arg::<strong>second arg</strong>::{{ unsafeDataThatWillBeSanitized }}
|
||||
* </span>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikTranslate', piwikTranslate);
|
||||
|
||||
function piwikTranslate() {
|
||||
return {
|
||||
priority: 1,
|
||||
restrict: 'A',
|
||||
compile: function(element, attrs) {
|
||||
var parts = element.html().split('::'),
|
||||
translated = _pk_translate(attrs.piwikTranslate, parts);
|
||||
element.html(translated);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
16
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/escape.js
vendored
Normal file
16
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/escape.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('escape', escape);
|
||||
|
||||
function escape() {
|
||||
|
||||
return function(value) {
|
||||
return piwikHelper.escape(piwikHelper.htmlEntities(value));
|
||||
};
|
||||
}
|
||||
})();
|
49
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/evolution.js
vendored
Normal file
49
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/evolution.js
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('evolution', evolutionFilter);
|
||||
|
||||
function evolutionFilter() {
|
||||
|
||||
function calculateEvolution(currentValue, pastValue)
|
||||
{
|
||||
pastValue = parseInt(pastValue, 10);
|
||||
currentValue = parseInt(currentValue, 10) - pastValue;
|
||||
|
||||
var evolution;
|
||||
|
||||
if (currentValue === 0 || isNaN(currentValue)) {
|
||||
evolution = 0;
|
||||
} else if (pastValue === 0 || isNaN(pastValue)) {
|
||||
evolution = 100;
|
||||
} else {
|
||||
evolution = (currentValue / pastValue) * 100;
|
||||
}
|
||||
|
||||
return evolution;
|
||||
}
|
||||
|
||||
function formatEvolution(evolution)
|
||||
{
|
||||
evolution = Math.round(evolution);
|
||||
|
||||
if (evolution > 0) {
|
||||
evolution = '+' + evolution;
|
||||
}
|
||||
|
||||
evolution += '%';
|
||||
|
||||
return evolution;
|
||||
}
|
||||
|
||||
return function(currentValue, pastValue) {
|
||||
var evolution = calculateEvolution(currentValue, pastValue);
|
||||
|
||||
return formatEvolution(evolution);
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter', []);
|
||||
})();
|
26
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/htmldecode.js
vendored
Normal file
26
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/htmldecode.js
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('htmldecode', htmldecode);
|
||||
|
||||
htmldecode.$inject = ['piwik'];
|
||||
|
||||
/**
|
||||
* Be aware that this filter can cause XSS so only use it when you're sure it is safe.
|
||||
* Eg it should be safe when it is afterwards escaped by angular sanitize again.
|
||||
*/
|
||||
function htmldecode(piwik) {
|
||||
|
||||
return function(text) {
|
||||
if (text && text.length) {
|
||||
return piwik.helper.htmlDecode(text);
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
}
|
||||
})();
|
21
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/length.js
vendored
Normal file
21
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/length.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('length', length);
|
||||
|
||||
function length() {
|
||||
|
||||
return function(stringOrArray) {
|
||||
if (stringOrArray && stringOrArray.length) {
|
||||
return stringOrArray.length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
16
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/pretty-url.js
vendored
Normal file
16
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/pretty-url.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('prettyUrl', prettyUrl);
|
||||
|
||||
function prettyUrl() {
|
||||
return function(input) {
|
||||
return input.trim().replace('http://', '');
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
16
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/startfrom.js
vendored
Normal file
16
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/startfrom.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('startFrom', startFrom);
|
||||
|
||||
function startFrom() {
|
||||
return function(input, start) {
|
||||
start = +start; //parse to int
|
||||
return input.slice(start);
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
describe('startFromFilter', function() {
|
||||
var startFrom;
|
||||
|
||||
beforeEach(module('piwikApp.filter'));
|
||||
beforeEach(inject(function($injector) {
|
||||
var $filter = $injector.get('$filter');
|
||||
startFrom = $filter('startFrom');
|
||||
}));
|
||||
|
||||
describe('#startFrom()', function() {
|
||||
|
||||
it('should return all entries if index is zero', function() {
|
||||
|
||||
var result = startFrom([1,2,3], 0);
|
||||
|
||||
expect(result).to.eql([1,2,3]);
|
||||
});
|
||||
|
||||
it('should return only partial entries if filter is higher than zero', function() {
|
||||
|
||||
var result = startFrom([1,2,3], 2);
|
||||
|
||||
expect(result).to.eql([3]);
|
||||
});
|
||||
|
||||
it('should return no entries if start is higher than input length', function() {
|
||||
|
||||
var result = startFrom([1,2,3], 11);
|
||||
|
||||
expect(result).to.eql([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
22
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/translate.js
vendored
Normal file
22
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/translate.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('translate', translate);
|
||||
|
||||
function translate() {
|
||||
|
||||
return function(key, value1, value2, value3) {
|
||||
var values = [];
|
||||
if (arguments && arguments.length > 1) {
|
||||
for (var index = 1; index < arguments.length; index++) {
|
||||
values.push(arguments[index]);
|
||||
}
|
||||
}
|
||||
return _pk_translate(key, values);
|
||||
};
|
||||
}
|
||||
})();
|
20
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/trim.js
vendored
Normal file
20
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/trim.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('trim', trim);
|
||||
|
||||
function trim() {
|
||||
|
||||
return function(string) {
|
||||
if (string) {
|
||||
return $.trim('' + string);
|
||||
}
|
||||
|
||||
return string;
|
||||
};
|
||||
}
|
||||
})();
|
21
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/ucfirst.js
vendored
Normal file
21
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/filters/ucfirst.js
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.filter').filter('ucfirst', ucfirst);
|
||||
|
||||
function ucfirst() {
|
||||
|
||||
return function(value) {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
var firstLetter = (value + '').charAt(0).toUpperCase();
|
||||
return firstLetter + value.substr(1);
|
||||
};
|
||||
}
|
||||
})();
|
14
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/global-ajax-queue.js
vendored
Normal file
14
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/global-ajax-queue.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.service').service('globalAjaxQueue', ajaxQueue);
|
||||
|
||||
function ajaxQueue() {
|
||||
|
||||
return globalAjaxQueue;
|
||||
}
|
||||
})();
|
358
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/periods.js
vendored
Normal file
358
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/periods.js
vendored
Normal file
@ -0,0 +1,358 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Piwik period management service for the frontend.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* var DayPeriod = piwikPeriods.get('day');
|
||||
* var day = new DayPeriod(new Date());
|
||||
*
|
||||
* or
|
||||
*
|
||||
* var day = piwikPeriods.parse('day', '2013-04-05');
|
||||
*
|
||||
* Adding custom periods:
|
||||
*
|
||||
* To add your own period to the frontend, create a period class for it
|
||||
* w/ the following methods:
|
||||
*
|
||||
* - **getPrettyString()**: returns a human readable display string for the period.
|
||||
* - **getDateRange()**: returns an array w/ two elements, the first being the start
|
||||
* Date of the period, the second being the end Date. The dates
|
||||
* must be Date objects, not strings, and are inclusive.
|
||||
* - (_static_) **parse(strDate)**: creates a new instance of this period from the
|
||||
* value of the 'date' query parameter.
|
||||
* - (_static_) **getDisplayText**: returns translated text for the period, eg, 'month',
|
||||
* 'week', etc.
|
||||
*
|
||||
* Then call piwik.addCustomPeriod w/ your period class:
|
||||
*
|
||||
* piwik.addCustomPeriod('mycustomperiod', MyCustomPeriod);
|
||||
*
|
||||
* NOTE: currently only single date periods like day, week, month year can
|
||||
* be extended. Other types of periods that require a special UI to
|
||||
* view/edit aren't, since there is currently no way to use a
|
||||
* custom UI for a custom period.
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.service').factory('piwikPeriods', piwikPeriods);
|
||||
|
||||
var periods = {}, periodOrder = [];
|
||||
|
||||
piwik.addCustomPeriod = addCustomPeriod;
|
||||
|
||||
// day period
|
||||
function DayPeriod(date) {
|
||||
this.dateInPeriod = date;
|
||||
}
|
||||
|
||||
DayPeriod.parse = singleDatePeriodFactory(DayPeriod);
|
||||
DayPeriod.getDisplayText = function () {
|
||||
return _pk_translate('Intl_PeriodDay');
|
||||
};
|
||||
|
||||
DayPeriod.prototype = {
|
||||
getPrettyString: function () {
|
||||
return $.datepicker.formatDate('yy-mm-dd', this.dateInPeriod);
|
||||
},
|
||||
|
||||
getDateRange: function () {
|
||||
return [this.dateInPeriod, this.dateInPeriod];
|
||||
}
|
||||
};
|
||||
|
||||
addCustomPeriod('day', DayPeriod);
|
||||
|
||||
// week period
|
||||
function WeekPeriod(date) {
|
||||
this.dateInPeriod = date;
|
||||
}
|
||||
|
||||
WeekPeriod.parse = singleDatePeriodFactory(WeekPeriod);
|
||||
|
||||
WeekPeriod.getDisplayText = function () {
|
||||
return _pk_translate('Intl_PeriodWeek');
|
||||
};
|
||||
|
||||
WeekPeriod.prototype = {
|
||||
getPrettyString: function () {
|
||||
var weekDates = this.getDateRange(this.dateInPeriod);
|
||||
var startWeek = $.datepicker.formatDate('yy-mm-dd', weekDates[0]);
|
||||
var endWeek = $.datepicker.formatDate('yy-mm-dd', weekDates[1]);
|
||||
|
||||
return _pk_translate('General_DateRangeFromTo', [startWeek, endWeek]);
|
||||
},
|
||||
|
||||
getDateRange: function () {
|
||||
var daysToMonday = (this.dateInPeriod.getDay() + 6) % 7;
|
||||
|
||||
var startWeek = new Date(this.dateInPeriod.getTime());
|
||||
startWeek.setDate(this.dateInPeriod.getDate() - daysToMonday);
|
||||
|
||||
var endWeek = new Date(startWeek.getTime());
|
||||
endWeek.setDate(startWeek.getDate() + 6);
|
||||
|
||||
return [startWeek, endWeek];
|
||||
}
|
||||
};
|
||||
|
||||
addCustomPeriod('week', WeekPeriod);
|
||||
|
||||
// month period
|
||||
function MonthPeriod(date) {
|
||||
this.dateInPeriod = date;
|
||||
}
|
||||
|
||||
MonthPeriod.parse = singleDatePeriodFactory(MonthPeriod);
|
||||
|
||||
MonthPeriod.getDisplayText = function () {
|
||||
return _pk_translate('Intl_PeriodMonth');
|
||||
};
|
||||
|
||||
MonthPeriod.prototype = {
|
||||
getPrettyString: function () {
|
||||
return _pk_translate('Intl_Month_Long_StandAlone_' + (this.dateInPeriod.getMonth() + 1)) + ' ' +
|
||||
this.dateInPeriod.getFullYear();
|
||||
},
|
||||
|
||||
getDateRange: function () {
|
||||
var startMonth = new Date(this.dateInPeriod.getTime());
|
||||
startMonth.setDate(1);
|
||||
|
||||
var endMonth = new Date(this.dateInPeriod.getTime());
|
||||
endMonth.setMonth(endMonth.getMonth() + 1);
|
||||
endMonth.setDate(0);
|
||||
|
||||
return [startMonth, endMonth];
|
||||
}
|
||||
};
|
||||
|
||||
addCustomPeriod('month', MonthPeriod);
|
||||
|
||||
// year period
|
||||
function YearPeriod(date) {
|
||||
this.dateInPeriod = date;
|
||||
}
|
||||
|
||||
YearPeriod.parse = singleDatePeriodFactory(YearPeriod);
|
||||
|
||||
YearPeriod.getDisplayText = function () {
|
||||
return _pk_translate('Intl_PeriodYear');
|
||||
};
|
||||
|
||||
YearPeriod.prototype = {
|
||||
getPrettyString: function () {
|
||||
return this.dateInPeriod.getFullYear();
|
||||
},
|
||||
|
||||
getDateRange: function () {
|
||||
var startYear = new Date(this.dateInPeriod.getTime());
|
||||
startYear.setMonth(0);
|
||||
startYear.setDate(1);
|
||||
|
||||
var endYear = new Date(this.dateInPeriod.getTime());
|
||||
endYear.setMonth(12);
|
||||
endYear.setDate(0);
|
||||
|
||||
return [startYear, endYear];
|
||||
}
|
||||
};
|
||||
|
||||
addCustomPeriod('year', YearPeriod);
|
||||
|
||||
// range period
|
||||
function RangePeriod(startDate, endDate, childPeriodType) {
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
this.childPeriodType = childPeriodType;
|
||||
}
|
||||
|
||||
RangePeriod.parse = function parseRangePeriod(strDate, childPeriodType) {
|
||||
childPeriodType = childPeriodType || 'day';
|
||||
|
||||
if (/^previous/.test(strDate)) {
|
||||
var endDate = RangePeriod.getLastNRange(childPeriodType, 2).startDate;
|
||||
return RangePeriod.getLastNRange(childPeriodType, strDate.substring(8), endDate);
|
||||
} else if (/^last/.test(strDate)) {
|
||||
return RangePeriod.getLastNRange(childPeriodType, strDate.substring(4));
|
||||
} else {
|
||||
var parts = strDate.split(',');
|
||||
return new RangePeriod(parseDate(parts[0]), parseDate(parts[1]), childPeriodType)
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a range representing the last N childPeriodType periods, including the current one.
|
||||
*
|
||||
* @param childPeriodType
|
||||
* @param strAmount
|
||||
* @param endDate
|
||||
* @returns {RangePeriod}
|
||||
*/
|
||||
RangePeriod.getLastNRange = function (childPeriodType, strAmount, endDate) {
|
||||
var nAmount = Math.max(parseInt(strAmount) - 1, 0);
|
||||
if (isNaN(nAmount)) {
|
||||
throw new Error('Invalid range date: ' + strDate);
|
||||
}
|
||||
|
||||
endDate = endDate ? parseDate(endDate) : getToday();
|
||||
|
||||
var 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.setMonth(startDate.getMonth() - nAmount);
|
||||
} else if (childPeriodType === 'year') {
|
||||
startDate.setFullYear(startDate.getFullYear() - nAmount);
|
||||
} else {
|
||||
throw new Error("Unknown period type '" + childPeriodType + "'.");
|
||||
}
|
||||
|
||||
if (childPeriodType !== 'day') {
|
||||
var startPeriod = periods[childPeriodType].parse(startDate);
|
||||
var endPeriod = periods[childPeriodType].parse(endDate);
|
||||
|
||||
startDate = startPeriod.getDateRange()[0];
|
||||
endDate = endPeriod.getDateRange()[1];
|
||||
}
|
||||
|
||||
var firstWebsiteDate = new Date(1991, 7, 6);
|
||||
if (startDate - firstWebsiteDate < 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 RangePeriod(startDate, endDate, childPeriodType);
|
||||
};
|
||||
|
||||
RangePeriod.getDisplayText = function () {
|
||||
return _pk_translate('General_DateRangeInPeriodList');
|
||||
};
|
||||
|
||||
RangePeriod.prototype = {
|
||||
getPrettyString: function () {
|
||||
var start = $.datepicker.formatDate('yy-mm-dd', this.startDate);
|
||||
var end = $.datepicker.formatDate('yy-mm-dd', this.endDate);
|
||||
return _pk_translate('General_DateRangeFromTo', [start, end]);
|
||||
},
|
||||
|
||||
getDateRange: function () {
|
||||
return [this.startDate, this.endDate];
|
||||
}
|
||||
};
|
||||
|
||||
addCustomPeriod('range', RangePeriod);
|
||||
|
||||
// piwikPeriods service
|
||||
function piwikPeriods() {
|
||||
return {
|
||||
getAllLabels: getAllLabels,
|
||||
isRecognizedPeriod: isRecognizedPeriod,
|
||||
get: get,
|
||||
parse: parse,
|
||||
parseDate: parseDate
|
||||
};
|
||||
|
||||
function getAllLabels() {
|
||||
return [].concat(periodOrder);
|
||||
}
|
||||
|
||||
function get(strPeriod) {
|
||||
var periodClass = periods[strPeriod];
|
||||
if (!periodClass) {
|
||||
throw new Error('Invalid period label: ' + strPeriod);
|
||||
}
|
||||
return periodClass;
|
||||
}
|
||||
|
||||
function parse(strPeriod, strDate) {
|
||||
return get(strPeriod).parse(strDate);
|
||||
}
|
||||
|
||||
function isRecognizedPeriod(strPeriod) {
|
||||
return !! periods[strPeriod];
|
||||
}
|
||||
}
|
||||
|
||||
function addCustomPeriod(name, periodClass) {
|
||||
if (periods[name]) {
|
||||
throw new Error('The "' + name + '" period already exists! It cannot be overridden.');
|
||||
}
|
||||
|
||||
periods[name] = periodClass;
|
||||
periodOrder.push(name);
|
||||
}
|
||||
|
||||
function singleDatePeriodFactory(periodClass) {
|
||||
return function (strDate) {
|
||||
return new periodClass(parseDate(strDate));
|
||||
};
|
||||
}
|
||||
|
||||
function parseDate(strDate) {
|
||||
if (strDate instanceof Date) {
|
||||
return strDate;
|
||||
}
|
||||
|
||||
if (strDate === 'today'
|
||||
|| strDate === 'now'
|
||||
) {
|
||||
return getToday();
|
||||
}
|
||||
|
||||
if (strDate === 'yesterday'
|
||||
// note: ignoring the 'same time' part since the frontend doesn't care about the time
|
||||
|| strDate === 'yesterdaySameTime'
|
||||
) {
|
||||
var yesterday = getToday();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return yesterday;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function getToday() {
|
||||
var date = new Date(Date.now());
|
||||
|
||||
// undo browser timezone
|
||||
date.setTime(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
|
||||
|
||||
// apply piwik site timezone (if it exists)
|
||||
date.setHours(date.getHours() + ((piwik.timezoneOffset || 0) / 3600));
|
||||
|
||||
// get rid of hours/minutes/seconds/etc.
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
date.setMilliseconds(0);
|
||||
return date;
|
||||
}
|
||||
})();
|
342
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/piwik-api.js
vendored
Normal file
342
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/piwik-api.js
vendored
Normal file
@ -0,0 +1,342 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
// see https://github.com/piwik/piwik/issues/5094 used to detect an ad blocker
|
||||
var hasBlockedContent = false;
|
||||
|
||||
(function () {
|
||||
angular.module('piwikApp.service').factory('piwikApi', piwikApiService);
|
||||
|
||||
piwikApiService.$inject = ['$http', '$q', '$rootScope', 'piwik', '$window'];
|
||||
|
||||
function piwikApiService ($http, $q, $rootScope, piwik, $window) {
|
||||
|
||||
var url = 'index.php';
|
||||
var format = 'json';
|
||||
var getParams = {};
|
||||
var postParams = {};
|
||||
var allRequests = [];
|
||||
|
||||
/**
|
||||
* Adds params to the request.
|
||||
* If params are given more then once, the latest given value is used for the request
|
||||
*
|
||||
* @param {object} params
|
||||
* @return {void}
|
||||
*/
|
||||
function addParams (params) {
|
||||
if (typeof params == 'string') {
|
||||
params = piwik.broadcast.getValuesFromUrl(params);
|
||||
}
|
||||
|
||||
for (var key in params) {
|
||||
getParams[key] = params[key];
|
||||
}
|
||||
}
|
||||
|
||||
function withTokenInUrl()
|
||||
{
|
||||
postParams['token_auth'] = piwik.token_auth;
|
||||
}
|
||||
|
||||
function isRequestToApiMethod() {
|
||||
return getParams && getParams['module'] === 'API' && getParams['method'];
|
||||
}
|
||||
|
||||
function reset () {
|
||||
getParams = {};
|
||||
postParams = {};
|
||||
}
|
||||
|
||||
function isErrorResponse(response) {
|
||||
return response && angular.isObject(response) && response.result == 'error';
|
||||
}
|
||||
|
||||
function createResponseErrorNotification(response, options) {
|
||||
if (response.message
|
||||
&& options.createErrorNotification
|
||||
) {
|
||||
var UI = require('piwik/UI');
|
||||
var notification = new UI.Notification();
|
||||
notification.show(response.message, {
|
||||
context: 'error',
|
||||
type: 'toast',
|
||||
id: 'ajaxHelper',
|
||||
placeat: options.placeat
|
||||
});
|
||||
setTimeout(function () {
|
||||
// give some time for angular to render it
|
||||
notification.scrollToNotification();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the request
|
||||
* @return $promise
|
||||
*/
|
||||
function send (options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (options.createErrorNotification === undefined) {
|
||||
options.createErrorNotification = true;
|
||||
}
|
||||
|
||||
function onSuccess(response)
|
||||
{
|
||||
var headers = response.headers;
|
||||
response = response.data;
|
||||
|
||||
if (!angular.isDefined(response) || response === null) {
|
||||
return $q.reject(null);
|
||||
|
||||
} else if (isErrorResponse(response)) {
|
||||
|
||||
createResponseErrorNotification(response, options);
|
||||
|
||||
return $q.reject(response.message || null);
|
||||
} else {
|
||||
return options.includeHeaders ? { headers: headers, response: response } : response;
|
||||
}
|
||||
}
|
||||
|
||||
function onError(response)
|
||||
{
|
||||
var message = 'Something went wrong';
|
||||
if (response && (response.status === 0 || response.status === -1)) {
|
||||
message = 'Request was possibly aborted';
|
||||
}
|
||||
|
||||
return $q.reject(message);
|
||||
}
|
||||
|
||||
var deferred = $q.defer(),
|
||||
requestPromise = deferred.promise;
|
||||
|
||||
var headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
// ie 8,9,10 caches ajax requests, prevent this
|
||||
'cache-control': 'no-cache'
|
||||
};
|
||||
|
||||
var requestFormat = format;
|
||||
if (getParams.format && getParams.format.toLowerCase() !== 'json' && getParams.format.toLowerCase() !== 'json2') {
|
||||
requestFormat = getParams.format;
|
||||
}
|
||||
|
||||
var ajaxCall = {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
responseType: requestFormat,
|
||||
params: mixinDefaultGetParams(getParams),
|
||||
data: $.param(getPostParams(postParams)),
|
||||
timeout: requestPromise,
|
||||
headers: headers
|
||||
};
|
||||
|
||||
var promise = $http(ajaxCall).then(onSuccess, onError);
|
||||
|
||||
// we can't modify requestPromise directly and add an abort method since for some reason it gets
|
||||
// removed after then/finally/catch is called.
|
||||
var addAbortMethod = function (to, deferred) {
|
||||
return {
|
||||
then: function () {
|
||||
return addAbortMethod(to.then.apply(to, arguments), deferred);
|
||||
},
|
||||
|
||||
'finally': function () {
|
||||
return addAbortMethod(to.finally.apply(to, arguments), deferred);
|
||||
},
|
||||
|
||||
'catch': function () {
|
||||
return addAbortMethod(to.catch.apply(to, arguments), deferred);
|
||||
},
|
||||
|
||||
abort: function () {
|
||||
deferred.resolve();
|
||||
return this;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var request = addAbortMethod(promise, deferred);
|
||||
|
||||
allRequests.push(request);
|
||||
return request.finally(function() {
|
||||
var index = allRequests.indexOf(request);
|
||||
if (index !== -1) {
|
||||
allRequests.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameters to send as POST
|
||||
*
|
||||
* @param {object} params parameter object
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
function getPostParams (params) {
|
||||
if (isRequestToApiMethod() || piwik.shouldPropagateTokenAuth) {
|
||||
params.token_auth = piwik.token_auth;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin the default parameters to send as GET
|
||||
*
|
||||
* @param {object} getParamsToMixin parameter object
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
function mixinDefaultGetParams (getParamsToMixin) {
|
||||
var segment = piwik.broadcast.getValueFromHash('segment', $window.location.href.split('#')[1]);
|
||||
|
||||
// we have to decode the value manually because broadcast will not decode anything itself. if we don't,
|
||||
// angular will encode it again before sending the value in an HTTP request.
|
||||
segment = decodeURIComponent(segment);
|
||||
|
||||
var defaultParams = {
|
||||
idSite: piwik.idSite || piwik.broadcast.getValueFromUrl('idSite'),
|
||||
period: piwik.period || piwik.broadcast.getValueFromUrl('period'),
|
||||
segment: segment
|
||||
};
|
||||
|
||||
// never append token_auth to url
|
||||
if (getParamsToMixin.token_auth) {
|
||||
getParamsToMixin.token_auth = null;
|
||||
delete getParamsToMixin.token_auth;
|
||||
}
|
||||
|
||||
for (var key in defaultParams) {
|
||||
if (!(key in getParamsToMixin) && !(key in postParams) && defaultParams[key]) {
|
||||
getParamsToMixin[key] = defaultParams[key];
|
||||
}
|
||||
}
|
||||
|
||||
// handle default date & period if not already set
|
||||
if (!getParamsToMixin.date && !postParams.date) {
|
||||
getParamsToMixin.date = piwik.currentDateString;
|
||||
}
|
||||
|
||||
return getParamsToMixin;
|
||||
}
|
||||
|
||||
function abortAll() {
|
||||
reset();
|
||||
|
||||
allRequests.forEach(function (request) {
|
||||
request.abort();
|
||||
});
|
||||
|
||||
allRequests = [];
|
||||
}
|
||||
|
||||
function abort () {
|
||||
abortAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a reading API request.
|
||||
* @param getParams
|
||||
*/
|
||||
function fetch (getParams, options) {
|
||||
|
||||
getParams.module = getParams.module || 'API';
|
||||
|
||||
if (!getParams.format) {
|
||||
getParams.format = 'JSON2';
|
||||
}
|
||||
|
||||
addParams(getParams);
|
||||
|
||||
var promise = send(options);
|
||||
|
||||
reset();
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function post(getParams, _postParams_, options) {
|
||||
if (_postParams_) {
|
||||
if (postParams && postParams.token_auth && !_postParams_.token_auth) {
|
||||
_postParams_.token_auth = postParams.token_auth;
|
||||
}
|
||||
postParams = _postParams_;
|
||||
}
|
||||
|
||||
return fetch(getParams, options);
|
||||
}
|
||||
|
||||
function addPostParams(_postParams_) {
|
||||
if (_postParams_) {
|
||||
angular.merge(postParams, _postParams_);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that will perform a bulk request using Piwik's API.getBulkRequest method.
|
||||
* Bulk requests allow you to execute multiple Piwik requests with one HTTP request.
|
||||
*
|
||||
* @param {object[]} requests
|
||||
* @param {object} options
|
||||
* @return {HttpPromise} a promise that is resolved when the request finishes. The argument passed
|
||||
* to the .then(...) callback will be an array with one element per request
|
||||
* made.
|
||||
*/
|
||||
function bulkFetch(requests, options) {
|
||||
var bulkApiRequestParams = {
|
||||
urls: requests.map(function (requestObj) { return '?' + $.param(requestObj); })
|
||||
};
|
||||
|
||||
var deferred = $q.defer(),
|
||||
requestPromise = post({method: "API.getBulkRequest"}, bulkApiRequestParams, options).then(function (response) {
|
||||
if (!(response instanceof Array)) {
|
||||
response = [response];
|
||||
}
|
||||
|
||||
// check for errors
|
||||
for (var i = 0; i != response.length; ++i) {
|
||||
var specificResponse = response[i];
|
||||
|
||||
if (isErrorResponse(specificResponse)) {
|
||||
deferred.reject(specificResponse.message || null);
|
||||
|
||||
createResponseErrorNotification(specificResponse, options || {});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve(response);
|
||||
}).catch(function () {
|
||||
deferred.reject.apply(deferred, arguments);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
return {
|
||||
withTokenInUrl: withTokenInUrl,
|
||||
bulkFetch: bulkFetch,
|
||||
post: post,
|
||||
fetch: fetch,
|
||||
addPostParams: addPostParams,
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
abort: abort,
|
||||
abortAll: abortAll,
|
||||
mixinDefaultGetParams: mixinDefaultGetParams
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,273 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
describe('piwikApiClient', function () {
|
||||
var piwikApi,
|
||||
$httpBackend;
|
||||
|
||||
if (!window.piwik) window.piwik = {};
|
||||
if (!window.piwik.UI) window.piwik.UI = {};
|
||||
if (!window.piwik.UI.Notification) {
|
||||
window.piwik.UI.Notification = function () {
|
||||
this.show = function () {};
|
||||
this.scrollToNotification = function () {};
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(module('piwikApp.service'));
|
||||
beforeEach(inject(function($injector) {
|
||||
piwikApi = $injector.get('piwikApi');
|
||||
|
||||
$httpBackend = $injector.get('$httpBackend');
|
||||
|
||||
$httpBackend.when('POST', /.*getBulkRequest.*/, /.*errorAction.*/).respond(function (method, url, data, headers) {
|
||||
url = url.replace(/date=[^&]+/, "date=");
|
||||
|
||||
var errorResponse = {result: 'error', message: "error message"},
|
||||
successResponse= "Response #2: " + url + " - " + data;
|
||||
|
||||
return [200, [errorResponse, successResponse]];
|
||||
});
|
||||
|
||||
$httpBackend.when('POST', /.*getBulkRequest.*/).respond(function (method, url, data, headers) {
|
||||
url = url.replace(/date=[^&]+/, "date=");
|
||||
|
||||
var responses = [
|
||||
"Response #1: " + url + " - " + data,
|
||||
"Response #2: " + url + " - " + data
|
||||
];
|
||||
|
||||
return [200, JSON.stringify(responses)];
|
||||
});
|
||||
|
||||
$httpBackend.when('POST', /.*/).respond(function (method, url, data, headers) {
|
||||
url = url.replace(/date=[^&]+/, "date=");
|
||||
return [200, "Request url: " + url];
|
||||
});
|
||||
}));
|
||||
|
||||
it("should successfully send a request to Piwik when fetch is called", function (done) {
|
||||
piwikApi.fetch({
|
||||
method: "SomePlugin.action"
|
||||
}).then(function (response) {
|
||||
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
|
||||
|
||||
done();
|
||||
}).catch(function (ex) {
|
||||
done(ex);
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should chain multiple then callbacks correctly when a fetch succeeds", function (done) {
|
||||
var firstThenDone = false;
|
||||
|
||||
piwikApi.fetch({
|
||||
method: "SomePlugin.action"
|
||||
}).then(function (response) {
|
||||
firstThenDone = true;
|
||||
|
||||
return "newval";
|
||||
}).then(function (response) {
|
||||
expect(firstThenDone).to.equal(true);
|
||||
expect(response).to.equal("newval");
|
||||
|
||||
done();
|
||||
}).catch(function (ex) {
|
||||
done(ex);
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should fail when multiple aborts are issued", function (done) {
|
||||
var request = piwikApi.fetch({
|
||||
method: "SomePlugin.action"
|
||||
}).then(function (response) {
|
||||
done(new Error("Aborted request succeeded but should fail!"));
|
||||
}).catch(function (ex) {
|
||||
done();
|
||||
});
|
||||
|
||||
request.abort();
|
||||
request.abort();
|
||||
|
||||
$httpBackend.flush();
|
||||
|
||||
request.abort();
|
||||
});
|
||||
|
||||
it("should send multiple requests concurrently when fetch is called more than once", function (done) {
|
||||
var request1Done, request2Done;
|
||||
|
||||
function finishIfBothDone() {
|
||||
if (request1Done && request2Done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
piwikApi.fetch({
|
||||
method: "SomePlugin.action"
|
||||
}).then(function (response) {
|
||||
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomePlugin.action&module=API&period=day");
|
||||
|
||||
request1Done = true;
|
||||
|
||||
finishIfBothDone();
|
||||
}).catch(function (ex) {
|
||||
done(ex);
|
||||
});
|
||||
|
||||
piwikApi.fetch({
|
||||
method: "SomeOtherPlugin.action"
|
||||
}).then(function (response) {
|
||||
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
|
||||
|
||||
request2Done = true;
|
||||
|
||||
finishIfBothDone();
|
||||
}).catch(function (ex) {
|
||||
done(ex);
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should abort individual requests when abort() is called on a promise", function (done) {
|
||||
var request1Done, request2Done;
|
||||
|
||||
function finishIfBothDone() {
|
||||
if (request1Done && request2Done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
var request = piwikApi.fetch({
|
||||
method: "SomePlugin.waitAction"
|
||||
}).then(function (response) {
|
||||
done(new Error("Aborted request finished!"));
|
||||
}).catch(function (ex) {
|
||||
request1Done = true;
|
||||
finishIfBothDone();
|
||||
});
|
||||
|
||||
piwikApi.fetch({
|
||||
method: "SomeOtherPlugin.action"
|
||||
}).then(function (response) {
|
||||
expect(response).to.equal("Request url: index.php?date=&format=JSON2&idSite=1&method=SomeOtherPlugin.action&module=API&period=day");
|
||||
|
||||
request2Done = true;
|
||||
|
||||
finishIfBothDone();
|
||||
}).catch(function (ex) {
|
||||
done(ex);
|
||||
});
|
||||
|
||||
request.abort();
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should abort all requests when abortAll() is called on the piwikApi", function (done) {
|
||||
var request1Done, request2Done;
|
||||
|
||||
function finishIfBothDone() {
|
||||
if (request1Done && request2Done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
piwikApi.fetch({
|
||||
method: "SomePlugin.waitAction"
|
||||
}).then(function (response) {
|
||||
done(new Error("Aborted request finished (request 1)!"));
|
||||
}).catch(function (ex) {
|
||||
request1Done = true;
|
||||
finishIfBothDone();
|
||||
});
|
||||
|
||||
piwikApi.fetch({
|
||||
method: "SomePlugin.waitAction"
|
||||
}).then(function (response) {
|
||||
done(new Error("Aborted request finished (request 2)!"));
|
||||
}).catch(function (ex) {
|
||||
request2Done = true;
|
||||
finishIfBothDone();
|
||||
});
|
||||
|
||||
piwikApi.abortAll();
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should perform a bulk request correctly when bulkFetch is called on the piwikApi", function (done) {
|
||||
piwikApi.bulkFetch([
|
||||
{
|
||||
method: "SomePlugin.action",
|
||||
param: "value"
|
||||
},
|
||||
{
|
||||
method: "SomeOtherPlugin.action"
|
||||
}
|
||||
]).then(function (response) {
|
||||
var restOfExpected = "index.php?date=&format=JSON2&idSite=1&method=API.getBulkRequest&" +
|
||||
"module=API&period=day - urls%5B%5D=%3Fmethod%3DSomePlugin.action%26param%3D" +
|
||||
"value&urls%5B%5D=%3Fmethod%3DSomeOtherPlugin.action&token_auth=100bf5eeeed1468f3f9d93750044d3dd";
|
||||
|
||||
expect(response.length).to.equal(2);
|
||||
expect(response[0]).to.equal("Response #1: " + restOfExpected);
|
||||
expect(response[1]).to.equal("Response #2: " + restOfExpected);
|
||||
|
||||
done();
|
||||
}).catch(function (ex) {
|
||||
done(ex);
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it("should correctly handle errors in a bulk request response", function (done) {
|
||||
piwikApi.bulkFetch([
|
||||
{
|
||||
method: "SomePlugin.errorAction"
|
||||
},
|
||||
{
|
||||
method: "SomeOtherPlugin.whatever"
|
||||
}
|
||||
]).then(function (response) {
|
||||
done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
|
||||
}).catch(function (error) {
|
||||
expect(error).to.equal("error message");
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it("shuld correctly handle errors in a bulk request response, regardless of error location", function (done) {
|
||||
piwikApi.bulkFetch([
|
||||
{
|
||||
method: "SomeOtherPlugin.whatever"
|
||||
},
|
||||
{
|
||||
method: "SomePlugin.errorAction"
|
||||
}
|
||||
]).then(function (response) {
|
||||
done(new Error("promise resolved after bulkFetch request returned an error (response = " + JSON.stringify(response) + ")"));
|
||||
}).catch(function (error) {
|
||||
expect(error).to.equal("error message");
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
});
|
||||
})();
|
@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
describe('piwikHelper', function() {
|
||||
var piwikHelper;
|
||||
|
||||
beforeEach(module('piwikApp.service'));
|
||||
beforeEach(inject(function ($injector) {
|
||||
piwikHelper = $injector.get('piwik').helper;
|
||||
}));
|
||||
beforeEach(function () {
|
||||
delete window._dosomething;
|
||||
});
|
||||
|
||||
describe('#htmlDecode', function () {
|
||||
|
||||
it('should correctly decode html entities', function (done) {
|
||||
var called = false;
|
||||
window._dosomething = function () {
|
||||
called = true;
|
||||
};
|
||||
|
||||
var encoded = 'str <img src=\'x/\' onerror=\'_dosomething()\'/>';
|
||||
var decoded = piwikHelper.htmlDecode(encoded);
|
||||
|
||||
setTimeout(function () {
|
||||
expect(called).to.be.false;
|
||||
expect(decoded).to.equal('str <img src=\'x/\' onerror=\'_dosomething()\'/>');
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
63
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/piwik-url.js
vendored
Normal file
63
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/piwik-url.js
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.service').service('piwikUrl', piwikUrl);
|
||||
|
||||
piwikUrl.$inject = ['$location', 'piwik'];
|
||||
|
||||
/**
|
||||
* Similar to angulars $location but works around some limitation. Use it if you need to access search params
|
||||
*/
|
||||
function piwikUrl($location, piwik) {
|
||||
|
||||
var model = {
|
||||
getSearchParam: getSearchParam
|
||||
};
|
||||
|
||||
return model;
|
||||
|
||||
function getSearchParam(paramName)
|
||||
{
|
||||
if (paramName === 'segment') {
|
||||
var hash = window.location.href.split('#');
|
||||
if (hash && hash[1]) {
|
||||
return piwik.broadcast.getValueFromHash(paramName, hash[1]);
|
||||
}
|
||||
|
||||
return broadcast.getValueFromUrl(paramName);
|
||||
}
|
||||
|
||||
// available in global scope
|
||||
var search = $location.search();
|
||||
|
||||
if (!search[paramName]) {
|
||||
// see https://github.com/angular/angular.js/issues/7239 (issue is resolved but problem still exists)
|
||||
var paramUrlValue = piwik.broadcast.getValueFromUrl(paramName);
|
||||
if (paramUrlValue !== false
|
||||
&& paramUrlValue !== ''
|
||||
&& paramUrlValue !== null
|
||||
&& paramUrlValue !== undefined
|
||||
&& paramName !== 'token_auth') {
|
||||
search[paramName] = paramUrlValue;
|
||||
} else {
|
||||
return paramUrlValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (search[paramName]) {
|
||||
var value = search[paramName];
|
||||
|
||||
if (angular.isArray(search[paramName])) {
|
||||
// use last one. Eg when having period=day&period=year angular would otherwise return ['day', 'year']
|
||||
return value[value.length - 1];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
86
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/piwik.js
vendored
Normal file
86
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/piwik.js
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.service').service('piwik', piwikService);
|
||||
|
||||
piwikService.$inject = ['piwikPeriods'];
|
||||
|
||||
function piwikService(piwikPeriods) {
|
||||
var originalTitle;
|
||||
piwik.helper = piwikHelper;
|
||||
piwik.broadcast = broadcast;
|
||||
piwik.updatePeriodParamsFromUrl = updatePeriodParamsFromUrl;
|
||||
piwik.updateDateInTitle = updateDateInTitle;
|
||||
piwik.hasUserCapability = hasUserCapability;
|
||||
return piwik;
|
||||
|
||||
function hasUserCapability(capability) {
|
||||
return angular.isArray(piwik.userCapabilities) && piwik.userCapabilities.indexOf(capability) !== -1;
|
||||
}
|
||||
|
||||
function updatePeriodParamsFromUrl() {
|
||||
var date = piwik.broadcast.getValueFromHash('date') || piwik.broadcast.getValueFromUrl('date');
|
||||
var period = piwik.broadcast.getValueFromHash('period') || piwik.broadcast.getValueFromUrl('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;
|
||||
|
||||
var dateRange = piwikPeriods.parse(period, date).getDateRange();
|
||||
piwik.startDateString = $.datepicker.formatDate('yy-mm-dd', dateRange[0]);
|
||||
piwik.endDateString = $.datepicker.formatDate('yy-mm-dd', dateRange[1]);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function isValidPeriod(periodStr, dateStr) {
|
||||
try {
|
||||
piwikPeriods.get(periodStr).parse(dateStr);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateDateInTitle( date, period ) {
|
||||
if (!$('.top_controls #periodString').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache server-rendered page title
|
||||
originalTitle = originalTitle || document.title;
|
||||
|
||||
if (0 === originalTitle.indexOf(piwik.siteName)) {
|
||||
var dateString = ' - ' + piwikPeriods.parse(period, date).getPrettyString() + ' ';
|
||||
document.title = piwik.siteName + dateString + originalTitle.substr(piwik.siteName.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('piwikApp.service').run(initPiwikService);
|
||||
|
||||
initPiwikService.$inject = ['piwik', '$rootScope'];
|
||||
|
||||
function initPiwikService(piwik, $rootScope) {
|
||||
$rootScope.$on('$locationChangeSuccess', piwik.updatePeriodParamsFromUrl);
|
||||
}
|
||||
})();
|
@ -0,0 +1,176 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
describe('piwikService', function() {
|
||||
var piwikService;
|
||||
|
||||
beforeEach(module('piwikApp.service'));
|
||||
beforeEach(inject(function($injector) {
|
||||
piwikService = $injector.get('piwik');
|
||||
}));
|
||||
|
||||
describe('#piwikService', function() {
|
||||
|
||||
it('should be the same as piwik global var', function() {
|
||||
piwik.should.equal(piwikService);
|
||||
});
|
||||
|
||||
it('should mixin broadcast', function() {
|
||||
expect(piwikService.broadcast).to.be.an('object');
|
||||
});
|
||||
|
||||
it('should mixin piwikHelper', function() {
|
||||
expect(piwikService.helper).to.be.an('object');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#piwik_url', function() {
|
||||
|
||||
it('should contain the piwik url', function() {
|
||||
expect(piwikService.piwik_url).to.eql('http://localhost/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updatePeriodParamsFromUrl()', function() {
|
||||
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(function (test) {
|
||||
var date = test.date,
|
||||
period = test.period,
|
||||
expected = test.expected;
|
||||
|
||||
it('should parse the period in the URL correctly when date=' + date + ' and period=' + period, function () {
|
||||
delete piwikService.currentDateString;
|
||||
delete piwikService.period;
|
||||
delete piwikService.startDateString;
|
||||
delete piwikService.endDateString;
|
||||
|
||||
history.pushState(null, null, '?date=' + date + '&period=' + period);
|
||||
|
||||
piwikService.updatePeriodParamsFromUrl();
|
||||
|
||||
expect(piwikService.currentDateString).to.equal(expected.currentDateString);
|
||||
expect(piwikService.period).to.equal(expected.period);
|
||||
expect(piwikService.startDateString).to.equal(expected.startDateString);
|
||||
expect(piwikService.endDateString).to.equal(expected.endDateString);
|
||||
});
|
||||
|
||||
it('should parse the period in the URL hash correctly when date=' + date + ' and period=' + period, function () {
|
||||
delete piwikService.currentDateString;
|
||||
delete piwikService.period;
|
||||
delete piwikService.startDateString;
|
||||
delete piwikService.endDateString;
|
||||
|
||||
history.pushState(null, null, '?someparam=somevalue#?date=' + date + '&period=' + period);
|
||||
|
||||
piwikService.updatePeriodParamsFromUrl();
|
||||
|
||||
expect(piwikService.currentDateString).to.equal(expected.currentDateString);
|
||||
expect(piwikService.period).to.equal(expected.period);
|
||||
expect(piwikService.startDateString).to.equal(expected.startDateString);
|
||||
expect(piwikService.endDateString).to.equal(expected.endDateString);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not change object values if the current date/period is the same as the URL date/period', function () {
|
||||
piwik.period = 'range';
|
||||
piwik.currentDateString = '2012-01-01,2012-01-02';
|
||||
piwik.startDateString = 'shouldnotchange';
|
||||
piwik.endDateString = 'shouldnotchangeeither';
|
||||
|
||||
history.pushState(null, null, '?someparam=somevalue#?date=' + piwik.currentDateString + '&period=' + piwik.period);
|
||||
|
||||
piwikService.updatePeriodParamsFromUrl();
|
||||
|
||||
expect(piwikService.startDateString).to.equal('shouldnotchange');
|
||||
expect(piwikService.endDateString).to.equal('shouldnotchangeeither');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
53
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/report-metadata-model.js
vendored
Normal file
53
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/report-metadata-model.js
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.service').factory('reportMetadataModel', reportMetadataModel);
|
||||
|
||||
reportMetadataModel.$inject = ['piwik', 'piwikApi'];
|
||||
|
||||
function reportMetadataModel (piwik, piwikApi) {
|
||||
|
||||
var reportsPromise = null;
|
||||
|
||||
var model = {
|
||||
reports: [],
|
||||
fetchReportMetadata: fetchReportMetadata,
|
||||
findReport: findReport
|
||||
};
|
||||
|
||||
return model;
|
||||
|
||||
function findReport(module, action)
|
||||
{
|
||||
var found = [];
|
||||
|
||||
angular.forEach(model.reports, function (report) {
|
||||
if (report.module === module && report.action === action) {
|
||||
found = report;
|
||||
}
|
||||
});
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
function fetchReportMetadata()
|
||||
{
|
||||
if (!reportsPromise) {
|
||||
reportsPromise = piwikApi.fetch({
|
||||
method: 'API.getReportMetadata',
|
||||
filter_limit: '-1',
|
||||
idSite: piwik.idSite || piwik.broadcast.getValueFromUrl('idSite')
|
||||
}).then(function (response) {
|
||||
model.reports = response;
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
return reportsPromise;
|
||||
}
|
||||
}
|
||||
})();
|
76
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/reporting-pages-model.js
vendored
Normal file
76
msd2/tracking/piwik/plugins/CoreHome/angularjs/common/services/reporting-pages-model.js
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.service').factory('reportingPagesModel', reportingPagesModelService);
|
||||
|
||||
reportingPagesModelService.$inject = ['piwikApi'];
|
||||
|
||||
function reportingPagesModelService (piwikApi) {
|
||||
var fetchAllPagesPromise = false;
|
||||
|
||||
// those sites are going to be displayed
|
||||
var model = {
|
||||
pages : [],
|
||||
findPage: findPage,
|
||||
findPageInCategory: findPageInCategory,
|
||||
reloadAllPages : reloadAllPages,
|
||||
getAllPages : getAllPages
|
||||
};
|
||||
|
||||
return model;
|
||||
|
||||
function findPageInCategory(categoryId) {
|
||||
var found = null;
|
||||
|
||||
angular.forEach(model.pages, function (page) {
|
||||
// happens when user switches between sites, in this case check if the same category exists and if so,
|
||||
// select first entry from that category
|
||||
if (!found && page &&
|
||||
page.category && page.subcategory &&
|
||||
page.category.id === categoryId && page.subcategory.id) {
|
||||
found = page;
|
||||
}
|
||||
});
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
function findPage(categoryId, subcategoryId)
|
||||
{
|
||||
var found = null;
|
||||
|
||||
angular.forEach(model.pages, function (page) {
|
||||
if (!found &&
|
||||
page &&
|
||||
page.category && page.subcategory &&
|
||||
page.category.id === categoryId && ('' + page.subcategory.id) === subcategoryId) {
|
||||
found = page;
|
||||
}
|
||||
});
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
function reloadAllPages()
|
||||
{
|
||||
fetchAllPagesPromise = null;
|
||||
return getAllPages();
|
||||
}
|
||||
|
||||
function getAllPages()
|
||||
{
|
||||
if (!fetchAllPagesPromise) {
|
||||
fetchAllPagesPromise = piwikApi.fetch({method: 'API.getReportPagesMetadata', filter_limit: '-1'}).then(function (response) {
|
||||
model.pages = response;
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
return fetchAllPagesPromise;
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp.service', []);
|
||||
})();
|
@ -0,0 +1,10 @@
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h2 ng-if="contentTitle && !feature && !helpUrl && !helpText" class="card-title">{{contentTitle}}</h2>
|
||||
<h2 ng-if="contentTitle && (feature || helpUrl || helpText)" class="card-title"
|
||||
piwik-enriched-headline feature-name="{{feature}}" help-url="{{helpUrl}}" inline-help="{{ helpText }}">
|
||||
{{contentTitle}}</h2>
|
||||
<div ng-transclude>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,90 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-content-block>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikContentBlock', piwikContentBlock);
|
||||
|
||||
piwikContentBlock.$inject = ['piwik'];
|
||||
|
||||
function piwikContentBlock(piwik){
|
||||
|
||||
var adminContent = null;
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
scope: {
|
||||
contentTitle: '@',
|
||||
feature: '@',
|
||||
helpUrl: '@',
|
||||
helpText: '@',
|
||||
anchor: '@?'
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/content-block/content-block.directive.html?cb=' + piwik.cacheBuster,
|
||||
controllerAs: 'contentBlock',
|
||||
compile: function (element, attrs) {
|
||||
|
||||
if (attrs.feature === 'true') {
|
||||
attrs.feature = true;
|
||||
}
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
if (scope.anchor) {
|
||||
var anchor = $('<a></a>').attr('id', scope.anchor);
|
||||
element.prepend(anchor);
|
||||
}
|
||||
|
||||
var inlineHelp = element.find('[ng-transclude] > .contentHelp');
|
||||
if (inlineHelp.length) {
|
||||
scope.helpText = inlineHelp.html();
|
||||
inlineHelp.remove();
|
||||
}
|
||||
|
||||
if (scope.feature && (scope.feature===true || scope.feature ==='true')) {
|
||||
scope.feature = scope.contentTitle;
|
||||
}
|
||||
|
||||
if (adminContent === null) {
|
||||
// cache admin node for further content blocks
|
||||
adminContent = $('#content.admin');
|
||||
}
|
||||
|
||||
var contentTopPosition = false;
|
||||
|
||||
if (adminContent.length) {
|
||||
contentTopPosition = adminContent.offset().top;
|
||||
}
|
||||
|
||||
if (contentTopPosition || contentTopPosition === 0) {
|
||||
var parents = element.parentsUntil('.col', '[piwik-widget-loader]');
|
||||
var topThis;
|
||||
if (parents.length) {
|
||||
// when shown within the widget loader, we need to get the offset of that element
|
||||
// as the widget loader might be still shown. Would otherwise not position correctly
|
||||
// the widgets on the admin home page
|
||||
topThis = parents.offset().top;
|
||||
} else {
|
||||
topThis = element.offset().top;
|
||||
}
|
||||
|
||||
if ((topThis - contentTopPosition) < 17) {
|
||||
// we make sure to display the first card with no margin-top to have it on same as line as
|
||||
// navigation
|
||||
element.css('marginTop', '0');
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,26 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-content-block>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikContentIntro', piwikContentIntro);
|
||||
|
||||
piwikContentIntro.$inject = ['piwik'];
|
||||
|
||||
function piwikContentIntro(piwik){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function (element, attrs) {
|
||||
element.addClass('piwik-content-intro');
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,30 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-content-table>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikContentTable', piwikContentTable);
|
||||
|
||||
piwikContentTable.$inject = ['piwik'];
|
||||
|
||||
function piwikContentTable(piwik){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
compile: function (element, attrs) {
|
||||
element.addClass('card card-table entityTable');
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,369 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* A wrapper around the jquery UI date picker that allows you to show multiple dates selected
|
||||
* or highlighted.
|
||||
*
|
||||
* Properties:
|
||||
* - selectedDates: null or a two element array of Date instances. The first element is the start
|
||||
* of the range of selected dates. The second element is the end of the range
|
||||
* of selected dates. The range is inclusive.
|
||||
* - highlightedDates: null or a two element array of Date instances. The first element is the
|
||||
* start of the range of highlighted dates. The second element is the end of
|
||||
* the range of highlighted dates. The range is inclusive.
|
||||
* - viewDate: The date that should be displayed. The month & year of this date is what will
|
||||
* be displayed to the user.
|
||||
* - stepMonths: The number of months to move when the left/right arrows are clicked.
|
||||
* - disableMonthDropdown: true if the month dropdown should be disabled, false if enabled.
|
||||
* - options: extra options to pass the jquery ui's datepicker. They will not be re-applied if
|
||||
* the value for this property changes.
|
||||
* - cellHover: called when the user hovers over a calendar cell. Called w/ 'date' argument set
|
||||
* to the Date of the cell clicked & '$cell' set to the <td> element from the datepicker.
|
||||
* - cellHoverLeave: called when the user leaves all of the calendar cells.
|
||||
* - dateSelect: called when the user selects a date.
|
||||
*
|
||||
* Usage:
|
||||
* <div piwik-date-picker .../>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikDatePicker', piwikDatePicker);
|
||||
|
||||
piwikDatePicker.$inject = ['piwik', '$timeout'];
|
||||
|
||||
function piwikDatePicker(piwik, $timeout) {
|
||||
var DEFAULT_STEP_MONTHS = 1;
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
selectedDateStart: '<',
|
||||
selectedDateEnd: '<',
|
||||
highlightedDateStart: '<',
|
||||
highlightedDateEnd: '<',
|
||||
viewDate: '<',
|
||||
stepMonths: '<',
|
||||
disableMonthDropdown: '<',
|
||||
options: '<',
|
||||
cellHover: '&',
|
||||
cellHoverLeave: '&',
|
||||
dateSelect: '&'
|
||||
},
|
||||
template: '',
|
||||
link: function (scope, element) {
|
||||
scope.$onChanges = $onChanges;
|
||||
|
||||
var customOptions = scope.options || {};
|
||||
var datePickerOptions = $.extend({}, piwik.getBaseDatePickerOptions(), customOptions, {
|
||||
onChangeMonthYear: function () {
|
||||
// datepicker renders the HTML after this hook is called, so we use setTimeout
|
||||
// to run some code after the render.
|
||||
setTimeout(function () {
|
||||
onJqueryUiRenderedPicker();
|
||||
});
|
||||
}
|
||||
});
|
||||
element.datepicker(datePickerOptions);
|
||||
|
||||
element.on('mouseover', 'tbody td a', function (event) {
|
||||
// this event is triggered when a user clicks a date as well. in that case,
|
||||
// the originalEvent is null. we don't need to redraw again for that, so
|
||||
// we ignore events like that.
|
||||
if (event.originalEvent) {
|
||||
setDatePickerCellColors();
|
||||
}
|
||||
});
|
||||
|
||||
// on hover cell, execute scope.cellHover()
|
||||
element.on('mouseenter', 'tbody td', function () {
|
||||
if (!scope.cellHover) {
|
||||
return;
|
||||
}
|
||||
|
||||
var monthYear = getMonthYearDisplayed();
|
||||
|
||||
var $dateCell = $(this);
|
||||
var dateValue = getCellDate($dateCell, monthYear[0], monthYear[1]);
|
||||
scope.cellHover({ date: dateValue, $cell: $dateCell });
|
||||
|
||||
$timeout(); // trigger new digest
|
||||
});
|
||||
|
||||
// overrides jquery UI handler that unhighlights a cell when the mouse leaves it
|
||||
element.on('mouseout', 'tbody td a', function () {
|
||||
setDatePickerCellColors();
|
||||
});
|
||||
|
||||
// call scope.cellHover() when mouse leaves table body (can't do event on tbody, for some reason
|
||||
// that fails, so we do two events, one on the table & one on thead)
|
||||
element
|
||||
.on('mouseleave', 'table', onCalendarHoverLeave)
|
||||
.on('mouseenter', 'thead', onCalendarHoverLeave);
|
||||
|
||||
// make sure whitespace is clickable when the period makes it appropriate
|
||||
element.on('click', 'tbody td.ui-datepicker-other-month', handleOtherMonthClick);
|
||||
|
||||
// NOTE: using a selector w/ .on() doesn't seem to work for some reason...
|
||||
element.on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $target = $(e.target).closest('a');
|
||||
if (!$target.is('.ui-datepicker-next')
|
||||
&& !$target.is('.ui-datepicker-prev')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
onCalendarViewChange();
|
||||
});
|
||||
|
||||
// when a cell is clicked, invoke the onDateSelected function. this, in conjunction
|
||||
// with onJqueryUiRenderedPicker(), overrides the date picker's click behavior.
|
||||
element.on('click', 'td[data-month]', function (event) {
|
||||
var $cell = $(event.target).closest('td');
|
||||
var month = parseInt($cell.attr('data-month'));
|
||||
var year = parseInt($cell.attr('data-year'));
|
||||
var day = parseInt($cell.children('a,span').text());
|
||||
onDateSelected(new Date(year, month, day));
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
var renderPostProcessed = stepMonthsChanged();
|
||||
|
||||
viewDateChanged();
|
||||
enableDisableMonthDropdown();
|
||||
|
||||
if (!renderPostProcessed) {
|
||||
onJqueryUiRenderedPicker();
|
||||
}
|
||||
|
||||
setDatePickerCellColors();
|
||||
}
|
||||
|
||||
// remove the ui-state-active class & click handlers for every cell. we bypass
|
||||
// the datepicker's date selection logic for smoother browser rendering.
|
||||
function onJqueryUiRenderedPicker() {
|
||||
element.find('td[data-event]').off('click');
|
||||
element.find('.ui-state-active').removeClass('ui-state-active');
|
||||
element.find('.ui-datepicker-current-day').removeClass('ui-datepicker-current-day');
|
||||
|
||||
// add href to left/right nav in calendar so they can be accessed via keyboard
|
||||
element.find('.ui-datepicker-prev,.ui-datepicker-next').attr('href', '');
|
||||
}
|
||||
|
||||
function $onChanges(changesObj) {
|
||||
// redraw when selected/highlighted dates change
|
||||
var redraw = changesObj.selectedDateStart
|
||||
|| changesObj.selectedDateEnd
|
||||
|| changesObj.highlightedDateStart
|
||||
|| changesObj.highlightedDateEnd;
|
||||
|
||||
if (changesObj.viewDate && viewDateChanged()) {
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (changesObj.stepMonths) {
|
||||
stepMonthsChanged();
|
||||
}
|
||||
|
||||
if (changesObj.enableDisableMonthDropdown) {
|
||||
enableDisableMonthDropdown();
|
||||
}
|
||||
|
||||
if (redraw) {
|
||||
// timeout ensures the style change happens after jquery datepicker
|
||||
// does its own rendering, so we're overriding jquery.
|
||||
setDatePickerCellColors();
|
||||
}
|
||||
}
|
||||
|
||||
function viewDateChanged() {
|
||||
var date = scope.viewDate;
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(date instanceof Date)) {
|
||||
try {
|
||||
date = $.datepicker.parseDate('yy-mm-dd', date);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// only change the datepicker date if the date is outside of the current month/year.
|
||||
// this avoids a re-render in other cases.
|
||||
var monthYear = getMonthYearDisplayed();
|
||||
if (monthYear[0] !== date.getMonth() || monthYear[1] !== date.getFullYear()) {
|
||||
element.datepicker('setDate', date);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function stepMonthsChanged() {
|
||||
var stepMonths = scope.stepMonths || DEFAULT_STEP_MONTHS;
|
||||
if (element.datepicker('option', 'stepMonths') === stepMonths) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// setting stepMonths will change the month in view back to the selected date. to avoid
|
||||
// we set the selected date to the month in view.
|
||||
var currentMonth = $('.ui-datepicker-month', element).val(),
|
||||
currentYear = $('.ui-datepicker-year', element).val();
|
||||
|
||||
element
|
||||
.datepicker('option', 'stepMonths', stepMonths)
|
||||
.datepicker('setDate', new Date(currentYear, currentMonth));
|
||||
|
||||
onJqueryUiRenderedPicker();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function onDateSelected(date) {
|
||||
if (!scope.dateSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.dateSelect({
|
||||
date: date
|
||||
});
|
||||
|
||||
// this event comes from jquery, so we must apply manually
|
||||
scope.$apply();
|
||||
}
|
||||
|
||||
function handleOtherMonthClick() {
|
||||
if (!$(this).hasClass('ui-state-hover')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $row = $(this).parent(),
|
||||
$tbody = $row.parent();
|
||||
|
||||
if ($row.is(':first-child')) {
|
||||
// click on first of the month
|
||||
$tbody.find('a').first().click();
|
||||
} else {
|
||||
// click on last of month
|
||||
$tbody.find('a').last().click();
|
||||
}
|
||||
}
|
||||
|
||||
function onCalendarHoverLeave() {
|
||||
if (!scope.cellHoverLeave) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.cellHoverLeave();
|
||||
|
||||
$timeout();
|
||||
}
|
||||
|
||||
function onCalendarViewChange() {
|
||||
// clicking left/right re-enables the month dropdown, so we disable it again
|
||||
enableDisableMonthDropdown();
|
||||
|
||||
setDatePickerCellColors();
|
||||
}
|
||||
|
||||
function setDatePickerCellColors() {
|
||||
var $calendarTable = element.find('.ui-datepicker-calendar');
|
||||
|
||||
var monthYear = getMonthYearDisplayed();
|
||||
|
||||
// highlight the rest of the cells by first getting the date for the first cell
|
||||
// in the calendar, then just incrementing by one for the rest of the cells.
|
||||
var $cells = $calendarTable.find('td');
|
||||
var $firstDateCell = $cells.first();
|
||||
var currentDate = getCellDate($firstDateCell, monthYear[0], monthYear[1]);
|
||||
|
||||
$cells.each(function () {
|
||||
setDateCellColor($(this), currentDate);
|
||||
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function getMonthYearDisplayed() {
|
||||
var $firstCellWithMonth = element.find('td[data-month]');
|
||||
var month = parseInt($firstCellWithMonth.attr('data-month'));
|
||||
var year = parseInt($firstCellWithMonth.attr('data-year'));
|
||||
return [month, year];
|
||||
}
|
||||
|
||||
function setDateCellColor($dateCell, dateValue) {
|
||||
var $dateCellLink = $dateCell.children('a');
|
||||
|
||||
if (scope.selectedDateStart
|
||||
&& scope.selectedDateEnd
|
||||
&& dateValue >= scope.selectedDateStart
|
||||
&& dateValue <= scope.selectedDateEnd
|
||||
) {
|
||||
$dateCell.addClass('ui-datepicker-current-period');
|
||||
} else {
|
||||
$dateCell.removeClass('ui-datepicker-current-period');
|
||||
}
|
||||
|
||||
if (scope.highlightedDateStart
|
||||
&& scope.highlightedDateEnd
|
||||
&& dateValue >= scope.highlightedDateStart
|
||||
&& dateValue <= scope.highlightedDateEnd
|
||||
) {
|
||||
// other-month cells don't have links, so the <td> must have the ui-state-hover class
|
||||
var elementToAddClassTo = $dateCellLink.length ? $dateCellLink : $dateCell;
|
||||
elementToAddClassTo.addClass('ui-state-hover');
|
||||
} else {
|
||||
$dateCell.removeClass('ui-state-hover');
|
||||
$dateCellLink.removeClass('ui-state-hover');
|
||||
}
|
||||
}
|
||||
|
||||
function getCellDate($dateCell, month, year) {
|
||||
if ($dateCell.hasClass('ui-datepicker-other-month')) {
|
||||
return getOtherMonthDate($dateCell, month, year);
|
||||
}
|
||||
|
||||
var day = parseInt($dateCell.children('a,span').text());
|
||||
|
||||
return new Date(year, month, day);
|
||||
}
|
||||
|
||||
function getOtherMonthDate($dateCell, month, year) {
|
||||
var date;
|
||||
|
||||
var $row = $dateCell.parent();
|
||||
var $rowCells = $row.children('td');
|
||||
|
||||
// if in the first row, the date cell is before the current month
|
||||
if ($row.is(':first-child')) {
|
||||
var $firstDateInMonth = $row.children('td:not(.ui-datepicker-other-month)').first();
|
||||
|
||||
date = getCellDate($firstDateInMonth, month, year);
|
||||
date.setDate($rowCells.index($dateCell) - $rowCells.index($firstDateInMonth) + 1);
|
||||
return date;
|
||||
}
|
||||
|
||||
// the date cell is after the current month
|
||||
var $lastDateInMonth = $row.children('td:not(.ui-datepicker-other-month)').last();
|
||||
|
||||
date = getCellDate($lastDateInMonth, month, year);
|
||||
date.setDate(date.getDate() + $rowCells.index($dateCell) - $rowCells.index($lastDateInMonth));
|
||||
return date;
|
||||
}
|
||||
|
||||
function enableDisableMonthDropdown() {
|
||||
element.find('.ui-datepicker-month').attr('disabled', scope.disableMonthDropdown);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,58 @@
|
||||
<div id="calendarRangeFrom">
|
||||
<h6>
|
||||
{{ 'General_DateRangeFrom'|translate }}
|
||||
<input
|
||||
type="text"
|
||||
id="inputCalendarFrom"
|
||||
name="inputCalendarFrom"
|
||||
class="browser-default"
|
||||
ng-class="{invalid: $ctrl.startDateInvalid}"
|
||||
ng-model="$ctrl.startDate"
|
||||
ng-change="$ctrl.onRangeInputChanged('from')"
|
||||
ng-keyup="$ctrl.handleEnterPress($event)"
|
||||
/>
|
||||
</h6>
|
||||
|
||||
<div
|
||||
id="calendarFrom"
|
||||
piwik-date-picker
|
||||
view-date="$ctrl.startDate"
|
||||
selected-date-start="$ctrl.fromPickerSelectedDates[0]"
|
||||
selected-date-end="$ctrl.fromPickerSelectedDates[1]"
|
||||
highlighted-date-start="$ctrl.fromPickerHighlightedDates[0]"
|
||||
highlighted-date-end="$ctrl.fromPickerHighlightedDates[1]"
|
||||
date-select="$ctrl.setStartRangeDate(date)"
|
||||
cell-hover="$ctrl.fromPickerHighlightedDates = $ctrl.getNewHighlightedDates(date, $cell)"
|
||||
cell-hover-leave="$ctrl.fromPickerHighlightedDates = null"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div id="calendarRangeTo">
|
||||
<h6>
|
||||
{{ 'General_DateRangeTo'|translate }}
|
||||
<input
|
||||
type="text"
|
||||
id="inputCalendarTo"
|
||||
name="inputCalendarTo"
|
||||
class="browser-default"
|
||||
ng-class="{invalid: $ctrl.endDateInvalid}"
|
||||
ng-model="$ctrl.endDate"
|
||||
ng-change="$ctrl.onRangeInputChanged('to')"
|
||||
ng-keyup="$ctrl.handleEnterPress($event)"
|
||||
/>
|
||||
</h6>
|
||||
|
||||
<div
|
||||
id="calendarTo"
|
||||
piwik-date-picker
|
||||
view-date="$ctrl.endDate"
|
||||
selected-date-start="$ctrl.toPickerSelectedDates[0]"
|
||||
selected-date-end="$ctrl.toPickerSelectedDates[1]"
|
||||
highlighted-date-start="$ctrl.toPickerHighlightedDates[0]"
|
||||
highlighted-date-end="$ctrl.toPickerHighlightedDates[1]"
|
||||
date-select="$ctrl.setEndRangeDate(date)"
|
||||
cell-hover="$ctrl.toPickerHighlightedDates = $ctrl.getNewHighlightedDates(date, $cell)"
|
||||
cell-hover-leave="$ctrl.toPickerHighlightedDates = null"
|
||||
>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,156 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Combines two jquery UI datepickers to provide a date range picker (that picks inclusive
|
||||
* ranges).
|
||||
*
|
||||
* Properties:
|
||||
* - startDate: The start of the date range. Should be a string in the YYYY-MM-DD format.
|
||||
* - endDate: The end of the date range. Should be a string in the YYYY-MM-DD format. Note:
|
||||
* date ranges are inclusive.
|
||||
* - rangeChange: Called when one or both dates bounding the range change. If the dates are
|
||||
* in an invalid state, the date will be null in this event.
|
||||
* - submit: Called if the 'enter' key is pressed in either of the inputs.
|
||||
*
|
||||
* Usage:
|
||||
* <piwik-date-range-picker>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').component('piwikDateRangePicker', {
|
||||
templateUrl: 'plugins/CoreHome/angularjs/date-range-picker/date-range-picker.component.html?cb=' + piwik.cacheBuster,
|
||||
bindings: {
|
||||
startDate: '<',
|
||||
endDate: '<',
|
||||
rangeChange: '&',
|
||||
submit: '&'
|
||||
},
|
||||
controller: DateRangePickerController
|
||||
});
|
||||
|
||||
DateRangePickerController.$inject = [];
|
||||
|
||||
function DateRangePickerController() {
|
||||
var vm = this;
|
||||
|
||||
vm.fromPickerSelectedDates = null;
|
||||
vm.toPickerSelectedDates = null;
|
||||
vm.fromPickerHighlightedDates = null;
|
||||
vm.toPickerHighlightedDates = null;
|
||||
vm.startDateInvalid = false;
|
||||
vm.endDateInvalid = false;
|
||||
|
||||
vm.$onChanges = $onChanges;
|
||||
vm.setStartRangeDate = setStartRangeDate;
|
||||
vm.setEndRangeDate = setEndRangeDate;
|
||||
vm.onRangeInputChanged = onRangeInputChanged;
|
||||
vm.getNewHighlightedDates = getNewHighlightedDates;
|
||||
vm.handleEnterPress = handleEnterPress;
|
||||
|
||||
function $onChanges(changes) {
|
||||
if (changes.startDate) {
|
||||
setStartRangeDateFromStr(vm.startDate);
|
||||
}
|
||||
|
||||
if (changes.endDate) {
|
||||
setEndRangeDateFromStr(vm.endDate);
|
||||
}
|
||||
}
|
||||
|
||||
function onRangeInputChanged(source) {
|
||||
if (source === 'from') {
|
||||
setStartRangeDateFromStr(vm.startDate);
|
||||
} else {
|
||||
setEndRangeDateFromStr(vm.endDate);
|
||||
}
|
||||
}
|
||||
|
||||
function setStartRangeDateFromStr(dateStr) {
|
||||
vm.startDateInvalid = true;
|
||||
|
||||
var startDateParsed;
|
||||
try {
|
||||
startDateParsed = $.datepicker.parseDate('yy-mm-dd', dateStr);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (startDateParsed) {
|
||||
vm.fromPickerSelectedDates = [startDateParsed, startDateParsed];
|
||||
vm.startDateInvalid = false;
|
||||
}
|
||||
|
||||
rangeChanged();
|
||||
}
|
||||
|
||||
function setEndRangeDateFromStr(dateStr) {
|
||||
vm.endDateInvalid = true;
|
||||
|
||||
var endDateParsed;
|
||||
try {
|
||||
endDateParsed = $.datepicker.parseDate('yy-mm-dd', dateStr);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (endDateParsed) {
|
||||
vm.toPickerSelectedDates = [endDateParsed, endDateParsed];
|
||||
vm.endDateInvalid = false;
|
||||
}
|
||||
|
||||
rangeChanged();
|
||||
}
|
||||
|
||||
function handleEnterPress($event) {
|
||||
if ($event.keyCode !== 13 || !vm.submit) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm.submit({
|
||||
start: vm.startDate,
|
||||
end: vm.endDate
|
||||
});
|
||||
}
|
||||
|
||||
function setStartRangeDate(date) {
|
||||
vm.startDateInvalid = false;
|
||||
vm.startDate = $.datepicker.formatDate('yy-mm-dd', date);
|
||||
|
||||
vm.fromPickerSelectedDates = [date, date];
|
||||
|
||||
rangeChanged();
|
||||
}
|
||||
|
||||
function setEndRangeDate(date) {
|
||||
vm.endDateInvalid = false;
|
||||
vm.endDate = $.datepicker.formatDate('yy-mm-dd', date);
|
||||
|
||||
vm.toPickerSelectedDates = [date, date];
|
||||
|
||||
rangeChanged();
|
||||
}
|
||||
|
||||
function rangeChanged() {
|
||||
if (!vm.rangeChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm.rangeChange({
|
||||
start: vm.startDateInvalid ? null : vm.startDate,
|
||||
end: vm.endDateInvalid ? null : vm.endDate
|
||||
});
|
||||
}
|
||||
|
||||
function getNewHighlightedDates(date, $cell) {
|
||||
if ($cell.hasClass('ui-datepicker-unselectable')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [date, date];
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,3 @@
|
||||
piwik-date-range-picker {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* AngularJS service that handles the popover query parameter for Piwik's angular code.
|
||||
*
|
||||
* If the popover parameter's first part is the name of an existing AngularJS directive,
|
||||
* a dialog is created using ngDialog with the contents being an element with that directive.
|
||||
* The other parts of the parameter are treated as attributes for the element, eg,
|
||||
* `"mydirective:myparam=val:myotherparam=val2"`.
|
||||
*
|
||||
* It should not be necessary to use this service directly, instead the piwik-dialogtoggler
|
||||
* directive should be used.
|
||||
*
|
||||
* TODO: popover as a query parameter refers less to dialogs and more to any popup window
|
||||
* (ie, not necessarily modal). should replace it w/ 'dialog' or maybe 'modal'.
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').factory('piwikDialogtogglerUrllistener', piwikDialogtogglerUrllistener);
|
||||
|
||||
piwikDialogtogglerUrllistener.$inject = ['$rootScope', '$location', '$injector', '$rootElement', 'ngDialog'];
|
||||
|
||||
function piwikDialogtogglerUrllistener($rootScope, $location, $injector, $rootElement, ngDialog) {
|
||||
var service = {},
|
||||
dialogQueryParamName = 'popover';
|
||||
|
||||
function getHtmlFromDialogQueryParam(paramValue) {
|
||||
var info = paramValue.split(':'),
|
||||
directiveName = info.shift(),
|
||||
dialogContent = '';
|
||||
|
||||
dialogContent += '<div ' + directiveName;
|
||||
angular.forEach(info, function (argumentAssignment) {
|
||||
var pair = argumentAssignment.split('='),
|
||||
key = pair[0],
|
||||
value = pair[1];
|
||||
dialogContent += ' ' + key + '="' + decodeURIComponent(value) + '"';
|
||||
});
|
||||
dialogContent += '/>';
|
||||
|
||||
return dialogContent;
|
||||
}
|
||||
|
||||
function directiveExists(directiveAttributeString) {
|
||||
// NOTE: directiveNormalize is not exposed by angularjs and the devs don't seem to want to expose it:
|
||||
// https://github.com/angular/angular.js/issues/7955
|
||||
// so logic is duplicated here.
|
||||
var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i,
|
||||
directiveName = angular.element.camelCase(directiveAttributeString.replace(PREFIX_REGEXP, ''));
|
||||
|
||||
return $injector.has(directiveName + 'Directive');
|
||||
}
|
||||
|
||||
service.checkUrlForDialog = function () {
|
||||
var dialogParamValue = $location.search()[dialogQueryParamName];
|
||||
if (dialogParamValue && directiveExists(dialogParamValue)) {
|
||||
var dialog = ngDialog.open({
|
||||
template: getHtmlFromDialogQueryParam(dialogParamValue),
|
||||
plain: true,
|
||||
className: ''
|
||||
});
|
||||
|
||||
dialog.closePromise.then(function () {
|
||||
$location.search(dialogQueryParamName, null);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
service.propagatePersistedDialog = function (directive, attributes) {
|
||||
var paramValue = directive;
|
||||
angular.forEach(attributes, function (value, name) {
|
||||
paramValue += ':' + name + '=' + encodeURIComponent(value);
|
||||
});
|
||||
|
||||
$location.search(dialogQueryParamName, paramValue);
|
||||
};
|
||||
|
||||
$rootScope.$on('$locationChangeSuccess', function () {
|
||||
service.checkUrlForDialog();
|
||||
});
|
||||
|
||||
service.checkUrlForDialog(); // check on initial page load
|
||||
|
||||
return service;
|
||||
}
|
||||
})();
|
@ -0,0 +1,68 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller for the piwikDialogToggler directive. Adds a couple methods to the
|
||||
* scope allowing elements to open and close dialogs.
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('DialogTogglerController', DialogTogglerController);
|
||||
|
||||
DialogTogglerController.$inject = ['$scope', 'piwik', 'ngDialog', 'piwikDialogtogglerUrllistener'];
|
||||
|
||||
function DialogTogglerController($scope, piwik, ngDialog, piwikDialogtogglerUrllistener) {
|
||||
/**
|
||||
* Open a new dialog window using ngDialog.
|
||||
*
|
||||
* @param {object|string} contentsInfo If an object, it is assumed to be ngDialog open(...) config and is
|
||||
* passed to ngDialog.open unaltered.
|
||||
* If a string that beings with '#', we assume it is an ID of an element
|
||||
* with the dialog contents. (Note: ngDialog doesn't appear to support arbitrary
|
||||
* selectors).
|
||||
* If a string that ends with .html, we assume it is a link to a an angular
|
||||
* template.
|
||||
* Otherwise we assume it is a raw angular
|
||||
* @return {object} Returns the result of ngDialog.open. Can be used to close the dialog or listen for
|
||||
* when the dialog is closed.
|
||||
*/
|
||||
$scope.open = function (contentsInfo) {
|
||||
var ngDialogInfo;
|
||||
if (typeof(contentsInfo) == 'object') { // is info to pass directly to ngDialog
|
||||
ngDialogInfo = contentsInfo;
|
||||
} else if (contentsInfo.substr(0, 1) == '#') { // is ID of an element
|
||||
ngDialogInfo = {template: contentsInfo.substr(1)};
|
||||
} else if (contentsInfo.substr(-4) == '.html') { // is a link to an .html file
|
||||
ngDialogInfo = {template: contentsInfo};
|
||||
} else { // is a raw HTML string
|
||||
ngDialogInfo = {template: contentsInfo, plain: true};
|
||||
}
|
||||
|
||||
return ngDialog.open(ngDialogInfo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a persisted dialog. Persisted dialogs are dialogs that will be launched on reload
|
||||
* of the current URL. They are accomplished by modifying the URL and adding a 'popover'
|
||||
* query parameter.
|
||||
*
|
||||
* @param {string} directive The denormalized name of an angularjs directive. An element with
|
||||
* this directive will be the contents of the dialog.
|
||||
* @param {object} attributes Key value mapping of the HTML attributes to add to the dialog's
|
||||
* contents element.
|
||||
*/
|
||||
$scope.persist = function (directive, attributes) {
|
||||
piwikDialogtogglerUrllistener.propagatePersistedDialog(directive, attributes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the currently open dialog window.
|
||||
*/
|
||||
$scope.close = function () {
|
||||
ngDialog.close();
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,30 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Directive for an element (such as a link) that creates and/or closes dialogs.
|
||||
*
|
||||
* Usage:
|
||||
* <a piwik-dialogtoggler href="#" ng-click="open(...)" />
|
||||
*
|
||||
* or:
|
||||
*
|
||||
* <div piwik-dialogtoggler>
|
||||
* <a href="#" ng-click="open(...)">Open</a>
|
||||
* <a href="#" ng-click="close()">Close</a>
|
||||
* </div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikDialogtoggler', piwikDialogtoggler);
|
||||
|
||||
function piwikDialogtoggler() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
controller: 'DialogTogglerController'
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,70 @@
|
||||
.ngdialog {
|
||||
position:absolute;
|
||||
}
|
||||
|
||||
.ngdialog-overlay {
|
||||
opacity: 0.6;
|
||||
background: none #000;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.ngdialog-content {
|
||||
z-index: 1001;
|
||||
width: 950px;
|
||||
border-radius: 4px;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
background-color: @theme-color-background-base;
|
||||
padding: 1em 18px;
|
||||
position: relative;
|
||||
top: 100px;
|
||||
|
||||
h2:first-of-type {
|
||||
line-height:24px;
|
||||
padding:0 0 1em;
|
||||
}
|
||||
}
|
||||
|
||||
// remove some ngdialog animations (the remaining one is required for closing the dialog)
|
||||
.ngdialog-overlay, .ngdialog.ngdialog-closing .ngdialog-overlay,.ngdialog-content {
|
||||
-webkit-animation: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.ngdialog-close {
|
||||
// close button should be styled the same as other buttons
|
||||
.submit;
|
||||
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
top: 18px;
|
||||
width: 21px;
|
||||
margin: 0 0 0 0;
|
||||
height: 20px;
|
||||
|
||||
&:before {
|
||||
font-family:inherit;
|
||||
content:'';
|
||||
|
||||
display:inline-block;
|
||||
|
||||
// center in div
|
||||
position:absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -8px;
|
||||
margin-left: -8px;
|
||||
|
||||
// from jquery-ui css
|
||||
background-image: url(libs/jquery/themes/base/images/ui-icons_888888_256x240.png);
|
||||
background-position: -96px -128px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity:0.5;
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
background-image: url(libs/jquery/themes/base/images/ui-icons_454545_256x240.png);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* A materializecss dropdown menu that supports submenus.
|
||||
*
|
||||
* To use a submenu, just use this directive within another dropdown.
|
||||
*
|
||||
* Note: if submenus are used, then dropdowns will never scroll.
|
||||
*
|
||||
* Usage:
|
||||
* <a class='dropdown-trigger btn' href='' data-activates='mymenu' piwik-dropdown-menu>Menu</a>
|
||||
* <ul id='mymenu' class='dropdown-content'>
|
||||
* <li>
|
||||
* <a class='dropdown-trigger' data-activates="mysubmenu" piwik-dropdown-menu>Submenu</a>
|
||||
* <ul id="mysubmenu" class="dropdown-content">
|
||||
* <li>Submenu Item</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>
|
||||
* <a href="">Another item</a>
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikDropdownMenu', piwikDropdownMenu);
|
||||
|
||||
piwikDropdownMenu.$inject = ['$timeout'];
|
||||
|
||||
function piwikDropdownMenu($timeout){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
var options = {};
|
||||
|
||||
var isSubmenu = !! element.parent().closest('.dropdown-content').length;
|
||||
if (isSubmenu) {
|
||||
options = { hover: true };
|
||||
element.addClass('submenu');
|
||||
angular.element('#' + attrs.activates).addClass('submenu-dropdown-content');
|
||||
|
||||
// if a submenu is used, the dropdown will never scroll
|
||||
element.parents('.dropdown-content').addClass('submenu-container');
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
element.dropdown(options);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,27 @@
|
||||
[piwik-dropdown-menu] {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "▼";
|
||||
font-size: .7em;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&.submenu::after {
|
||||
float: right;
|
||||
content: "►";
|
||||
color: @color-black-piwik;
|
||||
font-size: .6em;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-dropdown-content.dropdown-content {
|
||||
left: 100% !important;
|
||||
}
|
||||
|
||||
.submenu-container.dropdown-content {
|
||||
overflow:visible; // required for submenus to display
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<div class="enrichedHeadline"
|
||||
ng-mouseenter="view.showIcons=true" ng-mouseleave="view.showIcons=false">
|
||||
<div ng-show="!editUrl" class="title" ng-transclude tabindex="6"></div>
|
||||
<a ng-show="editUrl" class="title" href="{{ editUrl }}"
|
||||
title="{{ 'CoreHome_ClickToEditX'|translate:(featureName|escape) }}"
|
||||
ng-transclude ></a>
|
||||
|
||||
<span ng-show="view.showIcons || view.showInlineHelp" class="iconsBar">
|
||||
<a ng-if="helpUrl && !inlineHelp"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
href="{{ helpUrl }}"
|
||||
title="{{ 'CoreHome_ExternalHelp'|translate }}"
|
||||
class="helpIcon"><span class="icon-help"></span></a>
|
||||
|
||||
<a ng-if="inlineHelp"
|
||||
title="{{ 'General_Help'|translate }}"
|
||||
ng-click="view.showInlineHelp=!view.showInlineHelp"
|
||||
class="helpIcon" ng-class="{ 'active': view.showInlineHelp }"><span class="icon-help"></span></a>
|
||||
|
||||
<div class="ratingIcons"
|
||||
piwik-rate-feature
|
||||
title="{{ featureName }}"></div>
|
||||
</span>
|
||||
|
||||
<div class="inlineHelp" ng-show="view.showInlineHelp">
|
||||
<div ng-bind-html="inlineHelp"></div>
|
||||
<a ng-if="helpUrl"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
href="{{ helpUrl }}"
|
||||
class="readMore">{{ 'General_MoreDetails'|translate }}</a>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,84 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
*
|
||||
* <h2 piwik-enriched-headline>All Websites Dashboard</h2>
|
||||
* -> uses "All Websites Dashboard" as featurename
|
||||
*
|
||||
* <h2 piwik-enriched-headline feature-name="All Websites Dashboard">All Websites Dashboard (Total: 309 Visits)</h2>
|
||||
* -> custom featurename
|
||||
*
|
||||
* <h2 piwik-enriched-headline help-url="http://piwik.org/guide">All Websites Dashboard</h2>
|
||||
* -> shows help icon and links to external url
|
||||
*
|
||||
* <h2 piwik-enriched-headline edit-url="index.php?module=Foo&action=bar&id=4">All Websites Dashboard</h2>
|
||||
* -> makes the headline clickable linking to the specified url
|
||||
*
|
||||
* <h2 piwik-enriched-headline inline-help="inlineHelp">Pages report</h2>
|
||||
* -> inlineHelp specified via a attribute shows help icon on headline hover
|
||||
*
|
||||
* <h2 piwik-enriched-headline>All Websites Dashboard
|
||||
* <div class="inlineHelp">My <strong>inline help</strong></div>
|
||||
* </h2>
|
||||
* -> alternative definition for inline help
|
||||
* -> shows help icon to display inline help on click. Note: You can combine inlinehelp and help-url
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikEnrichedHeadline', piwikEnrichedHeadline);
|
||||
|
||||
piwikEnrichedHeadline.$inject = ['$document', 'piwik', '$filter'];
|
||||
|
||||
function piwikEnrichedHeadline($document, piwik, $filter){
|
||||
var defaults = {
|
||||
helpUrl: '',
|
||||
editUrl: ''
|
||||
};
|
||||
|
||||
return {
|
||||
transclude: true,
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
helpUrl: '@',
|
||||
editUrl: '@',
|
||||
featureName: '@',
|
||||
inlineHelp: '@?'
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.directive.html?cb=' + piwik.cacheBuster,
|
||||
compile: function (element, attrs) {
|
||||
|
||||
for (var index in defaults) {
|
||||
if (!attrs[index]) { attrs[index] = defaults[index]; }
|
||||
}
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
if (!scope.inlineHelp) {
|
||||
|
||||
var helpNode = $('[ng-transclude] .inlineHelp', element);
|
||||
|
||||
if ((!helpNode || !helpNode.length) && element.next()) {
|
||||
// hack for reports :(
|
||||
helpNode = element.next().find('.reportDocumentation');
|
||||
}
|
||||
|
||||
if (helpNode && helpNode.length) {
|
||||
if ($.trim(helpNode.text())) {
|
||||
scope.inlineHelp = $.trim(helpNode.html());
|
||||
}
|
||||
helpNode.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (!attrs.featureName) {
|
||||
attrs.featureName = $.trim(element.find('.title').first().text());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,63 @@
|
||||
.inlineHelp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[piwik-enriched-headline] {
|
||||
visibility: hidden;
|
||||
height: 47px;
|
||||
}
|
||||
|
||||
[piwik-enriched-headline].ng-isolate-scope {
|
||||
visibility: visible;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.enrichedHeadline {
|
||||
min-height: 22px;
|
||||
|
||||
a.title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title {
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.inlineHelp {
|
||||
display: block;
|
||||
background: #F7F7F7;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
border: 1px solid #E4E5E4;
|
||||
margin: 10px 0 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 2px;
|
||||
|
||||
.readMore {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.iconsBar {
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
.ratingIcons {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.helpIcon {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0 0 -1px 4px;
|
||||
opacity: 0.3;
|
||||
font-size: 15px;
|
||||
color: @theme-color-text-light;
|
||||
&:hover, &.active {
|
||||
opacity: 0.9;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 350 B |
@ -0,0 +1,71 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('FieldArrayController', FieldArrayController);
|
||||
|
||||
FieldArrayController.$inject = ['$scope'];
|
||||
|
||||
function FieldArrayController($scope){
|
||||
|
||||
function getTemplate(field) {
|
||||
var control = field.uiControl;
|
||||
if (control === 'password' || control === 'url' || control === 'search' || control === 'email') {
|
||||
control = 'text'; // we use same template for text and password both
|
||||
}
|
||||
|
||||
var file = 'field-' + control;
|
||||
var fieldsSupportingArrays = ['textarea', 'checkbox', 'text'];
|
||||
if (field.type === 'array' && fieldsSupportingArrays.indexOf(control) !== -1) {
|
||||
file += '-array';
|
||||
}
|
||||
|
||||
return 'plugins/CorePluginsAdmin/angularjs/form-field/' + file + '.html?cb=' + piwik.cacheBuster;
|
||||
}
|
||||
|
||||
if ($scope.field && !$scope.field.templateFile) {
|
||||
$scope.field.templateFile = getTemplate($scope.field);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
$scope.$watch('formValue', function () {
|
||||
if (!$scope.formValue || !$scope.formValue.length) {
|
||||
self.addEntry();
|
||||
} else {
|
||||
self.onEntryChange();
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.onEntryChange = function () {
|
||||
var hasAny = true;
|
||||
angular.forEach($scope.formValue, function (entry) {
|
||||
if (!entry) {
|
||||
hasAny = false;
|
||||
}
|
||||
});
|
||||
if (hasAny) {
|
||||
this.addEntry();
|
||||
}
|
||||
};
|
||||
|
||||
this.addEntry = function () {
|
||||
if (angular.isArray($scope.formValue)) {
|
||||
$scope.formValue.push('');
|
||||
}
|
||||
};
|
||||
|
||||
this.removeEntry = function (index) {
|
||||
if (index > -1) {
|
||||
$scope.formValue.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
if (!$scope.formValue || !$scope.formValue.length) {
|
||||
this.addEntry();
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,22 @@
|
||||
<div class="fieldArray form-group">
|
||||
<div ng-repeat="item in formValue track by $index"
|
||||
class="fieldArrayTable fieldArrayTable{{ $index }} multiple valign-wrapper">
|
||||
|
||||
<div piwik-field uicontrol="{{ field.uiControl }}"
|
||||
title="{{ field.title }}"
|
||||
full-width="true"
|
||||
ng-if="field.templateFile"
|
||||
template-file="{{ field.templateFile }}"
|
||||
class="fieldUiControl"
|
||||
ng-model="formValue[$index]"
|
||||
options="field.availableValues"
|
||||
ng-change="fieldArray.onEntryChange()"
|
||||
placeholder=" ">
|
||||
</div>
|
||||
|
||||
<span ng-click="fieldArray.removeEntry($index)"
|
||||
title="{{ 'General_Remove'|translate }}"
|
||||
ng-hide="($index + 1) == (formValue|length)"
|
||||
class="icon-minus valign"></span>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,60 @@
|
||||
/*!
|
||||
* Matomo - free/libre analytics platform
|
||||
*
|
||||
* @link https://matomo.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div matomo-field-array field=".." ng-model="...">
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('matomoFieldArray', matomoFieldArray);
|
||||
|
||||
matomoFieldArray.$inject = ['$document', 'piwik', '$filter'];
|
||||
|
||||
function matomoFieldArray($document, piwik, $filter){
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
field: '='
|
||||
},
|
||||
require: "?ngModel",
|
||||
templateUrl: 'plugins/CoreHome/angularjs/field-array/field-array.directive.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'FieldArrayController',
|
||||
controllerAs: 'fieldArray',
|
||||
compile: function (element, attrs) {
|
||||
|
||||
return function (scope, element, attrs, ngModel) {
|
||||
|
||||
if (ngModel) {
|
||||
ngModel.$setViewValue(scope.formValue);
|
||||
}
|
||||
|
||||
scope.$watch('formValue', function (newValue, oldValue) {
|
||||
if (newValue != oldValue) {
|
||||
element.trigger('change', newValue);
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (ngModel) {
|
||||
ngModel.$render = function() {
|
||||
if (angular.isString(ngModel.$viewValue)) {
|
||||
scope.formValue = JSON.parse(ngModel.$viewValue);
|
||||
} else {
|
||||
scope.formValue = ngModel.$viewValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
scope.$watch('formValue', function (newValue, oldValue) {
|
||||
if (ngModel) {
|
||||
ngModel.$setViewValue(newValue);
|
||||
}
|
||||
}, true);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,17 @@
|
||||
.fieldArray {
|
||||
margin-top: 40px !important;
|
||||
|
||||
.form-group.row {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.fieldUiControl {
|
||||
width: ~"calc(100% - 60px)";
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
|
||||
.icon-minus {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* History service. Provides the ability to change the window hash, and makes sure broadcast.pageload
|
||||
* is called on every change.
|
||||
*
|
||||
* This service replaces the previously used jQuery history extension.
|
||||
*
|
||||
* Should only be used by the broadcast object.
|
||||
*
|
||||
* @deprecated in 3.2.2, will be removed in Piwik 4
|
||||
*/
|
||||
(function (window, $, broadcast) {
|
||||
angular.module('piwikApp').service('historyService', historyService);
|
||||
|
||||
historyService.$inject = ['$location', '$rootScope'];
|
||||
|
||||
function historyService($location, $rootScope) {
|
||||
var service = {};
|
||||
service.load = load;
|
||||
service.init = init;
|
||||
return service;
|
||||
|
||||
function init() {
|
||||
if ($location.path() != '/') {
|
||||
changePathToSearch();
|
||||
}
|
||||
|
||||
$rootScope.$on('$locationChangeSuccess', function () {
|
||||
loadCurrentPage();
|
||||
});
|
||||
|
||||
loadCurrentPage();
|
||||
}
|
||||
|
||||
// currently, the AJAX content URL is stored in $location.search(), but before it was stored in $location.path().
|
||||
// this function makes sure URLs like http://piwik.net/?...#/module=Whatever&action=whatever still work.
|
||||
function changePathToSearch() {
|
||||
var path = $location.path();
|
||||
if (!path || path == '/') {
|
||||
return;
|
||||
}
|
||||
|
||||
var searchParams = broadcast.getValuesFromUrl('?' + path.substring(1));
|
||||
// NOTE: we don't need to decode the parameters since $location.path() will decode the string itself
|
||||
|
||||
$location.search(searchParams);
|
||||
$location.path('');
|
||||
}
|
||||
|
||||
function loadCurrentPage() {
|
||||
var searchObject = $location.search(),
|
||||
searchString = [];
|
||||
for (var name in searchObject) {
|
||||
if (!searchObject.hasOwnProperty(name) || name == '_') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if more than one query parameter of the same name is supplied, angular will return all of them as
|
||||
// an array. we only want to use the last one, though.
|
||||
if (searchObject[name] instanceof Array) {
|
||||
searchObject[name] = searchObject[name][searchObject[name].length - 1];
|
||||
}
|
||||
|
||||
var value = searchObject[name];
|
||||
if (name != 'columns') { // the columns query parameter is not urldecoded in PHP code. TODO: this should be fixed in 3.0
|
||||
value = encodeURIComponent(value);
|
||||
}
|
||||
|
||||
searchString.push(name + '=' + value);
|
||||
}
|
||||
searchString = searchString.join('&');
|
||||
|
||||
// the location hash will have a / prefix, which broadcast.pageload doesn't want
|
||||
broadcast.pageload(searchString);
|
||||
}
|
||||
|
||||
function load(hash) {
|
||||
// make sure the hash is just the query parameter values, w/o a starting #, / or ? char. broadcast.pageload & $location.path should get neither
|
||||
hash = normalizeHash(hash);
|
||||
|
||||
var currentHash = normalizeHash(location.hash);
|
||||
if (currentHash === hash) {
|
||||
loadCurrentPage(); // it would not trigger a location change success event as URL is the same, call it manually
|
||||
} else if (hash) {
|
||||
$location.search(hash);
|
||||
} else {
|
||||
// NOTE: this works around a bug in angularjs. when unsetting the hash (ie, removing in the URL),
|
||||
// angular will enter an infinite loop of digests. this is because $locationWatch will trigger
|
||||
// $locationChangeStart if $browser.url() != $location.absUrl(), and $browser.url() will contain
|
||||
// the '#' character and $location.absUrl() will not. so the watch continues to trigger the event.
|
||||
$location.search('_=');
|
||||
}
|
||||
|
||||
setTimeout(function () { $rootScope.$apply(); }, 1);
|
||||
}
|
||||
|
||||
function normalizeHash(hash) {
|
||||
var chars = ['#', '/', '?'];
|
||||
for (var i = 0; i != chars.length; ++i) {
|
||||
var charToRemove = chars[i];
|
||||
if (hash.charAt(0) == charToRemove) {
|
||||
hash = hash.substring(1);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
})(window, jQuery, broadcast);
|
52
msd2/tracking/piwik/plugins/CoreHome/angularjs/http404check.js
vendored
Normal file
52
msd2/tracking/piwik/plugins/CoreHome/angularjs/http404check.js
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
(function () {
|
||||
angular.module('piwikApp').factory('http404CheckInterceptor', http404CheckInterceptor);
|
||||
|
||||
http404CheckInterceptor.$inject = ['$q', 'globalAjaxQueue'];
|
||||
|
||||
function http404CheckInterceptor($q, globalAjaxQueue) {
|
||||
|
||||
function isClientError(rejection)
|
||||
{
|
||||
if (rejection.status === 500) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return rejection.status >= 400 && rejection.status < 408;
|
||||
}
|
||||
|
||||
return {
|
||||
'responseError': function(rejection) {
|
||||
|
||||
if (rejection &&
|
||||
isClientError(rejection) &&
|
||||
rejection.config &&
|
||||
rejection.config.url &&
|
||||
-1 !== rejection.config.url.indexOf('.html') &&
|
||||
-1 !== rejection.config.url.indexOf('plugins')) {
|
||||
|
||||
var posEndUrl = rejection.config.url.indexOf('.html') + 5;
|
||||
var url = rejection.config.url.substr(0, posEndUrl);
|
||||
|
||||
var message = 'Please check your server configuration. You may want to whitelist "*.html" files from the "plugins" directory.';
|
||||
message += ' The HTTP status code is ' + rejection.status + ' for URL "' + url + '"';
|
||||
|
||||
var UI = require('piwik/UI');
|
||||
var notification = new UI.Notification();
|
||||
notification.show(message, {
|
||||
title: 'Failed to load HTML file:',
|
||||
context: 'error',
|
||||
id: 'Network_HtmlFileLoadingError'
|
||||
});
|
||||
}
|
||||
|
||||
return $q.reject(rejection);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('piwikApp').config(['$httpProvider',function($httpProvider) {
|
||||
$httpProvider.interceptors.push('http404CheckInterceptor');
|
||||
}]);
|
||||
|
||||
|
||||
})();
|
@ -0,0 +1,33 @@
|
||||
<div piwik-focus-anywhere-but-here="view.showItems=false" class="menuDropdown">
|
||||
|
||||
<span class="title"
|
||||
ng-click="view.showItems=!view.showItems"
|
||||
title="{{ tooltip }}">
|
||||
<span ng-bind-html="menuTitle"></span>
|
||||
<span class="icon-arrow-bottom"></span>
|
||||
</span>
|
||||
|
||||
<div class="items" ng-show="view.showItems">
|
||||
<div class="search" ng-if="showSearch && view.showItems">
|
||||
<input type="text"
|
||||
piwik-focus-if="view.showItems"
|
||||
ng-model="view.searchTerm"
|
||||
placeholder="{{ 'General_Search'|translate }}"
|
||||
ng-change="searchItems(view.searchTerm)">
|
||||
|
||||
<img title="{{ 'General_Search'|translate }}"
|
||||
ng-show="!view.searchTerm"
|
||||
class="search_ico"
|
||||
src="plugins/Morpheus/images/search_ico.png"/>
|
||||
<img title="{{ 'General_Clear'|translate }}"
|
||||
ng-show="view.searchTerm"
|
||||
ng-click="view.searchTerm='';searchItems('')"
|
||||
class="reset"
|
||||
src="plugins/CoreHome/images/reset_search.png"/>
|
||||
</div>
|
||||
<div ng-transclude ng-click="selectItem($event)">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -0,0 +1,76 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-menudropdown menu-title="MyMenuItem" tooltip="My Tooltip" show-search="false">
|
||||
* <a class="item" href="/url">An Item</a>
|
||||
* <a class="item disabled">Disabled</a>
|
||||
* <a class="item active">Active item</a>
|
||||
* <hr class="item separator"/>
|
||||
* <a class="item disabled category">Category</a>
|
||||
* <a class="item" href="/url"></a>
|
||||
* </div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikMenudropdown', piwikMenudropdown);
|
||||
|
||||
function piwikMenudropdown(){
|
||||
|
||||
return {
|
||||
transclude: true,
|
||||
replace: true,
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
menuTitle: '@',
|
||||
tooltip: '@',
|
||||
showSearch: '=',
|
||||
menuTitleChangeOnClick: '='
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/menudropdown/menudropdown.directive.html?cb=' + piwik.cacheBuster,
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
scope.selectItem = function (event) {
|
||||
var $self = angular.element(event.target);
|
||||
|
||||
if (!$self.hasClass('item') || $self.hasClass('disabled') || $self.hasClass('separator')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope.menuTitleChangeOnClick !== false) {
|
||||
scope.menuTitle = $self.text().replace(/[\u0000-\u2666]/g, function(c) {
|
||||
return '&#'+c.charCodeAt(0)+';';
|
||||
});
|
||||
}
|
||||
scope.$eval('view.showItems = false');
|
||||
|
||||
setTimeout(function () {
|
||||
scope.$apply();
|
||||
}, 0);
|
||||
|
||||
element.find('.item').removeClass('active');
|
||||
$self.addClass('active');
|
||||
};
|
||||
|
||||
scope.searchItems = function (searchTerm)
|
||||
{
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
|
||||
element.find('.item').each(function (index, node) {
|
||||
var $node = angular.element(node);
|
||||
|
||||
if (-1 === $node.text().toLowerCase().indexOf(searchTerm)) {
|
||||
$node.hide();
|
||||
} else {
|
||||
$node.show();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,90 @@
|
||||
|
||||
.menuDropdown {
|
||||
display: inline-block;
|
||||
padding-right: 14px;
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.items {
|
||||
z-index: 200;
|
||||
position: absolute;
|
||||
border: 1px solid @color-silver-l80;
|
||||
background: @theme-color-background-contrast;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 !important;
|
||||
|
||||
.search {
|
||||
margin: 15px 6px 10px 6px;
|
||||
display: block;
|
||||
|
||||
.search_ico {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 27px;
|
||||
margin: 0px;
|
||||
left: initial;
|
||||
}
|
||||
.reset {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
right: 25px;
|
||||
left: initial;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
color: @theme-color-text !important;
|
||||
text-decoration: none !important;
|
||||
padding: 6px 25px 6px 6px !important;
|
||||
font-size: 11px;
|
||||
float: none;
|
||||
text-align: left;
|
||||
line-height: 30px;
|
||||
|
||||
&:hover {
|
||||
background: @theme-color-background-tinyContrast;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: @theme-color-background-tinyContrast;
|
||||
}
|
||||
|
||||
&.category {
|
||||
color: @theme-color-text-light !important
|
||||
}
|
||||
|
||||
&.separator {
|
||||
padding: 0px !important;
|
||||
border-bottom: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
&.separator,
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background: @theme-color-background-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('MultiPairFieldController', MultiPairFieldController);
|
||||
|
||||
MultiPairFieldController.$inject = ['$scope'];
|
||||
|
||||
function MultiPairFieldController($scope){
|
||||
|
||||
function getTemplate(field) {
|
||||
var control = field.uiControl;
|
||||
if (control === 'password' || control === 'url' || control === 'search' || control === 'email') {
|
||||
control = 'text'; // we use same template for text and password both
|
||||
}
|
||||
|
||||
var file = 'field-' + control;
|
||||
var fieldsSupportingArrays = ['textarea', 'checkbox', 'text'];
|
||||
if (field.type === 'array' && fieldsSupportingArrays.indexOf(control) !== -1) {
|
||||
file += '-array';
|
||||
}
|
||||
|
||||
return 'plugins/CorePluginsAdmin/angularjs/form-field/' + file + '.html?cb=' + piwik.cacheBuster;
|
||||
}
|
||||
|
||||
if ($scope.field1 && !$scope.field1.templateFile) {
|
||||
$scope.field1.templateFile = getTemplate($scope.field1);
|
||||
}
|
||||
|
||||
if ($scope.field2 && !$scope.field2.templateFile) {
|
||||
$scope.field2.templateFile = getTemplate($scope.field2);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
$scope.$watch('formValue', function () {
|
||||
if (!$scope.formValue || !$scope.formValue.length) {
|
||||
self.addEntry();
|
||||
} else {
|
||||
self.onEntryChange();
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.onEntryChange = function () {
|
||||
var hasAny = true;
|
||||
angular.forEach($scope.formValue, function (table) {
|
||||
if (!table) {
|
||||
hasAny = false;
|
||||
return;
|
||||
}
|
||||
if ($scope.field1 && $scope.field2) {
|
||||
if (!table[$scope.field1.key] && !table[$scope.field2.key]) {
|
||||
hasAny = false;
|
||||
}
|
||||
} else if ($scope.field1) {
|
||||
if (!table[$scope.field1.key]) {
|
||||
hasAny = false;
|
||||
}
|
||||
} else if ($scope.field2) {
|
||||
if (!table[$scope.field2.key]) {
|
||||
hasAny = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (hasAny) {
|
||||
this.addEntry();
|
||||
}
|
||||
};
|
||||
|
||||
this.addEntry = function () {
|
||||
if (angular.isArray($scope.formValue)) {
|
||||
var obj = {};
|
||||
if ($scope.field1 && $scope.field1.key) {
|
||||
obj[$scope.field1.key] = '';
|
||||
}
|
||||
if ($scope.field2 && $scope.field2.key) {
|
||||
obj[$scope.field2.key] = '';
|
||||
}
|
||||
$scope.formValue.push(obj);
|
||||
}
|
||||
};
|
||||
|
||||
this.removeEntry = function (index) {
|
||||
if (index > -1) {
|
||||
$scope.formValue.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
if (!$scope.formValue || !$scope.formValue.length) {
|
||||
this.addEntry();
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,35 @@
|
||||
<div class="multiPairField form-group">
|
||||
<div ng-repeat="(index, item) in formValue"
|
||||
class="multiPairFieldTable multiPairFieldTable{{ index }} multiple valign-wrapper">
|
||||
|
||||
<div piwik-field uicontrol="{{ field1.uiControl }}"
|
||||
title="{{ field1.title }}"
|
||||
full-width="true"
|
||||
ng-if="field1.templateFile"
|
||||
template-file="{{ field1.templateFile }}"
|
||||
class="fieldUiControl1"
|
||||
ng-class="{'hasMultiFields': (field1.templateFile && field2.templateFile)}"
|
||||
ng-model="formValue[index][field1.key]"
|
||||
options="field1.availableValues"
|
||||
ng-change="multiPairField.onEntryChange()"
|
||||
placeholder=" ">
|
||||
</div>
|
||||
|
||||
<div piwik-field uicontrol="{{ field2.uiControl }}"
|
||||
title="{{ field2.title }}"
|
||||
full-width="true"
|
||||
ng-if="field2.templateFile"
|
||||
class="fieldUiControl2"
|
||||
template-file="{{ field2.templateFile }}"
|
||||
options="field2.availableValues"
|
||||
ng-change="multiPairField.onEntryChange()"
|
||||
ng-model="formValue[index][field2.key]"
|
||||
placeholder=" ">
|
||||
</div>
|
||||
|
||||
<span ng-click="multiPairField.removeEntry(index)"
|
||||
title="{{ 'General_Remove'|translate }}"
|
||||
ng-hide="(index + 1) == (formValue|length)"
|
||||
class="icon-minus valign"></span>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,61 @@
|
||||
/*!
|
||||
* Matomo - free/libre analytics platform
|
||||
*
|
||||
* @link https://matomo.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div matomo-multi-pair-field field1=".." field2="" ng-model="...">
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('matomoMultiPairField', matomoMultiPairField);
|
||||
|
||||
matomoMultiPairField.$inject = ['$document', 'piwik', '$filter'];
|
||||
|
||||
function matomoMultiPairField($document, piwik, $filter){
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
field1: '=',
|
||||
field2: '='
|
||||
},
|
||||
require: "?ngModel",
|
||||
templateUrl: 'plugins/CoreHome/angularjs/multipairfield/multipairfield.directive.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'MultiPairFieldController',
|
||||
controllerAs: 'multiPairField',
|
||||
compile: function (element, attrs) {
|
||||
|
||||
return function (scope, element, attrs, ngModel) {
|
||||
|
||||
if (ngModel) {
|
||||
ngModel.$setViewValue(scope.formValue);
|
||||
}
|
||||
|
||||
scope.$watch('formValue', function (newValue, oldValue) {
|
||||
if (newValue != oldValue) {
|
||||
element.trigger('change', newValue);
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (ngModel) {
|
||||
ngModel.$render = function() {
|
||||
if (angular.isString(ngModel.$viewValue)) {
|
||||
scope.formValue = JSON.parse(ngModel.$viewValue);
|
||||
} else {
|
||||
scope.formValue = ngModel.$viewValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
scope.$watch('formValue', function (newValue, oldValue) {
|
||||
if (ngModel) {
|
||||
ngModel.$setViewValue(newValue);
|
||||
}
|
||||
}, true);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,27 @@
|
||||
.multiPairField {
|
||||
margin-top: 40px !important;
|
||||
|
||||
.form-group.row {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.fieldUiControl1.hasMultiFields {
|
||||
width: 160px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.fieldUiControl1:not(.hasMultiFields) {
|
||||
width: ~"calc(100% - 60px)";
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
|
||||
.fieldUiControl2 {
|
||||
width: ~"calc(100% - 190px)";
|
||||
padding: 0.75rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.icon-minus {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('NotificationController', NotificationController);
|
||||
|
||||
NotificationController.$inject = ['piwikApi'];
|
||||
|
||||
function NotificationController(piwikApi) {
|
||||
/**
|
||||
* Marks a persistent notification as read so it will not reappear on the next page
|
||||
* load.
|
||||
*/
|
||||
this.markNotificationAsRead = function () {
|
||||
var notificationId = this.notificationId;
|
||||
if (!notificationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
piwikApi.post(
|
||||
{ // GET params
|
||||
module: 'CoreHome',
|
||||
action: 'markNotificationAsRead'
|
||||
},
|
||||
{ // POST params
|
||||
notificationId: notificationId
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,8 @@
|
||||
<div class="notification system">
|
||||
<button type="button" class="close" data-dismiss="alert" ng-if="!noclear" ng-click="notification.markNotificationAsRead()">×</button>
|
||||
<strong ng-if="title">{{ title }}</strong>
|
||||
|
||||
<!-- ng-transclude causes directive child elements to be added here -->
|
||||
<div ng-transclude></div>
|
||||
|
||||
</div>
|
@ -0,0 +1,98 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Directive to show a notification.
|
||||
*
|
||||
* Note: using this directive is preferred over the Notification class (which uses jquery
|
||||
* exclusively).
|
||||
*
|
||||
* Supports the following attributes:
|
||||
*
|
||||
* * **context**: Either 'success', 'error', 'info', 'warning'
|
||||
* * **type**: Either 'toast', 'persistent', 'transient'
|
||||
* * **noclear**: If truthy, no clear button is displayed. For persistent notifications, has no effect.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* <div piwik-notification context="success" type="persistent" noclear="true">
|
||||
* <strong>Info: </strong>My notification message.
|
||||
* </div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikNotification', piwikNotification);
|
||||
|
||||
piwikNotification.$inject = ['piwik', '$timeout'];
|
||||
|
||||
function piwikNotification(piwik, $timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
notificationId: '@?',
|
||||
title: '@?notificationTitle', // TODO: shouldn't need this since the title can be specified within
|
||||
// HTML of the node that uses the directive.
|
||||
context: '@?',
|
||||
type: '@?',
|
||||
noclear: '@?',
|
||||
toastLength: '@?'
|
||||
},
|
||||
transclude: true,
|
||||
templateUrl: 'plugins/CoreHome/angularjs/notification/notification.directive.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'NotificationController',
|
||||
controllerAs: 'notification',
|
||||
link: function (scope, element) {
|
||||
scope.toastLength = scope.toastLength || 12 * 1000;
|
||||
|
||||
if (scope.notificationId) {
|
||||
closeExistingNotificationHavingSameIdIfNeeded(scope.notificationId, element);
|
||||
}
|
||||
|
||||
if (scope.context) {
|
||||
element.children('.notification').addClass('notification-' + scope.context);
|
||||
}
|
||||
|
||||
if (scope.type == 'persistent') {
|
||||
// otherwise it is never possible to dismiss the notification
|
||||
scope.noclear = false;
|
||||
}
|
||||
|
||||
if ('toast' == scope.type) {
|
||||
addToastEvent();
|
||||
}
|
||||
|
||||
if (!scope.noclear) {
|
||||
addCloseEvent();
|
||||
}
|
||||
|
||||
function addToastEvent() {
|
||||
$timeout(function () {
|
||||
element.fadeOut('slow', function() {
|
||||
element.remove();
|
||||
});
|
||||
}, scope.toastLength);
|
||||
}
|
||||
|
||||
function addCloseEvent() {
|
||||
element.on('click', '.close', function (event) {
|
||||
if (event && event.delegateTarget) {
|
||||
angular.element(event.delegateTarget).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeExistingNotificationHavingSameIdIfNeeded(id, notificationElement) {
|
||||
// TODO: instead of doing a global query for notification, there should be a notification-container
|
||||
// directive that manages notifications.
|
||||
var existingNode = angular.element('[notification-id=' + id + ']').not(notificationElement);
|
||||
if (existingNode && existingNode.length) {
|
||||
existingNode.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,57 @@
|
||||
.system.notification {
|
||||
.alert;
|
||||
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
|
||||
border: 0 !important;
|
||||
|
||||
// We have to use !important because the default button style is crazy
|
||||
.close {
|
||||
position: relative;
|
||||
top: -5px;
|
||||
right: -10px;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
float: right;
|
||||
font-size: 20px !important;
|
||||
font-weight: bold;
|
||||
line-height: 20px !important;
|
||||
color: inherit !important;
|
||||
opacity: 0.3;
|
||||
filter: alpha(opacity=30);
|
||||
}
|
||||
|
||||
&.notification-success {
|
||||
.alert-success;
|
||||
color: #eef6ef !important;
|
||||
background-color: @color-green-piwik !important;
|
||||
&:before, p, a {
|
||||
color: #eef6ef;
|
||||
}
|
||||
}
|
||||
&.notification-warning {
|
||||
.alert-warning;
|
||||
background-color: #f57c00;
|
||||
color: #fbf7f1 !important;
|
||||
&:before, p, a {
|
||||
color: #fbf7f1;
|
||||
}
|
||||
}
|
||||
&.notification-danger,
|
||||
&.notification-error {
|
||||
.alert-danger;
|
||||
color: #fdf0f2 !important;
|
||||
background-color: #e53935;
|
||||
&:before, p, a {
|
||||
color: #fdf0f2;
|
||||
}
|
||||
}
|
||||
&.notification-info {
|
||||
.alert-info;
|
||||
color: #f3feff !important;
|
||||
background-color: #00bcd4;
|
||||
&:before, p, a {
|
||||
color: #f3feff;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').factory('notifications', NotificationFactory);
|
||||
|
||||
NotificationFactory.$inject = [];
|
||||
|
||||
function NotificationFactory() {
|
||||
|
||||
return {
|
||||
parseNotificationDivs: parseNotificationDivs,
|
||||
clearTransientNotifications: clearTransientNotifications,
|
||||
};
|
||||
|
||||
function parseNotificationDivs() {
|
||||
var UI = require('piwik/UI');
|
||||
|
||||
var $notificationNodes = $('[data-role="notification"]');
|
||||
|
||||
$notificationNodes.each(function (index, notificationNode) {
|
||||
$notificationNode = $(notificationNode);
|
||||
var attributes = $notificationNode.data();
|
||||
var message = $notificationNode.html();
|
||||
|
||||
if (message) {
|
||||
var notification = new UI.Notification();
|
||||
attributes.animate = false;
|
||||
notification.show(message, attributes);
|
||||
}
|
||||
|
||||
$notificationNodes.remove();
|
||||
});
|
||||
}
|
||||
|
||||
function clearTransientNotifications() {
|
||||
$('[piwik-notification][type=transient]').each(function () {
|
||||
var $element = angular.element(this);
|
||||
$element.scope().$destroy();
|
||||
$element.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('piwikApp').run(['notifications', function (notifications) {
|
||||
$(function () {
|
||||
notifications.parseNotificationDivs();
|
||||
});
|
||||
}]);
|
||||
})();
|
@ -0,0 +1,14 @@
|
||||
<div
|
||||
piwik-date-picker
|
||||
selected-date-start="$ctrl.selectedDates[0]"
|
||||
selected-date-end="$ctrl.selectedDates[1]"
|
||||
highlighted-date-start="$ctrl.highlightedDates[0]"
|
||||
highlighted-date-end="$ctrl.highlightedDates[1]"
|
||||
view-date="$ctrl.viewDate"
|
||||
step-months="$ctrl.period === 'year' ? 12 : 1"
|
||||
disable-month-dropdown="$ctrl.period === 'year'"
|
||||
cell-hover="$ctrl.onHoverNormalCell(date, $cell)"
|
||||
cell-hover-leave="$ctrl.onHoverLeaveNormalCells()"
|
||||
date-select="$ctrl.onDateSelected(date)"
|
||||
>
|
||||
</div>
|
@ -0,0 +1,105 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wraps a date picker to provide more intuitive date picking for periods.
|
||||
*
|
||||
* Supports all Piwik periods that have one date (so not range periods). When a user
|
||||
* hovers over a date, the entire period for the date is highlighted. The selected
|
||||
* period is colored similarly.
|
||||
*
|
||||
* Properties:
|
||||
* - period: The label of the period. 'week', 'day', etc.
|
||||
* - date: A date inside the period. Must be a Date object.
|
||||
* - select: called when a date is selected in the picker.
|
||||
*
|
||||
* Usage:
|
||||
* <piwik-period-date-picker>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').component('piwikPeriodDatePicker', {
|
||||
templateUrl: 'plugins/CoreHome/angularjs/period-date-picker/period-date-picker.component.html?cb=' + piwik.cacheBuster,
|
||||
bindings: {
|
||||
period: '<',
|
||||
date: '<',
|
||||
select: '&'
|
||||
},
|
||||
controller: PeriodDatePickerController
|
||||
});
|
||||
|
||||
PeriodDatePickerController.$inject = ['piwikPeriods', 'piwik'];
|
||||
|
||||
function PeriodDatePickerController(piwikPeriods, piwik) {
|
||||
var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
|
||||
piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
|
||||
|
||||
var vm = this;
|
||||
vm.selectedDates = [null, null];
|
||||
vm.highlightedDates = [null, null];
|
||||
vm.viewDate = null;
|
||||
vm.onHoverNormalCell = onHoverNormalCell;
|
||||
vm.onHoverLeaveNormalCells = onHoverLeaveNormalCells;
|
||||
vm.onDateSelected = onDateSelected;
|
||||
vm.$onChanges = $onChanges;
|
||||
vm.$onInit = $onInit;
|
||||
|
||||
function onHoverNormalCell(cellDate, $cell) {
|
||||
var isOutOfMinMaxDateRange = cellDate < piwikMinDate || cellDate > piwikMaxDate;
|
||||
|
||||
// don't highlight anything if the period is month or day, and we're hovering over calendar whitespace.
|
||||
// since there are no dates, it's doesn't make sense what you're selecting.
|
||||
var shouldNotHighlightFromWhitespace = $cell.hasClass('ui-datepicker-other-month') && (vm.period === 'month'
|
||||
|| vm.period === 'day');
|
||||
|
||||
if (isOutOfMinMaxDateRange
|
||||
|| shouldNotHighlightFromWhitespace
|
||||
) {
|
||||
vm.highlightedDates = [null, null];
|
||||
return;
|
||||
}
|
||||
|
||||
vm.highlightedDates = getBoundedDateRange(cellDate);
|
||||
}
|
||||
|
||||
function onHoverLeaveNormalCells() {
|
||||
vm.highlightedDates = [null, null];
|
||||
}
|
||||
|
||||
function $onInit() {
|
||||
// vm.date is only guaranteed to be set here
|
||||
vm.viewDate = vm.date;
|
||||
}
|
||||
|
||||
function $onChanges() {
|
||||
if (!vm.period || !vm.date) {
|
||||
vm.selectedDates = [null, null];
|
||||
return;
|
||||
}
|
||||
|
||||
vm.selectedDates = getBoundedDateRange(vm.date);
|
||||
}
|
||||
|
||||
function onDateSelected(date) {
|
||||
if (!vm.select) {
|
||||
return;
|
||||
}
|
||||
|
||||
vm.select({ date: date });
|
||||
}
|
||||
|
||||
function getBoundedDateRange(date) {
|
||||
var periodClass = piwikPeriods.get(vm.period);
|
||||
var dates = (new periodClass(date)).getDateRange();
|
||||
|
||||
// make sure highlighted date range is within min/max date range
|
||||
dates[0] = piwikMinDate < dates[0] ? dates[0] : piwikMinDate;
|
||||
dates[1] = piwikMaxDate > dates[1] ? dates[1] : piwikMaxDate;
|
||||
|
||||
return dates;
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,3 @@
|
||||
piwik-period-date-picker {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('PeriodSelectorController', PeriodSelectorController);
|
||||
|
||||
PeriodSelectorController.$inject = ['piwik', '$location', 'piwikPeriods'];
|
||||
|
||||
function PeriodSelectorController(piwik, $location, piwikPeriods) {
|
||||
var piwikMinDate = new Date(piwik.minDateYear, piwik.minDateMonth - 1, piwik.minDateDay),
|
||||
piwikMaxDate = new Date(piwik.maxDateYear, piwik.maxDateMonth - 1, piwik.maxDateDay);
|
||||
|
||||
var vm = this;
|
||||
|
||||
// the period & date currently being viewed
|
||||
vm.periodValue = null;
|
||||
vm.dateValue = null;
|
||||
|
||||
vm.selectedPeriod = null;
|
||||
|
||||
vm.startRangeDate = null;
|
||||
vm.endRangeDate = null;
|
||||
vm.isRangeValid = null;
|
||||
|
||||
vm.isLoadingNewPage = false;
|
||||
|
||||
vm.getCurrentlyViewingText = getCurrentlyViewingText;
|
||||
vm.changeViewedPeriod = changeViewedPeriod;
|
||||
vm.setPiwikPeriodAndDate = setPiwikPeriodAndDate;
|
||||
vm.onApplyClicked = onApplyClicked;
|
||||
vm.updateSelectedValuesFromHash = updateSelectedValuesFromHash;
|
||||
vm.getPeriodDisplayText = getPeriodDisplayText;
|
||||
vm.$onChanges = $onChanges;
|
||||
vm.onRangeChange = onRangeChange;
|
||||
vm.isApplyEnabled = isApplyEnabled;
|
||||
vm.$onInit = init;
|
||||
|
||||
function init() {
|
||||
vm.updateSelectedValuesFromHash();
|
||||
initTopControls(); // must be called when a top control changes width
|
||||
}
|
||||
|
||||
function $onChanges(changesObj) {
|
||||
if (changesObj.periods) {
|
||||
removeUnrecognizedPeriods();
|
||||
}
|
||||
}
|
||||
|
||||
function onRangeChange(start, end) {
|
||||
if (!start || !end) {
|
||||
vm.isRangeValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
vm.isRangeValid = true;
|
||||
vm.startRangeDate = start;
|
||||
vm.endRangeDate = end;
|
||||
}
|
||||
|
||||
function isApplyEnabled() {
|
||||
if (vm.selectedPeriod === 'range'
|
||||
&& !vm.isRangeValid
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function removeUnrecognizedPeriods() {
|
||||
vm.periods = vm.periods.filter(function (periodLabel) {
|
||||
return piwikPeriods.isRecognizedPeriod(periodLabel);
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectedValuesFromHash() {
|
||||
var strDate = getQueryParamValue('date');
|
||||
var strPeriod = getQueryParamValue('period');
|
||||
|
||||
vm.periodValue = strPeriod;
|
||||
vm.selectedPeriod = strPeriod;
|
||||
|
||||
vm.dateValue = vm.startRangeDate = vm.endRangeDate = null;
|
||||
|
||||
if (strPeriod === 'range') {
|
||||
var period = piwikPeriods.get(strPeriod).parse(strDate);
|
||||
vm.dateValue = period.startDate;
|
||||
vm.startRangeDate = formatDate(period.startDate);
|
||||
vm.endRangeDate = formatDate(period.endDate);
|
||||
} else {
|
||||
vm.dateValue = piwikPeriods.parseDate(strDate);
|
||||
setRangeStartEndFromPeriod(strPeriod, strDate);
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryParamValue(name) {
|
||||
// $location doesn't parse the URL before the hashbang, but it can hold the query param
|
||||
// values, if the page doesn't have the hashbang.
|
||||
var result = $location.search()[name];
|
||||
if (!result) {
|
||||
result = broadcast.getValueFromUrl(name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getPeriodDisplayText(periodLabel) {
|
||||
return piwikPeriods.get(periodLabel).getDisplayText();
|
||||
}
|
||||
|
||||
function getCurrentlyViewingText() {
|
||||
var date;
|
||||
if (vm.periodValue === 'range') {
|
||||
date = vm.startRangeDate + ',' + vm.endRangeDate;
|
||||
} else {
|
||||
date = formatDate(vm.dateValue);
|
||||
}
|
||||
|
||||
try {
|
||||
return piwikPeriods.parse(vm.periodValue, date).getPrettyString();
|
||||
} catch (e) {
|
||||
return _pk_translate('General_Error');
|
||||
}
|
||||
}
|
||||
|
||||
function changeViewedPeriod(period) {
|
||||
// only change period if it's different from what's being shown currently
|
||||
if (period === vm.periodValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// can't just change to a range period, w/o setting two new dates
|
||||
if (period === 'range') {
|
||||
return;
|
||||
}
|
||||
|
||||
setPiwikPeriodAndDate(period, vm.dateValue);
|
||||
}
|
||||
|
||||
function onApplyClicked() {
|
||||
if (vm.selectedPeriod === 'range') {
|
||||
var dateFrom = vm.startRangeDate,
|
||||
dateTo = vm.endRangeDate,
|
||||
oDateFrom = piwikPeriods.parseDate(dateFrom),
|
||||
oDateTo = piwikPeriods.parseDate(dateTo);
|
||||
|
||||
if (!isValidDate(oDateFrom)
|
||||
|| !isValidDate(oDateTo)
|
||||
|| oDateFrom > oDateTo
|
||||
) {
|
||||
// TODO: use a notification instead?
|
||||
$('#alert').find('h2').text(_pk_translate('General_InvalidDateRange'));
|
||||
piwik.helper.modalConfirm('#alert', {});
|
||||
return;
|
||||
}
|
||||
|
||||
vm.periodValue = 'range';
|
||||
|
||||
propagateNewUrlParams(dateFrom + ',' + dateTo, 'range');
|
||||
return;
|
||||
}
|
||||
|
||||
setPiwikPeriodAndDate(vm.selectedPeriod, vm.dateValue);
|
||||
}
|
||||
|
||||
function setPiwikPeriodAndDate(period, date) {
|
||||
vm.periodValue = period;
|
||||
vm.selectedPeriod = period;
|
||||
vm.dateValue = date;
|
||||
|
||||
var currentDateString = formatDate(date);
|
||||
setRangeStartEndFromPeriod(period, currentDateString);
|
||||
|
||||
propagateNewUrlParams(currentDateString, vm.selectedPeriod);
|
||||
initTopControls();
|
||||
}
|
||||
|
||||
function setRangeStartEndFromPeriod(period, dateStr) {
|
||||
var dateRange = piwikPeriods.parse(period, dateStr).getDateRange();
|
||||
vm.startRangeDate = formatDate(dateRange[0] < piwikMinDate ? piwikMinDate : dateRange[0]);
|
||||
vm.endRangeDate = formatDate(dateRange[1] > piwikMaxDate ? piwikMaxDate : dateRange[1]);
|
||||
}
|
||||
|
||||
function propagateNewUrlParams(date, period) {
|
||||
if (piwik.helper.isAngularRenderingThePage()) {
|
||||
vm.closePeriodSelector(); // defined in directive
|
||||
|
||||
var $search = $location.search();
|
||||
if (date !== $search.date || period !== $search.period) {
|
||||
// eg when using back button the date might be actually already changed in the URL and we do not
|
||||
// want to change the URL again
|
||||
$search.date = date;
|
||||
$search.period = period;
|
||||
$location.search($search);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
vm.isLoadingNewPage = true;
|
||||
|
||||
// not in an angular context (eg, embedded dashboard), so must actually
|
||||
// change the URL
|
||||
broadcast.propagateNewPage('date=' + date + '&period=' + period);
|
||||
}
|
||||
|
||||
function isValidDate(d) {
|
||||
if (Object.prototype.toString.call(d) !== "[object Date]") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isNaN(d.getTime());
|
||||
}
|
||||
|
||||
function formatDate(date) {
|
||||
return $.datepicker.formatDate('yy-mm-dd', date);
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,86 @@
|
||||
<div
|
||||
piwik-expand-on-click
|
||||
class="periodSelector piwikSelector"
|
||||
>
|
||||
<a
|
||||
id="date"
|
||||
class="title"
|
||||
title="{{ 'General_ChooseDate'|translate:periodSelector.getCurrentlyViewingText() }}"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span class="icon icon-calendar"></span>
|
||||
{{ periodSelector.getCurrentlyViewingText() }}
|
||||
</a>
|
||||
<div id="periodMore" class="dropdown">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<piwik-date-range-picker
|
||||
ng-show="periodSelector.selectedPeriod === 'range'"
|
||||
class="period-range"
|
||||
start-date="periodSelector.startRangeDate"
|
||||
end-date="periodSelector.endRangeDate"
|
||||
range-change="periodSelector.onRangeChange(start, end)"
|
||||
submit="periodSelector.onApplyClicked()"
|
||||
>
|
||||
</piwik-date-range-picker>
|
||||
<div
|
||||
class="period-date"
|
||||
ng-show="periodSelector.selectedPeriod !== 'range'"
|
||||
>
|
||||
<piwik-period-date-picker
|
||||
id="datepicker"
|
||||
period="periodSelector.selectedPeriod"
|
||||
date="periodSelector.periodValue === periodSelector.selectedPeriod ? periodSelector.dateValue : null"
|
||||
select="periodSelector.setPiwikPeriodAndDate(periodSelector.selectedPeriod, date)"
|
||||
>
|
||||
</piwik-period-date-picker>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="period-type">
|
||||
<h6>{{ 'General_Period'|translate }}</h6>
|
||||
<div id="otherPeriods">
|
||||
<p ng-repeat="period in periodSelector.periods">
|
||||
<input
|
||||
type="radio"
|
||||
name="period"
|
||||
ng-attr-id="period_id_{{ period }}"
|
||||
ng-model="periodSelector.selectedPeriod"
|
||||
ng-value="period"
|
||||
ng-change="periodSelector.selectedPeriod = period"
|
||||
ng-dblclick="periodSelector.changeViewedPeriod(period)"
|
||||
/>
|
||||
|
||||
<label
|
||||
ng-attr-for="period_id_{{ period }}"
|
||||
ng-attr-title="{{ period === periodSelector.periodValue ? '' : ('General_DoubleClickToChangePeriod'|translate) }}"
|
||||
ng-class="{'selected-period-label': period === periodSelector.selectedPeriod}"
|
||||
ng-dblclick="periodSelector.changeViewedPeriod(period)"
|
||||
>
|
||||
{{ periodSelector.getPeriodDisplayText(period) }}
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="submit"
|
||||
value="{{ 'General_Apply'|translate }}"
|
||||
id="calendarApply"
|
||||
class="btn"
|
||||
ng-click="periodSelector.onApplyClicked()"
|
||||
ng-disabled="!periodSelector.isApplyEnabled()"
|
||||
/>
|
||||
<div id="ajaxLoadingCalendar" ng-if="periodSelector.isLoadingNewPage">
|
||||
<div class="loadingPiwik">
|
||||
<img src="plugins/Morpheus/images/loading-blue.gif" alt="{{ 'General_LoadingData'|translate }}" />{{ 'General_LoadingData'|translate }}
|
||||
</div>
|
||||
<div class="loadingSegment">
|
||||
{{ 'SegmentEditor_LoadingSegmentedDataMayTakeSomeTime'|translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-period-selector>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikPeriodSelector', piwikPeriodSelector);
|
||||
|
||||
piwikPeriodSelector.$inject = ['piwik'];
|
||||
|
||||
function piwikPeriodSelector(piwik) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
periods: '<'
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/period-selector/period-selector.directive.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'PeriodSelectorController',
|
||||
controllerAs: 'periodSelector',
|
||||
bindToController: true,
|
||||
link: function (scope, element) {
|
||||
scope.periodSelector.closePeriodSelector = closePeriodSelector;
|
||||
|
||||
scope.$on('$locationChangeSuccess', scope.periodSelector.updateSelectedValuesFromHash);
|
||||
|
||||
function closePeriodSelector() {
|
||||
element.find('.periodSelector').removeClass('expanded');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,10 @@
|
||||
[piwik-period-selector] {
|
||||
display: inline-block;
|
||||
|
||||
// overrides theme CSS that always hides .loadingPiwik. since we use an ng-if to make
|
||||
// sure it's only shown when we're actually loading a new page, it's not necessary to
|
||||
// forcefully hide it.
|
||||
.loadingPiwik {
|
||||
display: inline-block !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
(function () {
|
||||
angular.module('piwikApp.config', []);
|
||||
|
||||
if ('undefined' === (typeof piwik) || !piwik) {
|
||||
return;
|
||||
}
|
||||
|
||||
var piwikAppConfig = angular.module('piwikApp.config');
|
||||
// we probably want this later as a separate config file, till then it serves as a "bridge"
|
||||
for (var index in piwik.config) {
|
||||
piwikAppConfig.constant(index.toUpperCase(), piwik.config[index]);
|
||||
}
|
||||
})();
|
23
msd2/tracking/piwik/plugins/CoreHome/angularjs/piwikApp.js
vendored
Normal file
23
msd2/tracking/piwik/plugins/CoreHome/angularjs/piwikApp.js
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp', [
|
||||
'ngSanitize',
|
||||
'ngAnimate',
|
||||
'ngCookies',
|
||||
'ngDialog',
|
||||
'piwikApp.config',
|
||||
'piwikApp.service',
|
||||
'piwikApp.directive',
|
||||
'piwikApp.filter'
|
||||
]);
|
||||
angular.module('app', []);
|
||||
|
||||
angular.module('piwikApp').config(['$locationProvider', function($locationProvider) {
|
||||
$locationProvider.hashPrefix('');
|
||||
}]);
|
||||
})();
|
@ -0,0 +1,78 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* When present in the page it listens to a popover URL parameter.
|
||||
*
|
||||
* If present it will try to load the related content in a popover or if the URL is empty it will close an
|
||||
* opened popover.
|
||||
*
|
||||
* Example:
|
||||
* <div piwik-popover-handler></div>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikPopoverHandler', piwikPopoverHandler);
|
||||
|
||||
piwikPopoverHandler.$inject = ['$location', '$rootScope', 'piwik'];
|
||||
|
||||
function piwikPopoverHandler($location, $rootScope, piwik){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {},
|
||||
controller: function () {
|
||||
|
||||
function close()
|
||||
{
|
||||
Piwik_Popover.close();
|
||||
}
|
||||
|
||||
function open(popoverParam)
|
||||
{
|
||||
// in case the $ was encoded (e.g. when using copy&paste on urls in some browsers)
|
||||
popoverParam = decodeURIComponent(popoverParam);
|
||||
// revert special encoding from broadcast.propagateNewPopoverParameter()
|
||||
popoverParam = popoverParam.replace(/\$/g, '%');
|
||||
popoverParam = decodeURIComponent(popoverParam);
|
||||
|
||||
var popoverParamParts = popoverParam.split(':');
|
||||
var handlerName = popoverParamParts[0];
|
||||
popoverParamParts.shift();
|
||||
var param = popoverParamParts.join(':');
|
||||
if (typeof piwik.broadcast.popoverHandlers[handlerName] != 'undefined'
|
||||
&& !piwik.broadcast.isLoginPage()) {
|
||||
piwik.broadcast.popoverHandlers[handlerName](param);
|
||||
}
|
||||
}
|
||||
|
||||
function openOrClose()
|
||||
{
|
||||
close();
|
||||
|
||||
// should be rather done by routing
|
||||
var popoverParam = $location.search().popover;
|
||||
if (popoverParam) {
|
||||
open(popoverParam);
|
||||
} else {
|
||||
// the URL should only be set to an empty popover if there are no popovers in the stack.
|
||||
// to avoid avoid any strange inconsistent states, we reset the popover stack here.
|
||||
broadcast.resetPopoverStack();
|
||||
}
|
||||
}
|
||||
|
||||
$rootScope.$on('$locationChangeSuccess', function () {
|
||||
// should be rather done by routing
|
||||
$(function () {
|
||||
// make sure all popover handles were registered
|
||||
openOrClose();
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,7 @@
|
||||
<div class="progressbar">
|
||||
<div class="progress">
|
||||
<div class="determinate" style="width: 0"
|
||||
ng-style="{width: (progress + '%')}"></div>
|
||||
</div>
|
||||
<span ng-show="!!label"><img src='./plugins/Morpheus/images/loading-blue.gif'/> <span ng-bind-html="label"></span></span>
|
||||
</div>
|
@ -0,0 +1,54 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-progressbar>
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikProgressbar', piwikProgressbar);
|
||||
|
||||
piwikProgressbar.$inject = ['piwik'];
|
||||
|
||||
function piwikProgressbar(piwik){
|
||||
var defaults = {
|
||||
label: '',
|
||||
progress: 0
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
progress: '=',
|
||||
label: '='
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/progressbar/progressbar.directive.html?cb=' + piwik.cacheBuster,
|
||||
compile: function (element, attrs) {
|
||||
|
||||
for (var index in defaults) {
|
||||
if (defaults.hasOwnProperty(index) && attrs[index] === undefined) {
|
||||
attrs[index] = defaults[index];
|
||||
}
|
||||
}
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
scope.$watch('progress', function (val, oldVal) {
|
||||
if (val !== oldVal) {
|
||||
if (val > 100) {
|
||||
scope.progress = 100;
|
||||
} else if (val < 0) {
|
||||
scope.progress = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,3 @@
|
||||
.progressbar {
|
||||
margin: 22px 24px;
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('QuickAccessController', QuickAccessController);
|
||||
|
||||
QuickAccessController.$inject = ['$scope', '$filter', 'siteSelectorModel'];
|
||||
|
||||
function QuickAccessController($scope, $filter, siteSelectorModel){
|
||||
|
||||
this.menuItems = [];
|
||||
this.numMenuItems = 0;
|
||||
this.sitesModel = siteSelectorModel;
|
||||
|
||||
this.onKeypress = function (event) {
|
||||
var areSearchResultsDisplayed = $scope.search && $scope.search.term && $scope.view && $scope.view.searchActive;
|
||||
var isTabKey = 9 == event.which;
|
||||
var isEscKey = 27 == event.which;
|
||||
|
||||
if (38 == event.which) {
|
||||
$scope.highlightPreviousItem();
|
||||
event.preventDefault();
|
||||
} else if (40 == event.which) {
|
||||
$scope.highlightNextItem();
|
||||
event.preventDefault();
|
||||
} else if (13 == event.which) {
|
||||
$scope.clickQuickAccessMenuItem();
|
||||
} else if (isTabKey && areSearchResultsDisplayed) {
|
||||
$scope.deactivateSearch();
|
||||
} else if (isEscKey && areSearchResultsDisplayed) {
|
||||
$scope.deactivateSearch();
|
||||
}
|
||||
};
|
||||
|
||||
this.searchMenu = function (searchTerm) {
|
||||
searchTerm = searchTerm.toLowerCase();
|
||||
|
||||
var index = -1;
|
||||
var menuItemsIndex = {};
|
||||
var menuItems = [];
|
||||
|
||||
var moveToCategory = function (i, submenuItem) {
|
||||
submenuItem = angular.copy(submenuItem); // force rerender of element to prevent weird side effects
|
||||
submenuItem.menuIndex = ++index; // needed for proper highlighting with arrow keys
|
||||
|
||||
var category = submenuItem.category;
|
||||
if (!(category in menuItemsIndex)) {
|
||||
menuItems.push({title: category, items: []});
|
||||
menuItemsIndex[category] = menuItems.length - 1;
|
||||
}
|
||||
|
||||
var indexOfCategory = menuItemsIndex[category];
|
||||
menuItems[indexOfCategory].items.push(submenuItem);
|
||||
};
|
||||
|
||||
$scope.resetSearchIndex();
|
||||
|
||||
if ($scope.hasSitesSelector) {
|
||||
this.sitesModel.searchSite(searchTerm);
|
||||
}
|
||||
|
||||
var topMenuItems = $filter('filter')($scope.getTopMenuItems(), searchTerm);
|
||||
var leftMenuItems = $filter('filter')($scope.getLeftMenuItems(), searchTerm);
|
||||
var segmentItems = $filter('filter')($scope.getSegmentItems(), searchTerm);
|
||||
|
||||
$.each(topMenuItems, moveToCategory);
|
||||
$.each(leftMenuItems, moveToCategory);
|
||||
$.each(segmentItems, moveToCategory);
|
||||
|
||||
this.numMenuItems = topMenuItems.length + leftMenuItems.length + segmentItems.length;
|
||||
this.menuItems = menuItems;
|
||||
};
|
||||
|
||||
this.selectSite = function (idsite) {
|
||||
this.sitesModel.loadSite(idsite);
|
||||
};
|
||||
|
||||
this.selectMenuItem = function (index) {
|
||||
$scope.selectMenuItem(index);
|
||||
};
|
||||
|
||||
if (typeof initTopControls !== 'undefined' && initTopControls) {
|
||||
initTopControls();
|
||||
}
|
||||
|
||||
}
|
||||
})();
|
@ -0,0 +1,40 @@
|
||||
<div class="quick-access piwikSelector"
|
||||
ng-class="{active: view.searchActive, expanded: view.searchActive}"
|
||||
piwik-focus-anywhere-but-here="view.searchActive = false;">
|
||||
<span class="icon-search" ng-hide="search.term || view.searchActive"
|
||||
ng-mouseenter="view.searchActive=true"></span>
|
||||
<input class="s"
|
||||
title="{{ quickAccessTitle }}"
|
||||
ng-keydown="quickAccess.onKeypress($event)"
|
||||
ng-change="view.searchActive=true;quickAccess.searchMenu(search.term)"
|
||||
ng-focus="view.searchActive=true"
|
||||
ng-model="search.term" piwik-focus-if="view.searchActive"
|
||||
type="text" tabindex="2"/>
|
||||
<div class="dropdown" ng-show="search.term && view.searchActive">
|
||||
<ul ng-hide="(quickAccess.numMenuItems > 0) || (quickAccess.sitesModel.sites | length)">
|
||||
<li class="no-result">{{ 'General_SearchNoResults' | translate }}</li>
|
||||
</ul>
|
||||
<ul ng-repeat="subcategory in quickAccess.menuItems">
|
||||
<li class="quick-access-category"
|
||||
ng-click="search.term = subcategory.title;quickAccess.searchMenu(search.term)">{{ subcategory.title }}</li>
|
||||
<li class="result"
|
||||
ng-class="{selected: submenuEntry.menuIndex == search.index}"
|
||||
ng-mouseenter="search.index=submenuEntry.menuIndex"
|
||||
ng-click="quickAccess.selectMenuItem(submenuEntry.index)"
|
||||
ng-repeat="submenuEntry in subcategory.items"><a>{{ submenuEntry.name | trim }}</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="quick-access-category websiteCategory"
|
||||
ng-show="hasSitesSelector && ((quickAccess.sitesModel.sites | length) || quickAccess.sitesModel.isLoading)"
|
||||
>{{ 'SitesManager_Sites' | translate }}</li>
|
||||
<li class="no-result"
|
||||
ng-show="hasSitesSelector && quickAccess.sitesModel.isLoading">{{ 'MultiSites_LoadingWebsites' | translate }}</li>
|
||||
<li class="result"
|
||||
ng-show="hasSitesSelector && !quickAccess.sitesModel.isLoading"
|
||||
ng-mouseenter="search.index=(quickAccess.numMenuItems + $index)"
|
||||
ng-class="{selected: (quickAccess.numMenuItems + $index) == search.index}"
|
||||
ng-click="quickAccess.selectSite(site.idsite)"
|
||||
ng-repeat="site in quickAccess.sitesModel.sites"><a ng-bind="site.name"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,280 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-dialog="showDialog">...</div>
|
||||
* Will show dialog once showDialog evaluates to true.
|
||||
*
|
||||
* Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikQuickAccess', QuickAccessDirective);
|
||||
|
||||
QuickAccessDirective.$inject = ['$rootElement', '$timeout', 'piwik', '$filter'];
|
||||
|
||||
function QuickAccessDirective ($rootElement, $timeout, piwik, $filter) {
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
replace: true,
|
||||
scope: {},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/quick-access/quick-access.directive.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'QuickAccessController',
|
||||
controllerAs: 'quickAccess',
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
var menuIndex = -1; // the menu index is used to identify which element to click
|
||||
var topMenuItems = []; // cache for top menu items
|
||||
var leftMenuItems = []; // cache for left menu items
|
||||
var segmentItems = []; // cache for segment items
|
||||
var hasSegmentSelector = angular.element('.segmentEditorPanel').length;
|
||||
scope.hasSitesSelector = angular.element('.top_controls [piwik-siteselector]').length;
|
||||
|
||||
|
||||
var translate = $filter('translate');
|
||||
var searchAreasTitle = '';
|
||||
var searchAreas = [translate('CoreHome_MenuEntries')];
|
||||
|
||||
if (hasSegmentSelector) {
|
||||
searchAreas.push(translate('CoreHome_Segments'));
|
||||
}
|
||||
|
||||
if (scope.hasSitesSelector) {
|
||||
searchAreas.push(translate('SitesManager_Sites'));
|
||||
}
|
||||
|
||||
while (searchAreas.length) {
|
||||
searchAreasTitle += searchAreas.shift();
|
||||
if (searchAreas.length >= 2) {
|
||||
searchAreasTitle += ', ';
|
||||
} else if (searchAreas.length === 1) {
|
||||
searchAreasTitle += ' ' + translate('General_And') + ' ';
|
||||
}
|
||||
}
|
||||
|
||||
scope.quickAccessTitle = translate('CoreHome_QuickAccessTitle', searchAreasTitle);
|
||||
|
||||
function trim(str) {
|
||||
return str.replace(/^\s+|\s+$/g,'');
|
||||
}
|
||||
|
||||
scope.getTopMenuItems = function()
|
||||
{
|
||||
if (topMenuItems && topMenuItems.length) {
|
||||
return topMenuItems;
|
||||
}
|
||||
|
||||
var category = _pk_translate('CoreHome_Menu');
|
||||
|
||||
$rootElement.find('nav .side-nav li > a').each(function (index, element) {
|
||||
var $element = $(element);
|
||||
|
||||
var text = trim($element.text());
|
||||
|
||||
if (!text) {
|
||||
text = trim($element.attr('title')); // possibly a icon, use title instead
|
||||
}
|
||||
|
||||
if (text) {
|
||||
topMenuItems.push({name: text, index: ++menuIndex, category: category});
|
||||
$element.attr('quick_access', menuIndex);
|
||||
}
|
||||
});
|
||||
|
||||
return topMenuItems;
|
||||
};
|
||||
|
||||
scope.getLeftMenuItems = function ()
|
||||
{
|
||||
if (leftMenuItems && leftMenuItems.length) {
|
||||
return leftMenuItems;
|
||||
}
|
||||
|
||||
$rootElement.find('#secondNavBar .menuTab').each(function (index, element) {
|
||||
var $element = angular.element(element);
|
||||
var category = trim($element.find('> .item').text());
|
||||
|
||||
if (category && -1 !== category.lastIndexOf("\n")) {
|
||||
// remove "\n\nMenu"
|
||||
category = trim(category.substr(0, category.lastIndexOf("\n")));
|
||||
}
|
||||
|
||||
$element.find('li .item').each(function (i, element) {
|
||||
var $element = angular.element(element);
|
||||
var text = trim($element.text());
|
||||
|
||||
if (text) {
|
||||
leftMenuItems.push({name: text, category: category, index: ++menuIndex});
|
||||
$element.attr('quick_access', menuIndex);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return leftMenuItems;
|
||||
};
|
||||
|
||||
scope.getSegmentItems = function()
|
||||
{
|
||||
if (!hasSegmentSelector) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (segmentItems && segmentItems.length) {
|
||||
return segmentItems;
|
||||
}
|
||||
|
||||
var category = _pk_translate('CoreHome_Segments');
|
||||
|
||||
$rootElement.find('.segmentList [data-idsegment]').each(function (index, element) {
|
||||
var $element = angular.element(element);
|
||||
var text = trim($element.find('.segname').text());
|
||||
|
||||
if (text) {
|
||||
segmentItems.push({name: text, category: category, index: ++menuIndex});
|
||||
$element.attr('quick_access', menuIndex);
|
||||
}
|
||||
});
|
||||
|
||||
return segmentItems;
|
||||
};
|
||||
|
||||
scope.activateSearch = function()
|
||||
{
|
||||
scope.$eval('view.searchActive = true');
|
||||
$timeout(function () {
|
||||
scope.$apply();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
scope.deactivateSearch = function()
|
||||
{
|
||||
scope.$eval('search.term = ""');
|
||||
scope.$eval('view.searchActive = false');
|
||||
element.find('input').blur();
|
||||
$timeout(function () {
|
||||
scope.$apply();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
function isElementInViewport(element) {
|
||||
|
||||
var rect = element.getBoundingClientRect();
|
||||
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= $(window).height() &&
|
||||
rect.right <= $(window).width()
|
||||
);
|
||||
}
|
||||
|
||||
function getCurrentlySelectedElement(index)
|
||||
{
|
||||
var results = element.find('li.result');
|
||||
if (results && results.length && results[scope.search.index]) {
|
||||
return $(results[scope.search.index]);
|
||||
}
|
||||
}
|
||||
|
||||
function makeSureSelectedItemIsInViewport() {
|
||||
var element = getCurrentlySelectedElement();
|
||||
|
||||
if (element && element[0] && !isElementInViewport(element[0])) {
|
||||
scrollFirstElementIntoView(element);
|
||||
}
|
||||
}
|
||||
|
||||
function scrollFirstElementIntoView(element)
|
||||
{
|
||||
if (element && element[0] && element[0].scrollIntoView) {
|
||||
// make sure search is visible
|
||||
element[0].scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
scope.highlightPreviousItem = function()
|
||||
{
|
||||
if (0 >= (scope.search.index - 1)) {
|
||||
scope.search.index = 0;
|
||||
} else {
|
||||
scope.search.index--;
|
||||
}
|
||||
makeSureSelectedItemIsInViewport();
|
||||
};
|
||||
|
||||
scope.resetSearchIndex = function () {
|
||||
scope.search.index = 0;
|
||||
makeSureSelectedItemIsInViewport();
|
||||
};
|
||||
|
||||
scope.highlightNextItem = function()
|
||||
{
|
||||
var numTotal = element.find('li.result').length;
|
||||
|
||||
if (numTotal <= (scope.search.index + 1)) {
|
||||
scope.search.index = numTotal - 1;
|
||||
} else {
|
||||
scope.search.index++;
|
||||
}
|
||||
|
||||
makeSureSelectedItemIsInViewport();
|
||||
};
|
||||
|
||||
scope.clickQuickAccessMenuItem = function()
|
||||
{
|
||||
var selectedMenuElement = getCurrentlySelectedElement();
|
||||
if (selectedMenuElement) {
|
||||
$timeout(function () {
|
||||
selectedMenuElement.click();
|
||||
}, 20);
|
||||
}
|
||||
};
|
||||
|
||||
scope.selectMenuItem = function(index)
|
||||
{
|
||||
var target = $rootElement.find('[quick_access=' + index + ']');
|
||||
|
||||
if (target && target.length && target[0]) {
|
||||
scope.deactivateSearch();
|
||||
|
||||
var actualTarget = target[0];
|
||||
|
||||
var href = $(actualTarget).attr('href');
|
||||
|
||||
if (href && href.length > 10 && actualTarget && actualTarget.click) {
|
||||
try {
|
||||
actualTarget.click();
|
||||
} catch (e) {
|
||||
$(actualTarget).click();
|
||||
}
|
||||
} else {
|
||||
$(actualTarget).click();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
piwikHelper.registerShortcut('f', _pk_translate('CoreHome_ShortcutSearch'), function(event) {
|
||||
if (event.altKey) {
|
||||
return;
|
||||
}
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
event.returnValue = false; // IE
|
||||
}
|
||||
|
||||
scrollFirstElementIntoView(element);
|
||||
|
||||
scope.activateSearch();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,62 @@
|
||||
.quick-access {
|
||||
position: relative;
|
||||
|
||||
&:hover,
|
||||
&.expanded,
|
||||
&.active {
|
||||
input {
|
||||
background-color: @theme-color-background-contrast !important;
|
||||
}
|
||||
}
|
||||
li {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
li a {
|
||||
padding: 10px 19px;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.icon-search {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
|
||||
}
|
||||
input {
|
||||
width:100%;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background-color: @theme-color-background-base !important;
|
||||
font-size: 11px;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
.selected {
|
||||
background-color: @theme-color-background-tinyContrast !important;
|
||||
}
|
||||
.quick-access-category {
|
||||
text-align: left !important;
|
||||
font-size: 11px;
|
||||
padding: 5px 5px 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.result {
|
||||
cursor: pointer;
|
||||
}
|
||||
.quick-access-category:hover {
|
||||
background: none !important;
|
||||
}
|
||||
.no-result {
|
||||
padding: 10px 19px;
|
||||
cursor: default;
|
||||
}
|
||||
.websiteCategory {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
/*!
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikReportExport', piwikReportExport);
|
||||
|
||||
piwikReportExport.$inject = ['$document', 'piwik', '$compile', '$timeout', '$location', '$httpParamSerializerJQLike'];
|
||||
|
||||
function piwikReportExport($document, piwik, $compile, $timeout, $location, $httpParamSerializerJQLike){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
'reportTitle': '@',
|
||||
'requestParams': '@',
|
||||
'reportFormats': '@',
|
||||
'apiMethod': '@'
|
||||
},
|
||||
link: function(scope, element, attr) {
|
||||
|
||||
var popoverParamBackup;
|
||||
|
||||
scope.showUrl = false;
|
||||
|
||||
scope.getExportLink = function() {
|
||||
|
||||
var dataTable = scope.dataTable;
|
||||
var format = scope.reportFormat;
|
||||
|
||||
if (!format) {
|
||||
return;
|
||||
}
|
||||
|
||||
var method = scope.apiMethod;
|
||||
var limit = scope.reportLimitAll == 'yes' ? -1 : scope.reportLimit;
|
||||
var type = scope.reportType;
|
||||
var params = scope.requestParams;
|
||||
|
||||
if (params && typeof params == "string") {
|
||||
params = JSON.parse(params);
|
||||
} else {
|
||||
params = {};
|
||||
}
|
||||
|
||||
var segment = dataTable.param.segment;
|
||||
var label = dataTable.param.label;
|
||||
var idGoal = dataTable.param.idGoal;
|
||||
var idDimension = dataTable.param.idDimension;
|
||||
var param_date = dataTable.param.date;
|
||||
|
||||
if (format == 'RSS') {
|
||||
param_date = 'last10';
|
||||
}
|
||||
if (typeof dataTable.param.dateUsedInGraph != 'undefined') {
|
||||
param_date = dataTable.param.dateUsedInGraph;
|
||||
}
|
||||
var period = dataTable.param.period;
|
||||
|
||||
var formatsUseDayNotRange = piwik.config.datatable_export_range_as_day.toLowerCase();
|
||||
|
||||
if (formatsUseDayNotRange.indexOf(format.toLowerCase()) != -1
|
||||
&& dataTable.param.period == 'range') {
|
||||
period = 'day';
|
||||
}
|
||||
|
||||
// Below evolution graph, show daily exports
|
||||
if(dataTable.param.period == 'range'
|
||||
&& dataTable.param.viewDataTable == "graphEvolution") {
|
||||
period = 'day';
|
||||
}
|
||||
|
||||
var exportUrlParams = {
|
||||
module: 'API'
|
||||
};
|
||||
|
||||
if (type == 'processed') {
|
||||
var apiParams = method.split('.');
|
||||
exportUrlParams.method = 'API.getProcessedReport';
|
||||
exportUrlParams.apiModule = apiParams[0];
|
||||
exportUrlParams.apiAction = apiParams[1];
|
||||
} else {
|
||||
exportUrlParams.method = method;
|
||||
}
|
||||
|
||||
exportUrlParams.format = format;
|
||||
exportUrlParams.idSite = dataTable.param.idSite;
|
||||
exportUrlParams.period = period;
|
||||
exportUrlParams.date = param_date;
|
||||
|
||||
if (typeof dataTable.param.filter_pattern != "undefined") {
|
||||
exportUrlParams.filter_pattern = dataTable.param.filter_pattern;
|
||||
}
|
||||
|
||||
if (typeof dataTable.param.filter_pattern_recursive != "undefined") {
|
||||
exportUrlParams.filter_pattern_recursive = dataTable.param.filter_pattern_recursive;
|
||||
}
|
||||
|
||||
if ($.isPlainObject(params)) {
|
||||
$.each(params, function (index, param) {
|
||||
if (param === true) {
|
||||
param = 1;
|
||||
} else if (param === false) {
|
||||
param = 0;
|
||||
}
|
||||
exportUrlParams[index] = param;
|
||||
});
|
||||
}
|
||||
|
||||
if (scope.optionFlat) {
|
||||
exportUrlParams.flat = 1;
|
||||
if (typeof dataTable.param.include_aggregate_rows != "undefined" && dataTable.param.include_aggregate_rows == '1') {
|
||||
exportUrlParams.include_aggregate_rows = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!scope.optionFlat && scope.optionExpanded) {
|
||||
exportUrlParams.expanded = 1;
|
||||
}
|
||||
|
||||
if (scope.optionFormatMetrics) {
|
||||
exportUrlParams.format_metrics = 1;
|
||||
}
|
||||
|
||||
if (dataTable.param.pivotBy) {
|
||||
exportUrlParams.pivotBy = dataTable.param.pivotBy;
|
||||
exportUrlParams.pivotByColumnLimit = 20;
|
||||
|
||||
if (dataTable.props.pivot_by_column) {
|
||||
exportUrlParams.pivotByColumn = dataTable.props.pivot_by_column;
|
||||
}
|
||||
}
|
||||
if (format == 'CSV' || format == 'TSV' || format == 'RSS') {
|
||||
exportUrlParams.translateColumnNames = 1;
|
||||
exportUrlParams.language = piwik.language;
|
||||
}
|
||||
if (typeof segment != 'undefined') {
|
||||
exportUrlParams.segment = segment;
|
||||
}
|
||||
// Export Goals specific reports
|
||||
if (typeof idGoal != 'undefined'
|
||||
&& idGoal != '-1') {
|
||||
exportUrlParams.idGoal = idGoal;
|
||||
}
|
||||
// Export Dimension specific reports
|
||||
if (typeof idDimension != 'undefined'
|
||||
&& idDimension != '-1') {
|
||||
exportUrlParams.idDimension = idDimension;
|
||||
}
|
||||
if (label) {
|
||||
label = label.split(',');
|
||||
|
||||
if (label.length > 1) {
|
||||
exportUrlParams.label = label;
|
||||
} else {
|
||||
exportUrlParams.label = label[0];
|
||||
}
|
||||
}
|
||||
|
||||
exportUrlParams.token_auth = piwik.token_auth;
|
||||
exportUrlParams.filter_limit = limit;
|
||||
|
||||
var currentUrl = $location.absUrl();
|
||||
var urlParts = currentUrl.split('/');
|
||||
urlParts.pop();
|
||||
var url = urlParts.join('/');
|
||||
|
||||
return url + '/index.php?' + $httpParamSerializerJQLike(exportUrlParams);
|
||||
};
|
||||
|
||||
element.on('click', function () {
|
||||
|
||||
popoverParamBackup = broadcast.getValueFromHash('popover');
|
||||
|
||||
var dataTable = scope.dataTable = element.parents('[data-report]').data('uiControlObject');
|
||||
var popover = Piwik_Popover.showLoading('Export');
|
||||
var formats = JSON.parse(scope.reportFormats);
|
||||
|
||||
scope.reportType = 'default';
|
||||
scope.reportLimit = dataTable.param.filter_limit > 0 ? dataTable.param.filter_limit : 100;
|
||||
scope.reportLimitAll = dataTable.param.filter_limit == -1 ? 'yes' : 'no';
|
||||
scope.optionFlat = dataTable.param.flat === true || dataTable.param.flat === 1 || dataTable.param.flat === "1";
|
||||
scope.optionExpanded = 1;
|
||||
scope.optionFormatMetrics = 0;
|
||||
scope.hasSubtables = scope.optionFlat || dataTable.numberOfSubtables > 0;
|
||||
|
||||
scope.availableReportFormats = {
|
||||
default: formats,
|
||||
processed: {
|
||||
'XML': formats['XML'],
|
||||
'JSON': formats['JSON']
|
||||
}
|
||||
};
|
||||
scope.availableReportTypes = {
|
||||
default: _pk_translate('CoreHome_StandardReport'),
|
||||
processed: _pk_translate('CoreHome_ReportWithMetadata')
|
||||
};
|
||||
scope.limitAllOptions = {
|
||||
yes: _pk_translate('General_All'),
|
||||
no: _pk_translate('CoreHome_CustomLimit')
|
||||
};
|
||||
|
||||
scope.$watch('reportType', function (newVal, oldVal) {
|
||||
if (!scope.availableReportFormats[newVal].hasOwnProperty(scope.reportFormat)) {
|
||||
scope.reportFormat = 'XML';
|
||||
}
|
||||
}, true);
|
||||
|
||||
var elem = $document.find('#reportExport').eq(0);
|
||||
|
||||
if (!elem.length) {
|
||||
elem = angular.element('<span ng-include="\'plugins/CoreHome/angularjs/report-export/reportexport.popover.html?cb=' + piwik.cacheBuster + '\'" id="reportExport"></span>');
|
||||
}
|
||||
|
||||
$compile(elem)(scope, function (compiled){
|
||||
Piwik_Popover.setTitle(_pk_translate('General_Export') + ' ' + piwikHelper.htmlEntities(scope.reportTitle));
|
||||
Piwik_Popover.setContent(compiled);
|
||||
|
||||
if (popoverParamBackup != '') {
|
||||
Piwik_Popover.onClose(function(){
|
||||
$timeout(function(){
|
||||
$location.search('popover', popoverParamBackup);
|
||||
|
||||
$timeout(function () {
|
||||
angular.element(document).injector().get('$rootScope').$apply();
|
||||
}, 10);
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
$timeout(function(){
|
||||
popover.dialog({position: ['center', 'center']});
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
@ -0,0 +1,65 @@
|
||||
<div class="report-export-popover row">
|
||||
|
||||
<div class="col l6">
|
||||
<div piwik-field uicontrol="radio" name="format"
|
||||
title="{{ 'CoreHome_ExportFormat'|translate }}"
|
||||
ng-model="$parent.reportFormat"
|
||||
full-width="true"
|
||||
value="XML"
|
||||
options="availableReportFormats[$parent.reportType]">
|
||||
</div>
|
||||
|
||||
<div piwik-field uicontrol="checkbox" name="option_flat"
|
||||
title="{{ 'CoreHome_FlattenReport'|translate }}"
|
||||
ng-model="$parent.optionFlat" ng-show="$parent.hasSubtables">
|
||||
</div>
|
||||
<div piwik-field uicontrol="checkbox" name="option_expanded"
|
||||
title="{{ 'CoreHome_ExpandSubtables'|translate }}"
|
||||
ng-model="$parent.optionExpanded" ng-show="$parent.hasSubtables && !$parent.optionFlat"
|
||||
>
|
||||
</div>
|
||||
<div piwik-field uicontrol="checkbox" name="option_format_metrics"
|
||||
title="{{ 'CoreHome_FormatMetrics'|translate }}"
|
||||
ng-model="$parent.optionFormatMetrics"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col l6">
|
||||
<div piwik-field uicontrol="radio" name="filter_type"
|
||||
title="{{ 'CoreHome_ReportType'|translate }}"
|
||||
ng-model="$parent.reportType"
|
||||
full-width="true"
|
||||
options="availableReportTypes">
|
||||
</div>
|
||||
|
||||
<div class="filter_limit">
|
||||
<div piwik-field uicontrol="radio" name="filter_limit_all"
|
||||
title="{{ 'CoreHome_RowLimit'|translate }}"
|
||||
ng-model="$parent.reportLimitAll"
|
||||
full-width="false"
|
||||
options="limitAllOptions">
|
||||
</div>
|
||||
<div piwik-field uicontrol="number" name="filter_limit"
|
||||
min="1"
|
||||
ng-model="$parent.reportLimit"
|
||||
full-width="false"
|
||||
ng-show="$parent.reportLimitAll == 'no'">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col l12" ng-show="showUrl">
|
||||
<textarea piwik-select-on-focus readonly class="exportFullUrlFull" ng-show="showFullUrl">{{ getExportLink() }}</textarea>
|
||||
<textarea readonly ng-show="!showFullUrl" title="{{ 'CoreHome_ClickToSeeFullInformation'|translate }}" class="exportFullUrlPartial" ng-click="showFullUrl=true">{{ getExportLink()|limitTo:50 }}...</textarea>
|
||||
</div>
|
||||
|
||||
<div class="col l12">
|
||||
<a class="btn" ng-attr-href="{{ getExportLink() }}" target="_new">{{ 'General_Export'|translate }}</a>
|
||||
<a href="javascript:;" ng-click="showUrl=!showUrl;showFullUrl=false;" class="toggle-export-url">
|
||||
<span ng-show="!showUrl">{{ 'CoreHome_ShowExportUrl'|translate }}</span>
|
||||
<span ng-show="showUrl">{{ 'CoreHome_HideExportUrl'|translate }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user