Skip to content

Commit

Permalink
Merge pull request #1615 from drgrice1/time-locale
Browse files Browse the repository at this point in the history
Use time strings formatted for the locale for homework set dates.
  • Loading branch information
pstaabp authored Apr 7, 2022
2 parents 13e731c + 4f20299 commit 943664f
Show file tree
Hide file tree
Showing 16 changed files with 453 additions and 269 deletions.
13 changes: 0 additions & 13 deletions conf/defaults.config
Original file line number Diff line number Diff line change
Expand Up @@ -983,11 +983,7 @@ $pg{displayModes} = [
#### Default settings for homework editor pages
##########################################################################################

# Whether the homework editor pages should show the datetimepicker
$options{useDateTimePicker} = 1;

# Whether the homework editor pages should show options for conditional release

$options{enableConditionalRelease} = 0;

# In the hmwk sets editor, how deep to search within templates for .def files;
Expand Down Expand Up @@ -1627,15 +1623,6 @@ $ConfigValues = [
type => 'popuplist',
hashVar => '{hardcopyTheme}'
},
{
var => 'options{useDateTimePicker}',
doc => x('Use Date Picker'),
doc2 => x(
'Enables the use of the date picker on the Homework Sets Editor 2 page and the '
. 'Problem Set Detail page'
),
type => 'boolean'
},
{
var => 'showCourseHomeworkTotals',
doc => x('Show Total Homework Grade on Grades Page'),
Expand Down
96 changes: 87 additions & 9 deletions htdocs/js/apps/DatePicker/datepicker.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
(() => {
document.querySelectorAll('.datepicker-group').forEach((open_rule) => {
if (open_rule.dataset.enableDatepicker !== '1') return;
// Date/time formats for the languages supported by webwork.
// Note that these formats are chosen to match the perl DateTime::Locale formats.
// Make sure that anytime a new language is added, its format is added here.
const datetimeFormats = {
en: 'L/d/yy, h:mm a',
'en-US': 'L/d/yy, h:mm a',
'cs-CZ': 'dd.LL.yy H:mm',
de: 'dd.LL.yy, HH:mm',
es: 'd/L/yy H:mm',
'fr-CA': "yyyy-LL-dd HH 'h' mm",
fr: 'dd/LL/yyyy HH:mm',
'he-IL': 'd.L.yyyy, H:mm',
hu: 'yyyy. LL. dd. H:mm',
ko: 'yy. L. d. a h:mm',
'ru-RU': 'dd.LL.yyyy, HH:mm',
tr: 'd.LL.yyyy HH:mm',
'zh-CN': 'yyyy/L/d ah:mm',
'zh-HK': 'yyyy/L/d ah:mm'
};

document.querySelectorAll('.datepicker-group').forEach((open_rule) => {
const name = open_rule.name.replace('.open_date', '');

const groupRules = [
Expand All @@ -26,23 +44,83 @@
for (const rule of groupRules) {
const orig_value = rule.value;

flatpickr(rule.parentNode, {
luxon.Settings.defaultLocale = rule.dataset.locale ?? 'en';

// Compute the time difference between the current browser timezone and the the course timezone.
// flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone.
// Note that this is converted to seconds.
const timezoneAdjustment =
parseInt(Intl.DateTimeFormat('en-US', { timeZoneName: 'shortOffset' })
.format(new Date).split(' ')[1].slice(3) || '0') * 3600000
- parseInt(Intl.DateTimeFormat('en-US',
{ timeZone: rule.dataset.timezone ?? 'America/New_York', timeZoneName: 'shortOffset' })
.format(new Date).split(' ')[1].slice(3) || '0') * 3600000;

const fp = flatpickr(rule.parentNode, {
allowInput: true,
enableTime: true,
minuteIncrement: 1,
dateFormat: 'm/d/Y at h:iK',
altInput: true,
dateFormat: 'U',
altFormat: datetimeFormats[luxon.Settings.defaultLocale],
ariaDateFormat: datetimeFormats[luxon.Settings.defaultLocale],
defaultDate: orig_value,
defaultHour: 0,
locale: rule.dataset.locale ? rule.dataset.locale.substring(0, 2) : 'en',
clickOpens: false,
disableMobile: true,
wrap: true,
plugins: [ new confirmDatePlugin({ confirmText: rule.dataset.doneText ?? 'Done', showAlways: true }) ],
onChange() {
if (rule.value.toLowerCase() !== orig_value) rule.classList.add('changed');
else rule.classList.remove('changed');
onChange(selectedDates) {
if (this.input.value === orig_value) this.altInput.classList.remove('changed');
else this.altInput.classList.add('changed');
},
onClose: update
onClose: update,
onReady(selectedDates) {
// Flatpickr hides the original input and adds the alternate input after it. That messes up the
// bootstrap input group styling. So move the now hidden original input after the created alternate
// input to fix that.
this.altInput.after(this.input);

// Make the alternate input left-to-right even for right-to-left languages.
this.altInput.dir = 'ltr';

this.altInput.addEventListener('blur', update);
},
parseDate(datestr, format) {
// Deal with the case of a unix timestamp. The timezone needs to be adjusted back as this is for
// the unix timestamp stored in the hidden input whose value will be sent to the server.
if (format === 'U') return new Date(parseInt(datestr) * 1000 - timezoneAdjustment);

// Next attempt to parse the datestr with the current format. This should not be adjusted. It is
// for display only.
const date = luxon.DateTime.fromFormat(datestr, format);
if (date.isValid) return date.toJSDate();

// Finally, fall back to the previous value in the original input if that failed. This is the case
// that the user typed a time that isn't in the valid format. So fallback to the last valid time
// that was displayed. This also should not be adjusted.
return new Date(this.lastFormattedDate.getTime());
},
formatDate(date, format) {
// Save this date for the fallback in parseDate.
this.lastFormattedDate = date;

// In this case the date provided is in the browser's time zone. So it needs to be adjusted to the
// timezone of the course.
if (format === 'U') return (date.getTime() + timezoneAdjustment) / 1000;

return luxon.DateTime.fromMillis(date.getTime())
.toFormat(datetimeFormats[luxon.Settings.defaultLocale]);
}
});

rule.addEventListener('blur', update);
rule.nextElementSibling.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
fp.open();
}
});
}
});
})();
11 changes: 9 additions & 2 deletions htdocs/js/apps/ProblemSetDetail/problemsetdetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,15 @@
if (!overrideCheck) return;
const changeHandler = () => overrideCheck.checked = input.value != '';
input.addEventListener('change', changeHandler);
input.addEventListener('keyup', changeHandler);
input.addEventListener('blur', () => { if (input.value == '') overrideCheck.checked = false; });
if (input.parentElement.classList.contains('flatpickr')) {
// Attach the keyup and blur handlers to the flatpickr alternate input.
input.previousElementSibling?.addEventListener('keyup', changeHandler);
input.previousElementSibling?.addEventListener('blur',
() => { if (input.previousElementSibling.value == '') overrideCheck.checked = false; });
} else {
input.addEventListener('keyup', changeHandler);
input.addEventListener('blur', () => { if (input.value == '') overrideCheck.checked = false; });
}
});

// Make the override checkboxes for selects checked or unchecked appropriately
Expand Down
87 changes: 83 additions & 4 deletions htdocs/js/apps/ProblemSetList/problemsetlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,98 @@
});
}

// Date/time formats for the languages supported by webwork.
// Note that these formats are chosen to match the perl DateTime::Locale formats.
// Make sure that anytime a new language is added, its format is added here.
const datetimeFormats = {
en: 'L/d/yy, h:mm a',
'en-US': 'L/d/yy, h:mm a',
'cs-CZ': 'dd.LL.yy H:mm',
de: 'dd.LL.yy, HH:mm',
es: 'd/L/yy H:mm',
'fr-CA': "yyyy-LL-dd HH 'h' mm",
fr: 'dd/LL/yyyy HH:mm',
'he-IL': 'd.L.yyyy, H:mm',
hu: 'yyyy. LL. dd. H:mm',
ko: 'yy. L. d. a h:mm',
'ru-RU': 'dd.LL.yyyy, HH:mm',
tr: 'd.LL.yyyy HH:mm',
'zh-CN': 'yyyy/L/d ah:mm',
'zh-HK': 'yyyy/L/d ah:mm'
};

// Initialize the date/time picker for the import form.
const importDateShift = document.getElementById('import_date_shift');
if (importDateShift) {
flatpickr(importDateShift.parentNode, {

luxon.Settings.defaultLocale = importDateShift.dataset.locale ?? 'en';

// Compute the time difference between the current browser timezone and the the course timezone.
// flatpickr gives the time in the browser's timezone, and this is used to adjust to the course timezone.
// Note that this is converted to microseconds.
const timezoneAdjustment =
parseInt(Intl.DateTimeFormat('en-US', { timeZoneName: 'shortOffset' })
.format(new Date).split(' ')[1].slice(3) || '0') * 3600000
- parseInt(Intl.DateTimeFormat('en-US',
{ timeZone: importDateShift.dataset.timezone ?? 'UTC', timeZoneName: 'shortOffset' })
.format(new Date).split(' ')[1].slice(3) || '0') * 3600000

const fp = flatpickr(importDateShift.parentNode, {
allowInput: true,
enableTime: true,
minuteIncrement: 1,
dateFormat: 'm/d/Y at h:iK',
altInput: true,
dateFormat: 'U',
altFormat: datetimeFormats[luxon.Settings.defaultLocale],
ariaDateFormat: datetimeFormats[luxon.Settings.defaultLocale],
defaultHour: 0,
locale: importDateShift.dataset.locale ? importDateShift.dataset.locale.substring(0, 2) : 'en',
clickOpens: false,
disableMobile: true,
wrap: true,
plugins: [ new confirmDatePlugin({ confirmText: importDateShift.dataset.doneText, showAlways: true })
],
plugins: [ new confirmDatePlugin({ confirmText: importDateShift.dataset.doneText, showAlways: true }) ],
onReady(selectedDates) {
// Flatpickr hides the original input and adds the alternate input after it. That messes up the
// bootstrap input group styling. So move the now hidden original input after the created alternate
// input to fix that.
this.altInput.after(this.input);

// Make the alternate input left-to-right even for right-to-left languages.
this.altInput.dir = 'ltr';
},
parseDate(datestr, format) {
// Deal with the case of a unix timestamp. The timezone needs to be adjusted back as this is for
// the unix timestamp stored in the hidden input whose value will be sent to the server.
if (format === 'U') return new Date(parseInt(datestr) * 1000 - timezoneAdjustment);

// Next attempt to parse the datestr with the current format. This should not be adjusted. It is
// for display only.
const date = luxon.DateTime.fromFormat(datestr, format);
if (date.isValid) return date.toJSDate();

// Finally, fall back to the previous value in the original input if that failed. This is the case
// that the user typed a time that isn't in the valid format. So fallback to the last valid time
// that was displayed. This also should not be adjusted.
return new Date(this.lastFormattedDate.getTime());
},
formatDate(date, format) {
// Save this date for the fallback in parseDate.
this.lastFormattedDate = date;

// In this case the date provided is in the browser's time zone. So it needs to be adjusted to the
// timezone of the course.
if (format === 'U') return (date.getTime() + timezoneAdjustment) / 1000;

return luxon.DateTime.fromMillis(date.getTime())
.toFormat(datetimeFormats[luxon.Settings.defaultLocale]);
}
});

importDateShift.nextElementSibling.addEventListener('keydown', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
fp.open();
}
});
}
})();
6 changes: 4 additions & 2 deletions htdocs/js/apps/UserDetail/userdetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
if (!overrideCheck) return;
const changeHandler = () => overrideCheck.checked = input.value != '';
input.addEventListener('change', changeHandler);
input.addEventListener('keyup', changeHandler);
input.addEventListener('blur', () => { if (input.value == '') overrideCheck.checked = false; });
// Attach the keyup and blur handlers to the flatpickr alternate input.
input.previousElementSibling?.addEventListener('keyup', changeHandler);
input.previousElementSibling?.addEventListener('blur',
() => { if (input.previousElementSibling.value == '') overrideCheck.checked = false; });
});

// If the "Assign All Sets to Current User" button is clicked, then check all assignments.
Expand Down
1 change: 1 addition & 0 deletions htdocs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"iframe-resizer": "^4.3.2",
"jquery": "^3.6.0",
"jquery-ui-dist": "^1.13.1",
"luxon": "^2.3.1",
"mathjax": "^3.2.0",
"sortablejs": "^1.14.0"
},
Expand Down
2 changes: 1 addition & 1 deletion htdocs/themes/math4/math4.scss
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ span {

/* Problem set list */
.set_table .input-group .form-control {
max-width: 11rem;
max-width: 10rem;
}

/* Problem graders */
Expand Down
10 changes: 10 additions & 0 deletions htdocs/third-party-assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,23 @@
"node_modules/codemirror/lib/codemirror.js": "https://cdn.jsdelivr.net/npm/[email protected]/lib/codemirror.min.js",
"node_modules/flatpickr/dist/flatpickr.min.css": "https://cdn.jsdelivr.net/npm/[email protected]/dist/flatpickr.min.css",
"node_modules/flatpickr/dist/flatpickr.min.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/flatpickr.min.js",
"node_modules/flatpickr/dist/l10n/cs.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/cs.min.js",
"node_modules/flatpickr/dist/l10n/es.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/es.min.js",
"node_modules/flatpickr/dist/l10n/fr.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/fr.min.js",
"node_modules/flatpickr/dist/l10n/he.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/he.min.js",
"node_modules/flatpickr/dist/l10n/hu.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/hu.min.js",
"node_modules/flatpickr/dist/l10n/ko.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/ko.min.js",
"node_modules/flatpickr/dist/l10n/ru.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/ru.min.js",
"node_modules/flatpickr/dist/l10n/tr.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/tr.min.js",
"node_modules/flatpickr/dist/l10n/zh.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/l10n/zh.min.js",
"node_modules/flatpickr/dist/plugins/confirmDate/confirmDate.css": "https://cdn.jsdelivr.net/npm/[email protected]/dist/plugins/confirmDate/confirmDate.min.css",
"node_modules/flatpickr/dist/plugins/confirmDate/confirmDate.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/plugins/confirmDate/confirmDate.min.js",
"node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js": "https://cdn.jsdelivr.net/npm/[email protected]/js/iframeResizer.contentWindow.min.js",
"node_modules/iframe-resizer/js/iframeResizer.min.js": "https://cdn.jsdelivr.net/npm/[email protected]/js/iframeResizer.min.js",
"node_modules/jquery-ui-dist/jquery-ui.min.css": "https://cdn.jsdelivr.net/npm/[email protected]/jquery-ui.min.css",
"node_modules/jquery-ui-dist/jquery-ui.min.js": "https://cdn.jsdelivr.net/npm/[email protected]/jquery-ui.min.js",
"node_modules/jquery/dist/jquery.min.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js",
"node_modules/luxon/build/global/luxon.min.js": "https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js",
"node_modules/mathjax/es5/tex-chtml.js": "https://cdn.jsdelivr.net/npm/[email protected]/es5/tex-chtml.js",
"node_modules/sortablejs/Sortable.min.js": "https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js"
}
Loading

0 comments on commit 943664f

Please sign in to comment.