bower update
This commit is contained in:
parent
270410224d
commit
e8340646b8
|
@ -2,7 +2,7 @@
|
|||
* angular-ui-bootstrap
|
||||
* http://angular-ui.github.io/bootstrap/
|
||||
|
||||
* Version: 2.4.0 - 2016-12-29
|
||||
* Version: 2.5.0 - 2017-01-28
|
||||
* License: MIT
|
||||
*/angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse","ui.bootstrap.tabindex","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.multiMap","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
|
||||
angular.module("ui.bootstrap.tpls", ["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/year.html","uib/template/datepickerPopup/popup.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]);
|
||||
|
@ -80,7 +80,7 @@ angular.module('ui.bootstrap.collapse', [])
|
|||
to: getScrollFromElement(element[0])
|
||||
}).then(expandDone);
|
||||
}
|
||||
});
|
||||
}, angular.noop);
|
||||
}
|
||||
|
||||
function expandDone() {
|
||||
|
@ -119,7 +119,7 @@ angular.module('ui.bootstrap.collapse', [])
|
|||
to: cssTo
|
||||
}).then(collapseDone);
|
||||
}
|
||||
});
|
||||
}, angular.noop);
|
||||
}
|
||||
|
||||
function collapseDone() {
|
||||
|
@ -436,7 +436,7 @@ angular.module('ui.bootstrap.carousel', [])
|
|||
slides = self.slides = $scope.slides = [],
|
||||
SLIDE_DIRECTION = 'uib-slideDirection',
|
||||
currentIndex = $scope.active,
|
||||
currentInterval, isPlaying, bufferedTransitions = [];
|
||||
currentInterval, isPlaying;
|
||||
|
||||
var destroyed = false;
|
||||
$element.addClass('carousel');
|
||||
|
@ -498,11 +498,6 @@ angular.module('ui.bootstrap.carousel', [])
|
|||
self.removeSlide = function(slide) {
|
||||
var index = findSlideIndex(slide);
|
||||
|
||||
var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
|
||||
if (bufferedIndex !== -1) {
|
||||
bufferedTransitions.splice(bufferedIndex, 1);
|
||||
}
|
||||
|
||||
//get the index of the slide inside the carousel
|
||||
slides.splice(index, 1);
|
||||
if (slides.length > 0 && currentIndex === index) {
|
||||
|
@ -526,7 +521,6 @@ angular.module('ui.bootstrap.carousel', [])
|
|||
if (slides.length === 0) {
|
||||
currentIndex = null;
|
||||
$scope.active = null;
|
||||
clearBufferedTransitions();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -541,8 +535,6 @@ angular.module('ui.bootstrap.carousel', [])
|
|||
if (nextSlide.slide.index !== currentIndex &&
|
||||
!$scope.$currentTransition) {
|
||||
goNext(nextSlide.slide, nextIndex, direction);
|
||||
} else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
|
||||
bufferedTransitions.push(slides[nextIndex]);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -611,12 +603,6 @@ angular.module('ui.bootstrap.carousel', [])
|
|||
}
|
||||
});
|
||||
|
||||
function clearBufferedTransitions() {
|
||||
while (bufferedTransitions.length) {
|
||||
bufferedTransitions.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function getSlideByIndex(index) {
|
||||
for (var i = 0, l = slides.length; i < l; ++i) {
|
||||
if (slides[i].index === index) {
|
||||
|
@ -652,14 +638,6 @@ angular.module('ui.bootstrap.carousel', [])
|
|||
if (phase === 'close') {
|
||||
$scope.$currentTransition = null;
|
||||
$animate.off('addClass', element);
|
||||
if (bufferedTransitions.length) {
|
||||
var nextSlide = bufferedTransitions.pop().slide;
|
||||
var nextIndex = nextSlide.index;
|
||||
var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
|
||||
clearBufferedTransitions();
|
||||
|
||||
goNext(nextSlide, nextIndex, nextDirection);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -690,7 +668,6 @@ angular.module('ui.bootstrap.carousel', [])
|
|||
function resetTransition(slides) {
|
||||
if (!slides.length) {
|
||||
$scope.$currentTransition = null;
|
||||
clearBufferedTransitions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1552,7 +1529,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
$scope.$watch('datepickerOptions.' + key, function(value) {
|
||||
if (value) {
|
||||
if (angular.isDate(value)) {
|
||||
self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
|
||||
self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.getOption('timezone'));
|
||||
} else {
|
||||
if ($datepickerLiteralWarning) {
|
||||
$log.warn('Literal date support has been deprecated, please switch to date object usage');
|
||||
|
@ -1562,7 +1539,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
}
|
||||
} else {
|
||||
self[key] = datepickerConfig[key] ?
|
||||
dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) :
|
||||
dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.getOption('timezone')) :
|
||||
null;
|
||||
}
|
||||
|
||||
|
@ -1609,14 +1586,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
|
||||
this.init = function(ngModelCtrl_) {
|
||||
ngModelCtrl = ngModelCtrl_;
|
||||
ngModelOptions = ngModelCtrl_.$options ||
|
||||
$scope.datepickerOptions.ngModelOptions ||
|
||||
datepickerConfig.ngModelOptions;
|
||||
ngModelOptions = extractOptions(ngModelCtrl);
|
||||
|
||||
if ($scope.datepickerOptions.initDate) {
|
||||
self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
|
||||
self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.getOption('timezone')) || new Date();
|
||||
$scope.$watch('datepickerOptions.initDate', function(initDate) {
|
||||
if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
|
||||
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
|
||||
self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.getOption('timezone'));
|
||||
self.refreshView();
|
||||
}
|
||||
});
|
||||
|
@ -1626,8 +1602,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
|
||||
var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
|
||||
this.activeDate = !isNaN(date) ?
|
||||
dateParser.fromTimezone(date, ngModelOptions.timezone) :
|
||||
dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
|
||||
dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')) :
|
||||
dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
|
||||
|
||||
ngModelCtrl.$render = function() {
|
||||
self.render();
|
||||
|
@ -1640,7 +1616,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
isValid = !isNaN(date);
|
||||
|
||||
if (isValid) {
|
||||
this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
|
||||
this.activeDate = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
|
||||
} else if (!$datepickerSuppressError) {
|
||||
$log.error('Datepicker directive: "ng-model" value must be a Date object');
|
||||
}
|
||||
|
@ -1657,7 +1633,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
}
|
||||
|
||||
var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
|
||||
date = dateParser.fromTimezone(date, ngModelOptions.timezone);
|
||||
date = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
|
||||
ngModelCtrl.$setValidity('dateDisabled', !date ||
|
||||
this.element && !this.isDisabled(date));
|
||||
}
|
||||
|
@ -1665,9 +1641,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
|
||||
this.createDateObject = function(date, format) {
|
||||
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
|
||||
model = dateParser.fromTimezone(model, ngModelOptions.timezone);
|
||||
model = dateParser.fromTimezone(model, ngModelOptions.getOption('timezone'));
|
||||
var today = new Date();
|
||||
today = dateParser.fromTimezone(today, ngModelOptions.timezone);
|
||||
today = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
|
||||
var time = this.compare(date, today);
|
||||
var dt = {
|
||||
date: date,
|
||||
|
@ -1713,9 +1689,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
|
||||
$scope.select = function(date) {
|
||||
if ($scope.datepickerMode === self.minMode) {
|
||||
var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
|
||||
var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.getOption('timezone')) : new Date(0, 0, 0, 0, 0, 0, 0);
|
||||
dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
|
||||
dt = dateParser.toTimezone(dt, ngModelOptions.getOption('timezone'));
|
||||
ngModelCtrl.$setViewValue(dt);
|
||||
ngModelCtrl.$render();
|
||||
} else {
|
||||
|
@ -1800,6 +1776,37 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
|
|||
$scope.datepickerMode = mode;
|
||||
$scope.datepickerOptions.datepickerMode = mode;
|
||||
}
|
||||
|
||||
function extractOptions(ngModelCtrl) {
|
||||
var ngModelOptions;
|
||||
|
||||
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
|
||||
// guarantee a value
|
||||
ngModelOptions = ngModelCtrl.$options ||
|
||||
$scope.datepickerOptions.ngModelOptions ||
|
||||
datepickerConfig.ngModelOptions ||
|
||||
{};
|
||||
|
||||
// mimic 1.6+ api
|
||||
ngModelOptions.getOption = function (key) {
|
||||
return ngModelOptions[key];
|
||||
};
|
||||
} else { // in angular >=1.6 $options is always present
|
||||
// ng-model-options defaults timezone to null; don't let its precedence squash a non-null value
|
||||
var timezone = ngModelCtrl.$options.getOption('timezone') ||
|
||||
($scope.datepickerOptions.ngModelOptions ? $scope.datepickerOptions.ngModelOptions.timezone : null) ||
|
||||
(datepickerConfig.ngModelOptions ? datepickerConfig.ngModelOptions.timezone : null);
|
||||
|
||||
// values passed to createChild override existing values
|
||||
ngModelOptions = ngModelCtrl.$options // start with a ModelOptions instance
|
||||
.createChild(datepickerConfig.ngModelOptions) // lowest precedence
|
||||
.createChild($scope.datepickerOptions.ngModelOptions)
|
||||
.createChild(ngModelCtrl.$options) // highest precedence
|
||||
.createChild({timezone: timezone}); // to keep from squashing a non-null value
|
||||
}
|
||||
|
||||
return ngModelOptions;
|
||||
}
|
||||
}])
|
||||
|
||||
.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
|
||||
|
@ -2757,11 +2764,7 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
|
|||
|
||||
this.init = function(_ngModel_) {
|
||||
ngModel = _ngModel_;
|
||||
ngModelOptions = angular.isObject(_ngModel_.$options) ?
|
||||
_ngModel_.$options :
|
||||
{
|
||||
timezone: null
|
||||
};
|
||||
ngModelOptions = extractOptions(ngModel);
|
||||
closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
|
||||
$scope.$parent.$eval($attrs.closeOnDateSelection) :
|
||||
datepickerPopupConfig.closeOnDateSelection;
|
||||
|
@ -2852,13 +2855,13 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
|
|||
value = new Date(value);
|
||||
}
|
||||
|
||||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
|
||||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
|
||||
|
||||
return dateParser.filter($scope.date, dateFormat);
|
||||
});
|
||||
} else {
|
||||
ngModel.$formatters.push(function(value) {
|
||||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.timezone);
|
||||
$scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
@ -2910,7 +2913,7 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
|
|||
|
||||
$scope.isDisabled = function(date) {
|
||||
if (date === 'today') {
|
||||
date = dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
|
||||
date = dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
|
||||
}
|
||||
|
||||
var dates = {};
|
||||
|
@ -2967,7 +2970,7 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
|
|||
date = new Date($scope.date);
|
||||
date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
|
||||
} else {
|
||||
date = dateParser.fromTimezone(today, ngModelOptions.timezone);
|
||||
date = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
|
||||
date.setHours(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
@ -3058,11 +3061,11 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
|
|||
if (angular.isString(viewValue)) {
|
||||
var date = parseDateString(viewValue);
|
||||
if (!isNaN(date)) {
|
||||
return dateParser.toTimezone(date, ngModelOptions.timezone);
|
||||
return dateParser.toTimezone(date, ngModelOptions.getOption('timezone'));
|
||||
}
|
||||
}
|
||||
|
||||
return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
|
||||
return ngModelOptions.getOption('allowInvalid') ? viewValue : undefined;
|
||||
}
|
||||
|
||||
function validator(modelValue, viewValue) {
|
||||
|
@ -3137,6 +3140,28 @@ function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $
|
|||
}
|
||||
}
|
||||
|
||||
function extractOptions(ngModelCtrl) {
|
||||
var ngModelOptions;
|
||||
|
||||
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
|
||||
// guarantee a value
|
||||
ngModelOptions = angular.isObject(ngModelCtrl.$options) ?
|
||||
ngModelCtrl.$options :
|
||||
{
|
||||
timezone: null
|
||||
};
|
||||
|
||||
// mimic 1.6+ api
|
||||
ngModelOptions.getOption = function (key) {
|
||||
return ngModelOptions[key];
|
||||
};
|
||||
} else { // in angular >=1.6 $options is always present
|
||||
ngModelOptions = ngModelCtrl.$options;
|
||||
}
|
||||
|
||||
return ngModelOptions;
|
||||
}
|
||||
|
||||
$scope.$on('uib:datepicker.mode', function() {
|
||||
$timeout(positionPopup, 0, false);
|
||||
});
|
||||
|
@ -3340,7 +3365,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
|
|||
var closeDropdown = function(evt) {
|
||||
// This method may still be called during the same mouse event that
|
||||
// unbound this event handler. So check openScope before proceeding.
|
||||
if (!openScope) { return; }
|
||||
if (!openScope || !openScope.isOpen) { return; }
|
||||
|
||||
if (evt && openScope.getAutoClose() === 'disabled') { return; }
|
||||
|
||||
|
@ -3396,8 +3421,6 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
|
|||
getIsOpen,
|
||||
setIsOpen = angular.noop,
|
||||
toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
|
||||
appendToBody = false,
|
||||
appendTo = null,
|
||||
keynavEnabled = false,
|
||||
selectedOption = null,
|
||||
body = $document.find('body');
|
||||
|
@ -3414,26 +3437,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
|
|||
});
|
||||
}
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendTo)) {
|
||||
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
|
||||
if (appendToEl) {
|
||||
appendTo = angular.element(appendToEl);
|
||||
}
|
||||
}
|
||||
|
||||
appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
|
||||
keynavEnabled = angular.isDefined($attrs.keyboardNav);
|
||||
|
||||
if (appendToBody && !appendTo) {
|
||||
appendTo = body;
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
appendTo.append(self.dropdownMenu);
|
||||
$element.on('$destroy', function handleDestroyEvent() {
|
||||
self.dropdownMenu.remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.toggle = function(open) {
|
||||
|
@ -3505,7 +3509,42 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.
|
|||
}
|
||||
};
|
||||
|
||||
function removeDropdownMenu() {
|
||||
$element.append(self.dropdownMenu);
|
||||
}
|
||||
|
||||
scope.$watch('isOpen', function(isOpen, wasOpen) {
|
||||
var appendTo = null,
|
||||
appendToBody = false;
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendTo)) {
|
||||
var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
|
||||
if (appendToEl) {
|
||||
appendTo = angular.element(appendToEl);
|
||||
}
|
||||
}
|
||||
|
||||
if (angular.isDefined($attrs.dropdownAppendToBody)) {
|
||||
var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope);
|
||||
if (appendToBodyValue !== false) {
|
||||
appendToBody = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (appendToBody && !appendTo) {
|
||||
appendTo = body;
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
if (isOpen) {
|
||||
appendTo.append(self.dropdownMenu);
|
||||
$element.on('$destroy', removeDropdownMenu);
|
||||
} else {
|
||||
$element.off('$destroy', removeDropdownMenu);
|
||||
removeDropdownMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (appendTo && self.dropdownMenu) {
|
||||
var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
|
||||
css,
|
||||
|
@ -4152,10 +4191,6 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.sta
|
|||
var appendToElement = modal.appendTo,
|
||||
currBackdropIndex = backdropIndex();
|
||||
|
||||
if (!appendToElement.length) {
|
||||
throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
|
||||
}
|
||||
|
||||
if (currBackdropIndex >= 0 && !backdropDomEl) {
|
||||
backdropScope = $rootScope.$new(true);
|
||||
backdropScope.modalOptions = modal;
|
||||
|
@ -4432,6 +4467,10 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.sta
|
|||
modalOptions.resolve = modalOptions.resolve || {};
|
||||
modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
|
||||
|
||||
if (!modalOptions.appendTo.length) {
|
||||
throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
|
||||
}
|
||||
|
||||
//verify options
|
||||
if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
|
||||
throw new Error('One of component or template or templateUrl options is required.');
|
||||
|
@ -4709,6 +4748,7 @@ angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.
|
|||
pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
|
||||
$scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
|
||||
$scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
|
||||
$attrs.$set('role', 'menu');
|
||||
|
||||
uibPaging.create(this, $scope, $attrs);
|
||||
|
||||
|
@ -5349,6 +5389,13 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
|
|||
}
|
||||
}
|
||||
|
||||
// KeyboardEvent handler to hide the tooltip on Escape key press
|
||||
function hideOnEscapeKey(e) {
|
||||
if (e.which === 27) {
|
||||
hideTooltipBind();
|
||||
}
|
||||
}
|
||||
|
||||
var unregisterTriggers = function() {
|
||||
triggers.show.forEach(function(trigger) {
|
||||
if (trigger === 'outsideClick') {
|
||||
|
@ -5357,6 +5404,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
|
|||
element.off(trigger, showTooltipBind);
|
||||
element.off(trigger, toggleTooltipBind);
|
||||
}
|
||||
element.off('keypress', hideOnEscapeKey);
|
||||
});
|
||||
triggers.hide.forEach(function(trigger) {
|
||||
if (trigger === 'outsideClick') {
|
||||
|
@ -5396,12 +5444,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
|
|||
element.on(trigger, showTooltipBind);
|
||||
element.on(triggers.hide[idx], hideTooltipBind);
|
||||
}
|
||||
|
||||
element.on('keypress', function(e) {
|
||||
if (e.which === 27) {
|
||||
hideTooltipBind();
|
||||
}
|
||||
});
|
||||
element.on('keypress', hideOnEscapeKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6764,7 +6807,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap
|
|||
var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
|
||||
var $setModelValue = function(scope, newValue) {
|
||||
if (angular.isFunction(parsedModel(originalScope)) &&
|
||||
ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
|
||||
ngModelOptions.getOption('getterSetter')) {
|
||||
return invokeModelSetter(scope, {$$$p: newValue});
|
||||
}
|
||||
|
||||
|
@ -7177,11 +7220,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap
|
|||
element.after($popup);
|
||||
}
|
||||
|
||||
this.init = function(_modelCtrl, _ngModelOptions) {
|
||||
this.init = function(_modelCtrl) {
|
||||
modelCtrl = _modelCtrl;
|
||||
ngModelOptions = _ngModelOptions;
|
||||
ngModelOptions = extractOptions(modelCtrl);
|
||||
|
||||
scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
|
||||
scope.debounceUpdate = $parse(ngModelOptions.getOption('debounce'))(originalScope);
|
||||
|
||||
//plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
|
||||
//$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
|
||||
|
@ -7241,14 +7284,32 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap
|
|||
return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
|
||||
});
|
||||
};
|
||||
|
||||
function extractOptions(ngModelCtrl) {
|
||||
var ngModelOptions;
|
||||
|
||||
if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
|
||||
// guarantee a value
|
||||
ngModelOptions = ngModelCtrl.$options || {};
|
||||
|
||||
// mimic 1.6+ api
|
||||
ngModelOptions.getOption = function (key) {
|
||||
return ngModelOptions[key];
|
||||
};
|
||||
} else { // in angular >=1.6 $options is always present
|
||||
ngModelOptions = ngModelCtrl.$options;
|
||||
}
|
||||
|
||||
return ngModelOptions;
|
||||
}
|
||||
}])
|
||||
|
||||
.directive('uibTypeahead', function() {
|
||||
return {
|
||||
controller: 'UibTypeaheadController',
|
||||
require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
|
||||
require: ['ngModel', 'uibTypeahead'],
|
||||
link: function(originalScope, element, attrs, ctrls) {
|
||||
ctrls[2].init(ctrls[0], ctrls[1]);
|
||||
ctrls[1].init(ctrls[0]);
|
||||
}
|
||||
};
|
||||
})
|
||||
|
@ -7532,11 +7593,11 @@ angular.module("uib/template/pager/pager.html", []).run(["$templateCache", funct
|
|||
|
||||
angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
|
||||
$templateCache.put("uib/template/pagination/pagination.html",
|
||||
"<li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('first')}}</a></li>\n" +
|
||||
"<li ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a href ng-click=\"selectPage(page - 1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n" +
|
||||
"<li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a href ng-click=\"selectPage(page.number, $event)\" ng-disabled=\"ngDisabled&&!page.active\" uib-tabindex-toggle>{{page.text}}</a></li>\n" +
|
||||
"<li ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a href ng-click=\"selectPage(page + 1, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n" +
|
||||
"<li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('last')}}</a></li>\n" +
|
||||
"<li role=\"menuitem\" ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('first')}}</a></li>\n" +
|
||||
"<li role=\"menuitem\" ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a href ng-click=\"selectPage(page - 1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n" +
|
||||
"<li role=\"menuitem\" ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a href ng-click=\"selectPage(page.number, $event)\" ng-disabled=\"ngDisabled&&!page.active\" uib-tabindex-toggle>{{page.text}}</a></li>\n" +
|
||||
"<li role=\"menuitem\" ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a href ng-click=\"selectPage(page + 1, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n" +
|
||||
"<li role=\"menuitem\" ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('last')}}</a></li>\n" +
|
||||
"");
|
||||
}]);
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @license Angular UI Tree v2.22.2
|
||||
* (c) 2010-2016. https://github.com/angular-ui-tree/angular-ui-tree
|
||||
* @license Angular UI Tree v2.22.5
|
||||
* (c) 2010-2017. https://github.com/angular-ui-tree/angular-ui-tree
|
||||
* License: MIT
|
||||
*/
|
||||
(function () {
|
||||
|
@ -756,6 +756,7 @@
|
|||
|
||||
//Check if it or it's parents has a 'data-nodrag' attribute
|
||||
el = angular.element(e.target);
|
||||
isUiTreeRoot = el[0].attributes['ui-tree'];
|
||||
while (el && el[0] && el[0] !== element && !isUiTreeRoot) {
|
||||
|
||||
//Checking that I can access attributes.
|
||||
|
@ -822,7 +823,7 @@
|
|||
|
||||
//Getting starting position of element being moved.
|
||||
pos = UiTreeHelper.positionStarted(eventObj, element);
|
||||
placeElm.css('height', UiTreeHelper.height(element) + 'px');
|
||||
placeElm.css('height', element.prop('offsetHeight') + 'px');
|
||||
|
||||
//Creating drag element to represent node.
|
||||
dragElm = angular.element($window.document.createElement(scope.$parentNodesScope.$element.prop('tagName')))
|
||||
|
@ -1150,6 +1151,7 @@
|
|||
if (targetNode.collapsed) {
|
||||
if (scope.expandOnHover === true || (angular.isNumber(scope.expandOnHover) && scope.expandOnHover === 0)) {
|
||||
targetNode.collapsed = false;
|
||||
targetNode.$treeScope.$callbacks.toggle(false, targetNode);
|
||||
} else if (scope.expandOnHover !== false && angular.isNumber(scope.expandOnHover) && scope.expandOnHover > 0) {
|
||||
|
||||
//Triggering expansion.
|
||||
|
@ -1161,6 +1163,7 @@
|
|||
{
|
||||
scope.$callbacks.expandTimeoutEnd();
|
||||
targetNode.collapsed = false;
|
||||
targetNode.$treeScope.$callbacks.toggle(false, targetNode);
|
||||
}, scope.expandOnHover);
|
||||
}
|
||||
}
|
||||
|
@ -1362,6 +1365,7 @@
|
|||
}
|
||||
]);
|
||||
})();
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
.angular-ui-tree-empty{border:1px dashed #bbb;min-height:100px;background-color:#e5e5e5;background-image:-webkit-linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff),-webkit-linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff);background-image:linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff),linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff);background-size:60px 60px;background-position:0 0,30px 30px;pointer-events:none}.angular-ui-tree-nodes{position:relative;margin:0;padding:0;list-style:none}.angular-ui-tree-nodes .angular-ui-tree-nodes{padding-left:20px}.angular-ui-tree-node,.angular-ui-tree-placeholder{position:relative;margin:0;padding:0;min-height:20px;line-height:20px}.angular-ui-tree-hidden{display:none}.angular-ui-tree-placeholder{margin:5px 0;padding:0;min-height:30px}.angular-ui-tree-handle{cursor:move;text-decoration:none;font-weight:700;box-sizing:border-box;min-height:20px;line-height:20px}.angular-ui-tree-drag{position:absolute;pointer-events:none;z-index:999;opacity:.8}
|
||||
.angular-ui-tree-empty{border:1px dashed #bbb;min-height:100px;background-color:#e5e5e5;background-image:-webkit-linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff),-webkit-linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff);background-image:linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff),linear-gradient(45deg,#fff 25%,transparent 0,transparent 75%,#fff 0,#fff);background-size:60px 60px;background-position:0 0,30px 30px;pointer-events:none}.angular-ui-tree-nodes{position:relative;margin:0;padding:0;list-style:none}.angular-ui-tree-nodes .angular-ui-tree-nodes{padding-left:20px}.angular-ui-tree-node,.angular-ui-tree-placeholder{position:relative;margin:0;padding:0;min-height:20px;line-height:20px}.angular-ui-tree-hidden{display:none}.angular-ui-tree-placeholder{margin:10px;padding:0;min-height:30px}.angular-ui-tree-handle{cursor:move;text-decoration:none;font-weight:700;box-sizing:border-box;min-height:20px;line-height:20px}.angular-ui-tree-drag{position:absolute;pointer-events:none;z-index:999;opacity:.8}.angular-ui-tree-drag .tree-node-content{margin-top:0}
|
File diff suppressed because one or more lines are too long
|
@ -1,100 +0,0 @@
|
|||
# Lemonldap::NG::Manager kinematic
|
||||
|
||||
## Main requests (index.pl)
|
||||
|
||||
### Main initialization (`new()`)
|
||||
|
||||
Simple::new():
|
||||
|
||||
* `getConf()`
|
||||
* load `Menu` and `Display`
|
||||
* load `Auth/UserDB/PasswordDB/RegisterDB`
|
||||
* load `IssuerDBx`
|
||||
* (load `Notifications`)
|
||||
|
||||
### Request managing
|
||||
|
||||
Scenarii:
|
||||
|
||||
* F: unknown user comes for the first time
|
||||
* P: (good) post for authentication
|
||||
* M: menu display
|
||||
* L: simple logout
|
||||
|
||||
| | Method | Comment | F | P | M | L | Proposed PSGI route (for 2.0)
|
||||
|---|-----------------------------------|:-------------------------------------:|---|---|---|---|------------------------------
|
||||
| 0 | _startSoapServices_ | Manage som path info | | | | | /sessions
|
||||
| 1 | controlUrlOrigin | check `url` parameter (+confirmation) | X | X | X | X |
|
||||
| 2 | checkNotifBack | check accepted notifications | X | X | X | X | /notif ?
|
||||
| 3 | controlExistingSession | check cookie | X | X | X | X |
|
||||
| | | * display captcha image | X | | | | /captcha
|
||||
| | | * logout | | | | | /logout
|
||||
| | | * remove existing sessions | | X | | |
|
||||
| | | * respond to ping | | | | | /ping
|
||||
| | | * respond to `storeAppsListOrder` | | | | | /storeAppsListOrder
|
||||
| | | * _If user is authenticated, call:_ | | | | |
|
||||
| | | - _issuerForAuthUser_ | | | | |
|
||||
| | | - _authFinish_ | | | | |
|
||||
| | | - _autoRedirect_ | | | | |
|
||||
| | _existingSession_ | manage reauthentication and force | | | X | |
|
||||
| | _authForce_ | | | | X | |
|
||||
| | _IssuerDB::issuerDBInit_ | | X | X | X | X | _(init^)_
|
||||
| | _IssuerDB::logout_ | | | | | X |
|
||||
| | _Auth::authInit_ | | X | X | X | X | _(init^)_
|
||||
| | _Auth::logout_ | | | | | X |
|
||||
| 4 | __Issuer__::issuerForUnAuthUser | | X | X | | | Many (SSO, SLO, SOAP,...)
|
||||
| 5 | __Auth__::extractFormInfo | First call to auth module | X | X | | |
|
||||
| | _UserDB::userDBInit_ | | | X | | | _(init^)_
|
||||
| 6 | __UserDB__::getUser | First call to UserDB: set $\_user | | X | | |
|
||||
| 7 | __Auth__::setAuthSessionInfo | Auth module can set infos to session | | X | | |
|
||||
| | _PasswordDB::passwordDBInit_ | | | X | | | _(init^)_
|
||||
| 8 | __PasswordDB__::modifyPassword | Unique call to PasswordDB | | X | | | ?
|
||||
| 9 | setSessionInfo | Store datas in `$sessionInfo` | | X | | |
|
||||
| 10 | setMacros | Update $sessionInfo with macros | | X | | |
|
||||
| | _create safe jail_ | | | X | | |
|
||||
| 11 | __UserDB__::setGroups | Set `$sessionInfo->{group}` | | X | | |
|
||||
| 12 | setPersistentSessionInfo | Store some datas in persistent DB | | X | | |
|
||||
| 13 | setLocalGroups | Set `$sessionInfo->{group}` | | X | | |
|
||||
| 14 | __MailReset__::sendPasswordMail | Called if password was changed | | X |_3_| |
|
||||
| 15 | __Auth__::authenticate | 3rd call to _Auth_ module (for LDAP) | | X | | |
|
||||
| 16 | __Auth__::authFinish | Last call to _Auth_ | | X |_1_| |
|
||||
| 17 | __UserDB__::userDBFinish | Last call to _UserDB_ | | X | | |
|
||||
| 18 | __PasswordDB__::passwordDBFinish | Last call to _PasswordDB_ | | X |_2_| |
|
||||
| 19 | grantSession | Apply the rule (user is authenticated | | X | | |
|
||||
| 20 | removeOther | Remove other opened sessions | | X | | |
|
||||
| 21 | store | Store session in DB | | X | | |
|
||||
| | _setApacheUser_ | | | | | |
|
||||
| 22 | buildCookie | Build LLNG cookie(s) | | X | | |
|
||||
| 23 | checkNotification | Check if current user has messages | | X | X | |
|
||||
| 24 | __IssuerDB__::issuerForAuthUser | | | X | X | | Many (SSO, SLO, SOAP, Attribute query,...)
|
||||
| 25 | autoRedirect | Redirects to wanted url | | X | | |
|
||||
| | _menuInit_ | | | | X | |
|
||||
|
||||
Notes:
|
||||
|
||||
1. Called after issuerForAuthUser
|
||||
2. Called after menuInit
|
||||
3. called after passwordDBFinish
|
||||
|
||||
## Other requests
|
||||
|
||||
### /saml/metadata (metadata.pl)
|
||||
|
||||
Returns the content of Lemonldap::NG::Common::Conf::SAML::Metadata->serviceToXML()
|
||||
|
||||
### /openid-configuration.pl
|
||||
|
||||
Display OpenID-Connect JSON configuration
|
||||
|
||||
### /mail.pl
|
||||
|
||||
Launch MailReset
|
||||
|
||||
### /register.pl
|
||||
|
||||
Registration
|
||||
|
||||
### /cdc.pl
|
||||
|
||||
Display SAML cross domain cookies
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Fingerprintjs2 1.4.4 - Modern & flexible browser fingerprint library v2
|
||||
* Fingerprintjs2 1.5.0 - Modern & flexible browser fingerprint library v2
|
||||
* https://github.com/Valve/fingerprintjs2
|
||||
* Copyright (c) 2015 Valentin Vasilyev (valentin.vasilyev@outlook.com)
|
||||
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
|
||||
|
@ -92,6 +92,7 @@
|
|||
keys = this.languageKey(keys);
|
||||
keys = this.colorDepthKey(keys);
|
||||
keys = this.pixelRatioKey(keys);
|
||||
keys = this.hardwareConcurrencyKey(keys);
|
||||
keys = this.screenResolutionKey(keys);
|
||||
keys = this.availableScreenResolutionKey(keys);
|
||||
keys = this.timezoneOffsetKey(keys);
|
||||
|
@ -610,6 +611,12 @@
|
|||
}
|
||||
return keys;
|
||||
},
|
||||
hardwareConcurrencyKey: function(keys){
|
||||
if(!this.options.excludeHardwareConcurrency){
|
||||
keys.push({key: "hardware_concurrency", value: this.getHardwareConcurrency()});
|
||||
}
|
||||
return keys;
|
||||
},
|
||||
hasSessionStorage: function () {
|
||||
try {
|
||||
return !!window.sessionStorage;
|
||||
|
@ -626,7 +633,17 @@
|
|||
}
|
||||
},
|
||||
hasIndexedDB: function (){
|
||||
return !!window.indexedDB;
|
||||
try {
|
||||
return !!window.indexedDB;
|
||||
} catch(e) {
|
||||
return true; // SecurityError when referencing it means it exists
|
||||
}
|
||||
},
|
||||
getHardwareConcurrency: function(){
|
||||
if(navigator.hardwareConcurrency){
|
||||
return navigator.hardwareConcurrency;
|
||||
}
|
||||
return "unknown";
|
||||
},
|
||||
getNavigatorCpuClass: function () {
|
||||
if(navigator.cpuClass){
|
||||
|
@ -810,6 +827,19 @@
|
|||
result.push("webgl vendor:" + gl.getParameter(gl.VENDOR));
|
||||
result.push("webgl version:" + gl.getParameter(gl.VERSION));
|
||||
|
||||
try {
|
||||
// Add the unmasked vendor and unmasked renderer if the debug_renderer_info extension is available
|
||||
var extensionDebugRendererInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
||||
if (!extensionDebugRendererInfo) {
|
||||
if (typeof NODEBUG === "undefined") {
|
||||
this.log("WebGL fingerprinting is incomplete, because your browser does not have the extension WEBGL_debug_renderer_info");
|
||||
}
|
||||
} else {
|
||||
result.push("webgl unmasked vendor:" + gl.getParameter(extensionDebugRendererInfo.UNMASKED_VENDOR_WEBGL));
|
||||
result.push("webgl unmasked renderer:" + gl.getParameter(extensionDebugRendererInfo.UNMASKED_RENDERER_WEBGL));
|
||||
}
|
||||
} catch(e) { /* squelch */ }
|
||||
|
||||
if (!gl.getShaderPrecisionFormat) {
|
||||
if (typeof NODEBUG === "undefined") {
|
||||
this.log("WebGL fingerprinting is incomplete, because your browser does not support getShaderPrecisionFormat");
|
||||
|
@ -1297,6 +1327,6 @@
|
|||
return ("00000000" + (h1[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (h1[1] >>> 0).toString(16)).slice(-8) + ("00000000" + (h2[0] >>> 0).toString(16)).slice(-8) + ("00000000" + (h2[1] >>> 0).toString(16)).slice(-8);
|
||||
}
|
||||
};
|
||||
Fingerprint2.VERSION = "1.4.4";
|
||||
Fingerprint2.VERSION = "1.5.0";
|
||||
return Fingerprint2;
|
||||
});
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user