Implement localized datetimes in browser
moment-timezone require config jasmine test fix, duplicate req. cleanup
This commit is contained in:
committed by
Gregory Martin
parent
65723e2b12
commit
4ddda443b8
@@ -50,8 +50,8 @@
|
||||
'jquery.immediateDescendents': 'coffee/src/jquery.immediateDescendents',
|
||||
'datepair': 'js/vendor/timepicker/datepair',
|
||||
'date': 'js/vendor/date',
|
||||
'moment': 'js/vendor/moment.min',
|
||||
'moment-with-locales': 'js/vendor/moment-with-locales.min',
|
||||
moment: 'common/js/vendor/moment-with-locales',
|
||||
'moment-timezone': 'common/js/vendor/moment-timezone-with-data',
|
||||
'text': 'js/vendor/requirejs/text',
|
||||
'underscore': 'common/js/vendor/underscore',
|
||||
'underscore.string': 'common/js/vendor/underscore.string',
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
'jquery.simulate': 'xmodule_js/common_static/js/vendor/jquery.simulate',
|
||||
'datepair': 'xmodule_js/common_static/js/vendor/timepicker/datepair',
|
||||
'date': 'xmodule_js/common_static/js/vendor/date',
|
||||
'moment': 'xmodule_js/common_static/js/vendor/moment.min',
|
||||
'moment-with-locales': 'xmodule_js/common_static/js/vendor/moment-with-locales.min',
|
||||
moment: 'common/js/vendor/moment-with-locales',
|
||||
'text': 'xmodule_js/common_static/js/vendor/requirejs/text',
|
||||
'underscore': 'common/js/vendor/underscore',
|
||||
'underscore.string': 'common/js/vendor/underscore.string',
|
||||
|
||||
@@ -32,7 +32,7 @@ var options = {
|
||||
{pattern: 'common_static/js/vendor/jquery-ui.min.js', included: true},
|
||||
{pattern: 'common_static/js/vendor/jquery.ui.draggable.js', included: true},
|
||||
{pattern: 'common_static/js/vendor/json2.js', included: true},
|
||||
{pattern: 'common_static/js/vendor/moment.min.js', included: true},
|
||||
{pattern: 'common_static/js/vendor/moment-with-locales.js', included: true},
|
||||
{pattern: 'common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce.min.js', included: true},
|
||||
{pattern: 'common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min.js', included: true},
|
||||
{pattern: 'common_static/js/src/accessibility_tools.js', included: true},
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
requirejs.config({
|
||||
baseUrl: '/base/',
|
||||
paths: {
|
||||
'moment': 'common_static/js/vendor/moment.min',
|
||||
moment: 'common_static/js/vendor/moment-with-locales',
|
||||
'draggabilly': 'common_static/js/vendor/draggabilly',
|
||||
'edx-ui-toolkit': 'common_static/edx-ui-toolkit'
|
||||
},
|
||||
|
||||
12251
common/static/js/vendor/moment-with-locales.js
vendored
Normal file
12251
common/static/js/vendor/moment-with-locales.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
494
common/static/js/vendor/moment.min.js
vendored
494
common/static/js/vendor/moment.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -919,7 +919,7 @@ class ViewsTestCase(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
# removes newlines and whitespace from the returned view string
|
||||
view = ''.join(render_accordion(request, self.course, table_of_contents['chapters']).split())
|
||||
view = ''.join(render_accordion(request, self.course, table_of_contents['chapters'], 'en').split())
|
||||
# the course id unicode is re-encoded here because the quote function does not accept unicode
|
||||
course_id = quote(unicode(self.course.id).encode("utf-8"))
|
||||
|
||||
|
||||
@@ -398,7 +398,12 @@ class CoursewareIndex(View):
|
||||
self.section_url_name,
|
||||
self.field_data_cache,
|
||||
)
|
||||
courseware_context['accordion'] = render_accordion(self.request, self.course, table_of_contents['chapters'])
|
||||
courseware_context['accordion'] = render_accordion(
|
||||
self.request,
|
||||
self.course,
|
||||
table_of_contents['chapters'],
|
||||
courseware_context['language_preference'],
|
||||
)
|
||||
|
||||
# entrance exam data
|
||||
if course_has_entrance_exam(self.course):
|
||||
@@ -494,7 +499,7 @@ class CoursewareIndex(View):
|
||||
raise
|
||||
|
||||
|
||||
def render_accordion(request, course, table_of_contents):
|
||||
def render_accordion(request, course, table_of_contents, language_preference):
|
||||
"""
|
||||
Returns the HTML that renders the navigation for the given course.
|
||||
Expects the table_of_contents to have data on each chapter and section,
|
||||
@@ -506,7 +511,9 @@ def render_accordion(request, course, table_of_contents):
|
||||
('course_id', unicode(course.id)),
|
||||
('csrf', csrf(request)['csrf_token']),
|
||||
('due_date_display_format', course.due_date_display_format),
|
||||
('time_zone', get_user_time_zone(request.user).zone),
|
||||
('time_zone', request.user.preferences.model.get_value(request.user, "time_zone", None)),
|
||||
('language', language_preference),
|
||||
|
||||
] + TEMPLATE_IMPORTS.items()
|
||||
)
|
||||
return render_to_string('courseware/accordion.html', context)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
define(['jquery',
|
||||
'underscore',
|
||||
'moment-with-locales',
|
||||
'moment',
|
||||
'teams/js/views/team_card',
|
||||
'teams/js/models/team'],
|
||||
function($, _, moment, TeamCardView, Team) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'backbone',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'moment-with-locales',
|
||||
'moment',
|
||||
'js/components/card/views/card',
|
||||
'teams/js/views/team_utils',
|
||||
'text!teams/templates/team-membership-details.underscore',
|
||||
|
||||
@@ -1567,7 +1567,7 @@ PIPELINE_JS = {
|
||||
[
|
||||
'js/sticky_filter.js',
|
||||
'js/query-params.js',
|
||||
'js/vendor/moment-with-locales.min.js',
|
||||
'common/js/vendor/moment-with-locales.js',
|
||||
]
|
||||
),
|
||||
'output_filename': 'js/lms-application.js',
|
||||
@@ -1715,7 +1715,7 @@ REQUIRE_ENVIRONMENT = "node"
|
||||
REQUIRE_JS_PATH_OVERRIDES = {
|
||||
'js/bookmarks/views/bookmark_button': 'js/bookmarks/views/bookmark_button.js',
|
||||
'js/views/message_banner': 'js/views/message_banner.js',
|
||||
'moment': 'js/vendor/moment-with-locales.min.js',
|
||||
'moment': 'common/js/vendor/moment-with-locales.js',
|
||||
'js/courseware/course_home_events': 'js/courseware/course_home_events.js',
|
||||
'js/courseware/accordion_events': 'js/courseware/accordion_events.js',
|
||||
'js/courseware/link_clicked_events': 'js/courseware/link_clicked_events.js',
|
||||
|
||||
91
lms/static/js/dateutil_factory.js
Normal file
91
lms/static/js/dateutil_factory.js
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
/**
|
||||
*
|
||||
* A helper function to utilize DateUtils quickly in display templates.
|
||||
*
|
||||
* @param: {string} data-datetime A pre-localized datetime string, assumed to be in UTC.
|
||||
* @param: {string} lang The user's preferred language.
|
||||
* @param: {string} data-timezone (optional) A user-set timezone preference.
|
||||
* @param: {object} data-format (optional) a format constant as defined in DataUtil.dateFormatEnum.
|
||||
* @param: {string} data-string (optional) a string for parsing through StringUtils after localizing
|
||||
* datetime
|
||||
*
|
||||
* @return: {string} a user-time, localized, formatted datetime string
|
||||
*
|
||||
*/
|
||||
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define([
|
||||
'jquery',
|
||||
'edx-ui-toolkit/js/utils/date-utils',
|
||||
'edx-ui-toolkit/js/utils/string-utils'
|
||||
], function($, DateUtils, StringUtils) {
|
||||
var DateUtilFactory;
|
||||
var localizedTime;
|
||||
var stringHandler;
|
||||
var displayDatetime;
|
||||
var isValid;
|
||||
var transform;
|
||||
|
||||
transform = function(iterationKey) {
|
||||
var context;
|
||||
$(iterationKey).each(function() {
|
||||
if (isValid($(this).data('datetime'))) {
|
||||
context = {
|
||||
datetime: $(this).data('datetime'),
|
||||
timezone: $(this).data('timezone'),
|
||||
language: $(this).attr('lang'),
|
||||
format: $(this).data('format')
|
||||
};
|
||||
displayDatetime = stringHandler(
|
||||
localizedTime(context),
|
||||
$(this).data('string'),
|
||||
$(this).data('datetoken')
|
||||
);
|
||||
$(this).text(displayDatetime);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
localizedTime = function(context) {
|
||||
return DateUtils.localize(context);
|
||||
};
|
||||
|
||||
stringHandler = function(localTimeString, containerString, token) {
|
||||
var returnString;
|
||||
var interpolateDict = {};
|
||||
var dateToken;
|
||||
if (isValid(token)) {
|
||||
dateToken = token;
|
||||
} else {
|
||||
dateToken = 'date';
|
||||
}
|
||||
interpolateDict[dateToken] = localTimeString;
|
||||
|
||||
if (isValid(containerString)) {
|
||||
returnString = StringUtils.interpolate(
|
||||
containerString,
|
||||
interpolateDict
|
||||
);
|
||||
} else {
|
||||
returnString = localTimeString;
|
||||
}
|
||||
return returnString;
|
||||
};
|
||||
|
||||
isValid = function(candidateVariable) {
|
||||
return candidateVariable !== undefined
|
||||
&& candidateVariable !== ''
|
||||
&& candidateVariable !== 'Invalid date'
|
||||
&& candidateVariable !== 'None';
|
||||
};
|
||||
DateUtilFactory = {
|
||||
transform: transform,
|
||||
stringHandler: stringHandler
|
||||
};
|
||||
return DateUtilFactory;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
50
lms/static/js/spec/dateutil_factory_spec.js
Normal file
50
lms/static/js/spec/dateutil_factory_spec.js
Normal file
@@ -0,0 +1,50 @@
|
||||
define(['../dateutil_factory.js'], function(DateUtilIterator) {
|
||||
'use strict';
|
||||
|
||||
describe('DateUtilFactory', function() {
|
||||
beforeEach(function() {
|
||||
setFixtures('<div class="test"></div>');
|
||||
});
|
||||
|
||||
describe('stringHandler', function() {
|
||||
it('returns a complete string', function() {
|
||||
var localTimeString = 'RANDOM_STRING';
|
||||
var containerString = 'RANDOM_STRING_TWO {random_token}';
|
||||
var dateToken = 'random_token';
|
||||
var answer = 'RANDOM_STRING_TWO RANDOM_STRING';
|
||||
expect(DateUtilIterator.stringHandler(localTimeString, containerString, dateToken)).toEqual(answer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transform', function() {
|
||||
var $form;
|
||||
|
||||
it('localizes some times', function() {
|
||||
/* we have to generate a fake span and then test the resultant texts */
|
||||
var iterationKey = '.localized-datetime';
|
||||
var testLangs = {
|
||||
en: 'Due Oct 14, 2016 08:00 UTC',
|
||||
ru: 'Due 14 окт. 2016 г. 08:00 UTC',
|
||||
ar: 'Due ١٤ تشرين الأول أكتوبر ٢٠١٦ ٠٨:٠٠ UTC',
|
||||
fr: 'Due 14 oct. 2016 08:00 UTC'
|
||||
};
|
||||
$form = $(
|
||||
'<span class="subtitle-name localized-datetime" ' +
|
||||
'data-timezone="UTC" ' +
|
||||
'data-datetime="2016-10-14 08:00:00+00:00" ' +
|
||||
'data-string="Due {date}"></span>'
|
||||
);
|
||||
Object.keys(testLangs).forEach(function(key) {
|
||||
$form.attr('lang', String(key));
|
||||
$(document.body).append($form);
|
||||
|
||||
DateUtilIterator.transform(iterationKey);
|
||||
expect($form.text()).toEqual(testLangs[key]);
|
||||
|
||||
$form.remove();
|
||||
});
|
||||
$form = null;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -57,8 +57,8 @@
|
||||
paths: {
|
||||
'annotator_1.2.9': 'js/vendor/edxnotes/annotator-full.min',
|
||||
'date': 'js/vendor/date',
|
||||
'moment': 'js/vendor/moment.min',
|
||||
'moment-with-locales': 'xmodule_js/common_static/js/vendor/moment-with-locales.min',
|
||||
moment: 'common/js/vendor/moment-with-locales',
|
||||
'moment-timezone': 'common/js/vendor/moment-timezone-with-data',
|
||||
'text': 'js/vendor/requirejs/text',
|
||||
'logger': 'js/src/logger',
|
||||
'backbone': 'common/js/vendor/backbone',
|
||||
@@ -212,8 +212,9 @@
|
||||
'moment': {
|
||||
exports: 'moment'
|
||||
},
|
||||
'moment-with-locales': {
|
||||
exports: 'moment'
|
||||
'moment-timezone': {
|
||||
exports: 'moment',
|
||||
deps: ['moment']
|
||||
},
|
||||
// Because Draggabilly is being used by video code, the namespaced version of
|
||||
// require is not being recognized. Therefore the library is being added to the
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
'jquery.url': 'xmodule_js/common_static/js/vendor/url.min',
|
||||
'datepair': 'xmodule_js/common_static/js/vendor/timepicker/datepair',
|
||||
'date': 'xmodule_js/common_static/js/vendor/date',
|
||||
'moment': 'xmodule_js/common_static/js/vendor/moment.min',
|
||||
'moment-with-locales': 'xmodule_js/common_static/js/vendor/moment-with-locales.min',
|
||||
moment: 'common/js/vendor/moment-with-locales',
|
||||
'moment-timezone': 'common/js/vendor/moment-timezone-with-data',
|
||||
'text': 'xmodule_js/common_static/js/vendor/requirejs/text',
|
||||
'underscore': 'common/js/vendor/underscore',
|
||||
'underscore.string': 'common/js/vendor/underscore.string',
|
||||
@@ -731,6 +731,7 @@
|
||||
'js/spec/learner_dashboard/course_card_view_spec.js',
|
||||
'js/spec/learner_dashboard/course_enroll_view_spec.js',
|
||||
'js/spec/markdown_editor_spec.js',
|
||||
'js/spec/dateutil_factory_spec.js',
|
||||
'js/spec/navigation_spec.js',
|
||||
'js/spec/search/search_spec.js',
|
||||
'js/spec/shoppingcart/shoppingcart_spec.js',
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
requirejs.config({
|
||||
baseUrl: '/base/',
|
||||
paths: {
|
||||
'moment': 'xmodule_js/common_static/js/vendor/moment.min',
|
||||
moment: 'xmodule_js/common_static/js/vendor/moment-with-locales',
|
||||
'draggabilly': 'xmodule_js/common_static/js/vendor/draggabilly',
|
||||
'edx-ui-toolkit': 'edx-ui-toolkit'
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"backbone-validation": "~0.11.5",
|
||||
"coffee-script": "1.6.1",
|
||||
"edx-pattern-library": "0.16.6",
|
||||
"edx-ui-toolkit": "1.4.1",
|
||||
"edx-ui-toolkit": "1.5.0",
|
||||
"jquery": "~2.2.0",
|
||||
"jquery-migrate": "^1.4.1",
|
||||
"jquery.scrollto": "~2.1.2",
|
||||
|
||||
@@ -55,6 +55,8 @@ NPM_INSTALLED_LIBRARIES = [
|
||||
'backbone/backbone.js',
|
||||
'edx-ui-toolkit/node_modules/backbone.paginator/lib/backbone.paginator.js',
|
||||
'backbone-validation/dist/backbone-validation-min.js',
|
||||
'edx-ui-toolkit/node_modules/moment-timezone/builds/moment-timezone-with-data.js',
|
||||
'edx-ui-toolkit/node_modules/moment/min/moment-with-locales.js',
|
||||
]
|
||||
|
||||
# Directory to install static vendor files
|
||||
|
||||
Reference in New Issue
Block a user