/
githubmirror
/
zulip
Обзор
Документация
Войти
/
githubmirror
/
zulip
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
web/src/settings_profile_fields.ts
989 строк
37 KB
Anders Kaseorg
eslint: Fix unicorn/no-computed-property-existence-check.
29 июл 2026, 02:35
29 июл 2026, 02:35
1164601
Код
Авторство
О чём код?
import {$} from "jquery"; import assert from "minimalistic-assert"; import SortableJS from "sortablejs"; import type * as tippy from "tippy.js"; import render_confirm_delete_profile_field from "../templates/confirm_dialog/confirm_delete_profile_field.hbs"; import render_confirm_delete_profile_field_option from "../templates/confirm_dialog/confirm_delete_profile_field_option.hbs"; import render_add_new_custom_profile_field_form from "../templates/settings/add_new_custom_profile_field_form.hbs"; import render_admin_profile_field_list from "../templates/settings/admin_profile_field_list.hbs"; import render_edit_custom_profile_field_form from "../templates/settings/edit_custom_profile_field_form.hbs"; import render_settings_profile_field_choice from "../templates/settings/profile_field_choice.hbs"; import * as channel from "./channel.ts"; import * as confirm_dialog from "./confirm_dialog.ts"; import * as dialog_widget from "./dialog_widget.ts"; import type {DropdownWidget, Option} from "./dropdown_widget.ts"; import * as dropdown_widget from "./dropdown_widget.ts"; import {$t, $t_html} from "./i18n.ts"; import * as ListWidget from "./list_widget.ts"; import * as loading from "./loading.ts"; import * as people from "./people.ts"; import * as settings_components from "./settings_components.ts"; import type {SelectFieldData} from "./settings_components.ts"; import * as settings_ui from "./settings_ui.ts"; import type {CustomProfileField} from "./state_data.ts"; import {current_user, realm} from "./state_data.ts"; import type {HTMLSelectOneElement} from "./types.ts"; import * as ui_report from "./ui_report.ts"; import {place_caret_at_end} from "./ui_util.ts"; import * as util from "./util.ts"; type FieldChoice = { value: string; text: string; order: string; }; const meta: { loaded: boolean; } = { loaded: false, }; let external_accounts_dropdown_widget: DropdownWidget; function setup_external_accounts_dropdown_widget(): void { const custom_option = { name: $t_html({defaultMessage: "Custom"}), unique_id: "custom", make_italic: true, }; function get_options_for_external_accounts_dropdown_widget(): Option[] { const existing_default_external_accounts_fields = new Set( realm.custom_profile_fields .filter( (field) => field.type === realm.custom_profile_field_types.EXTERNAL_ACCOUNT.id, ) .map((field) => settings_components.external_account_field_schema.parse( JSON.parse(field.field_data), ), ) .filter((fieldData) => fieldData.subtype && fieldData.subtype !== "custom") .map((fieldData) => fieldData.subtype), ); const options = [ custom_option, ...Object.entries(realm.realm_default_external_accounts) .toSorted(([, a], [, b]) => util.strcmp(a.text, b.text)) .filter(([key]) => !existing_default_external_accounts_fields.has(key)) .map(([key, account]) => ({ name: account.text, unique_id: key, })), ]; return options; } external_accounts_dropdown_widget = new dropdown_widget.DropdownWidget({ widget_name: "external_accounts_type", get_options: get_options_for_external_accounts_dropdown_widget, item_click_callback: external_account_item_click_callback, $events_container: $("#profile_field_external_accounts"), text_if_current_value_not_in_options: $t({ defaultMessage: "Select an account type", }), unique_id_type: "string", on_show_callback(_dropdown: tippy.Instance, widget: dropdown_widget.DropdownWidget) { external_accounts_dropdown_widget = widget; }, }); external_accounts_dropdown_widget.setup(); settings_components.set_dropdown_setting_widget( "external_accounts_type", external_accounts_dropdown_widget, ); } function display_success_status(): void { const $spinner = $("#admin-profile-field-status").expectOne(); const success_msg_html = settings_ui.strings.success_html; ui_report.success(success_msg_html, $spinner, 1000); settings_ui.display_checkmark($spinner); } export function maybe_disable_widgets(): void { if (current_user.is_admin) { return; } $(".organization-box [data-name='profile-field-settings']") .find("input, button, select") .prop("disabled", true); } let display_in_profile_summary_fields_limit_reached = false; let order: number[] = []; export function field_type_id_to_string(type_id: number): string | undefined { for (const field_type of Object.values(realm.custom_profile_field_types)) { if (field_type.id === type_id) { return field_type.name; } } return undefined; } // Checking custom profile field type is valid for showing display on user card checkbox field. function is_valid_to_display_in_summary(field_type: number): boolean { const field_types = realm.custom_profile_field_types; if (field_type === field_types.USER.id) { return false; } return true; } // Checking custom profile field type is valid for showing use for user matching checkbox field. function is_valid_to_use_for_user_matching(field_type: number): boolean { const field_types = realm.custom_profile_field_types; if ( field_type === field_types.EXTERNAL_ACCOUNT.id || field_type === field_types.SHORT_TEXT.id ) { return true; } return false; } function delete_profile_field(this: HTMLElement, e: JQuery.ClickEvent): void { e.preventDefault(); e.stopPropagation(); const profile_field_id = Number.parseInt( $(this).closest("tr").attr("data-profile-field-id")!, 10, ); const profile_field = get_profile_field(profile_field_id); const active_user_ids = people.get_active_user_ids(); let users_using_deleting_profile_field = 0; for (const user_id of active_user_ids) { const user_profile_data = people.get_custom_profile_data(user_id, profile_field_id); if (user_profile_data !== undefined) { users_using_deleting_profile_field += 1; } } assert(profile_field !== undefined); const modal_content_html = render_confirm_delete_profile_field({ profile_field_name: profile_field.name, count: users_using_deleting_profile_field, }); function request_delete(): void { const url = "/json/realm/profile_fields/" + profile_field_id; const opts = { success_continuation() { display_success_status(); }, }; dialog_widget.submit_api_request(channel.del, url, {}, opts); } confirm_dialog.launch({ modal_title_html: $t_html({defaultMessage: "Delete custom profile field?"}), modal_content_html, is_compact: true, on_click: request_delete, }); } export function get_value_for_new_option(container: JQuery): number { let value = 0; for (const row of $(container).find(".choice-row")) { value = Math.max(value, Number.parseInt($(row).attr("data-value")!, 10) + 1); } return value; } function create_choice_row(container: JQuery): void { const context = { text: "", value: get_value_for_new_option(container), new_empty_choice_row: true, }; const row_html = render_settings_profile_field_choice(context); $(container).append($(row_html)); } function initialize_form_to_defaults(): void { const field_types = realm.custom_profile_field_types; $("#profile_field_name").val("").closest(".input-group").show(); $("#profile_field_hint").val("").closest(".input-group").show(); // Set default type "Short text" in field type dropdown $("#profile_field_type").val(field_types.SHORT_TEXT.id); // Clear data from select field form $("#profile_field_choices").empty(); create_choice_row($("#profile_field_choices")); $("#profile_field_choices_row").hide(); // Clear external account field form $("#custom_field_url_pattern").val(""); $("#custom_external_account_url_pattern").hide(); $("#profile_field_external_accounts").hide(); external_accounts_dropdown_widget.render(); } function should_check_user_matching_for_external_account( account_type: string | number | undefined, ): boolean { const unchecked_account_types = new Set(["atlassian", "mastodon"]); const should_uncheck_checkbox = typeof account_type === "string" && unchecked_account_types.has(account_type); return !should_uncheck_checkbox; } function external_account_item_click_callback( event: JQuery.ClickEvent, dropdown: tippy.Instance, widget: dropdown_widget.DropdownWidget, ): void { $("#dialog_error").hide(); dropdown.hide(); event.preventDefault(); event.stopPropagation(); const external_account_type = external_accounts_dropdown_widget.value(); assert(external_account_type !== undefined); if (external_account_type === "custom") { $("#custom_external_account_url_pattern").show(); $("#profile_field_name").val("").prop("disabled", false); $("#profile_field_hint").val("").prop("disabled", false); widget.render(); return; } $("#custom_external_account_url_pattern").hide(); const external_account = realm.realm_default_external_accounts[external_account_type]; assert(external_account !== undefined); $("#profile_field_name").val(external_account.name).prop("disabled", true); $("#profile_field_hint").val(external_account.hint).prop("disabled", true); $("#profile_field_use_for_user_matching").prop( "checked", should_check_user_matching_for_external_account(external_account.text.toLowerCase()), ); widget.render(); } function update_form_for_field_type_selection(): void { // Hide error on field type change. $("#dialog_error").hide(); $("#profile_field_name").val("").prop("disabled", false); $("#profile_field_hint").val("").prop("disabled", false); $("#profile_field_external_accounts").hide(); $("#profile_field_choices_row").hide(); $("#custom_external_account_url_pattern").hide(); const field_types = realm.custom_profile_field_types; const profile_field_type = Number.parseInt( $<HTMLSelectOneElement>("select:not([multiple])#profile_field_type").val()!, 10, ); switch (profile_field_type) { case field_types.EXTERNAL_ACCOUNT.id: { $("#profile_field_external_accounts").show(); external_accounts_dropdown_widget.render(); $("#profile_field_name").val("").prop("disabled", true); $("#profile_field_hint").val("").prop("disabled", true); break; } case field_types.PRONOUNS.id: { const default_label = $t({defaultMessage: "Pronouns"}); const default_hint = $t({ defaultMessage: "What pronouns should people use to refer to you?", }); $("#profile_field_name").val(default_label); $("#profile_field_hint").val(default_hint); break; } case field_types.DROPDOWN.id: { $("#profile_field_choices_row").show(); } } // Not showing "display on user card" option for long text/user profile field. if (is_valid_to_display_in_summary(profile_field_type)) { $("#profile_field_display_in_profile_summary").closest(".input-group").show(); const check_display_in_profile_summary_by_default = profile_field_type === field_types.PRONOUNS.id && !display_in_profile_summary_fields_limit_reached; $("#profile_field_display_in_profile_summary").prop( "checked", check_display_in_profile_summary_by_default, ); } else { $("#profile_field_display_in_profile_summary").closest(".input-group").hide(); $("#profile_field_display_in_profile_summary").prop("checked", false); } if (is_valid_to_use_for_user_matching(profile_field_type)) { $("#profile_field_use_for_user_matching").closest(".input-group").show(); const check_use_for_user_mentions = profile_field_type === field_types.EXTERNAL_ACCOUNT.id && should_check_user_matching_for_external_account( external_accounts_dropdown_widget.value(), ); $("#profile_field_use_for_user_matching").prop("checked", check_use_for_user_mentions); } else { $("#profile_field_use_for_user_matching").closest(".input-group").hide(); $("#profile_field_use_for_user_matching").prop("checked", false); } } function open_custom_profile_field_creation_form_modal(): void { const sorted_custom_profile_field_types = Object.values( realm.custom_profile_field_types, ).toSorted((a, b) => util.strcmp(a.name, b.name)); const modal_content_html = render_add_new_custom_profile_field_form({ realm_default_external_accounts: realm.realm_default_external_accounts, custom_profile_field_types: sorted_custom_profile_field_types, }); function create_profile_field(): void { const field_type = $<HTMLSelectOneElement>( "select:not([multiple])#profile_field_type", ).val()!; const field_data = settings_components.read_field_data_from_form( Number.parseInt(field_type, 10), $(".new-profile-field-form"), undefined, ); const data = { name: $("#profile_field_name").val(), hint: $("#profile_field_hint").val(), field_type, field_data: JSON.stringify(field_data), display_in_profile_summary: $("#profile_field_display_in_profile_summary").is( ":checked", ), required: $("#profile-field-required").is(":checked"), editable_by_user: $("#profile_field_editable_by_user").is(":checked"), use_for_user_matching: $("#profile_field_use_for_user_matching").is(":checked"), }; const url = "/json/realm/profile_fields"; const opts = { success_continuation() { display_success_status(); }, }; dialog_widget.submit_api_request(channel.post, url, data, opts); } function initialize_custom_profile_field_form(): void { set_up_custom_profile_field_choices(); setup_external_accounts_dropdown_widget(); $("#profile_field_type").on("change", () => { update_form_for_field_type_selection(); }); initialize_form_to_defaults(); // If we already have 2 custom profile fields configured to be // displayed on the user card, disable the input to change it. $("#add-new-custom-profile-field-form #profile_field_display_in_profile_summary").prop( "disabled", display_in_profile_summary_fields_limit_reached, ); $("#add-new-custom-profile-field-form .profile_field_display_label").toggleClass( "disabled_label", display_in_profile_summary_fields_limit_reached, ); $("#add-new-custom-profile-field-form .profile_field_display_label").toggleClass( "display_in_profile_summary_tooltip", display_in_profile_summary_fields_limit_reached, ); } dialog_widget.launch({ form_id: "add-new-custom-profile-field-form", help_link: "/help/custom-profile-fields#add-a-custom-profile-field", modal_title_html: $t_html({defaultMessage: "Add a new custom profile field"}), modal_content_html, modal_submit_button_text: $t({defaultMessage: "Add"}), on_click: create_profile_field, post_render: initialize_custom_profile_field_form, loading_spinner: true, on_shown() { $("#profile_field_type").trigger("focus"); }, }); } function add_choice_row(this: HTMLElement, e: JQuery.TriggeredEvent): void { const $curr_choice_row = $(this).parent(); if ($curr_choice_row.next().hasClass("choice-row")) { return; } // Display delete buttons for all existing choices before creating the new row, // which will not have the delete button so that there is at least one option present. $curr_choice_row.find("button.delete-choice").removeClass("hide"); $curr_choice_row.find("span.move-handle").removeClass("invisible"); assert(e.delegateTarget instanceof HTMLElement); const choices_div = e.delegateTarget; create_choice_row($(choices_div)); } function delete_choice_row(row: HTMLElement): void { const $row = $(row).parent(); $row.remove(); } function delete_choice_row_for_edit( row: HTMLElement, $profile_field_form: JQuery, field: CustomProfileField, ): void { delete_choice_row(row); disable_submit_button_if_no_property_changed($profile_field_form, field); } function show_modal_for_deleting_options( field: CustomProfileField, deleted_values: Map<string, string>, update_profile_field: () => void, ): void { const active_user_ids = people.get_active_user_ids(); let users_count_with_deleted_option_selected = 0; for (const user_id of active_user_ids) { const field_value = people.get_custom_profile_data(user_id, field.id); if (field_value !== undefined && deleted_values.has(field_value.value)) { users_count_with_deleted_option_selected += 1; } } const deleted_options_count = deleted_values.size; const modal_content_html = render_confirm_delete_profile_field_option({ count: users_count_with_deleted_option_selected, field_name: field.name, deleted_options_count, deleted_values, }); confirm_dialog.launch({ modal_title_html: $t_html( { defaultMessage: "{N, plural, one {Delete this option?} other {Delete these options?}}", }, {N: deleted_options_count}, ), modal_content_html, on_click: update_profile_field, }); } function get_profile_field(id: number): CustomProfileField | undefined { return realm.custom_profile_fields.find((field) => field.id === id); } export function parse_field_choices_from_field_data(field_data: SelectFieldData): FieldChoice[] { const choices: FieldChoice[] = []; for (const [value, choice] of Object.entries(field_data)) { choices.push({ value, text: choice.text, order: choice.order, }); } choices.sort((a, b) => Number.parseInt(a.order, 10) - Number.parseInt(b.order, 10)); return choices; } function set_up_external_account_field_edit_form( $profile_field_form: JQuery, url_pattern_val: string, ): void { if ($profile_field_form.find("select[name=external_acc_field_type]").val() === "custom") { $profile_field_form.find("input[name=url_pattern]").val(url_pattern_val); $profile_field_form.find(".custom_external_account_detail").show(); $profile_field_form.find("input[name=name]").prop("disabled", false); $profile_field_form.find("input[name=hint]").prop("disabled", false); } else { $profile_field_form.find("input[name=name]").prop("disabled", true); $profile_field_form.find("input[name=hint]").prop("disabled", true); $profile_field_form.find(".custom_external_account_detail").hide(); } } function disable_submit_button_if_no_property_changed( $profile_field_form: JQuery, field: CustomProfileField, ): void { const data = settings_components.populate_data_for_custom_profile_field_request( $profile_field_form, field, ); let save_changes_button_disabled = false; if (Object.keys(data).length === 0 || data["field_data"] === "{}") { save_changes_button_disabled = true; } $("#edit-custom-profile-field-form-modal .dialog_submit_button").prop( "disabled", save_changes_button_disabled, ); } function alphabetize_profile_field_choices($sortable_element: JQuery): void { assert($sortable_element[0] !== undefined); const sortable_instance = SortableJS.get($sortable_element[0]); assert(sortable_instance !== undefined); const choices_array: [string, string][] = []; const empty_choices_array: [string, string][] = []; const choices_id_array = sortable_instance.toArray(); for (const choice_id of choices_id_array) { const choice_value = $(sortable_instance.el) .find<HTMLInputElement>(`div[data-value="${choice_id}"] input`) .val()!; // Remove empty choices from the array that we will sort. After sorting, we append these // to the sorted array.; if (choice_value.length === 0) { empty_choices_array.push(["", choice_id]); continue; } choices_array.push([choice_value, choice_id]); } choices_array.sort((a, b) => util.strcmp(a[0], b[0])); choices_array.push(...empty_choices_array); sortable_instance.sort(choices_array.map((v) => v[1])); } function set_up_custom_profile_field_choices_edit_form( $profile_field_form: JQuery, field: CustomProfileField, ): void { // Re-render field choices in edit form to load initial select data const $choice_list = $profile_field_form.find(".edit_profile_field_choices_container"); $choice_list.off(); $choice_list.empty(); const choices_data = parse_field_choices_from_field_data( settings_components.custom_profile_field_choices_schema.parse(JSON.parse(field.field_data)), ); for (const choice of choices_data) { $choice_list.append( $( render_settings_profile_field_choice({ text: choice.text, value: choice.value, is_existing_choice: true, }), ), ); } // Add blank choice at last create_choice_row($choice_list); SortableJS.create(util.the($choice_list), { onUpdate() { // Do nothing on drag. We process the order on submission }, filter: "input", preventOnFilter: false, dataIdAttr: "data-value", onSort() { disable_submit_button_if_no_property_changed($profile_field_form, field); }, }); } function open_custom_profile_field_edit_form_modal(this: HTMLElement): void { const field_types = realm.custom_profile_field_types; const field_id = Number.parseInt($(this).closest("tr").attr("data-profile-field-id")!, 10); const field = get_profile_field(field_id)!; let field_data: unknown = {}; if (field.field_data) { field_data = JSON.parse(field.field_data); } let choices: FieldChoice[] = []; if (field.type === field_types.DROPDOWN.id) { const custom_profile_field_choices = settings_components.custom_profile_field_choices_schema.parse(field_data); choices = parse_field_choices_from_field_data(custom_profile_field_choices); } const modal_content_html = render_edit_custom_profile_field_form({ profile_field_info: { id: field.id, name: field.name, hint: field.hint, choices, display_in_profile_summary: field.display_in_profile_summary === true, required: field.required, editable_by_user: field.editable_by_user, use_for_user_matching: field.use_for_user_matching, is_dropdown_field: field.type === field_types.DROPDOWN.id, is_external_account_field: field.type === field_types.EXTERNAL_ACCOUNT.id, valid_to_display_in_summary: is_valid_to_display_in_summary(field.type), valid_to_use_for_user_matching: is_valid_to_use_for_user_matching(field.type), }, realm_default_external_accounts: realm.realm_default_external_accounts, }); function set_initial_values_of_profile_field(): void { const $profile_field_form = $("#edit-custom-profile-field-form-" + field_id); // If it exceeds or equals the max limit, we are disabling option for display custom // profile field on user card and adding tooltip, unless the field is already checked. if (display_in_profile_summary_fields_limit_reached && !field.display_in_profile_summary) { $profile_field_form .find("input[name=display_in_profile_summary]") .prop("disabled", true); $profile_field_form .find(".edit_profile_field_display_label") .addClass("display_in_profile_summary_tooltip disabled_label"); } if (field.type === field_types.DROPDOWN.id) { set_up_custom_profile_field_choices_edit_form($profile_field_form, field); } if (field.type === field_types.EXTERNAL_ACCOUNT.id) { const external_account_data = settings_components.external_account_field_schema.parse(field_data); $profile_field_form .find("select[name=external_acc_field_type]") .val(external_account_data.subtype); set_up_external_account_field_edit_form( $profile_field_form, external_account_data.url_pattern!, ); } // Set initial value in edit form $profile_field_form.find("input[name=name]").val(field.name); $profile_field_form.find("input[name=hint]").val(field.hint); const $edit_profile_field_choices_container = $profile_field_form.find( ".edit_profile_field_choices_container", ); $edit_profile_field_choices_container.on("input", ".choice-row input", add_choice_row); $edit_profile_field_choices_container.on( "click", "button.delete-choice", function (this: HTMLElement) { delete_choice_row_for_edit(this, $profile_field_form, field); }, ); $edit_profile_field_choices_container.on( "click", "button.edit-choice", function (this: HTMLElement) { $(this) .closest(".choice-row") .find("input") .prop("disabled", false) .trigger("focus"); $(this).hide(); }, ); $profile_field_form.on( "click", ".profile-field-choices-wrapper > button.alphabetize-choices-button", () => { alphabetize_profile_field_choices($edit_profile_field_choices_container); disable_submit_button_if_no_property_changed($profile_field_form, field); }, ); $("#edit-custom-profile-field-form-modal .dialog_submit_button").prop("disabled", true); // Setup onInput event listeners to disable/enable submit button, // select field add/update/remove operations are covered in onSort and // row delete button is separately covered in delete_choice_row_for_edit. $profile_field_form.on("input", () => { disable_submit_button_if_no_property_changed($profile_field_form, field); }); } function submit_form(): void { const $profile_field_form = $("#edit-custom-profile-field-form-" + field_id); const data = settings_components.populate_data_for_custom_profile_field_request( $profile_field_form, field, ); function update_profile_field(): void { const url = "/json/realm/profile_fields/" + field_id; const opts = { success_continuation() { display_success_status(); }, }; dialog_widget.submit_api_request(channel.patch, url, data, opts); } if (field.type === field_types.DROPDOWN.id && data["field_data"] !== undefined) { const new_values = new Set( Object.keys( settings_components.custom_profile_field_choices_schema.parse( JSON.parse(data["field_data"].toString()), ), ), ); const deleted_values = new Map<string, string>(); const custom_profile_field_choices = settings_components.custom_profile_field_choices_schema.parse(field_data); for (const [value, option] of Object.entries(custom_profile_field_choices)) { if (!new_values.has(value)) { deleted_values.set(value, option.text); } } if (deleted_values.size > 0) { const edit_custom_profile_field_modal_callback = (): void => { show_modal_for_deleting_options(field, deleted_values, update_profile_field); }; dialog_widget.close(edit_custom_profile_field_modal_callback); return; } } update_profile_field(); } const edit_custom_profile_field_form_id = "edit-custom-profile-field-form-" + field_id; dialog_widget.launch({ form_id: edit_custom_profile_field_form_id, modal_title_html: $t_html({defaultMessage: "Edit custom profile field"}), modal_content_html, id: "edit-custom-profile-field-form-modal", on_click: submit_form, post_render: set_initial_values_of_profile_field, loading_spinner: true, on_shown() { place_caret_at_end(util.the($("#id-custom-profile-field-name"))); }, }); } // If exceeds or equals the max limit, we are disabling option for // display custom profile field on user card and adding tooltip. function update_profile_fields_checkboxes(): void { // Disabling only uncheck checkboxes in table, so user should able uncheck checked checkboxes. $("#admin_profile_fields_table .display_in_profile_summary_checkbox_false").prop( "disabled", display_in_profile_summary_fields_limit_reached, ); $("#admin_profile_fields_table .display_in_profile_summary_false").toggleClass( "display_in_profile_summary_tooltip", display_in_profile_summary_fields_limit_reached, ); } function toggle_display_in_profile_summary_profile_field( this: HTMLInputElement, _event: JQuery.Event, ): void { const field_id = Number.parseInt($(this).attr("data-profile-field-id")!, 10); const data = { display_in_profile_summary: this.checked, }; const $profile_field_status = $("#admin-profile-field-status").expectOne(); settings_ui.do_settings_change( channel.patch, "/json/realm/profile_fields/" + field_id, data, $profile_field_status, ); } function toggle_required(this: HTMLInputElement, _event: JQuery.Event): void { const field_id = Number.parseInt($(this).attr("data-profile-field-id")!, 10); const data = { required: this.checked, }; const $profile_field_status = $("#admin-profile-field-status").expectOne(); settings_ui.do_settings_change( channel.patch, "/json/realm/profile_fields/" + field_id, data, $profile_field_status, ); } export function reset(): void { meta.loaded = false; } function update_field_order(this: HTMLElement): void { order = []; $(".profile-field-row").each(function () { order.push(Number.parseInt($(this).attr("data-profile-field-id")!, 10)); }); settings_ui.do_settings_change( channel.patch, "/json/realm/profile_fields", {order: JSON.stringify(order)}, $("#admin-profile-field-status").expectOne(), ); } export function populate_profile_fields(profile_fields_data: CustomProfileField[]): void { if (!meta.loaded) { // If outside callers call us when we're not loaded, just // exit and we'll draw the widgets again during set_up(). return; } do_populate_profile_fields(profile_fields_data); } export function do_populate_profile_fields(profile_fields_data: CustomProfileField[]): void { // We should only call this internally or from tests. const $profile_fields_table = $("#admin_profile_fields_table").expectOne(); order = []; let display_in_profile_summary_fields_count = 0; for (const profile_field of profile_fields_data) { order.push(profile_field.id); // Keeping counts of all display_in_profile_summary profile fields, // to keep track of whether the limit has been reached. if (profile_field.display_in_profile_summary) { display_in_profile_summary_fields_count += 1; } } ListWidget.create($profile_fields_table, profile_fields_data, { name: "settings_profile_fields_list", get_item(profile_field) { return profile_field; }, modifier_html(profile_field) { const display_in_profile_summary = profile_field.display_in_profile_summary === true; const required = profile_field.required; return render_admin_profile_field_list({ profile_field: { id: profile_field.id, name: profile_field.name, hint: profile_field.hint, type: field_type_id_to_string(profile_field.type), display_in_profile_summary, valid_to_display_in_summary: is_valid_to_display_in_summary(profile_field.type), required, }, can_modify: current_user.is_admin, realm_default_external_accounts: realm.realm_default_external_accounts, }); }, $parent_container: $("#profile-field-settings").expectOne(), $simplebar_container: $("#profile-field-settings .progressive-table-wrapper"), }); // Update whether we're at the limit for display_in_profile_summary. display_in_profile_summary_fields_limit_reached = display_in_profile_summary_fields_count >= 2; if (current_user.is_admin) { const field_list = util.the($("#admin_profile_fields_table")); SortableJS.create(field_list, { onUpdate: update_field_order, filter: "input", preventOnFilter: false, }); } update_profile_fields_checkboxes(); loading.destroy_indicator($("#admin_page_profile_fields_loading_indicator")); } function set_up_custom_profile_field_choices(): void { const $profile_field_choices = $("#profile_field_choices"); create_choice_row($profile_field_choices); if (current_user.is_admin) { const choice_list = util.the($profile_field_choices); SortableJS.create(choice_list, { onUpdate() { // Do nothing on drag. We process the order on submission }, filter: "input", preventOnFilter: false, dataIdAttr: "data-value", }); } $profile_field_choices.on("input", ".choice-row input", add_choice_row); $profile_field_choices.on("click", "button.delete-choice", function (this: HTMLElement) { delete_choice_row(this); }); $("#profile_field_choices_row").on("click", "button.alphabetize-choices-button", () => { alphabetize_profile_field_choices($profile_field_choices); }); } export function get_external_account_link( field_data: settings_components.ExternalAccountFieldData, value: string, ): string | undefined { const field_subtype = field_data.subtype; let field_url_pattern: string; if (field_subtype === "custom") { if (!field_data.url_pattern) { return undefined; } field_url_pattern = field_data.url_pattern; } else { const external_account = realm.realm_default_external_accounts[field_subtype]; assert(external_account !== undefined); field_url_pattern = external_account.url_pattern; // Use as text fields when there is no URL pattern if (!field_url_pattern) { return field_url_pattern; } } return field_url_pattern.replace("%(username)s", () => value); } export function set_up(): void { build_page(); maybe_disable_widgets(); } export function build_page(): void { // create loading indicators loading.make_indicator($("#admin_page_profile_fields_loading_indicator")); // Populate profile_fields table do_populate_profile_fields(realm.custom_profile_fields); meta.loaded = true; $("#admin_profile_fields_table").on("click", ".delete", delete_profile_field); $("#add-custom-profile-field-button").on( "click", open_custom_profile_field_creation_form_modal, ); $("#admin_profile_fields_table").on( "click", ".open-edit-form-modal", open_custom_profile_field_edit_form_modal, ); $("#admin_profile_fields_table").on( "click", "input.display_in_profile_summary", toggle_display_in_profile_summary_profile_field, ); $("#admin_profile_fields_table").on("click", ".required-field-toggle", toggle_required); }