From 6eda0138ca7ac9fda52d8ccdce1339925902ded4 Mon Sep 17 00:00:00 2001 From: Brooke <46959407+nimajnebec@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:39:47 +0000 Subject: [PATCH 1/4] feat: add modal component --- .../src/components/modals/modals.ts | 42 ++++++++++++++ .../src/components/modals/options.ts | 40 +++++++++++++ .../src/components/modals/plugin.ts | 57 +++++++++++++++++++ packages/jellycommands/src/plugins/core.ts | 2 + 4 files changed, 141 insertions(+) create mode 100644 packages/jellycommands/src/components/modals/modals.ts create mode 100644 packages/jellycommands/src/components/modals/options.ts create mode 100644 packages/jellycommands/src/components/modals/plugin.ts diff --git a/packages/jellycommands/src/components/modals/modals.ts b/packages/jellycommands/src/components/modals/modals.ts new file mode 100644 index 0000000..9492b5d --- /dev/null +++ b/packages/jellycommands/src/components/modals/modals.ts @@ -0,0 +1,42 @@ +import { type ModalOptions, modalSchema } from './options'; +import type { JellyCommands } from '../../JellyCommands'; +import type { ModalSubmitInteraction } from 'discord.js'; +import { Component, isComponent } from '../components'; +import type { MaybePromise } from '../../utils/types'; +import { MODALS_COMPONENT_ID } from './plugin'; +import { parseSchema } from '../../utils/zod'; + +export type ModalCallback = (context: { + client: JellyCommands; + props: Props; + interaction: ModalSubmitInteraction; +}) => MaybePromise; + +/** + * Represents a modal. + * @see https://jellycommands.dev/components/modals + */ +export class Modal extends Component { + public readonly options: ModalOptions; + + constructor( + _options: ModalOptions, + public readonly run: ModalCallback, + ) { + super(MODALS_COMPONENT_ID, 'Modal'); + this.options = parseSchema('modal', modalSchema, _options); + } + + static is(item: any): item is Modal { + return isComponent(item) && item.id === MODALS_COMPONENT_ID; + } +} + +/** + * Creates a modal. + * @see https://jellycommands.dev/components/modals + */ +export const modal = (options: ModalOptions & { run: ModalCallback }) => { + const { run, ...rest } = options; + return new Modal(rest, run); +}; diff --git a/packages/jellycommands/src/components/modals/options.ts b/packages/jellycommands/src/components/modals/options.ts new file mode 100644 index 0000000..ff09405 --- /dev/null +++ b/packages/jellycommands/src/components/modals/options.ts @@ -0,0 +1,40 @@ +import type { InteractionDeferReplyOptions } from 'discord.js'; +import type { BaseComponentOptions } from '../components'; +import type { MaybePromise } from '../../utils/types'; +import { z } from 'zod'; + +export interface ModalOptions extends BaseComponentOptions { + /** + * The customId of the modal, or a regex/function to match against + */ + id: string | RegExp | ((id: string) => MaybePromise); + + /** + * Should the interaction be defered? + */ + defer?: boolean | InteractionDeferReplyOptions; +} + +export const modalSchema = z.object({ + id: z.union([ + z.string(), + z.instanceof(RegExp), + // todo test this + z + .function() + .args(z.string().optional()) + .returns(z.union([z.boolean(), z.promise(z.boolean())])), + ]), + + defer: z + .union([ + z.boolean().default(false), + z.object({ + ephemeral: z.boolean().optional(), + fetchReply: z.boolean().optional(), + }), + ]) + .optional(), + + disabled: z.boolean().default(false).optional(), +}); diff --git a/packages/jellycommands/src/components/modals/plugin.ts b/packages/jellycommands/src/components/modals/plugin.ts new file mode 100644 index 0000000..f36451e --- /dev/null +++ b/packages/jellycommands/src/components/modals/plugin.ts @@ -0,0 +1,57 @@ +import { defineComponentPlugin } from '../../plugins/plugins'; +import type { Modal } from './modals'; + +export const MODALS_COMPONENT_ID = 'jellycommands.modal'; + +// TODO test this function +async function findModal( + incomingId: string, + modals: Set, +): Promise { + for (const modal of modals) { + const { id } = modal.options; + + switch (typeof id) { + case 'string': + if (id === incomingId) return modal; + break; + + case 'function': + // todo should this be sync only? might cause issues when not deffered + if (await id(incomingId)) return modal; + break; + + case 'object': + if (id.test(incomingId)) return modal; + break; + } + } + + return null; +} + +export const modalsPlugin = defineComponentPlugin(MODALS_COMPONENT_ID, { + register(client, modals) { + client.on('interactionCreate', async (interaction) => { + if (interaction.isModalSubmit()) { + const modal = await findModal(interaction.customId, modals); + + if (modal) { + if (modal.options.defer) { + await interaction.deferReply( + typeof modal.options.defer === 'object' + ? modal.options.defer + : {}, + ); + } + + await modal.run({ + client, + props: client.props, + interaction, + }); + } + } + }); + }, +}); diff --git a/packages/jellycommands/src/plugins/core.ts b/packages/jellycommands/src/plugins/core.ts index 29334f3..791b72b 100644 --- a/packages/jellycommands/src/plugins/core.ts +++ b/packages/jellycommands/src/plugins/core.ts @@ -1,10 +1,12 @@ import { commandsPlugin } from '../components/commands/plugin'; import { buttonsPlugin } from '../components/buttons/plugin'; import { eventsPlugin } from '../components/events/plugin'; +import { modalsPlugin } from '../components/modals/plugin'; import type { AnyPlugin } from './plugins'; export const CORE_PLUGINS: AnyPlugin[] = [ buttonsPlugin, commandsPlugin, eventsPlugin, + modalsPlugin, ]; From ed37fb6663b612b98cb5d20ab2bac545fc0ea314 Mon Sep 17 00:00:00 2001 From: Brooke <46959407+nimajnebec@users.noreply.github.com> Date: Mon, 19 May 2025 12:47:43 +0100 Subject: [PATCH 2/4] feat: strongly typed modal fields --- .../src/components/modals/modals.ts | 31 +++++++++++++------ .../src/components/modals/options.ts | 16 ++++++++-- packages/jellycommands/src/index.ts | 4 +++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/packages/jellycommands/src/components/modals/modals.ts b/packages/jellycommands/src/components/modals/modals.ts index 9492b5d..a57815b 100644 --- a/packages/jellycommands/src/components/modals/modals.ts +++ b/packages/jellycommands/src/components/modals/modals.ts @@ -1,4 +1,4 @@ -import { type ModalOptions, modalSchema } from './options'; +import { type ModalOptions, modalSchema, type ModalField } from './options'; import type { JellyCommands } from '../../JellyCommands'; import type { ModalSubmitInteraction } from 'discord.js'; import { Component, isComponent } from '../components'; @@ -6,28 +6,39 @@ import type { MaybePromise } from '../../utils/types'; import { MODALS_COMPONENT_ID } from './plugin'; import { parseSchema } from '../../utils/zod'; -export type ModalCallback = (context: { +type InputComponentMapper = { + [K in T as K['customId']]: K['required'] extends false + ? string | null + : string; +}; + +export type ModalCallback = (context: { client: JellyCommands; props: Props; interaction: ModalSubmitInteraction; + fields: InputComponentMapper; }) => MaybePromise; /** * Represents a modal. * @see https://jellycommands.dev/components/modals */ -export class Modal extends Component { - public readonly options: ModalOptions; +export class Modal extends Component> { + public readonly options: ModalOptions; constructor( - _options: ModalOptions, - public readonly run: ModalCallback, + _options: ModalOptions, + public readonly run: ModalCallback, ) { super(MODALS_COMPONENT_ID, 'Modal'); - this.options = parseSchema('modal', modalSchema, _options); + this.options = parseSchema( + 'modal', + modalSchema, + _options, + ) as ModalOptions; } - static is(item: any): item is Modal { + static is(item: any): item is Modal { return isComponent(item) && item.id === MODALS_COMPONENT_ID; } } @@ -36,7 +47,9 @@ export class Modal extends Component { * Creates a modal. * @see https://jellycommands.dev/components/modals */ -export const modal = (options: ModalOptions & { run: ModalCallback }) => { +export const modal = ( + options: ModalOptions & { run: ModalCallback }, +) => { const { run, ...rest } = options; return new Modal(rest, run); }; diff --git a/packages/jellycommands/src/components/modals/options.ts b/packages/jellycommands/src/components/modals/options.ts index ff09405..004d673 100644 --- a/packages/jellycommands/src/components/modals/options.ts +++ b/packages/jellycommands/src/components/modals/options.ts @@ -1,9 +1,15 @@ -import type { InteractionDeferReplyOptions } from 'discord.js'; +import { + type InteractionDeferReplyOptions, + type TextInputComponentData, +} from 'discord.js'; import type { BaseComponentOptions } from '../components'; import type { MaybePromise } from '../../utils/types'; import { z } from 'zod'; -export interface ModalOptions extends BaseComponentOptions { +export type ModalField = Omit; + +export interface ModalOptions + extends BaseComponentOptions { /** * The customId of the modal, or a regex/function to match against */ @@ -13,6 +19,11 @@ export interface ModalOptions extends BaseComponentOptions { * Should the interaction be defered? */ defer?: boolean | InteractionDeferReplyOptions; + + /** + * The text input fields of the modal + */ + fields: (T & ModalField)[]; } export const modalSchema = z.object({ @@ -37,4 +48,5 @@ export const modalSchema = z.object({ .optional(), disabled: z.boolean().default(false).optional(), + fields: z.any().array().min(1).max(4), }); diff --git a/packages/jellycommands/src/index.ts b/packages/jellycommands/src/index.ts index be93eab..b249084 100644 --- a/packages/jellycommands/src/index.ts +++ b/packages/jellycommands/src/index.ts @@ -25,3 +25,7 @@ export type { EventOptions } from './components/events/options'; // Export button related export { button, Button } from './components/buttons/buttons'; export type { ButtonOptions } from './components/buttons/options'; + +// Export modal related +export { modal, Modal } from './components/modals/modals'; +export type { ModalOptions } from './components/modals/options'; From ee4ac379ef8276cc92482698ac957c88fbd8772b Mon Sep 17 00:00:00 2001 From: Brooke <46959407+nimajnebec@users.noreply.github.com> Date: Mon, 19 May 2025 14:18:48 +0100 Subject: [PATCH 3/4] docs: modal documentation --- packages/docs/astro.config.mjs | 13 ++ .../docs/src/assets/docs/modal-failed.png | Bin 0 -> 32036 bytes .../docs/src/assets/docs/working-modal.png | Bin 0 -> 18484 bytes .../content/docs/components/buttons/index.mdx | 71 +---------- .../content/docs/components/custom-ids.mdx | 73 +++++++++++ .../content/docs/components/modals/index.mdx | 114 ++++++++++++++++++ packages/jellycommands/src/index.ts | 4 + .../src/commands/file-loaded/testModal.js | 36 ++++++ packages/playground/src/index.js | 1 + packages/playground/src/modals/test.js | 11 ++ 10 files changed, 253 insertions(+), 70 deletions(-) create mode 100644 packages/docs/src/assets/docs/modal-failed.png create mode 100644 packages/docs/src/assets/docs/working-modal.png create mode 100644 packages/docs/src/content/docs/components/custom-ids.mdx create mode 100644 packages/docs/src/content/docs/components/modals/index.mdx create mode 100644 packages/playground/src/commands/file-loaded/testModal.js create mode 100644 packages/playground/src/modals/test.js diff --git a/packages/docs/astro.config.mjs b/packages/docs/astro.config.mjs index 6ad5f95..e07f1a5 100644 --- a/packages/docs/astro.config.mjs +++ b/packages/docs/astro.config.mjs @@ -88,10 +88,23 @@ export default defineConfig({ }, ], }, + { + label: 'Modals', + items: [ + { + label: 'Creating Modals', + link: '/components/modals', + }, + ], + }, { label: 'Props', link: '/components/props', }, + { + label: 'Custom Ids', + link: '/components/custom-ids', + }, { label: 'Deferring Interactions', link: '/components/deferring', diff --git a/packages/docs/src/assets/docs/modal-failed.png b/packages/docs/src/assets/docs/modal-failed.png new file mode 100644 index 0000000000000000000000000000000000000000..d4f980892066c4fb009e6041bc007bbe4bb7da34 GIT binary patch literal 32036 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 literal 0 HcmV?d00001 diff --git a/packages/docs/src/assets/docs/working-modal.png b/packages/docs/src/assets/docs/working-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..3c3d87f3fffbaa2052c0188c27ba5349491c6da6 GIT binary patch literal 18484 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 X$f=aWj!Hq(&%7OYf3NE8q0IjPX?fk_ literal 0 HcmV?d00001 diff --git a/packages/docs/src/content/docs/components/buttons/index.mdx b/packages/docs/src/content/docs/components/buttons/index.mdx index b4fff9d..795d319 100644 --- a/packages/docs/src/content/docs/components/buttons/index.mdx +++ b/packages/docs/src/content/docs/components/buttons/index.mdx @@ -99,73 +99,4 @@ Now when we click the button we see that it sends our "Hello World" response! ![the button works]($assets/docs/working-button.png) -## Custom Id - -Each button needs to be given a "custom id" when you create it, so when you handle a button press you can use the correct handler. The simplest example, as we saw above, is just to use a static custom id. - -```js {4} -import { button } from 'jellycommands'; - -export default button({ - id: 'test', - - async run({ interaction }) {}, -}); -``` - -Unlike commands, we don't need to tell Discord about the buttons before we use them. They are effectively created every time you reply to an interaction with them. This means that our custom ids can be dynamic! This can simplify some interactions since we can store some information on the id. In order to make this possible the `id` option on a button component can also be regex or a function. - -### Regex - -This regex will be used to see if we have found a match for the button interaction. It should always start with `^` and `$` to ensure it's matching the whole id rather than just a section of it, and not be global. - -```js -import { button } from 'jellycommands'; - -export default button({ - id: /^page_\d+$/, - - async run({ interaction }) { - console.log(interaction.customId); - }, -}); -``` - -### Matcher Function - -This function is passed the custom id, and then should return a boolean that indicates whether a match has been found. - -```js -import { button } from 'jellycommands'; - -export default button({ - id: (customId) => { - return customId.startsWith('page_'); - }, - - async run({ interaction }) { - console.log(interaction.customId); - }, -}); -``` - -### Deferring - -By default Discord requires you to respond to a button within 3 seconds, otherwise it marks the interaction as failed. Often it'll take longer than 3 seconds to respond, so you need to "defer" your reply. If you defer your command you need to use `followUp` instead of reply: - -```js {6,10} ins="followUp" del="reply" -import { button } from 'jellycommands'; - -export default button({ - id: 'test', - - defer: true, - - async run({ interaction }) { - await interaction.reply('Hello World'); - await interaction.followUp('Hello World'); - }, -}); -``` - -[Read more on deferring](/components/deferring). +The "custom id" system is very powerful, allowing for dynamic matching to store arbitrary data. [Learn more about custom ids](/components/custom-ids). diff --git a/packages/docs/src/content/docs/components/custom-ids.mdx b/packages/docs/src/content/docs/components/custom-ids.mdx new file mode 100644 index 0000000..013f020 --- /dev/null +++ b/packages/docs/src/content/docs/components/custom-ids.mdx @@ -0,0 +1,73 @@ +--- +title: Custom Ids +description: Learn how to leverage custom ids with modals and buttons. +--- + +Buttons and modals need to be given a "custom id" when you create them, so that the correct handler is used. The simplest way to do this is just to use a static custom id. + +```js {4} +import { button } from 'jellycommands'; + +export default button({ + id: 'test', + + async run({ interaction }) {}, +}); +``` + +Unlike commands, we don't need to tell Discord about buttons and modals before we use them. They are effectively created every time you reply to an interaction with them. This means that our custom ids can be dynamic! This can simplify some interactions since we can store some information on the id. In order to make this possible the `id` option on a button component can also be regex or a function. + +### Regex + +This regex will be used to see if we have found a match for the button interaction. It should always start with `^` and `$` to ensure it's matching the whole id rather than just a section of it, and not be global. + +```js +import { button } from 'jellycommands'; + +export default button({ + id: /^page_\d+$/, + + async run({ interaction }) { + console.log(interaction.customId); + }, +}); +``` + +### Matcher Function + +This function is passed the custom id, and then should return a boolean that indicates whether a match has been found. + +```js +import { modal } from 'jellycommands'; + +export default modal({ + id: (customId) => { + return customId.startsWith('page_'); + }, + + async run({ interaction }) { + console.log(interaction.customId); + }, +}); +``` + +### Deferring + +By default Discord requires you to respond to an interaction within 3 seconds, otherwise it marks the interaction as failed. Often it'll take longer than 3 seconds to respond, so you need to "defer" your reply. If you defer your command you need to use `followUp` instead of reply: + +```js {6,10} ins="followUp" del="reply" +import { button } from 'jellycommands'; + +export default button({ + id: 'test', + + defer: true, + + async run({ interaction }) { + await interaction.reply('Hello World'); + await interaction.followUp('Hello World'); + }, +}); +``` + +[Read more on deferring](/components/deferring). diff --git a/packages/docs/src/content/docs/components/modals/index.mdx b/packages/docs/src/content/docs/components/modals/index.mdx new file mode 100644 index 0000000..10ea2e0 --- /dev/null +++ b/packages/docs/src/content/docs/components/modals/index.mdx @@ -0,0 +1,114 @@ +--- +title: Modals +description: Learn about how to create modals with JellyCommands. +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +A modal component can be used to respond to modal submissions, from modal you create. You'll need to create and send these modal yourself, so let's get started by creating a simple slash command to do so: + + + + ```ts {2-7,14-18,20-21,23-27,29-30} + import { command } from 'jellycommands'; + import { + TextInputBuilder, + TextInputStyle, + ModalBuilder, + ActionRowBuilder, + } from 'discord.js'; + + export default command({ + name: 'test-modal', + description: 'Shows a modal with a text input', + + async run({ interaction }) { + // Create a text input component with the builder + const nameInput = new TextInputBuilder() + .setCustomId('nameInput') + .setLabel('Whats your name?') + .setStyle(TextInputStyle.Short); + + // All components need to be in a "row" + const row = new ActionRowBuilder()).addComponents(nameInput); + + // Create the actual modal + const modal = new ModalBuilder() + .setCustomId('test') + .setTitle('Whats Your Name?') + .addComponents(row); + + // Send the modal + interaction.showModal(modal); + }, + }); + ``` + + + + + ```js {2-7,14-18,20-23,25-29,31-32} + import { command } from 'jellycommands'; + import { + TextInputBuilder, + TextInputStyle, + ModalBuilder, + ActionRowBuilder, + } from 'discord.js'; + + export default command({ + name: 'test-modal', + description: 'Shows a modal with a text input', + + async run({ interaction }) { + // Create a text input component with the builder + const nameInput = new TextInputBuilder() + .setCustomId('nameInput') + .setLabel('Whats your name?') + .setStyle(TextInputStyle.Short); + + // All components need to be in a "row" + const row = /** @type {ActionRowBuilder} */ ( + new ActionRowBuilder() + ).addComponents(nameInput); + + // Create the actual modal + const modal = new ModalBuilder() + .setCustomId('test') + .setTitle('Whats Your Name?') + .addComponents(row); + + // Send the modal + interaction.showModal(modal); + }, + }); + ``` + + + + +On running this command, your modal is opened! However, you'll notice that when you submit the modal it fails: + +!["Something went wrong" message on the modal created earlier]($assets/docs/modal-failed.png) + +This is where the JellyCommands comes in, it allows us to create a modal component that can respond to modal submissions. All we need is the modal's "custom id", which you might have noticed us setting in the above example. From here we can read the value of the text input using [`interaction.fields.getTextInputValue`](https://discordjs.guide/interactions/modals.html#extracting-data-from-modal-submissions). + +```ts +import { modal } from 'jellycommands'; + +export default modal({ + id: 'test', + + async run({ interaction }) { + interaction.reply({ + content: `Hello, ${interaction.fields.getTextInputValue('nameInput')}`, + }); + }, +}); +``` + +Now when we submit the modal we see that it sends our response! + +![the modal works]($assets/docs/working-modal.png) + +The "custom id" system is very powerful, allowing for dynamic matching to store arbitrary data. [Learn more about custom ids](/components/custom-ids). diff --git a/packages/jellycommands/src/index.ts b/packages/jellycommands/src/index.ts index be93eab..b249084 100644 --- a/packages/jellycommands/src/index.ts +++ b/packages/jellycommands/src/index.ts @@ -25,3 +25,7 @@ export type { EventOptions } from './components/events/options'; // Export button related export { button, Button } from './components/buttons/buttons'; export type { ButtonOptions } from './components/buttons/options'; + +// Export modal related +export { modal, Modal } from './components/modals/modals'; +export type { ModalOptions } from './components/modals/options'; diff --git a/packages/playground/src/commands/file-loaded/testModal.js b/packages/playground/src/commands/file-loaded/testModal.js new file mode 100644 index 0000000..48a222d --- /dev/null +++ b/packages/playground/src/commands/file-loaded/testModal.js @@ -0,0 +1,36 @@ +import { + TextInputBuilder, + TextInputStyle, + ModalBuilder, + ActionRowBuilder, +} from 'discord.js'; +import { command } from 'jellycommands'; + +export default command({ + name: 'test-modal', + description: 'Shows a modal with a text input', + + global: true, + + async run({ interaction }) { + // Create a text input component with the builder + const nameInput = new TextInputBuilder() + .setCustomId('nameInput') + .setLabel('Whats your name?') + .setStyle(TextInputStyle.Short); + + // All components need to be in a "row" + const row = /** @type {ActionRowBuilder} */ ( + new ActionRowBuilder() + ).addComponents(nameInput); + + // Create the actual modal + const modal = new ModalBuilder() + .setCustomId('test') + .setTitle('Whats Your Name?') + .addComponents(row); + + // Send the modal + interaction.showModal(modal); + }, +}); diff --git a/packages/playground/src/index.js b/packages/playground/src/index.js index c0a09eb..d331866 100644 --- a/packages/playground/src/index.js +++ b/packages/playground/src/index.js @@ -19,6 +19,7 @@ const client = new JellyCommands({ components: [ pog, ready, + 'src/modals', 'src/commands/file-loaded', 'src/events/file-loaded', 'src/buttons', diff --git a/packages/playground/src/modals/test.js b/packages/playground/src/modals/test.js new file mode 100644 index 0000000..3a26d9b --- /dev/null +++ b/packages/playground/src/modals/test.js @@ -0,0 +1,11 @@ +import { modal } from 'jellycommands'; + +export default modal({ + id: 'test', + + async run({ interaction }) { + interaction.reply({ + content: `Hello, ${interaction.fields.getTextInputValue('nameInput')}`, + }); + }, +}); From 7d61e055200fe65a862c132db2bb943f40f3739b Mon Sep 17 00:00:00 2001 From: Brooke <46959407+nimajnebec@users.noreply.github.com> Date: Mon, 19 May 2025 14:31:21 +0100 Subject: [PATCH 4/4] chore: add changeset --- .changeset/four-buses-knock.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/four-buses-knock.md diff --git a/.changeset/four-buses-knock.md b/.changeset/four-buses-knock.md new file mode 100644 index 0000000..5ed4c14 --- /dev/null +++ b/.changeset/four-buses-knock.md @@ -0,0 +1,5 @@ +--- +"jellycommands": minor +--- + +feat: add modal component