Add support for toggleable /buttons Enhances the buttons slash command with toggleable multi-select capability Introduces a 'multiple' parameter to enable selecting several options Updates button styling with visual indicators for toggle states Modifies return value to handle array of selections when multiple is enabled

8eee1d09a87b98c8484f2fece32c9551dbbc6dd2

Wolfsblvt <wolfsblvt@gmail.com>

2 files changed, +94 -15Ignore whitespace
public/scripts/slash-commands.js+64 -14
@@ -77,6 +77,7 @@ import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHel
7777import { accountStorage } from './util/AccountStorage.js';
7878import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
7979import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
80+import { t } from './i18n.js';
8081export {
8182 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
8283};
@@ -1556,16 +1557,28 @@ export function initDefaultSlashCommands() {
15561557 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
15571558 name: 'buttons',
15581559 callback: buttonsCallback,
15591560 returns: 'clicked button label (or array of labels if multiple is enabled)',
15601561 namedArgumentList: [
15611562 new SlashCommandNamedArgument.fromProps({
1562- 'labels', 'button labels', [ARGUMENT_TYPE.LIST], true,
1563+ name: 'labels',
1563- ),
1564+ description: 'button labels',
1565+ typeList: [ARGUMENT_TYPE.LIST],
1566+ isRequired: true,
1567+ }),
1568+ SlashCommandNamedArgument.fromProps({
1569+ name: 'multiple',
1570+ description: 'if enabled multiple buttons can be clicked/toggled, and all clicked buttons are returned as an array',
1571+ typeList: [ARGUMENT_TYPE.BOOLEAN],
1572+ enumList: commonEnumProviders.boolean('trueFalse')(),
1573+ defaultValue: 'false',
1574+ }),
15641575 ],
15651576 unnamedArgumentList: [
15661577 new SlashCommandArgument.fromProps({
1567- 'text', [ARGUMENT_TYPE.STRING], true,
1578+ description: 'text',
1568- ),
1579+ typeList: [ARGUMENT_TYPE.STRING],
1580+ isRequired: true,
1581+ }),
15691582 ],
15701583 helpString: `
15711584 <div>
@@ -2377,6 +2390,18 @@ async function trimTokensCallback(arg, value) {
23772390 }
23782391}
23792392
2393+/**
2394+ * Displays a popup with buttons based on provided labels and handles button interactions.
2395+ *
2396+ * @param {object} args - Named arguments for the command
2397+ * @param {string} args.labels - JSON string of an array of button labels
2398+ * @param {string} [args.multiple=false] - Flag indicating if multiple buttons can be toggled
2399+ * @param {string} text - The text content to be displayed within the popup
2400+ *
2401+ * @returns {Promise<string>} - A promise that resolves to a string of the button labels selected
2402+ * If 'multiple' is true, returns a JSON string array of labels.
2403+ * If 'multiple' is false, returns a single label string.
2404+ */
23802405async function buttonsCallback(args, text) {
23812406 try {
23822407 /** @type {string[]} */
@@ -2387,6 +2412,10 @@ async function buttonsCallback(args, text) {
23872412 return '';
23882413 }
23892414
2415+ /** @type {Set<number>} */
2416+ const multipleToggledState = new Set();
2417+ const multiple = isTrueBoolean(args?.multiple);
2418+
23902419 // Map custom buttons to results. Start at 2 because 1 and 0 are reserved for ok and cancel
23912420 const resultToButtonMap = new Map(buttons.map((button, index) => [index + 2, button]));
23922421
@@ -2404,11 +2433,24 @@ async function buttonsCallback(args, text) {
24042433
24052434 for (const [result, button] of resultToButtonMap) {
24062435 const buttonElement = document.createElement('div');
24072436 buttonElement.classList.add('menu_button', 'result-control', 'wide100p');
2408- buttonElement.dataset.result = String(result);
2437+
2409- buttonElement.addEventListener('click', async () => {
2438+ if (multiple) {
2410- await popup.complete(result);
2439+ buttonElement.classList.add('toggleable');
2411- });
2440+ buttonElement.dataset.toggleValue = String(result);
2441+ buttonElement.addEventListener('click', async () => {
2442+ buttonElement.classList.toggle('toggled');
2443+ if (buttonElement.classList.contains('toggled')) {
2444+ multipleToggledState.add(result);
2445+ } else {
2446+ multipleToggledState.delete(result);
2447+ }
2448+ });
2449+ } else {
2450+ buttonElement.classList.add('result-control');
2451+ buttonElement.dataset.result = String(result);
2452+ }
2453+
24122454 buttonElement.innerText = button;
24132455 buttonContainer.appendChild(buttonElement);
24142456 }
@@ -2424,10 +2466,18 @@ async function buttonsCallback(args, text) {
24242466 popupContainer.style.flexDirection = 'column';
24252467 popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup
24262468
24272469 popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: 'multiple ? t`Ok` : t`Cancel'`, allowVerticalScrolling: true });
24282470 popup.show()
24292471 .then((result => resolve(typeof result === 'number' ? resultToButtonMap.getgetResult(result) ?? '' : '')))
24302472 .catch(() => resolve(''));
2473+
2474+ /** @returns {string} @param {string|number|boolean} result */
2475+ function getResult(result) {
2476+ if (multiple) {
2477+ return JSON.stringify(Array.from(multipleToggledState).map(r => resultToButtonMap.get(r) ?? ''));
2478+ }
2479+ return typeof result === 'number' ? resultToButtonMap.get(result) ?? '' : '';
2480+ }
24312481 });
24322482 } catch {
24332483 return '';
public/style.css+30 -1
@@ -2911,6 +2911,35 @@ select option:not(:checked) {
29112911 pointer-events: none;
29122912}
29132913
2914+.menu_button.toggleable {
2915+ padding-left: 20px;
2916+}
2917+
2918+.menu_button.toggleable.toggled {
2919+ border-color: var(--active);
2920+}
2921+
2922+.menu_button.toggleable:not(.toggled) {
2923+ filter: brightness(80%);
2924+}
2925+
2926+.menu_button.toggleable::before {
2927+ font-family: "Font Awesome 6 Free";
2928+ margin-left: 10px;
2929+ position: absolute;
2930+ left: 0;
2931+}
2932+
2933+.menu_button.toggleable.toggled::before {
2934+ content: "\f00c";
2935+ color: var(--active);
2936+}
2937+
2938+.menu_button.toggleable:not(.toggled)::before {
2939+ content: "\f00d";
2940+ color: var(--fullred);
2941+}
2942+
29142943.fav_on {
29152944 color: var(--golden) !important;
29162945}
@@ -2921,7 +2950,7 @@ select option:not(:checked) {
29212950}
29222951
29232952.menu_button.togglable:not(.toggleEnabled) {
29242953 color: redvar(--fullred);
29252954}
29262955
29272956.displayBlock {