TheMillsMessages/scripts/send_dialog_form.mjs
trotFunky d2b14dbe94 Dialog: Provide hints for the sender name
The dialog form has a simple text input for the sender's name.
This can lead to typos or mistakes if the GM wants to send a message
using the same sender name as a previous message.

Add a new function that goes through all the players' histories and
compiles a list of all unique senders, which we can then use to build
a list of hints that will be used to show suggestions when the GM fills
the sender name in the form.

This is currently quite ineficcient as it re-does all the work for every
single dialog.
A better way would be to cache it or store it explicitly somewhere when
we send a message, but this would require a way for the GM to edit it.
2025-06-11 18:48:44 +01:00

147 lines
5.6 KiB
JavaScript

/*
* This files separates the form-specific validation, creation and types,
* so they can be used in the main module.
*/
import { get_existing_senders } from "./utils.mjs";
const sender_hints_id = "sender_hints"
/**
* @typedef {Object} MessageFormResponse
* @property {string[]} recipients The list of player IDs the message will be sent to
* @property {string} sender The name to display as the origin of the message, and in the history
* @property {string} message The body of the message, a string of HTML
*/
/**
* @typedef {Object} DialogFormResponse
* @property {boolean} valid Is the response valid ?
* @property {MessageFormResponse} form_response The response form the dialog form
*/
/**
* Generate the HTML for a <datalist> whose options are the provided senders.
* @param {String} hints_id The ID to use for the <datalist> providing the hints
* @param {String[]} senders The list of existing senders, or an empty string if invalid.
*/
function generate_sender_hints(hints_id, senders) {
// Both arguments need to be usable to generate something useful.
if (!hints_id || !senders || senders.length === 0) {
return "";
}
let sender_hints_html = `<datalist id="${hints_id}">`
for (let sender of senders) {
sender_hints_html += `<option value="${sender}">`;
}
return sender_hints_html + "</datalist>";
}
/**
* Given that "required" does not seem to have much impact, check that everything
* has been properly filled.
* To be able to reopen the dialog with the existing inputs, return the result of the check
* as well as the input data.
* @param {MessageFormResponse} form_response The response from the form, pre-processed
* @return {DialogFormResponse} Pass the response through and a validity flag
*/
export function validate_form(form_response) {
let valid = true;
if (form_response.message.length === 0 ||
form_response.recipients.length === 0 ||
form_response.sender.length === 0) {
ui.notifications.warn("MM.Errors.MissingFields", {localize:true});
valid = false;
}
return {valid, form_response};
}
/**
* Prepare the HTML used for the sending message dialog, filling previous values and handling if it is a chained message.
* @param {MessageFormResponse|null} [existing_values=null] Values from a previously filled form
* @param {boolean} [chain=false] If this is a chained message or not
* @returns {string} HTML for the send message Dialog content
*/
export function prepare_send_message_html(existing_values = null, chain = false) {
let players_options = []
for (let user of game.users.players) {
players_options.push({
label: user.character ? user.character.name : user.name,
value: user.id,
})
}
if (existing_values && existing_values.recipients.length > 0) {
players_options
.filter((option) => existing_values.recipients.includes(option.value))
.forEach((option) => {option.selected = true})
}
const recipient_input = foundry.applications.fields.createMultiSelectInput({
name: "recipients_IDs",
required: true,
blank: false,
options: players_options,
/*
* Send chained messages to the same recipients, disallow changing it in this case.
* This is an arbitrary decision that simplifies the process of sending the messages.
*/
disabled: chain,
})
const recipient_group = foundry.applications.fields.createFormGroup({
input: recipient_input,
label: "MM.Dialogs.SendDialog.RecipientsLabel",
hint: chain ? "MM.Dialogs.SendDialog.RecipientsChainHint" : "MM.Dialogs.SendDialog.RecipientsHint",
localize: true,
})
const sender_input = foundry.applications.fields.createTextInput({
name: "sender_name",
required: true,
placeholder: game.i18n.localize("MM.Dialogs.SendDialog.SenderPlaceholder"),
value: existing_values ? existing_values.sender : null,
})
const sender_group = foundry.applications.fields.createFormGroup({
input: sender_input,
label: "MM.Dialogs.SendDialog.SenderLabel",
hint: "MM.Dialogs.SendDialog.SenderHint",
localize: true
})
/*
* If we can find previous senders in player's histories, create a datalist
* with them so that the names can appear as autocompletion when filling the form.
*/
/*
* TODO: This is very silly : it re-computes everything for every form that is opened.
* Find a way to cache it or store the senders list it explicitly rather than deduce it
* from the history.
*/
let existing_senders = get_existing_senders();
if (existing_senders.length > 0) {
sender_input.setAttribute("list", sender_hints_id)
// This keeps the datalist withing the input group as proper HTML.
sender_group.insertAdjacentHTML("beforeend", generate_sender_hints(sender_hints_id, existing_senders))
}
const message_input = foundry.applications.elements.HTMLProseMirrorElement.create({
name: "message",
required: true,
value: existing_values && existing_values.message ? existing_values.message : "",
editable: true,
compact: true,
collaborate: false,
toggled: false,
height: 350,
})
const message_group = foundry.applications.fields.createFormGroup({
input: message_input,
label: "MM.Dialogs.SendDialog.MessageEditorLabel",
stacked: true,
localize: true,
})
return recipient_group.outerHTML + sender_group.outerHTML + message_group.outerHTML;
}