correct URL-routes for Quest submissions and update translations for Questtype ?Submit?

This commit is contained in:
coderkun 2014-04-28 09:34:44 +02:00
commit dec3048077
3453 changed files with 593379 additions and 0 deletions

View file

@ -0,0 +1,108 @@
/*!
* Piwik - Web Analytics
*
* @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"
*/
(function () {
function scrollToAnchorNode($node)
{
$.scrollTo($node, 20);
}
function preventDefaultIfEventExists(event)
{
if (event) {
event.preventDefault();
}
}
function scrollToAnchorIfPossible(hash, event)
{
if (!hash) {
return;
}
if (-1 !== hash.indexOf('&')) {
return;
}
var $node = $('#' + hash);
if ($node && $node.length) {
scrollToAnchorNode($node);
preventDefaultIfEventExists(event);
return;
}
$node = $('a[name='+ hash + ']');
if ($node && $node.length) {
scrollToAnchorNode($node);
preventDefaultIfEventExists(event);
}
}
function isLinkWithinSamePage(location, newUrl)
{
if (location && location.origin && -1 === newUrl.indexOf(location.origin)) {
// link to different domain
return false;
}
if (location && location.pathname && -1 === newUrl.indexOf(location.pathname)) {
// link to different path
return false;
}
if (location && location.search && -1 === newUrl.indexOf(location.search)) {
// link with different search
return false;
}
return true;
}
function handleScrollToAnchorIfPresentOnPageLoad()
{
if (location.hash.substr(0, 2) == '#/') {
var hash = location.hash.substr(2);
scrollToAnchorIfPossible(hash, null);
}
}
function handleScrollToAnchorAfterPageLoad()
{
angular.module('piwikApp').run(['$rootScope', function ($rootScope) {
$rootScope.$on('$locationChangeStart', function (event, newUrl, oldUrl, $location) {
if (!newUrl) {
return;
}
var hashPos = newUrl.indexOf('#/');
if (-1 === hashPos) {
return;
}
if (!isLinkWithinSamePage(this.location, newUrl)) {
return;
}
var hash = newUrl.substr(hashPos + 2);
scrollToAnchorIfPossible(hash, event);
});
}]);
}
handleScrollToAnchorAfterPageLoad();
$(handleScrollToAnchorIfPresentOnPageLoad);
})();

View file

@ -0,0 +1,42 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* If the given text or resolved expression matches any text within the element, the matching text will be wrapped
* with a class.
*
* Example:
* <div piwik-autocomplete-matched="'text'">My text</div> ==> <div>My <span class="autocompleteMatched">text</span></div>
*
* <div piwik-autocomplete-matched="searchTerm">{{ name }}</div>
* <input type="text" ng-model="searchTerm">
*/
angular.module('piwikApp.directive').directive('piwikAutocompleteMatched', function() {
return function(scope, element, attrs) {
var searchTerm;
scope.$watch(attrs.piwikAutocompleteMatched, function(value) {
searchTerm = value;
updateText();
});
function updateText () {
if (!searchTerm || !element) {
return;
}
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);
}
}
};
});

View file

@ -0,0 +1,43 @@
/*!
* 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');
});
});
});

View file

@ -0,0 +1,41 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Usage:
* <div piwik-dialog="showDialog">...</div>
* Will show dialog once showDialog evaluates to true.
*
* <div piwik-dialog="showDialog" yes="executeMyFunction();">
* ... <input type="button" role="yes" value="button">
* </div>
* Will execute the "executeMyFunction" function in the current scope once the yes button is pressed.
*/
angular.module('piwikApp.directive').directive('piwikDialog', function(piwik) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.css('display', 'none');
element.on( "dialogclose", function() {
scope.$eval(attrs.piwikDialog+'=false');
});
scope.$watch(attrs.piwikDialog, function(newValue, oldValue) {
if (newValue) {
piwik.helper.modalConfirm(element, {yes: function() {
if (attrs.yes) {
scope.$eval(attrs.yes);
}
}});
}
});
}
};
});

View file

@ -0,0 +1,8 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.directive', []);

View file

@ -0,0 +1,40 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* The given expression will be executed when the user presses either escape or presses something outside
* of this element
*
* Example:
* <div piwik-focus-anywhere-but-here="closeDialog()">my dialog</div>
*/
angular.module('piwikApp.directive').directive('piwikFocusAnywhereButHere', function($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);
}
}
$document.on('keyup', onEscapeHandler);
$document.on('mouseup', onClickOutsideElement);
scope.$on('$destroy', function() {
$document.off('mouseup', onClickOutsideElement);
$document.off('keyup', onEscapeHandler);
});
}
};
});

View file

@ -0,0 +1,27 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* If the given expression evaluates to true the element will be focussed
*
* Example:
* <input type="text" piwik-focus-if="view.editName">
*/
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);
}
});
}
};
});

View file

@ -0,0 +1,21 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Prevents the default behavior of the click. For instance useful if a link should only work in case the user
* does a "right click open in new window".
*
* Example
* <a piwik-ignore-click ng-click="doSomething()" href="/">my link</a>
*/
angular.module('piwikApp.directive').directive('piwikIgnoreClick', function() {
return function(scope, element, attrs) {
$(element).click(function(event) {
event.preventDefault();
});
};
});

View file

@ -0,0 +1,27 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
/**
* Allows you to define any expression to be executed in case the user presses enter
*
* Example
* <div piwik-onenter="save()">
* <div piwik-onenter="showList=false">
*/
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});
});
event.preventDefault();
}
});
};
});

View file

@ -0,0 +1,44 @@
/*!
* 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').filter('evolution', function() {
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;
}
return evolution;
}
function formatEvolution(evolution)
{
evolution = Math.round(evolution);
if (evolution > 0) {
evolution = '+' + evolution;
}
evolution += '%';
return evolution;
}
return function(currentValue, pastValue) {
var evolution = calculateEvolution(currentValue, pastValue);
return formatEvolution(evolution);
};
});

View file

@ -0,0 +1,7 @@
/*!
* 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', []);

View file

@ -0,0 +1,13 @@
/*!
* 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').filter('startFrom', function() {
return function(input, start) {
start = +start; //parse to int
return input.slice(start);
};
});

View file

@ -0,0 +1,40 @@
/*!
* 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([]);
});
});
});

View file

@ -0,0 +1,19 @@
/*!
* 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').filter('translate', function() {
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);
};
});

View file

@ -0,0 +1,186 @@
/*!
* Piwik - Web Analytics
*
* @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) {
var url = 'index.php';
var format = 'json';
var getParams = {};
var postParams = {};
var requestHandle = null;
var piwikApi = {};
/**
* Adds params to the request.
* If params are given more then once, the latest given value is used for the request
*
* @param {object} params
* @return {void}
*/
function addParams (params) {
if (typeof params == 'string') {
params = piwik.broadcast.getValuesFromUrl(params);
}
for (var key in params) {
getParams[key] = params[key];
}
}
function reset () {
getParams = {};
postParams = {};
}
/**
* Send the request
* @return $promise
*/
function send () {
var deferred = $q.defer();
var requestHandle = deferred;
var onError = function (message) {
deferred.reject(message);
requestHandle = null;
};
var onSuccess = function (response) {
if (response && response.result == 'error') {
if (response.message) {
onError(response.message);
var UI = require('piwik/UI');
var notification = new UI.Notification();
notification.show(response.message, {
context: 'error',
type: 'toast',
id: 'ajaxHelper'
});
notification.scrollToNotification();
} else {
onError(null);
}
} else {
deferred.resolve(response);
}
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 ajaxCall = {
method: 'POST',
url: url,
responseType: format,
params: _mixinDefaultGetParams(getParams),
data: $.param(getPostParams(postParams)),
timeout: deferred.promise,
headers: headers
};
$http(ajaxCall).success(onSuccess).error(onError);
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
};
}
/**
* 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;
});

View file

@ -0,0 +1,13 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.service').service('piwik', function () {
piwik.helper = piwikHelper;
piwik.broadcast = broadcast;
return piwik;
});

View file

@ -0,0 +1,37 @@
/*!
* 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/');
});
});
});

View file

@ -0,0 +1,8 @@
/*!
* Piwik - Web Analytics
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
angular.module('piwikApp.service', []);

View file

@ -0,0 +1,66 @@
/*!
* 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());
}
};
}
};
});

View file

@ -0,0 +1,29 @@
<div class="enrichedHeadline"
ng-mouseenter="view.showIcons=true" ng-mouseleave="view.showIcons=false">
<span ng-transclude></span>
<span ng-show="view.showIcons">
<a ng-if="helpUrl && !inlineHelp"
target="_blank"
href="{{ helpUrl }}"
title="{{ 'CoreHome_ExternalHelp'|translate }}"
class="helpIcon"></a>
<a ng-if="inlineHelp"
title="{{ 'General_Help'|translate }}"
ng-click="view.showInlineHelp=!view.showInlineHelp"
class="helpIcon"></a>
<div class="ratingIcons"
piwik-rate-feature
title="{{ featureName }}"></div>
</span>
<div class="inlineHelp" ng-show="view.showIcons && view.showInlineHelp">
<div ng-bind-html="inlineHelp"></div>
<a ng-if="helpUrl"
target="_blank"
href="{{ helpUrl }}"
class="readMore">{{ 'General_MoreDetails'|translate }}</a>
</div>
</div>

View file

@ -0,0 +1,44 @@
.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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

View 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
*/
angular.module('piwikApp', [
'ngSanitize',
'ngAnimate',
'piwikApp.config',
'piwikApp.service',
'piwikApp.directive',
'piwikApp.filter'
]);
angular.module('app', []);

View file

@ -0,0 +1,9 @@
angular.module('piwikApp.config', []);
(function () {
var piwikAppConfig = angular.module('piwikApp.config');
// we probably want this later as a separate config file, till then it serves as a "bridge"
for (var index in piwik.config) {
piwikAppConfig.constant(index.toUpperCase(), piwik.config[index]);
}
})();

View file

@ -0,0 +1,49 @@
/*!
* 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);
};
});

View file

@ -0,0 +1,80 @@
/*!
* 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
}) */
};
}
};
});

View file

@ -0,0 +1,75 @@
/*!
* 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;
});

View file

@ -0,0 +1,61 @@
<div piwik-focus-anywhere-but-here="view.showSitesList=false" class="custom_select"
ng-class="{'sites_autocomplete--dropdown': (model.hasMultipleWebsites || showAllSitesItem || !model.sites.length)}">
<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>
</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()"
href="javascript:void(0)"
class="custom_select_main_link"
ng-class="{'loading': model.isLoading}">
<span ng-bind-html="selectedSite.name || model.firstSiteName">?</span>
</a>
<div ng-show="view.showSitesList" class="custom_select_block">
<div ng-if="allSitesLocation=='top' && showAllSitesItem"
ng-include="'siteselector_allsiteslink.html'"></div>
<div class="custom_select_container">
<ul class="custom_select_ul_list" ng-click="view.showSitesList=false;">
<li ng-click="switchSite(site)"
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>
</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">
<li class="ui-menu-item">
<a class="ui-corner-all" tabindex="-1">{{ ('SitesManager_NotFound' | translate) + ' ' + view.searchTerm }}</a>
</li>
</ul>
</div>
<div ng-if="allSitesLocation=='bottom' && showAllSitesItem"
ng-include="'siteselector_allsiteslink.html'"></div>
<div class="custom_select_search" ng-show="autocompleteMinSites <= model.sites.length || view.searchTerm">
<input type="text"
ng-click="view.searchTerm=''"
ng-model="view.searchTerm"
ng-change="model.searchSite(view.searchTerm)"
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()"
class="reset"
src="plugins/CoreHome/images/reset_search.png"/>
</div>
</div>
</div>

View file

@ -0,0 +1,177 @@
/*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;
}