mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
custom-profile-fields: Fix handling manual date input.
This commit fixes the behavior when manually changing the date input instead of selecting from the picker. Changes done are- - Users can enter date in various formats including the one showed in the input like "June 20, 1999", "MM-DD-YYY", and basically the formats which can be parsed by "Date". - Fixed handling of invalid strings where we show "Invalid date value" error message for some time without sending the request to server. Fixes #19936.
This commit is contained in:
@@ -34,7 +34,7 @@ async function open_settings(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
async function close_settings_and_date_picker(page: Page): Promise<void> {
|
||||
const date_picker_selector = ".custom_user_field_value.datepicker.form-control";
|
||||
const date_picker_selector = ".date-field-alt-input";
|
||||
await page.click(date_picker_selector);
|
||||
|
||||
await page.waitForSelector(".flatpickr-calendar", {visible: true});
|
||||
|
@@ -14,6 +14,7 @@ import * as settings_components from "./settings_components.ts";
|
||||
import * as settings_ui from "./settings_ui.ts";
|
||||
import {current_user, realm} from "./state_data.ts";
|
||||
import * as typeahead_helper from "./typeahead_helper.ts";
|
||||
import * as ui_report from "./ui_report.ts";
|
||||
import type {UserPillWidget} from "./user_pill.ts";
|
||||
import * as user_pill from "./user_pill.ts";
|
||||
|
||||
@@ -200,19 +201,131 @@ export function initialize_custom_user_type_fields(
|
||||
return user_pills;
|
||||
}
|
||||
|
||||
export function initialize_custom_date_type_fields(element_id: string): void {
|
||||
export function format_date(date: Date | undefined, format: string): string {
|
||||
if (date === undefined || date.toString() === "Invalid Date") {
|
||||
return "Invalid Date";
|
||||
}
|
||||
|
||||
return flatpickr.formatDate(date, format);
|
||||
}
|
||||
|
||||
export function initialize_custom_date_type_fields(element_id: string, user_id: number): void {
|
||||
const $date_picker_elements = $(element_id).find(".custom_user_field .datepicker");
|
||||
if ($date_picker_elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
function update_date(instance: flatpickr.Instance, date_str: string): void {
|
||||
const $input_elem = $(instance.element);
|
||||
const field_id = Number.parseInt($input_elem.attr("data-field-id")!, 10);
|
||||
|
||||
if (date_str === "Invalid Date") {
|
||||
// Date parses empty string to an invalid value but in
|
||||
// our case it is a valid value when user does not want
|
||||
// to set any value for the custom profile field.
|
||||
if ($input_elem.parent().find(".date-field-alt-input").val() === "") {
|
||||
if (user_id !== people.my_current_user_id()) {
|
||||
// For "Manage user" modal, API request is made after
|
||||
// clicking on "Save changes" button.
|
||||
return;
|
||||
}
|
||||
update_user_custom_profile_fields([{id: field_id}], channel.del);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show "Invalid date value" message briefly and set
|
||||
// the input to original value.
|
||||
const $spinner_element = $input_elem
|
||||
.closest(".custom_user_field")
|
||||
.find(".custom-field-status");
|
||||
ui_report.error(
|
||||
$t({defaultMessage: "Invalid date value"}),
|
||||
undefined,
|
||||
$spinner_element,
|
||||
1200,
|
||||
);
|
||||
const original_value = people.get_custom_profile_data(user_id, field_id)?.value ?? "";
|
||||
instance.setDate(original_value);
|
||||
if (user_id !== people.my_current_user_id()) {
|
||||
// Trigger "input" event so that save button state can
|
||||
// be toggled in "Manage user" modal.
|
||||
$input_elem
|
||||
.closest(".custom_user_field")
|
||||
.find(".date-field-alt-input")
|
||||
.trigger("input");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (user_id !== people.my_current_user_id()) {
|
||||
// For "Manage user" modal, API request is made after
|
||||
// clicking on "Save changes" button.
|
||||
return;
|
||||
}
|
||||
|
||||
const fields = [];
|
||||
if (date_str) {
|
||||
fields.push({id: field_id, value: date_str});
|
||||
update_user_custom_profile_fields(fields, channel.patch);
|
||||
} else {
|
||||
fields.push({id: field_id});
|
||||
update_user_custom_profile_fields(fields, channel.del);
|
||||
}
|
||||
}
|
||||
|
||||
flatpickr($date_picker_elements, {
|
||||
altInput: true,
|
||||
// We would need to handle the altInput separately
|
||||
// than ".custom_user_field_value" elements to handle
|
||||
// invalid values typed in the input.
|
||||
altInputClass: "date-field-alt-input settings_text_input",
|
||||
altFormat: "F j, Y",
|
||||
allowInput: true,
|
||||
static: true,
|
||||
// This helps us in accepting inputs in other formats
|
||||
// like MM/DD/YYYY and basically any other format
|
||||
// which is accepted by Date.
|
||||
parseDate: (date_str) => new Date(date_str),
|
||||
// We pass allowInvalidPreload as true because we handle
|
||||
// invalid values typed in the input ourselves. Also,
|
||||
// formatDate function is customized to handle "undefined"
|
||||
// values, which are returned by parseDate for invalid
|
||||
// values.
|
||||
formatDate: format_date,
|
||||
allowInvalidPreload: true,
|
||||
onChange(_selected_dates, date_str, instance) {
|
||||
update_date(instance, date_str);
|
||||
},
|
||||
});
|
||||
|
||||
// This "change" event handler is needed to make sure that
|
||||
// the date is successfully changed when typing a new value
|
||||
// in the input and blurring the input by clicking outside
|
||||
// while the calendar popover is opened, because onChange
|
||||
// callback is not executed in such a scenario.
|
||||
//
|
||||
// https://github.com/flatpickr/flatpickr/issues/1551#issuecomment-1601830680
|
||||
// has explanation on why that happens.
|
||||
//
|
||||
// However, this leads to a problem in a couple of cases
|
||||
// where both onChange callback and this "change" handlers
|
||||
// are executed when changing the date by typing in the
|
||||
// input. This occurs when pressing Enter while the input
|
||||
// is focused, and also when blurring the input by clicking
|
||||
// outside while the calendar popover is closed.
|
||||
$(element_id)
|
||||
.find<HTMLInputElement>("input.date-field-alt-input")
|
||||
.on("change", function (this: HTMLInputElement) {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const $datepicker = $(this).parent().find(".datepicker")[0] as HTMLInputElement & {
|
||||
_flatpickr: flatpickr.Instance;
|
||||
};
|
||||
const instance = $datepicker._flatpickr;
|
||||
const date = new Date($(this).val()!);
|
||||
const date_str = format_date(date, "Y-m-d");
|
||||
update_date(instance, date_str);
|
||||
});
|
||||
|
||||
// Enable the label associated to this field to open the datepicker when clicked.
|
||||
$(element_id)
|
||||
.find(".custom_user_field label.settings-field-label")
|
||||
@@ -234,6 +347,8 @@ export function initialize_custom_date_type_fields(element_id: string): void {
|
||||
.find(".custom_user_field .remove_date")
|
||||
.on("click", function () {
|
||||
const $custom_user_field = $(this).parent().find(".custom_user_field_value");
|
||||
const $displayed_input = $(this).parent().find(".date-field-alt-input");
|
||||
$displayed_input.val("");
|
||||
$custom_user_field.val("");
|
||||
$custom_user_field.trigger("input");
|
||||
});
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import $ from "jquery";
|
||||
import _ from "lodash";
|
||||
import assert from "minimalistic-assert";
|
||||
|
||||
import render_dialog_widget from "../templates/dialog_widget.hbs";
|
||||
|
||||
import type {AjaxRequestHandler} from "./channel.ts";
|
||||
import * as custom_profile_fields_ui from "./custom_profile_fields_ui.ts";
|
||||
import {$t_html} from "./i18n.ts";
|
||||
import * as loading from "./loading.ts";
|
||||
import * as modals from "./modals.ts";
|
||||
@@ -135,6 +137,24 @@ export function get_current_values($inputs: JQuery): Record<string, unknown> {
|
||||
current_values[property_name] = $(this).val();
|
||||
}
|
||||
}
|
||||
|
||||
if ($(this).hasClass("date-field-alt-input")) {
|
||||
// For date type custom profile fields, we convert the
|
||||
// input to the date format passed to the API.
|
||||
const value = $(this).val()!;
|
||||
const name = $(this).parent().find(".custom_user_field_value").attr("name")!;
|
||||
|
||||
if (value === "") {
|
||||
// This case is handled separately, because it will
|
||||
// otherwise be parsed as an invalid date.
|
||||
current_values[name] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(typeof value === "string");
|
||||
const date_str = new Date(value);
|
||||
current_values[name] = custom_profile_fields_ui.format_date(date_str, "Y-m-d");
|
||||
}
|
||||
});
|
||||
return current_values;
|
||||
}
|
||||
|
@@ -233,7 +233,10 @@ export function add_custom_profile_fields_to_settings(): void {
|
||||
true,
|
||||
pill_update_handler,
|
||||
);
|
||||
custom_profile_fields_ui.initialize_custom_date_type_fields(element_id);
|
||||
custom_profile_fields_ui.initialize_custom_date_type_fields(
|
||||
element_id,
|
||||
people.my_current_user_id(),
|
||||
);
|
||||
custom_profile_fields_ui.initialize_custom_pronouns_type_fields(element_id);
|
||||
}
|
||||
|
||||
@@ -724,22 +727,26 @@ export function set_up(): void {
|
||||
},
|
||||
);
|
||||
|
||||
$("#profile-settings").on("change", ".custom_user_field_value", function (this: HTMLElement) {
|
||||
const fields: CustomProfileFieldData[] = [];
|
||||
const value = $(this).val()!;
|
||||
assert(typeof value === "string");
|
||||
const field_id = Number.parseInt(
|
||||
$(this).closest(".custom_user_field").attr("data-field-id")!,
|
||||
10,
|
||||
);
|
||||
if (value) {
|
||||
fields.push({id: field_id, value});
|
||||
custom_profile_fields_ui.update_user_custom_profile_fields(fields, channel.patch);
|
||||
} else {
|
||||
fields.push({id: field_id});
|
||||
custom_profile_fields_ui.update_user_custom_profile_fields(fields, channel.del);
|
||||
}
|
||||
});
|
||||
$("#profile-settings").on(
|
||||
"change",
|
||||
".custom_user_field_value:not(.datepicker)",
|
||||
function (this: HTMLElement) {
|
||||
const fields: CustomProfileFieldData[] = [];
|
||||
const value = $(this).val()!;
|
||||
assert(typeof value === "string");
|
||||
const field_id = Number.parseInt(
|
||||
$(this).closest(".custom_user_field").attr("data-field-id")!,
|
||||
10,
|
||||
);
|
||||
if (value) {
|
||||
fields.push({id: field_id, value});
|
||||
custom_profile_fields_ui.update_user_custom_profile_fields(fields, channel.patch);
|
||||
} else {
|
||||
fields.push({id: field_id});
|
||||
custom_profile_fields_ui.update_user_custom_profile_fields(fields, channel.del);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
$("#account-settings .deactivate_realm_button").on(
|
||||
"click",
|
||||
|
@@ -1054,16 +1054,10 @@ function get_human_profile_data(fields_user_pills: Map<number, user_pill.UserPil
|
||||
*/
|
||||
const new_profile_data = [];
|
||||
$("#edit-user-form .custom_user_field_value").each(function () {
|
||||
// Remove duplicate datepicker input element generated flatpickr library
|
||||
if (!$(this).hasClass("form-control")) {
|
||||
new_profile_data.push({
|
||||
id: Number.parseInt(
|
||||
$(this).closest(".custom_user_field").attr("data-field-id")!,
|
||||
10,
|
||||
),
|
||||
value: $(this).val(),
|
||||
});
|
||||
}
|
||||
new_profile_data.push({
|
||||
id: Number.parseInt($(this).closest(".custom_user_field").attr("data-field-id")!, 10),
|
||||
value: $(this).val(),
|
||||
});
|
||||
});
|
||||
// Append user type field values also
|
||||
for (const [field_id, field_pills] of fields_user_pills) {
|
||||
@@ -1083,7 +1077,7 @@ function get_current_values(
|
||||
$edit_form: JQuery,
|
||||
): Record<string, unknown> & {user_id?: string | undefined} {
|
||||
const raw_current_values = dialog_widget.get_current_values(
|
||||
$edit_form.find("input, select, textarea, button, .pill-container"),
|
||||
$edit_form.find("input:not(.datepicker), select, textarea, button, .pill-container"),
|
||||
);
|
||||
const schema = z.intersection(
|
||||
z.object({
|
||||
@@ -1150,7 +1144,10 @@ export function show_edit_user_info_modal(user_id: number, $container: JQuery):
|
||||
custom_profile_field_form_selector,
|
||||
user_id,
|
||||
);
|
||||
custom_profile_fields_ui.initialize_custom_date_type_fields(custom_profile_field_form_selector);
|
||||
custom_profile_fields_ui.initialize_custom_date_type_fields(
|
||||
custom_profile_field_form_selector,
|
||||
user_id,
|
||||
);
|
||||
custom_profile_fields_ui.initialize_custom_pronouns_type_fields(
|
||||
custom_profile_field_form_selector,
|
||||
);
|
||||
|
@@ -740,6 +740,18 @@ input[type="checkbox"] {
|
||||
}
|
||||
}
|
||||
|
||||
#edit-user-form .alert-notification.custom-field-status,
|
||||
#profile-settings .alert-notification.custom-field-status,
|
||||
#profile-settings .alert-notification.full-name-status,
|
||||
#profile-settings .alert-notification.timezone-setting-status {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
margin-left: 5px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#profile-settings {
|
||||
.custom-profile-fields-form .custom_user_field label,
|
||||
.full-name-change-container label,
|
||||
@@ -747,17 +759,6 @@ input[type="checkbox"] {
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.alert-notification.custom-field-status,
|
||||
.alert-notification.full-name-status,
|
||||
.alert-notification.timezone-setting-status {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-top: 0;
|
||||
padding-left: 0;
|
||||
margin-left: 5px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.person_picker {
|
||||
/* Subtract 2 * (2px padding) + 2 * (1px border) */
|
||||
min-width: calc(var(--modal-input-width) - 6px);
|
||||
|
Reference in New Issue
Block a user