mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-05-22 03:45:25 +00:00
Localize datepicker and respect user's defaults
This commit is contained in:
@@ -104,3 +104,5 @@
|
||||
"Loading" = "Loading";
|
||||
"No such user." = "No such user.";
|
||||
"You cannot (un)subscribe to a folder that you own!" = "You cannot (un)subscribe to a folder that you own!";
|
||||
/* Aria label for datepicker button */
|
||||
"Open Calendar" = "Open Calendar";
|
||||
@@ -44,18 +44,6 @@ static SoProduct *preferencesProduct = nil;
|
||||
|
||||
@implementation UIxJSONPreferences
|
||||
|
||||
- (WOResponse *) _makeResponse: (NSDictionary *) values
|
||||
{
|
||||
WOResponse *response;
|
||||
|
||||
response = [context response];
|
||||
[response setHeader: @"text/plain; charset=utf-8"
|
||||
forKey: @"content-type"];
|
||||
[response appendContentString: [values jsonRepresentation]];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
- (WOResponse *) jsonDefaultsAction
|
||||
{
|
||||
NSMutableDictionary *values, *account;
|
||||
@@ -134,6 +122,8 @@ static SoProduct *preferencesProduct = nil;
|
||||
sortedArrayUsingSelector: @selector (localizedCaseInsensitiveCompare:)];
|
||||
|
||||
[defaults setCalendarCategories: categoryLabels];
|
||||
|
||||
// TODO: build categories colors dictionary with localized keys
|
||||
}
|
||||
if (![defaults calendarCategoriesColors])
|
||||
{
|
||||
@@ -222,6 +212,12 @@ static SoProduct *preferencesProduct = nil;
|
||||
// Add locale code (used by CK Editor)
|
||||
locale = [[preferencesProduct resourceManager] localeForLanguageNamed: [defaults language]];
|
||||
[values setObject: [locale objectForKey: @"NSLocaleCode"] forKey: @"LocaleCode"];
|
||||
[values setObject: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[locale objectForKey: @"NSMonthNameArray"], @"months",
|
||||
[locale objectForKey: @"NSShortMonthNameArray"], @"shortMonths",
|
||||
[locale objectForKey: @"NSWeekDayNameArray"], @"days",
|
||||
[locale objectForKey: @"NSShortWeekDayNameArray"], @"shortDays",
|
||||
nil] forKey: @"locale"];
|
||||
|
||||
accounts = [NSMutableArray arrayWithArray: [values objectForKey: @"AuxiliaryMailAccounts"]];
|
||||
account = [[[context activeUser] mailAccounts] objectAtIndex: 0];
|
||||
@@ -238,7 +234,7 @@ static SoProduct *preferencesProduct = nil;
|
||||
[values setObject: accounts forKey: @"AuxiliaryMailAccounts"];
|
||||
|
||||
|
||||
return [self _makeResponse: values];
|
||||
return [self responseWithStatus: 200 andJSONRepresentation: values];
|
||||
}
|
||||
|
||||
- (WOResponse *) jsonSettingsAction
|
||||
@@ -269,7 +265,7 @@ static SoProduct *preferencesProduct = nil;
|
||||
if (![settings objectForKey: @"Mail"])
|
||||
[settings setObject: [NSMutableDictionary dictionary] forKey: @"Mail"];
|
||||
|
||||
return [self _makeResponse: [[settings source] values]];
|
||||
return [self responseWithStatus: 200 andJSONRepresentation: [[settings source] values]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -436,14 +436,19 @@
|
||||
*
|
||||
* ngInject @constructor
|
||||
*/
|
||||
function TimePickerCtrl($scope, $element, $attrs, $compile, $timeout, $mdConstant, $mdMedia, $mdTheming,
|
||||
$mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF) {
|
||||
TimePickerCtrl.$inject = ["$scope", "$element", "$attrs", "$compile", "$timeout", "$window",
|
||||
"$mdConstant", "$mdMedia", "$mdTheming", "$mdUtil", "$mdDateLocale", "$$mdDateUtil", "$$rAF"];
|
||||
function TimePickerCtrl($scope, $element, $attrs, $compile, $timeout, $window,
|
||||
$mdConstant, $mdMedia, $mdTheming, $mdUtil, $mdDateLocale, $$mdDateUtil, $$rAF) {
|
||||
/** @final */
|
||||
this.$compile = $compile;
|
||||
|
||||
/** @final */
|
||||
this.$timeout = $timeout;
|
||||
|
||||
/** @final */
|
||||
this.$window = $window;
|
||||
|
||||
/** @final */
|
||||
this.dateLocale = $mdDateLocale;
|
||||
|
||||
@@ -542,32 +547,29 @@
|
||||
});
|
||||
}
|
||||
|
||||
TimePickerCtrl.$inject = ["$scope", "$element", "$attrs", "$compile", "$timeout", "$mdConstant", "$mdMedia", "$mdTheming",
|
||||
"$mdUtil", "$mdDateLocale", "$$mdDateUtil", "$$rAF"];
|
||||
|
||||
/**
|
||||
* Sets up the controller's reference to ngModelController.
|
||||
* @param {!angular.NgModelController} ngModelCtrl
|
||||
*/
|
||||
TimePickerCtrl.prototype.configureNgModel = function(ngModelCtrl) {
|
||||
this.ngModelCtrl = ngModelCtrl;
|
||||
|
||||
var self = this;
|
||||
ngModelCtrl.$render = function() {
|
||||
self.time = self.ngModelCtrl.$viewValue;
|
||||
self.inputElement.value = self.formatTime(self.time);
|
||||
var value = self.ngModelCtrl.$viewValue;
|
||||
|
||||
if (value && !(value instanceof Date)) {
|
||||
throw Error('The ng-model for sg-timepicker must be a Date instance. ' +
|
||||
'Currently the model is a: ' + (typeof value));
|
||||
}
|
||||
|
||||
self.time = value;
|
||||
self.inputElement.value = self.dateLocale.formatTime(value);
|
||||
self.resizeInputElement();
|
||||
self.updateErrorState();
|
||||
};
|
||||
};
|
||||
|
||||
TimePickerCtrl.prototype.formatTime = function(time) {
|
||||
var t = new Date(time);
|
||||
if (t) {
|
||||
var h = t.getHours();
|
||||
var m = t.getMinutes();
|
||||
return (h < 10? ('0' + h) : h) + ':' + (m < 10? ('0' + m) : m);
|
||||
}
|
||||
else return '';
|
||||
};
|
||||
/**
|
||||
* Attach event listeners for both the text input and the md-time.
|
||||
* Events are used instead of ng-model so that updates don't infinitely update the other
|
||||
@@ -580,7 +582,7 @@
|
||||
var time = new Date(data.date);
|
||||
self.ngModelCtrl.$setViewValue(time);
|
||||
self.time = time;
|
||||
self.inputElement.value = self.formatTime(self.time);
|
||||
self.inputElement.value = self.dateLocale.formatTime(time);
|
||||
if (data.changed == 'minutes') {
|
||||
self.closeTimePane();
|
||||
}
|
||||
@@ -646,6 +648,45 @@
|
||||
this.timeButton.disabled = isDisabled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the custom ngModel.$error flags to be consumed by ngMessages. Flags are:
|
||||
* - mindate: whether the selected date is before the minimum date.
|
||||
* - maxdate: whether the selected flag is after the maximum date.
|
||||
* - filtered: whether the selected date is allowed by the custom filtering function.
|
||||
* - valid: whether the entered text input is a valid date
|
||||
*
|
||||
* The 'required' flag is handled automatically by ngModel.
|
||||
*
|
||||
* @param {Date=} opt_date Date to check. If not given, defaults to the datepicker's model value.
|
||||
*/
|
||||
TimePickerCtrl.prototype.updateErrorState = function(opt_date) {
|
||||
var date = opt_date || this.date;
|
||||
|
||||
// Clear any existing errors to get rid of anything that's no longer relevant.
|
||||
this.clearErrorState();
|
||||
|
||||
if (!this.dateUtil.isValidDate(date)) {
|
||||
// The date is seen as "not a valid date" if there is *something* set
|
||||
// (i.e.., not null or undefined), but that something isn't a valid date.
|
||||
this.ngModelCtrl.$setValidity('valid', date === null);
|
||||
}
|
||||
|
||||
// TODO(jelbourn): Change this to classList.toggle when we stop using PhantomJS in unit tests
|
||||
// because it doesn't conform to the DOMTokenList spec.
|
||||
// See https://github.com/ariya/phantomjs/issues/12782.
|
||||
if (!this.ngModelCtrl.$valid) {
|
||||
this.inputContainer.classList.add(INVALID_CLASS);
|
||||
}
|
||||
};
|
||||
|
||||
/** Clears any error flags set by `updateErrorState`. */
|
||||
TimePickerCtrl.prototype.clearErrorState = function() {
|
||||
this.inputContainer.classList.remove(INVALID_CLASS);
|
||||
['valid'].forEach(function(field) {
|
||||
this.ngModelCtrl.$setValidity(field, true);
|
||||
}, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the input element based on the size of its content.
|
||||
*/
|
||||
@@ -659,7 +700,7 @@
|
||||
*/
|
||||
TimePickerCtrl.prototype.handleInputEvent = function(self) {
|
||||
var inputString = this.inputElement.value;
|
||||
var arr = inputString.split(':');
|
||||
var arr = inputString.split(/[\.:]/);
|
||||
|
||||
if (inputString === '') {
|
||||
this.ngModelCtrl.$setViewValue(null);
|
||||
@@ -721,7 +762,7 @@
|
||||
}
|
||||
|
||||
timePane.style.top = paneTop + 'px';
|
||||
document.body.appendChild(this.timePane);
|
||||
document.body.appendChild(timePane);
|
||||
|
||||
// The top of the calendar pane is a transparent box that shows the text input underneath.
|
||||
// Since the pane is floating, though, the page underneath the pane *adjacent* to the input is
|
||||
@@ -780,14 +821,16 @@
|
||||
|
||||
/** Close the floating time pane. */
|
||||
TimePickerCtrl.prototype.closeTimePane = function() {
|
||||
this.isTimeOpen = false;
|
||||
this.detachTimePane();
|
||||
this.timePaneOpenedFrom.focus();
|
||||
this.timePaneOpenedFrom = null;
|
||||
this.$mdUtil.enableScrolling();
|
||||
if (this.isTimeOpen) {
|
||||
this.isTimeOpen = false;
|
||||
this.detachTimePane();
|
||||
this.timePaneOpenedFrom.focus();
|
||||
this.timePaneOpenedFrom = null;
|
||||
this.$mdUtil.enableScrolling();
|
||||
|
||||
document.body.removeEventListener('click', this.bodyClickHandler);
|
||||
window.removeEventListener('resize', this.windowResizeHandler);
|
||||
document.body.removeEventListener('click', this.bodyClickHandler);
|
||||
window.removeEventListener('resize', this.windowResizeHandler);
|
||||
}
|
||||
};
|
||||
|
||||
/** Gets the controller instance for the time in the floating pane. */
|
||||
|
||||
@@ -173,6 +173,75 @@ String.prototype.timeInterval = function () {
|
||||
return interval;
|
||||
};
|
||||
|
||||
String.prototype.parseDate = function(localeProvider, format) {
|
||||
var string, formattingTokens, tokens, token, now, date, regexes, i, parsedInput, matchesCount;
|
||||
|
||||
string = '' + this;
|
||||
formattingTokens = /%[dembByY]/g;
|
||||
now = new Date();
|
||||
date = {
|
||||
year: -1,
|
||||
month: -1,
|
||||
day: -1
|
||||
};
|
||||
regexes = {
|
||||
'%d': [/\d\d/, function(input) {
|
||||
date.day = parseInt(input);
|
||||
return (date.day < 32);
|
||||
}],
|
||||
'%e': [/ ?\d?\d/, function(input) {
|
||||
date.day = parseInt(input);
|
||||
return (date.day < 32);
|
||||
}],
|
||||
'%m': [/\d\d/, function(input) {
|
||||
date.month = parseInt(input) - 1;
|
||||
return (date.month < 12);
|
||||
}],
|
||||
'%b': [/[^\d\s\.\/\-]{2,}/, function(input) {
|
||||
var i = _.indexOf(localeProvider.shortMonths, input);
|
||||
if (i >= 0)
|
||||
date.month = i;
|
||||
return (i >= 0);
|
||||
}],
|
||||
'%B': [/[^\d\s\.\/\-]{2,}/, function(input) {
|
||||
var i = _.indexOf(localeProvider.months, input);
|
||||
if (i >= 0)
|
||||
date.month = i;
|
||||
return (i >= 0);
|
||||
}],
|
||||
'%y': [/\d\d/, function(input) {
|
||||
var nearFuture = parseInt(now.getFullYear().toString().substring(2)) + 5;
|
||||
date.year = parseInt(input);
|
||||
if (date.year < nearFuture) date.year += 2000;
|
||||
else date.year += 1900;
|
||||
return true;
|
||||
}],
|
||||
'%Y': [/[12]\d\d\d/, function(input) {
|
||||
date.year = parseInt(input);
|
||||
return true;
|
||||
}]
|
||||
};
|
||||
tokens = format.match(formattingTokens) || [];
|
||||
matchesCount = 0;
|
||||
|
||||
for (i = 0; i < tokens.length; i++) {
|
||||
token = tokens[i];
|
||||
parsedInput = (string.match(regexes[token][0]) || [])[0];
|
||||
if (parsedInput) {
|
||||
string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
|
||||
if (regexes[token][1](parsedInput))
|
||||
matchesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tokens.length === matchesCount) {
|
||||
// console.debug(this + ' + ' + format + ' = ' + JSON.stringify(date));
|
||||
return new Date(date.year, date.month, date.day);
|
||||
}
|
||||
else
|
||||
return new Date(NaN);
|
||||
};
|
||||
|
||||
Date.prototype.daysUpTo = function(otherDate) {
|
||||
var days = [];
|
||||
|
||||
@@ -308,19 +377,83 @@ Date.prototype.getHourString = function() {
|
||||
return newString;
|
||||
};
|
||||
|
||||
Date.prototype.format = function(localeProvider, format) {
|
||||
var separators, parts, i, max,
|
||||
date = [],
|
||||
validParts = /%[daAmbByYHIM]/g,
|
||||
val = {
|
||||
'%d': this.getUTCDate(), // day of month (e.g., 01)
|
||||
'%e': this.getUTCDate(), // day of month, space padded
|
||||
'%a': localeProvider.shortDays[this.getUTCDay()], // locale's abbreviated weekday name (e.g., Sun)
|
||||
'%A': localeProvider.days[this.getUTCDay()], // locale's full weekday name (e.g., Sunday)
|
||||
'%m': this.getUTCMonth() + 1, // month (01..12)
|
||||
'%b': localeProvider.shortMonths[this.getUTCMonth()], // locale's abbreviated month name (e.g., Jan)
|
||||
'%B': localeProvider.months[this.getUTCMonth()], // locale's full month name (e.g., January)
|
||||
'%y': this.getUTCFullYear().toString().substring(2), // last two digits of year (00..99)
|
||||
'%Y': this.getUTCFullYear(), // year
|
||||
'%H': this.getHours(), // hour (00..23)
|
||||
'%M': this.getMinutes() }; // minute (00..59)
|
||||
val['%I'] = val['%H'] > 12 ? val['%H'] % 12 : val['%H']; // hour (01..12)
|
||||
|
||||
val['%d'] = (val['%d'] < 10 ? '0' : '') + val['%d'];
|
||||
val['%e'] = (val['%e'] < 10 ? ' ' : '') + val['%e'];
|
||||
val['%m'] = (val['%m'] < 10 ? '0' : '') + val['%m'];
|
||||
val['%H'] = (val['%H'] < 10 ? '0' : '') + val['%H'];
|
||||
val['%I'] = (val['%I'] < 10 ? '0' : '') + val['%I'];
|
||||
val['%M'] = (val['%M'] < 10 ? '0' : '') + val['%M'];
|
||||
|
||||
separators = format.replace(validParts, '\0').split('\0');
|
||||
parts = format.match(validParts);
|
||||
for (i = 0, max = parts.length; i <= max; i++){
|
||||
if (separators.length)
|
||||
date.push(separators.shift());
|
||||
date.push(val[parts[i]]);
|
||||
}
|
||||
|
||||
return date.join('');
|
||||
};
|
||||
|
||||
/* Functions */
|
||||
|
||||
function l() {
|
||||
var key = arguments[0];
|
||||
var value = key;
|
||||
var key = arguments[0], value = key, args = arguments, i, j;
|
||||
|
||||
// Retrieve translation
|
||||
if (labels[key]) {
|
||||
value = labels[key];
|
||||
}
|
||||
else if (clabels[key]) {
|
||||
value = clabels[key];
|
||||
}
|
||||
for (var i = 1, j = 0; i < arguments.length; i++, j++) {
|
||||
value = value.replace('%{' + j + '}', arguments[i]);
|
||||
|
||||
// Format placeholders %{0}, %{1], %{2}, ...
|
||||
for (i = 1, j = 0; i < args.length; i++, j++) {
|
||||
value = value.replace('%{' + j + '}', args[i]);
|
||||
}
|
||||
|
||||
// Format placeholders %d and %s
|
||||
i = 1;
|
||||
if (args.length > 1) {
|
||||
value = value.replace(/%((%)|s|d)/g, function(m) {
|
||||
// m is the matched format, e.g. %s, %d
|
||||
var val = null;
|
||||
if (m[2]) {
|
||||
val = m[2];
|
||||
}
|
||||
else {
|
||||
val = args[i];
|
||||
// A switch statement so that the formatter can be extended. Default is %s
|
||||
switch (m) {
|
||||
case '%d':
|
||||
val = parseFloat(val);
|
||||
if (isNaN(val))
|
||||
val = 0;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return val;
|
||||
});
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
@@ -65,11 +65,28 @@
|
||||
|
||||
angular.extend(_this.defaults, data);
|
||||
|
||||
angular.extend(Preferences.$mdDateLocaleProvider, data.locale);
|
||||
Preferences.$mdDateLocaleProvider.firstDayOfWeek = parseInt(data.SOGoFirstDayOfWeek);
|
||||
Preferences.$mdDateLocaleProvider.weekNumberFormatter = function(weekNumber) {
|
||||
return l('Week %d', weekNumber);
|
||||
};
|
||||
Preferences.$mdDateLocaleProvider.msgCalendar = l('Calender');
|
||||
Preferences.$mdDateLocaleProvider.msgOpenCalendar = l('Open Calendar');
|
||||
Preferences.$mdDateLocaleProvider.parseDate = function(dateString) {
|
||||
return dateString? dateString.parseDate(Preferences.$mdDateLocaleProvider, data.SOGoShortDateFormat) : new Date(NaN);
|
||||
};
|
||||
Preferences.$mdDateLocaleProvider.formatDate = function(date) {
|
||||
return date? date.format(Preferences.$mdDateLocaleProvider, data.SOGoShortDateFormat) : '';
|
||||
};
|
||||
Preferences.$mdDateLocaleProvider.formatTime = function(date) {
|
||||
return date? date.format(Preferences.$mdDateLocaleProvider, data.SOGoTimeFormat) : '';
|
||||
};
|
||||
|
||||
return _this.defaults;
|
||||
});
|
||||
|
||||
this.settingsPromise = Preferences.$$resource.fetch("jsonSettings").then(function(data) {
|
||||
// We convert our PreventInvitationsWhitelist hash into a array of user
|
||||
// We convert our PreventInvitationsWhitelist hash into a array of user
|
||||
if (data.Calendar) {
|
||||
if (data.Calendar.PreventInvitationsWhitelist)
|
||||
data.Calendar.PreventInvitationsWhitelist = _.map(data.Calendar.PreventInvitationsWhitelist, function(value, key) {
|
||||
@@ -91,11 +108,12 @@
|
||||
* @desc The factory we'll use to register with Angular
|
||||
* @returns the Preferences constructor
|
||||
*/
|
||||
Preferences.$factory = ['$q', '$timeout', '$log', 'sgSettings', 'Resource', 'User', function($q, $timeout, $log, Settings, Resource, User) {
|
||||
Preferences.$factory = ['$q', '$timeout', '$log', '$mdDateLocale', 'sgSettings', 'Resource', 'User', function($q, $timeout, $log, $mdDateLocaleProvider, Settings, Resource, User) {
|
||||
angular.extend(Preferences, {
|
||||
$q: $q,
|
||||
$timeout: $timeout,
|
||||
$log: $log,
|
||||
$mdDateLocaleProvider: $mdDateLocaleProvider,
|
||||
$$resource: new Resource(Settings.activeUser('folderURL'), Settings.activeUser()),
|
||||
activeUser: Settings.activeUser(),
|
||||
$User: User
|
||||
|
||||
Reference in New Issue
Block a user