Compare commits
3 commits
Author | SHA1 | Date | |
---|---|---|---|
8ee09b4e3e | |||
d2b14dbe94 | |||
30c0324799 |
6 changed files with 121 additions and 50 deletions
|
@ -1,4 +1,4 @@
|
|||
# The Mill's Messages - v0.2.0
|
||||
# The Mill's Messages - v0.3.0
|
||||
|
||||
This little [FoundryVTT](https://foundryvtt.com/) module gives the GMs a way to send messages to PCs with a bit more flair than
|
||||
the chat, and more spontaneity than journals !
|
||||
|
@ -31,6 +31,8 @@ each page containing the messages from one specific recipient.
|
|||
Both the journals and pages will be created automatically.
|
||||
Players only have observer permissions on their own journal, which will take the name of their
|
||||
character, or their own if they don't control one yet.
|
||||
If the history is enabled, the message form will go look for previous senders' names and provide
|
||||
a list for autocompletion.
|
||||
|
||||
The GM can send a message to an offline player, which will add it to its history if it is enabled,
|
||||
but won't show up as a pop-up when they next log in.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "the-mills-messages",
|
||||
"title": "The Mill's Messages",
|
||||
"description": "A little message-sending modules for GMs",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"compatibility": {
|
||||
"minimum": "12",
|
||||
"verified": "12",
|
||||
|
@ -49,5 +49,5 @@
|
|||
"readme": "README.md",
|
||||
"url": "https://git.tfk-astrodome.net/trotFunky/TheMillsMessages/src/branch/release",
|
||||
"manifest": "https://git.tfk-astrodome.net/trotFunky/TheMillsMessages/raw/branch/release/module.json",
|
||||
"download": "https://git.tfk-astrodome.net/trotFunky/TheMillsMessages/archive/v0.2.0.zip"
|
||||
"download": "https://git.tfk-astrodome.net/trotFunky/TheMillsMessages/archive/v0.3.0.zip"
|
||||
}
|
||||
|
|
|
@ -1,58 +1,13 @@
|
|||
export const module_id = "the-mills-messages";
|
||||
|
||||
import { module_id, history_flag, str_format, create_history_journal } from "./utils.mjs"
|
||||
import { module_settings, register_settings } from "./settings.mjs";
|
||||
import { prepare_send_message_html, validate_form } from "./send_dialog_form.mjs";
|
||||
|
||||
// User flag to point to the ID of the journal to store message history to
|
||||
const history_flag = "history-journal";
|
||||
|
||||
/** @type {SocketlibSocket} */
|
||||
let socket;
|
||||
const SocketMessages = Object.freeze({
|
||||
DisplayMessage: "display-message",
|
||||
})
|
||||
|
||||
/**
|
||||
* This is a copy of Foundry's internal formatting used by
|
||||
* localize.format(), so that we can be consistent.
|
||||
* @param {String} str The string whose "{named_args}" will be formatted
|
||||
* @param {Object} data An object with named_args members, whose data is replaced
|
||||
* @returns {String} The formatted string
|
||||
*/
|
||||
function str_format(str, data) {
|
||||
const fmt = /{[^}]+}/g;
|
||||
str = str.replace(fmt, k => {
|
||||
return data[k.slice(1, -1)];
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the journal entry for the history of the specified user,
|
||||
* update the user's history flag and return the entry for further
|
||||
* manipulation if needed.
|
||||
* @param {User} user The user object that we are creating the history for
|
||||
* @return {Promise<JournalEntry>} The JournalEntry that was created.
|
||||
*/
|
||||
async function create_history_journal(user) {
|
||||
// Use the settings' title for the journal, or revert to the default one if it is empty.
|
||||
let format_name = {name: user.character ? user.character.name : user.name};
|
||||
let formated_journal_title = str_format(game.settings.get(module_id, module_settings.HistoryJournalTitle), format_name)
|
||||
let title = formated_journal_title.length > 0 ?
|
||||
formated_journal_title :
|
||||
game.i18n.format("MM.UI.HistoryJournalTitle", format_name)
|
||||
|
||||
return JournalEntry.create({
|
||||
name: title,
|
||||
ownership: {
|
||||
default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE,
|
||||
[user.id]: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER // Make the history private and unmodifiable
|
||||
},
|
||||
}).then(journal => {
|
||||
return user.setFlag(module_id, history_flag, journal.id).then(() => journal);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the dialog to send messages to players from NPCs.
|
||||
* The basic dialog is simple : display a few fields selecting which players
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
* 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
|
||||
|
@ -16,6 +20,23 @@
|
|||
* @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.
|
||||
|
@ -88,6 +109,22 @@ export function prepare_send_message_html(existing_values = null, chain = false)
|
|||
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,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {module_id} from "./mills_messages.mjs";
|
||||
import { module_id } from "./utils.mjs";
|
||||
|
||||
export const module_settings = Object.freeze({
|
||||
MessageDialogTitle: "messageDialogTitle",
|
||||
|
|
77
scripts/utils.mjs
Normal file
77
scripts/utils.mjs
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* This file contains some module-wide constants and utility functions
|
||||
* that do not really fit elsewhere.
|
||||
*/
|
||||
|
||||
import { module_settings } from "./settings.mjs";
|
||||
|
||||
export const module_id = "the-mills-messages";
|
||||
|
||||
// User flag to point to the ID of the journal to store message history to
|
||||
export const history_flag = "history-journal";
|
||||
|
||||
/**
|
||||
* This is a copy of Foundry's internal formatting used by
|
||||
* localize.format(), so that we can be consistent.
|
||||
* @param {String} str The string whose "{named_args}" will be formatted
|
||||
* @param {Object} data An object with named_args members, whose data is replaced
|
||||
* @returns {String} The formatted string
|
||||
*/
|
||||
export function str_format(str, data) {
|
||||
const fmt = /{[^}]+}/g;
|
||||
str = str.replace(fmt, k => {
|
||||
return data[k.slice(1, -1)];
|
||||
});
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the journal entry for the history of the specified user,
|
||||
* update the user's history flag and return the entry for further
|
||||
* manipulation if needed.
|
||||
* @param {User} user The user object that we are creating the history for
|
||||
* @return {Promise<JournalEntry>} The JournalEntry that was created.
|
||||
*/
|
||||
export async function create_history_journal(user) {
|
||||
// Use the settings' title for the journal, or revert to the default one if it is empty.
|
||||
let format_name = {name: user.character ? user.character.name : user.name};
|
||||
let formated_journal_title = str_format(game.settings.get(module_id, module_settings.HistoryJournalTitle), format_name)
|
||||
let title = formated_journal_title.length > 0 ?
|
||||
formated_journal_title :
|
||||
game.i18n.format("MM.UI.HistoryJournalTitle", format_name)
|
||||
|
||||
return JournalEntry.create({
|
||||
name: title,
|
||||
ownership: {
|
||||
default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE,
|
||||
[user.id]: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER // Make the history private and unmodifiable
|
||||
},
|
||||
}).then(journal => {
|
||||
return user.setFlag(module_id, history_flag, journal.id).then(() => journal);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through all the user's histories to build a list of existing senders,
|
||||
* so that they can be displayed or re-used.
|
||||
* The final list is sorted and does not contain duplicates, as one sender
|
||||
* can send messages to multiple players at once.
|
||||
* @returns {String[]} A sorted array of the sender names, if any
|
||||
*/
|
||||
export function get_existing_senders() {
|
||||
/** @type {String[]} */
|
||||
let senders = []
|
||||
for (let user of game.users.players) {
|
||||
/** @type {JournalEntry} */
|
||||
let history = game.journal.get(user.getFlag(module_id, history_flag))
|
||||
if (!history) {
|
||||
continue;
|
||||
}
|
||||
for (let page of history.pages) {
|
||||
if (!senders.includes(page.name)) {
|
||||
senders.push(page.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return senders.sort();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue