update Piwik to version 2.16 (fixes #91)
This commit is contained in:
parent
296343bf3b
commit
d885a4baa9
5833 changed files with 418860 additions and 226988 deletions
|
|
@ -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,142 @@
|
|||
/*!
|
||||
* 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:
|
||||
*
|
||||
* - **save-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
|
||||
* save-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) {
|
||||
scope.$apply();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* See http://dev.piwik.org/trac/ticket/4795 "linking to #hash tag does not work after merging AngularJS"
|
||||
* See https://github.com/piwik/piwik/issues/4795 "linking to #hash tag does not work after merging AngularJS"
|
||||
*/
|
||||
(function () {
|
||||
|
||||
|
|
@ -32,7 +32,12 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var $node = $('#' + hash);
|
||||
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);
|
||||
|
|
@ -80,7 +85,9 @@
|
|||
{
|
||||
angular.module('piwikApp').run(['$rootScope', function ($rootScope) {
|
||||
|
||||
$rootScope.$on('$locationChangeStart', function (event, newUrl, oldUrl, $location) {
|
||||
$rootScope.$on('$locationChangeStart', onLocationChangeStart);
|
||||
|
||||
function onLocationChangeStart (event, newUrl, oldUrl, $location) {
|
||||
|
||||
if (!newUrl) {
|
||||
return;
|
||||
|
|
@ -98,7 +105,7 @@
|
|||
var hash = newUrl.substr(hashPos + 2);
|
||||
|
||||
scrollToAnchorIfPossible(hash, event);
|
||||
});
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -15,28 +15,39 @@
|
|||
* <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
|
||||
* <input type="text" ng-model="searchTerm">
|
||||
*/
|
||||
angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', function() {
|
||||
return function(scope, element, attrs) {
|
||||
var searchTerm;
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', piwikAutocompleteMatched);
|
||||
|
||||
scope.$watch(attrs.piwikAutocompleteMatched, function(value) {
|
||||
searchTerm = value;
|
||||
updateText();
|
||||
});
|
||||
piwikAutocompleteMatched.$inject = ['piwik', '$sanitize'];
|
||||
|
||||
function updateText () {
|
||||
if (!searchTerm || !element) {
|
||||
return;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var content = element.html();
|
||||
var startTerm = content.toLowerCase().indexOf(searchTerm.toLowerCase());
|
||||
|
||||
if (-1 !== startTerm) {
|
||||
var word = content.substr(startTerm, searchTerm.length);
|
||||
content = content.replace(word, '<span class="autocompleteMatched">' + word + '</span>');
|
||||
element.html(content);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
describe('piwikAutocompleteMatchedDirective', function() {
|
||||
var $compile;
|
||||
var $rootScope;
|
||||
|
||||
beforeEach(module('piwikApp.directive'));
|
||||
beforeEach(inject(function(_$compile_, _$rootScope_){
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
function assertRenderedContentIs(query, expectedResult) {
|
||||
var template = '<div piwik-autocomplete-matched="\'' + query + '\'">My Content</div>';
|
||||
var element = $compile(template)($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.html()).to.eql(expectedResult);
|
||||
}
|
||||
|
||||
describe('#piwikAutocompleteMatched()', function() {
|
||||
|
||||
it('should not change anything if query does not match the text', function() {
|
||||
assertRenderedContentIs('Whatever', 'My Content');
|
||||
});
|
||||
|
||||
it('should wrap the matching part and find case insensitive', function() {
|
||||
assertRenderedContentIs('y cont', 'M<span class="autocompleteMatched">y Cont</span>ent');
|
||||
});
|
||||
|
||||
it('should be able to wrap the whole content', function() {
|
||||
assertRenderedContentIs('my content', '<span class="autocompleteMatched">My Content</span>');
|
||||
});
|
||||
|
||||
it('should find matching content case sensitive', function() {
|
||||
assertRenderedContentIs('My Co', '<span class="autocompleteMatched">My Co</span>ntent');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -15,27 +15,36 @@
|
|||
* </div>
|
||||
* Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
|
||||
*/
|
||||
angular.module('piwikApp.directive').directive('piwikDialog', function(piwik) {
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikDialog', piwikDialog);
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
piwikDialog.$inject = ['piwik', '$parse'];
|
||||
|
||||
element.css('display', 'none');
|
||||
function piwikDialog(piwik, $parse) {
|
||||
|
||||
element.on( "dialogclose", function() {
|
||||
scope.$eval(attrs.piwikDialog+'=false');
|
||||
});
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
scope.$watch(attrs.piwikDialog, function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
piwik.helper.modalConfirm(element, {yes: function() {
|
||||
if (attrs.yes) {
|
||||
scope.$eval(attrs.yes);
|
||||
}
|
||||
}});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
element.css('display', 'none');
|
||||
|
||||
element.on( "dialogclose", function() {
|
||||
setTimeout(function () {
|
||||
scope.$apply($parse(attrs.piwikDialog).assign(scope, false));
|
||||
}, 0);
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -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', []);
|
||||
})();
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -12,29 +12,35 @@
|
|||
* Example:
|
||||
* <div piwik-focus-anywhere-but-here="closeDialog()">my dialog</div>
|
||||
*/
|
||||
angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', function($document){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', piwikFocusAnywhereButHere);
|
||||
|
||||
function onClickOutsideElement (event) {
|
||||
if (element.has(event.target).length === 0) {
|
||||
scope.$apply(attr.piwikFocusAnywhereButHere);
|
||||
piwikFocusAnywhereButHere.$inject = ['$document'];
|
||||
|
||||
function piwikFocusAnywhereButHere($document){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attr, ctrl) {
|
||||
|
||||
function onClickOutsideElement (event) {
|
||||
if (element.has(event.target).length === 0) {
|
||||
scope.$apply(attr.piwikFocusAnywhereButHere);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onEscapeHandler (event) {
|
||||
if (event.which === 27) {
|
||||
scope.$apply(attr.piwikFocusAnywhereButHere);
|
||||
function onEscapeHandler (event) {
|
||||
if (event.which === 27) {
|
||||
scope.$apply(attr.piwikFocusAnywhereButHere);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$document.on('keyup', onEscapeHandler);
|
||||
$document.on('mouseup', onClickOutsideElement);
|
||||
scope.$on('$destroy', function() {
|
||||
$document.off('mouseup', onClickOutsideElement);
|
||||
$document.off('keyup', onEscapeHandler);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
$document.on('keyup', onEscapeHandler);
|
||||
$document.on('mouseup', onClickOutsideElement);
|
||||
scope.$on('$destroy', function() {
|
||||
$document.off('mouseup', onClickOutsideElement);
|
||||
$document.off('keyup', onEscapeHandler);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -11,17 +11,23 @@
|
|||
* Example:
|
||||
* <input type="text" piwik-focus-if="view.editName">
|
||||
*/
|
||||
angular.module('piwikApp.directive').directive('piwikFocusIf', function($timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
scope.$watch(attrs.piwikFocusIf, function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
$timeout(function () {
|
||||
element[0].focus();
|
||||
}, 5);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -12,10 +12,14 @@
|
|||
* Example
|
||||
* <a piwik-ignore-click ng-click="doSomething()" href="/">my link</a>
|
||||
*/
|
||||
angular.module('piwikApp.directive').directive('piwikIgnoreClick', function() {
|
||||
return function(scope, element, attrs) {
|
||||
$(element).click(function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
});
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikIgnoreClick', piwikIgnoreClick);
|
||||
|
||||
function piwikIgnoreClick() {
|
||||
return function(scope, element, attrs) {
|
||||
$(element).click(function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
|
|
@ -12,16 +12,20 @@
|
|||
* <div piwik-onenter="save()">
|
||||
* <div piwik-onenter="showList=false">
|
||||
*/
|
||||
angular.module('piwikApp.directive').directive('piwikOnenter', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.bind("keydown keypress", function(event) {
|
||||
if(event.which === 13) {
|
||||
scope.$apply(function(){
|
||||
scope.$eval(attrs.piwikOnenter, {'event': event});
|
||||
});
|
||||
(function () {
|
||||
angular.module('piwikApp.directive').directive('piwikOnenter', piwikOnenter);
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
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();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
||||
36
www/analytics/plugins/CoreHome/angularjs/common/directives/translate.js
vendored
Normal file
36
www/analytics/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);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,44 +1,49 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* 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);
|
||||
|
||||
angular.module('piwikApp.filter').filter('evolution', function() {
|
||||
function evolutionFilter() {
|
||||
|
||||
function calculateEvolution(currentValue, pastValue)
|
||||
{
|
||||
pastValue = parseInt(pastValue, 10);
|
||||
currentValue = parseInt(currentValue, 10) - pastValue;
|
||||
function calculateEvolution(currentValue, pastValue)
|
||||
{
|
||||
pastValue = parseInt(pastValue, 10);
|
||||
currentValue = parseInt(currentValue, 10) - pastValue;
|
||||
|
||||
if (currentValue === 0 || isNaN(currentValue)) {
|
||||
evolution = 0;
|
||||
} else if (pastValue === 0 || isNaN(pastValue)) {
|
||||
evolution = 100;
|
||||
} else {
|
||||
evolution = (currentValue / pastValue) * 100;
|
||||
var evolution;
|
||||
|
||||
if (currentValue === 0 || isNaN(currentValue)) {
|
||||
evolution = 0;
|
||||
} else if (pastValue === 0 || isNaN(pastValue)) {
|
||||
evolution = 100;
|
||||
} else {
|
||||
evolution = (currentValue / pastValue) * 100;
|
||||
}
|
||||
|
||||
return evolution;
|
||||
}
|
||||
|
||||
return evolution;
|
||||
}
|
||||
function formatEvolution(evolution)
|
||||
{
|
||||
evolution = Math.round(evolution);
|
||||
|
||||
function formatEvolution(evolution)
|
||||
{
|
||||
evolution = Math.round(evolution);
|
||||
if (evolution > 0) {
|
||||
evolution = '+' + evolution;
|
||||
}
|
||||
|
||||
if (evolution > 0) {
|
||||
evolution = '+' + evolution;
|
||||
evolution += '%';
|
||||
|
||||
return evolution;
|
||||
}
|
||||
|
||||
evolution += '%';
|
||||
return function(currentValue, pastValue) {
|
||||
var evolution = calculateEvolution(currentValue, pastValue);
|
||||
|
||||
return evolution;
|
||||
return formatEvolution(evolution);
|
||||
};
|
||||
}
|
||||
|
||||
return function(currentValue, pastValue) {
|
||||
var evolution = calculateEvolution(currentValue, pastValue);
|
||||
|
||||
return formatEvolution(evolution);
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
angular.module('piwikApp.filter', []);
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
angular.module('piwikApp.service', []);
|
||||
(function () {
|
||||
angular.module('piwikApp.filter', []);
|
||||
})();
|
||||
26
www/analytics/plugins/CoreHome/angularjs/common/filters/htmldecode.js
vendored
Normal file
26
www/analytics/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
www/analytics/plugins/CoreHome/angularjs/common/filters/length.js
vendored
Normal file
21
www/analytics/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
www/analytics/plugins/CoreHome/angularjs/common/filters/pretty-url.js
vendored
Normal file
16
www/analytics/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://', '');
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* 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);
|
||||
|
||||
angular.module('piwikApp.filter').filter('startFrom', function() {
|
||||
return function(input, start) {
|
||||
start = +start; //parse to int
|
||||
return input.slice(start);
|
||||
};
|
||||
});
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* 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);
|
||||
|
||||
angular.module('piwikApp.filter').filter('translate', function() {
|
||||
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 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);
|
||||
};
|
||||
});
|
||||
return _pk_translate(key, values);
|
||||
};
|
||||
}
|
||||
})();
|
||||
20
www/analytics/plugins/CoreHome/angularjs/common/filters/trim.js
vendored
Normal file
20
www/analytics/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
www/analytics/plugins/CoreHome/angularjs/common/filters/ucfirst.js
vendored
Normal file
21
www/analytics/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);
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,186 +1,305 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
angular.module('piwikApp.service').factory('piwikApi', function ($http, $q, $rootScope, piwik, $window) {
|
||||
// see https://github.com/piwik/piwik/issues/5094 used to detect an ad blocker
|
||||
var hasBlockedContent = false;
|
||||
|
||||
var url = 'index.php';
|
||||
var format = 'json';
|
||||
var getParams = {};
|
||||
var postParams = {};
|
||||
var requestHandle = null;
|
||||
(function () {
|
||||
angular.module('piwikApp.service').factory('piwikApi', piwikApiService);
|
||||
|
||||
var piwikApi = {};
|
||||
piwikApiService.$inject = ['$http', '$q', '$rootScope', 'piwik', '$window'];
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
for (var key in params) {
|
||||
getParams[key] = params[key];
|
||||
function reset () {
|
||||
getParams = {};
|
||||
postParams = {};
|
||||
}
|
||||
}
|
||||
|
||||
function reset () {
|
||||
getParams = {};
|
||||
postParams = {};
|
||||
}
|
||||
function isErrorResponse(response) {
|
||||
return response && response.result == 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the request
|
||||
* @return $promise
|
||||
*/
|
||||
function send () {
|
||||
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
|
||||
});
|
||||
notification.scrollToNotification();
|
||||
}
|
||||
}
|
||||
|
||||
var deferred = $q.defer();
|
||||
var requestHandle = deferred;
|
||||
/**
|
||||
* Send the request
|
||||
* @return $promise
|
||||
*/
|
||||
function send (options) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
var onError = function (message) {
|
||||
deferred.reject(message);
|
||||
requestHandle = null;
|
||||
};
|
||||
if (options.createErrorNotification === undefined) {
|
||||
options.createErrorNotification = true;
|
||||
}
|
||||
|
||||
var onSuccess = function (response) {
|
||||
if (response && response.result == 'error') {
|
||||
function onSuccess(response)
|
||||
{
|
||||
response = response.data;
|
||||
|
||||
if (response.message) {
|
||||
onError(response.message);
|
||||
if (!angular.isDefined(response) || response === null) {
|
||||
return $q.reject(null);
|
||||
|
||||
var UI = require('piwik/UI');
|
||||
var notification = new UI.Notification();
|
||||
notification.show(response.message, {
|
||||
context: 'error',
|
||||
type: 'toast',
|
||||
id: 'ajaxHelper'
|
||||
});
|
||||
notification.scrollToNotification();
|
||||
} else if (isErrorResponse(response)) {
|
||||
|
||||
createResponseErrorNotification(response, options);
|
||||
|
||||
return $q.reject(response.message || null);
|
||||
} else {
|
||||
onError(null);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
function onError(response)
|
||||
{
|
||||
var message = 'Something went wrong';
|
||||
if (response && (response.status === 0 || response.status === -1)) {
|
||||
message = 'Request was possibly aborted';
|
||||
}
|
||||
|
||||
} else {
|
||||
deferred.resolve(response);
|
||||
return $q.reject(message);
|
||||
}
|
||||
requestHandle = null;
|
||||
};
|
||||
|
||||
var headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
// ie 8,9,10 caches ajax requests, prevent this
|
||||
'cache-control': 'no-cache'
|
||||
};
|
||||
var deferred = $q.defer(),
|
||||
requestPromise = deferred.promise;
|
||||
|
||||
var ajaxCall = {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
responseType: format,
|
||||
params: _mixinDefaultGetParams(getParams),
|
||||
data: $.param(getPostParams(postParams)),
|
||||
timeout: deferred.promise,
|
||||
headers: headers
|
||||
};
|
||||
var headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
// ie 8,9,10 caches ajax requests, prevent this
|
||||
'cache-control': 'no-cache'
|
||||
};
|
||||
|
||||
$http(ajaxCall).success(onSuccess).error(onError);
|
||||
var ajaxCall = {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
responseType: format,
|
||||
params: _mixinDefaultGetParams(getParams),
|
||||
data: $.param(getPostParams(postParams)),
|
||||
timeout: requestPromise,
|
||||
headers: headers
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameters to send as POST
|
||||
*
|
||||
* @param {object} params parameter object
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
function getPostParams (params) {
|
||||
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 (!getParamsToMixin[key] && !postParams[key] && defaultParams[key]) {
|
||||
getParamsToMixin[key] = defaultParams[key];
|
||||
}
|
||||
}
|
||||
|
||||
// handle default date & period if not already set
|
||||
if (!getParamsToMixin.date && !postParams.date) {
|
||||
getParamsToMixin.date = piwik.currentDateString || piwik.broadcast.getValueFromUrl('date');
|
||||
if (getParamsToMixin.period == 'range' && piwik.currentDateString) {
|
||||
getParamsToMixin.date = piwik.startDateString + ',' + getParamsToMixin.date;
|
||||
}
|
||||
}
|
||||
|
||||
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';
|
||||
getParams.format = 'JSON2';
|
||||
|
||||
addParams(getParams, 'GET');
|
||||
|
||||
var promise = send(options);
|
||||
|
||||
reset();
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function post(getParams, _postParams_, options) {
|
||||
if (_postParams_) {
|
||||
postParams = _postParams_;
|
||||
}
|
||||
|
||||
return fetch(getParams, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameters to send as POST
|
||||
*
|
||||
* @param {object} params parameter object
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
function getPostParams () {
|
||||
return {
|
||||
token_auth: piwik.token_auth
|
||||
bulkFetch: bulkFetch,
|
||||
post: post,
|
||||
fetch: fetch,
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
abort: abort,
|
||||
abortAll: abortAll
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin the default parameters to send as GET
|
||||
*
|
||||
* @param {object} getParamsToMixin parameter object
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
function _mixinDefaultGetParams (getParamsToMixin) {
|
||||
|
||||
var defaultParams = {
|
||||
idSite: piwik.idSite || piwik.broadcast.getValueFromUrl('idSite'),
|
||||
period: piwik.period || piwik.broadcast.getValueFromUrl('period'),
|
||||
segment: piwik.broadcast.getValueFromHash('segment', $window.location.href.split('#')[1])
|
||||
};
|
||||
|
||||
// never append token_auth to url
|
||||
if (getParamsToMixin.token_auth) {
|
||||
getParamsToMixin.token_auth = null;
|
||||
delete getParamsToMixin.token_auth;
|
||||
}
|
||||
|
||||
for (var key in defaultParams) {
|
||||
if (!getParamsToMixin[key] && !postParams[key] && defaultParams[key]) {
|
||||
getParamsToMixin[key] = defaultParams[key];
|
||||
}
|
||||
}
|
||||
|
||||
// handle default date & period if not already set
|
||||
if (!getParamsToMixin.date && !postParams.date) {
|
||||
getParamsToMixin.date = piwik.currentDateString || piwik.broadcast.getValueFromUrl('date');
|
||||
if (getParamsToMixin.period == 'range' && piwik.currentDateString) {
|
||||
getParamsToMixin.date = piwik.startDateString + ',' + getParamsToMixin.date;
|
||||
}
|
||||
}
|
||||
|
||||
return getParamsToMixin;
|
||||
}
|
||||
|
||||
piwikApi.abort = function () {
|
||||
reset();
|
||||
|
||||
if (requestHandle) {
|
||||
requestHandle.resolve();
|
||||
requestHandle = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform a reading API request.
|
||||
* @param getParams
|
||||
*/
|
||||
piwikApi.fetch = function (getParams) {
|
||||
|
||||
getParams.module = 'API';
|
||||
getParams.format = 'JSON';
|
||||
|
||||
addParams(getParams, 'GET');
|
||||
|
||||
var promise = send();
|
||||
|
||||
reset();
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
piwikApi.post = function (getParams, _postParams_) {
|
||||
if (_postParams_) {
|
||||
postParams = _postParams_;
|
||||
}
|
||||
|
||||
return this.fetch(getParams);
|
||||
};
|
||||
|
||||
return piwikApi;
|
||||
});
|
||||
})();
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* 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);
|
||||
|
||||
angular.module('piwikApp.service').service('piwik', function () {
|
||||
function piwikService() {
|
||||
|
||||
piwik.helper = piwikHelper;
|
||||
piwik.broadcast = broadcast;
|
||||
return piwik;
|
||||
});
|
||||
piwik.helper = piwikHelper;
|
||||
piwik.broadcast = broadcast;
|
||||
return piwik;
|
||||
}
|
||||
})();
|
||||
|
|
@ -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
|
||||
*/
|
||||
(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/');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
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/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
angular.module('piwikApp.directive', []);
|
||||
(function () {
|
||||
angular.module('piwikApp.service', []);
|
||||
})();
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @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>All Websites Dashboard
|
||||
* <div class="inlineHelp>My <strong>inline help</strong></div>
|
||||
* </h2>
|
||||
* -> shows help icon to display inline help on click. Note: You can combine inlinehelp and help-url
|
||||
*/
|
||||
angular.module('piwikApp').directive('piwikEnrichedHeadline', function($document, piwik, $filter){
|
||||
var defaults = {
|
||||
helpUrl: ''
|
||||
};
|
||||
|
||||
return {
|
||||
transclude: true,
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
helpUrl: '@',
|
||||
featureName: '@'
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/enrichedheadline/enrichedheadline.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) {
|
||||
|
||||
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.text());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -1,18 +1,22 @@
|
|||
<div class="enrichedHeadline"
|
||||
ng-mouseenter="view.showIcons=true" ng-mouseleave="view.showIcons=false">
|
||||
<span ng-transclude></span>
|
||||
<div ng-show="!editUrl" class="title" ng-transclude tabindex="6"></div>
|
||||
<a ng-show="editUrl" class="title" href="{{ editUrl }}"
|
||||
title="{{ 'CoreHome_ClickToEditX'|translate:featureName }}"
|
||||
ng-transclude ></a>
|
||||
|
||||
<span ng-show="view.showIcons">
|
||||
<a ng-if="helpUrl && !inlineHelp"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
href="{{ helpUrl }}"
|
||||
title="{{ 'CoreHome_ExternalHelp'|translate }}"
|
||||
class="helpIcon"></a>
|
||||
class="helpIcon"><span class="icon-help"></span></a>
|
||||
|
||||
<a ng-if="inlineHelp"
|
||||
title="{{ 'General_Help'|translate }}"
|
||||
ng-click="view.showInlineHelp=!view.showInlineHelp"
|
||||
class="helpIcon"></a>
|
||||
class="helpIcon"><span class="icon-help"></span></a>
|
||||
|
||||
<div class="ratingIcons"
|
||||
piwik-rate-feature
|
||||
|
|
@ -22,8 +26,9 @@
|
|||
<div class="inlineHelp" ng-show="view.showIcons && view.showInlineHelp">
|
||||
<div ng-bind-html="inlineHelp"></div>
|
||||
<a ng-if="helpUrl"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
href="{{ helpUrl }}"
|
||||
class="readMore">{{ 'General_MoreDetails'|translate }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*!
|
||||
* 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>All Websites Dashboard
|
||||
* <div class="inlineHelp">My <strong>inline help</strong></div>
|
||||
* </h2>
|
||||
* -> 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: '@'
|
||||
},
|
||||
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) {
|
||||
|
||||
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,60 @@
|
|||
.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 {
|
||||
color: @color-black-piwik;
|
||||
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: 4px;
|
||||
max-width: 500px;
|
||||
|
||||
.readMore {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.ratingIcons {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.helpIcon {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0 0 -1px 4px;
|
||||
opacity: 0.3;
|
||||
font-size: 15px;
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
.inlineHelp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.enrichedHeadline {
|
||||
min-height: 22px;
|
||||
|
||||
.inlineHelp {
|
||||
display:block;
|
||||
background: #F7F7F7;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
border: 1px solid #E4E5E4;
|
||||
margin: 10px 0 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
max-width: 500px;
|
||||
|
||||
.readMore {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.ratingIcons {
|
||||
display:inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.helpIcon:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.helpIcon {
|
||||
cursor: pointer;
|
||||
display:inline-block;
|
||||
margin: 0px 0px -1px 4px;
|
||||
width: 16px;
|
||||
opacity: 0.3;
|
||||
height: 16px;
|
||||
background: url(plugins/CoreHome/angularjs/enrichedheadline/help.png) no-repeat;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
*/
|
||||
(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
www/analytics/plugins/CoreHome/angularjs/http404check.js
vendored
Normal file
52
www/analytics/plugins/CoreHome/angularjs/http404check.js
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
(function () {
|
||||
angular.module('piwikApp').factory('http404CheckInterceptor', http404CheckInterceptor);
|
||||
|
||||
http404CheckInterceptor.$inject = ['$q'];
|
||||
|
||||
function http404CheckInterceptor($q) {
|
||||
|
||||
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,31 @@
|
|||
<div piwik-focus-anywhere-but-here="view.showItems=false" class="menuDropdown">
|
||||
|
||||
<span class="title"
|
||||
ng-click="view.showItems=!view.showItems"
|
||||
title="{{ tooltip }}"
|
||||
ng-bind-html="menuTitle"/>
|
||||
|
||||
<div class="items borderedControl" 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>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*!
|
||||
* 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) {
|
||||
|
||||
element.find('.item').on('click', function () {
|
||||
var $self = angular.element(this);
|
||||
|
||||
if ($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');
|
||||
scope.$apply();
|
||||
|
||||
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,105 @@
|
|||
|
||||
.menuDropdown {
|
||||
display: inline-block;
|
||||
padding-right: 14px;
|
||||
color: @theme-color-link;
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: -15px;
|
||||
color: @theme-color-link;
|
||||
display: inline;
|
||||
font-size: 1px;
|
||||
height: 0px;
|
||||
width: 0px;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid @theme-color-link;
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
z-index: 200;
|
||||
position: absolute;
|
||||
border: 1px solid @color-silver-l80;
|
||||
background: @theme-color-background-base;
|
||||
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;
|
||||
|
||||
&: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,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,95 @@
|
|||
/*!
|
||||
* 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: '@?'
|
||||
},
|
||||
transclude: true,
|
||||
templateUrl: 'plugins/CoreHome/angularjs/notification/notification.directive.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'NotificationController',
|
||||
controllerAs: 'notification',
|
||||
link: function (scope, element) {
|
||||
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();
|
||||
});
|
||||
}, 12 * 1000);
|
||||
}
|
||||
|
||||
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,34 @@
|
|||
.system.notification {
|
||||
.alert;
|
||||
|
||||
// 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;
|
||||
}
|
||||
&.notification-warning {
|
||||
.alert-warning;
|
||||
}
|
||||
&.notification-danger,
|
||||
&.notification-error {
|
||||
.alert-danger;
|
||||
}
|
||||
&.notification-info {
|
||||
.alert-info;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
angular.module('piwikApp.config', []);
|
||||
|
||||
(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) {
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
* Piwik - free/libre analytics platform
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
angular.module('piwikApp', [
|
||||
'ngSanitize',
|
||||
'ngAnimate',
|
||||
'piwikApp.config',
|
||||
'piwikApp.service',
|
||||
'piwikApp.directive',
|
||||
'piwikApp.filter'
|
||||
]);
|
||||
angular.module('app', []);
|
||||
(function () {
|
||||
angular.module('piwikApp', [
|
||||
'ngSanitize',
|
||||
'ngAnimate',
|
||||
'ngCookies',
|
||||
'ngDialog',
|
||||
'piwikApp.config',
|
||||
'piwikApp.service',
|
||||
'piwikApp.directive',
|
||||
'piwikApp.filter'
|
||||
]);
|
||||
angular.module('app', []);
|
||||
})();
|
||||
|
|
@ -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').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);
|
||||
};
|
||||
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<div class="quick-access"
|
||||
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"/>
|
||||
<ul ng-hide="!search.term || !view.searchActive || (quickAccess.numMenuItems > 0) || (quickAccess.sitesModel.sites | length)">
|
||||
<li class="no-result">{{ 'General_SearchNoResults' | translate }}</li>
|
||||
</ul>
|
||||
<ul ng-show="search.term && view.searchActive" 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 ng-show="search.term && view.searchActive">
|
||||
<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-html="site.name"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,282 @@
|
|||
/*!
|
||||
* 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('#topRightBar .navbar-right li > a').each(function (index, element) {
|
||||
var $element = $(element);
|
||||
|
||||
if ($element.is('#topmenu-usersmanager')) {
|
||||
// ignore languages manager
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Mousetrap.bind('f', function(event) {
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
event.returnValue = false; // IE
|
||||
}
|
||||
|
||||
scrollFirstElementIntoView(element);
|
||||
|
||||
scope.activateSearch();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
.quick-access {
|
||||
position: relative;
|
||||
|
||||
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%;
|
||||
border: 0 !important;
|
||||
box-shadow: 0 0 !important;
|
||||
border-radius: 0 !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,87 @@
|
|||
/*!
|
||||
* 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('piwikExpandOnClick', piwikExpandOnClick);
|
||||
|
||||
piwikExpandOnClick.$inject = ['$document'];
|
||||
|
||||
function piwikExpandOnClick($document){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attr) {
|
||||
|
||||
element.find('.title').on('click', function () {
|
||||
element.toggleClass('expanded');
|
||||
});
|
||||
|
||||
function onClickOutsideElement (event) {
|
||||
if (element.has(event.target).length === 0) {
|
||||
element.removeClass('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
function onEscapeHandler (event) {
|
||||
if (event.which === 27) {
|
||||
element.removeClass('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
$document.on('keyup', onEscapeHandler);
|
||||
$document.on('mouseup', onClickOutsideElement);
|
||||
scope.$on('$destroy', function() {
|
||||
$document.off('mouseup', onClickOutsideElement);
|
||||
$document.off('keyup', onEscapeHandler);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('piwikApp').directive('piwikExpandOnHover', piwikExpandOnHover);
|
||||
|
||||
piwikExpandOnHover.$inject = ['$document'];
|
||||
|
||||
function piwikExpandOnHover($document){
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attr) {
|
||||
|
||||
element.on('mouseenter', '.title', function () {
|
||||
element.addClass('expanded');
|
||||
});
|
||||
|
||||
element.on('mouseleave', function () {
|
||||
element.removeClass('expanded');
|
||||
});
|
||||
|
||||
function onClickOutsideElement (event) {
|
||||
if (element.has(event.target).length === 0) {
|
||||
element.removeClass('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
function onEscapeHandler (event) {
|
||||
if (event.which === 27) {
|
||||
element.removeClass('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
$document.on('keyup', onEscapeHandler);
|
||||
$document.on('mouseup', onClickOutsideElement);
|
||||
scope.$on('$destroy', function() {
|
||||
$document.off('mouseup', onClickOutsideElement);
|
||||
$document.off('keyup', onEscapeHandler);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
.piwikSelector {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
|
||||
span.title,
|
||||
a.title {
|
||||
.font-default(11px, 12px);
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 11px 19px 10px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
color: @theme-color-text;
|
||||
|
||||
&.activityIndicator {
|
||||
background: url(plugins/Morpheus/images/loading-blue.gif) no-repeat right 9px;
|
||||
|
||||
.icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding-left: 6px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
&.iconHidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.icon:not(.icon-fixed) {
|
||||
float: right;
|
||||
&:after {
|
||||
clear:right;
|
||||
content: ' ';
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
.font-default(11px, 15px);
|
||||
display: none;
|
||||
padding: 5px 19px 11px 19px;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.dropdown {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
angular.module('piwikApp').controller('SiteSelectorController', function($scope, siteSelectorModel, piwik, AUTOCOMPLETE_MIN_SITES){
|
||||
|
||||
$scope.model = siteSelectorModel;
|
||||
|
||||
$scope.autocompleteMinSites = AUTOCOMPLETE_MIN_SITES;
|
||||
$scope.selectedSite = {id: '', name: ''};
|
||||
$scope.activeSiteId = piwik.idSite;
|
||||
|
||||
$scope.switchSite = function (site) {
|
||||
$scope.selectedSite.id = site.idsite;
|
||||
|
||||
if (site.name === $scope.allSitesText) {
|
||||
$scope.selectedSite.name = $scope.allSitesText;
|
||||
} else {
|
||||
$scope.selectedSite.name = site.name.replace(/[\u0000-\u2666]/g, function(c) {
|
||||
return '&#'+c.charCodeAt(0)+';';
|
||||
});
|
||||
}
|
||||
|
||||
if (!$scope.switchSiteOnSelect || $scope.activeSiteId == site.idsite) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (site.idsite == 'all') {
|
||||
piwik.broadcast.propagateNewPage('module=MultiSites&action=index');
|
||||
} else {
|
||||
piwik.broadcast.propagateNewPage('segment=&idSite=' + site.idsite, false);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getUrlAllSites = function () {
|
||||
var newParameters = 'module=MultiSites&action=index';
|
||||
return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters);
|
||||
};
|
||||
$scope.getUrlForSiteId = function (idSite) {
|
||||
var idSiteParam = 'idSite=' + idSite;
|
||||
var newParameters = 'segment=&' + idSiteParam;
|
||||
var hash = piwik.broadcast.isHashExists() ? piwik.broadcast.getHashFromUrl() : "";
|
||||
return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters) +
|
||||
'#' + piwik.helper.getQueryStringWithParametersModified(hash.substring(1), newParameters);
|
||||
};
|
||||
});
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* <div piwik-siteselector>
|
||||
*
|
||||
* More advanced example
|
||||
* <div piwik-siteselector
|
||||
* show-selected-site="true" show-all-sites-item="true" switch-site-on-select="true"
|
||||
* all-sites-location="top|bottom" all-sites-text="test" show-selected-site="true"
|
||||
* show-all-sites-item="true">
|
||||
*
|
||||
* Within a form
|
||||
* <div piwik-siteselector input-name="siteId">
|
||||
*
|
||||
* Events:
|
||||
* Triggers a `change` event on any change
|
||||
* <div piwik-siteselector id="mySelector">
|
||||
* $('#mySelector').on('change', function (event) { event.id/event.name })
|
||||
*/
|
||||
angular.module('piwikApp').directive('piwikSiteselector', function($document, piwik, $filter){
|
||||
var defaults = {
|
||||
name: '',
|
||||
siteid: piwik.idSite,
|
||||
sitename: piwik.siteName,
|
||||
allSitesLocation: 'bottom',
|
||||
allSitesText: $filter('translate')('General_MultiSitesSummary'),
|
||||
showSelectedSite: 'false',
|
||||
showAllSitesItem: 'true',
|
||||
switchSiteOnSelect: 'true'
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
showSelectedSite: '=',
|
||||
showAllSitesItem: '=',
|
||||
switchSiteOnSelect: '=',
|
||||
inputName: '@name',
|
||||
allSitesText: '@',
|
||||
allSitesLocation: '@'
|
||||
},
|
||||
templateUrl: 'plugins/CoreHome/angularjs/siteselector/siteselector.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'SiteSelectorController',
|
||||
compile: function (element, attrs) {
|
||||
|
||||
for (var index in defaults) {
|
||||
if (!attrs[index]) { attrs[index] = defaults[index]; }
|
||||
}
|
||||
|
||||
return function (scope, element, attrs) {
|
||||
|
||||
// selectedSite.id|.name + model is hard-coded but actually the directive should not know about this
|
||||
scope.selectedSite.id = attrs.siteid;
|
||||
scope.selectedSite.name = attrs.sitename;
|
||||
|
||||
if (!attrs.siteid || !attrs.sitename) {
|
||||
scope.model.loadInitialSites();
|
||||
}
|
||||
|
||||
scope.$watch('selectedSite.id', function (newValue, oldValue, scope) {
|
||||
if (newValue != oldValue) {
|
||||
element.attr('siteid', newValue);
|
||||
element.trigger('change', scope.selectedSite);
|
||||
}
|
||||
});
|
||||
|
||||
/** use observe to monitor attribute changes
|
||||
attrs.$observe('maxsitenamewidth', function(val) {
|
||||
// for instance trigger a function or whatever
|
||||
}) */
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/*!
|
||||
* Piwik - Web Analytics
|
||||
*
|
||||
* @link http://piwik.org
|
||||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
angular.module('piwikApp').factory('siteSelectorModel', function (piwikApi, $filter) {
|
||||
|
||||
var model = {};
|
||||
model.sites = [];
|
||||
model.hasMultipleWebsites = false;
|
||||
model.isLoading = false;
|
||||
model.firstSiteName = '';
|
||||
|
||||
var initialSites = null;
|
||||
|
||||
model.updateWebsitesList = function (sites) {
|
||||
|
||||
if (!sites || !sites.length) {
|
||||
model.sites = [];
|
||||
return [];
|
||||
}
|
||||
|
||||
angular.forEach(sites, function (site) {
|
||||
if (site.group) site.name = '[' + site.group + '] ' + site.name;
|
||||
});
|
||||
|
||||
model.sites = $filter('orderBy')(sites, '+name');
|
||||
|
||||
if (!model.firstSiteName) {
|
||||
model.firstSiteName = model.sites[0].name;
|
||||
}
|
||||
|
||||
model.hasMultipleWebsites = model.hasMultipleWebsites || sites.length > 1;
|
||||
|
||||
return model.sites;
|
||||
};
|
||||
|
||||
model.searchSite = function (term) {
|
||||
|
||||
if (!term) {
|
||||
model.loadInitialSites();
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.isLoading) {
|
||||
piwikApi.abort();
|
||||
}
|
||||
|
||||
model.isLoading = true;
|
||||
|
||||
return piwikApi.fetch({
|
||||
method: 'SitesManager.getPatternMatchSites',
|
||||
pattern: term
|
||||
}).then(function (response) {
|
||||
return model.updateWebsitesList(response);
|
||||
})['finally'](function () { // .finally() is not IE8 compatible see https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c
|
||||
model.isLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
model.loadInitialSites = function () {
|
||||
if (initialSites) {
|
||||
model.sites = initialSites;
|
||||
return;
|
||||
}
|
||||
|
||||
this.searchSite('%').then(function (websites) {
|
||||
initialSites = websites;
|
||||
});
|
||||
};
|
||||
|
||||
return model;
|
||||
});
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
/*!
|
||||
* 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('siteSelectorModel', siteSelectorModel);
|
||||
|
||||
siteSelectorModel.$inject = ['piwikApi', '$filter', 'piwik'];
|
||||
|
||||
function siteSelectorModel(piwikApi, $filter, piwik) {
|
||||
|
||||
var initialSites = null;
|
||||
var limitPromise = null;
|
||||
|
||||
var model = {
|
||||
sites : [],
|
||||
hasMultipleWebsites : false,
|
||||
isLoading : false,
|
||||
firstSiteName : '',
|
||||
onlySitesWithAdminAccess: false,
|
||||
updateWebsitesList: updateWebsitesList,
|
||||
searchSite: searchSite,
|
||||
loadSite: loadSite,
|
||||
loadInitialSites: loadInitialSites
|
||||
};
|
||||
|
||||
return model;
|
||||
|
||||
function updateWebsitesList(sites) {
|
||||
|
||||
if (!sites || !sites.length) {
|
||||
model.sites = [];
|
||||
return [];
|
||||
}
|
||||
|
||||
angular.forEach(sites, function (site) {
|
||||
if (site.group) site.name = '[' + site.group + '] ' + site.name;
|
||||
if (!site.name) {
|
||||
return;
|
||||
}
|
||||
// Escape site names, see https://github.com/piwik/piwik/issues/7531
|
||||
site.name = site.name.replace(/[\u0000-\u2666]/g, function(c) {
|
||||
return '&#'+c.charCodeAt(0)+';';
|
||||
});
|
||||
});
|
||||
|
||||
model.sites = sortSites(sites);
|
||||
|
||||
if (!model.firstSiteName) {
|
||||
model.firstSiteName = model.sites[0].name;
|
||||
}
|
||||
|
||||
model.hasMultipleWebsites = model.hasMultipleWebsites || sites.length > 1;
|
||||
|
||||
return model.sites;
|
||||
}
|
||||
|
||||
function searchSite(term) {
|
||||
|
||||
if (!term) {
|
||||
loadInitialSites();
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.isLoading) {
|
||||
if (model.currentRequest) {
|
||||
model.currentRequest.abort();
|
||||
} else if (limitPromise) {
|
||||
limitPromise.abort();
|
||||
limitPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
model.isLoading = true;
|
||||
|
||||
if (!limitPromise) {
|
||||
limitPromise = piwikApi.fetch({method: 'SitesManager.getNumWebsitesToDisplayPerPage'});
|
||||
}
|
||||
|
||||
return limitPromise.then(function (response) {
|
||||
var limit = response.value;
|
||||
|
||||
var methodToCall = 'SitesManager.getPatternMatchSites';
|
||||
if (model.onlySitesWithAdminAccess) {
|
||||
methodToCall = 'SitesManager.getSitesWithAdminAccess';
|
||||
}
|
||||
|
||||
model.currentRequest = piwikApi.fetch({
|
||||
method: methodToCall,
|
||||
limit: limit,
|
||||
pattern: term
|
||||
});
|
||||
|
||||
return model.currentRequest;
|
||||
}).then(function (response) {
|
||||
if (angular.isDefined(response)) {
|
||||
return updateWebsitesList(response);
|
||||
}
|
||||
})['finally'](function () { // .finally() is not IE8 compatible see https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c
|
||||
model.isLoading = false;
|
||||
model.currentRequest = null;
|
||||
});
|
||||
}
|
||||
|
||||
function loadSite(idsite) {
|
||||
if (idsite == 'all') {
|
||||
piwik.broadcast.propagateNewPage('module=MultiSites&action=index');
|
||||
} else {
|
||||
piwik.broadcast.propagateNewPage('segment=&idSite=' + idsite, false);
|
||||
}
|
||||
}
|
||||
|
||||
function sortSites(websites)
|
||||
{
|
||||
return $filter('orderBy')(websites, '+name');
|
||||
}
|
||||
|
||||
function loadInitialSites() {
|
||||
if (initialSites) {
|
||||
model.sites = initialSites;
|
||||
return;
|
||||
}
|
||||
|
||||
searchSite('%').then(function () {
|
||||
initialSites = model.sites
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
@ -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
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').controller('SiteSelectorController', SiteSelectorController);
|
||||
|
||||
SiteSelectorController.$inject = ['$scope', 'siteSelectorModel', 'piwik', 'AUTOCOMPLETE_MIN_SITES'];
|
||||
|
||||
function SiteSelectorController($scope, siteSelectorModel, piwik, AUTOCOMPLETE_MIN_SITES){
|
||||
|
||||
$scope.model = siteSelectorModel;
|
||||
|
||||
$scope.autocompleteMinSites = AUTOCOMPLETE_MIN_SITES;
|
||||
$scope.selectedSite = {id: '', name: ''};
|
||||
$scope.activeSiteId = piwik.idSite;
|
||||
|
||||
$scope.switchSite = function (site) {
|
||||
$scope.selectedSite = {id: site.idsite, name: site.name};
|
||||
|
||||
if (!$scope.switchSiteOnSelect || $scope.activeSiteId == site.idsite) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.model.loadSite(site.idsite);
|
||||
};
|
||||
|
||||
$scope.getUrlAllSites = function () {
|
||||
var newParameters = 'module=MultiSites&action=index';
|
||||
return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters);
|
||||
};
|
||||
$scope.getUrlForSiteId = function (idSite) {
|
||||
var idSiteParam = 'idSite=' + idSite;
|
||||
var newParameters = 'segment=&' + idSiteParam;
|
||||
var hash = piwik.broadcast.isHashExists() ? piwik.broadcast.getHashFromUrl() : "";
|
||||
return piwik.helper.getCurrentQueryStringWithParametersModified(newParameters) +
|
||||
'#' + piwik.helper.getQueryStringWithParametersModified(hash.substring(1), newParameters);
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -1,25 +1,32 @@
|
|||
<div piwik-focus-anywhere-but-here="view.showSitesList=false" class="custom_select"
|
||||
ng-class="{'sites_autocomplete--dropdown': (model.hasMultipleWebsites || showAllSitesItem || !model.sites.length)}">
|
||||
<div piwik-focus-anywhere-but-here="view.showSitesList=false"
|
||||
class="siteSelector piwikSelector borderedControl"
|
||||
ng-class="{'expanded': view.showSitesList}">
|
||||
|
||||
<script type="text/ng-template" id="siteselector_allsiteslink.html">
|
||||
<div ng-click="switchSite({idsite: 'all', name: allSitesText});view.showSitesList=false;"
|
||||
class="custom_select_all">
|
||||
<a href="{{ getUrlAllSites() }}"
|
||||
piwik-ignore-click
|
||||
ng-bind-html="allSitesText"></a>
|
||||
ng-bind-html="allSitesText" tabindex="4"></a>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<input ng-if="inputName" type="hidden" name="{{ inputName }}" ng-value="selectedSite.id"/>
|
||||
|
||||
<a ng-click="view.showSitesList=!view.showSitesList; view.showSitesList && model.loadInitialSites()"
|
||||
<a ng-click="view.showSitesList=!view.showSitesList; view.showSitesList && !model.isLoading && model.loadInitialSites();"
|
||||
piwik-onenter="view.showSitesList=!view.showSitesList; view.showSitesList && !model.isLoading && model.loadInitialSites();"
|
||||
href="javascript:void(0)"
|
||||
class="custom_select_main_link"
|
||||
ng-class="{'loading': model.isLoading}">
|
||||
<span ng-bind-html="selectedSite.name || model.firstSiteName">?</span>
|
||||
title="{{ 'CoreHome_ChangeCurrentWebsite'|translate:((selectedSite.name || model.firstSiteName)|htmldecode) }}"
|
||||
ng-class="{'loading': model.isLoading}"
|
||||
class="title" tabindex="4">
|
||||
<span class="icon icon-arrow-bottom"
|
||||
ng-class="{'iconHidden': model.isLoading, 'collapsed': !view.showSitesList}"></span>
|
||||
<span>{{ 'General_Website'| translate }}:
|
||||
<span ng-bind-html="selectedSite.name || model.firstSiteName">?</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div ng-show="view.showSitesList" class="custom_select_block">
|
||||
<div ng-show="view.showSitesList" class="dropdown">
|
||||
<div ng-if="allSitesLocation=='top' && showAllSitesItem"
|
||||
ng-include="'siteselector_allsiteslink.html'"></div>
|
||||
|
||||
|
|
@ -29,7 +36,9 @@
|
|||
ng-repeat="site in model.sites"
|
||||
ng-hide="!showSelectedSite && activeSiteId==site.idsite">
|
||||
<a piwik-ignore-click href="{{ getUrlForSiteId(site.idsite) }}"
|
||||
piwik-autocomplete-matched="view.searchTerm">{{ site.name }}</a>
|
||||
piwik-autocomplete-matched="view.searchTerm"
|
||||
title="{{ site.name|htmldecode }}"
|
||||
ng-bind-html="site.name" tabindex="4"></a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul ng-show="!model.sites.length && view.searchTerm" class="ui-autocomplete ui-front ui-menu ui-widget ui-widget-content ui-corner-all siteSelect">
|
||||
|
|
@ -44,13 +53,12 @@
|
|||
|
||||
<div class="custom_select_search" ng-show="autocompleteMinSites <= model.sites.length || view.searchTerm">
|
||||
<input type="text"
|
||||
piwik-focus-if="view.showSitesList"
|
||||
ng-click="view.searchTerm=''"
|
||||
ng-model="view.searchTerm"
|
||||
ng-change="model.searchSite(view.searchTerm)"
|
||||
placeholder="{{ 'General_Search' | translate }}"
|
||||
class="websiteSearch inp"/>
|
||||
<input type="submit"
|
||||
ng-click="model.searchSite(view.searchTerm)"
|
||||
value="{{ 'General_Search' | translate }}" class="but"/>
|
||||
<img title="Clear"
|
||||
ng-show="view.searchTerm"
|
||||
ng-click="view.searchTerm=''; model.loadInitialSites()"
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*!
|
||||
* 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-siteselector>
|
||||
*
|
||||
* More advanced example
|
||||
* <div piwik-siteselector
|
||||
* show-selected-site="true" show-all-sites-item="true" switch-site-on-select="true"
|
||||
* all-sites-location="top|bottom" all-sites-text="test" show-selected-site="true"
|
||||
* show-all-sites-item="true" only-sites-with-admin-access="true">
|
||||
*
|
||||
* Within a form
|
||||
* <div piwik-siteselector input-name="siteId">
|
||||
*
|
||||
* Events:
|
||||
* Triggers a `change` event on any change
|
||||
* <div piwik-siteselector id="mySelector">
|
||||
* $('#mySelector').on('change', function (event) { event.id/event.name })
|
||||
*/
|
||||
(function () {
|
||||
angular.module('piwikApp').directive('piwikSiteselector', piwikSiteselector);
|
||||
|
||||
piwikSiteselector.$inject = ['$document', 'piwik', '$filter', '$timeout'];
|
||||
|
||||
function piwikSiteselector($document, piwik, $filter, $timeout){
|
||||
var defaults = {
|
||||
name: '',
|
||||
siteid: piwik.idSite,
|
||||
sitename: piwik.siteName,
|
||||
allSitesLocation: 'bottom',
|
||||
allSitesText: $filter('translate')('General_MultiSitesSummary'),
|
||||
showSelectedSite: 'false',
|
||||
showAllSitesItem: 'true',
|
||||
switchSiteOnSelect: 'true',
|
||||
onlySitesWithAdminAccess: 'false'
|
||||
};
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
showSelectedSite: '=',
|
||||
showAllSitesItem: '=',
|
||||
switchSiteOnSelect: '=',
|
||||
onlySitesWithAdminAccess: '=',
|
||||
inputName: '@name',
|
||||
allSitesText: '@',
|
||||
allSitesLocation: '@'
|
||||
},
|
||||
require: "?ngModel",
|
||||
templateUrl: 'plugins/CoreHome/angularjs/siteselector/siteselector.directive.html?cb=' + piwik.cacheBuster,
|
||||
controller: 'SiteSelectorController',
|
||||
compile: function (element, attrs) {
|
||||
|
||||
for (var index in defaults) {
|
||||
if (attrs[index] === undefined) {
|
||||
attrs[index] = defaults[index];
|
||||
}
|
||||
}
|
||||
|
||||
return function (scope, element, attrs, ngModel) {
|
||||
scope.selectedSite = {id: attrs.siteid, name: attrs.sitename};
|
||||
scope.model.onlySitesWithAdminAccess = scope.onlySitesWithAdminAccess;
|
||||
scope.model.loadInitialSites();
|
||||
|
||||
if (ngModel) {
|
||||
ngModel.$setViewValue(scope.selectedSite);
|
||||
}
|
||||
|
||||
scope.$watch('selectedSite.id', function (newValue, oldValue, scope) {
|
||||
if (newValue != oldValue) {
|
||||
element.attr('siteid', newValue);
|
||||
element.trigger('change', scope.selectedSite);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch('selectedSite', function (newValue) {
|
||||
if (ngModel) {
|
||||
ngModel.$setViewValue(newValue);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch('view.showSitesList', function (newValue) {
|
||||
element.toggleClass('expanded', !! newValue);
|
||||
});
|
||||
|
||||
$timeout(function () {
|
||||
initTopControls();
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
.autocompleteMatched {
|
||||
color: #5256BE;
|
||||
font-weight: bold;
|
||||
}
|
||||
.siteSelector {
|
||||
a.title {
|
||||
.icon.collapsed.iconHidden {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.dropdown {
|
||||
max-width: 210px;
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
.sites_autocomplete {
|
||||
position: static !important;
|
||||
height: 36px;
|
||||
z-index: 99;
|
||||
vertical-align: middle;
|
||||
|
||||
> .siteSelector {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
a.title {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.siteSelector.expanded {
|
||||
.loading {
|
||||
background: url(plugins/Morpheus/images/loading-blue.gif) no-repeat 94% 11px;
|
||||
}
|
||||
}
|
||||
|
||||
.siteSelector a.title,
|
||||
.siteSelector .custom_select_ul_list li a,
|
||||
.siteSelector .custom_select_all a,
|
||||
.siteSelector .custom_select_main_link > span {
|
||||
display: inline-block;
|
||||
max-width: 210px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0;
|
||||
color: @theme-color-text;
|
||||
text-transform: uppercase;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.siteSelector a.title {
|
||||
> span {
|
||||
max-width: 161px;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
span {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.siteSelector .custom_select_ul_list,
|
||||
.siteSelector ul.ui-autocomplete {
|
||||
position: relative;
|
||||
list-style: none;
|
||||
line-height: 18px;
|
||||
padding: 0 0 15px 0;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.siteSelector .custom_select_ul_list {
|
||||
padding: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.siteSelector .dropdown {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.siteSelector .custom_select_ul_list li a,
|
||||
.siteSelector .custom_select_all a {
|
||||
line-height: 18px;
|
||||
height: auto;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding-left: 5px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.siteSelector .custom_select_ul_list li a:hover,
|
||||
.siteSelector .custom_select_all a:hover {
|
||||
background: #ebeae6;
|
||||
}
|
||||
|
||||
.siteSelector .custom_select_all a {
|
||||
text-decoration: none;
|
||||
margin: 0 0 5px -5px;
|
||||
}
|
||||
|
||||
.siteSelector .custom_select_search {
|
||||
margin: 0;
|
||||
height: 33px;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
padding-top: 4px;
|
||||
|
||||
.inp {
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
padding: 4px 6px !important;
|
||||
border: 1px solid #d0d0d0 !important;
|
||||
background: transparent !important;
|
||||
font-size: 11px !important;
|
||||
color: #454545 !important;
|
||||
}
|
||||
.reset {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.siteSelector {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.sites_selector_container>.siteSelector {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.custom_selector_container .ui-menu-item,
|
||||
.custom_selector_container .ui-menu-item a {
|
||||
float:none;position:static
|
||||
}
|
||||
|
||||
.custom_select_block_show {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
max-width:inherit;
|
||||
}
|
||||
|
||||
.sites_selector_container {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.siteSelect a {
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
|
||||
|
||||
/*sites_autocomplete*/
|
||||
.sites_autocomplete {
|
||||
position: absolute;
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
height: 30px; /* Hack to not push the dashboard widget below */
|
||||
}
|
||||
|
||||
.sites_selector_in_dashboard {
|
||||
margin-top:10px;
|
||||
}
|
||||
.top_bar_sites_selector {
|
||||
float: right
|
||||
}
|
||||
|
||||
.top_bar_sites_selector > label {
|
||||
display: inline-block;
|
||||
padding: 7px 0 6px 0;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.top_bar_sites_selector > .sites_autocomplete {
|
||||
position: static;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.autocompleteMatched {
|
||||
color: #5256BE;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select {
|
||||
float: left;
|
||||
position: relative;
|
||||
z-index: 19;
|
||||
background: #fff url(plugins/Zeitgeist/images/sites_selection.png) repeat-x 0 0;
|
||||
border: 1px solid #d4d4d4;
|
||||
color: #255792;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
min-width: 165px;
|
||||
padding: 5px 6px 4px;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_main_link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
background: none;
|
||||
cursor: default;
|
||||
height:1.4em;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_ul_list li a,
|
||||
.sites_autocomplete .custom_select_all a,
|
||||
.sites_autocomplete .custom_select_main_link > span {
|
||||
display: inline-block;
|
||||
max-width: 140px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 20px 0 4px;
|
||||
}
|
||||
|
||||
.sites_autocomplete--dropdown .custom_select_main_link:not(.loading):before {
|
||||
content: " \25BC";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
font-size: 0.8em;
|
||||
margin-top: 0.2em;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.sites_autocomplete--dropdown .custom_select_main_link {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_main_link.loading {
|
||||
background: url(plugins/Zeitgeist/images/loading-blue.gif) no-repeat right 3px;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_ul_list,
|
||||
.sites_autocomplete ul.ui-autocomplete {
|
||||
position: relative;
|
||||
list-style: none;
|
||||
line-height: 18px;
|
||||
padding: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_ul_list li a,
|
||||
.sites_autocomplete .custom_select_all a {
|
||||
line-height: 18px;
|
||||
height: auto;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_ul_list li a:hover,
|
||||
.sites_autocomplete .custom_select_all a:hover {
|
||||
background: #ebeae6;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_all a {
|
||||
text-decoration: none;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_search {
|
||||
margin: 0 0 0 4px;
|
||||
height: 26px;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
background: url(plugins/Zeitgeist/images/search_bg.png) no-repeat 0 0;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_search .inp {
|
||||
vertical-align: top;
|
||||
width: 114px;
|
||||
padding: 2px 6px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
font-size: 10px;
|
||||
color: #454545;
|
||||
|
||||
}
|
||||
.sites_autocomplete {
|
||||
width: 165px;
|
||||
}
|
||||
|
||||
.sites_autocomplete .custom_select_search .but {
|
||||
vertical-align: top;
|
||||
font-size: 10px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
width: 21px;
|
||||
height: 17px;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sites_selector_container>.sites_autocomplete {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.custom_selector_container .ui-menu-item,
|
||||
.custom_selector_container .ui-menu-item a {
|
||||
float:none;position:static
|
||||
}
|
||||
|
||||
.custom_select_search .reset {
|
||||
position: relative; top: 4px; left: -44px; cursor: pointer;
|
||||
}
|
||||
|
||||
.custom_select_block {
|
||||
overflow: hidden;
|
||||
max-width: inherit;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.custom_select_block_show {
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
max-width:inherit;
|
||||
}
|
||||
|
||||
.sites_selector_container {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.siteSelect a {
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue