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
| @@ -77,6 +77,7 @@ import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHel | |||
| 77 | import { accountStorage } from './util/AccountStorage.js'; | 77 | import { accountStorage } from './util/AccountStorage.js'; |
| 78 | import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js'; | 78 | import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js'; |
| 79 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; | 79 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 80 | import { t } from './i18n.js'; | ||
| 80 | export { | 81 | export { |
| 81 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, | 82 | executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, |
| 82 | }; | 83 | }; |
| @@ -1556,16 +1557,28 @@ export function initDefaultSlashCommands() { | |||
| 1556 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1557 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1557 | name: 'buttons', | 1558 | name: 'buttons', |
| 1558 | callback: buttonsCallback, | 1559 | callback: buttonsCallback, |
| 1559 | returns: 'clicked button label', | 1560 | returns: 'clicked button label (or array of labels if multiple is enabled)', |
| 1560 | namedArgumentList: [ | 1561 | namedArgumentList: [ |
| 1561 | new SlashCommandNamedArgument( | 1562 | 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 | }), | ||
| 1564 | ], | 1575 | ], |
| 1565 | unnamedArgumentList: [ | 1576 | unnamedArgumentList: [ |
| 1566 | new SlashCommandArgument( | 1577 | SlashCommandArgument.fromProps({ |
| 1567 | 'text', [ARGUMENT_TYPE.STRING], true, | 1578 | description: 'text', |
| 1568 | ), | 1579 | typeList: [ARGUMENT_TYPE.STRING], |
| 1580 | isRequired: true, | ||
| 1581 | }), | ||
| 1569 | ], | 1582 | ], |
| 1570 | helpString: ` | 1583 | helpString: ` |
| 1571 | <div> | 1584 | <div> |
| @@ -2377,6 +2390,18 @@ async function trimTokensCallback(arg, value) { | |||
| 2377 | } | 2390 | } |
| 2378 | } | 2391 | } |
| 2379 | 2392 | ||
| 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 | */ | ||
| 2380 | async function buttonsCallback(args, text) { | 2405 | async function buttonsCallback(args, text) { |
| 2381 | try { | 2406 | try { |
| 2382 | /** @type {string[]} */ | 2407 | /** @type {string[]} */ |
| @@ -2387,6 +2412,10 @@ async function buttonsCallback(args, text) { | |||
| 2387 | return ''; | 2412 | return ''; |
| 2388 | } | 2413 | } |
| 2389 | 2414 | ||
| 2415 | /** @type {Set<number>} */ | ||
| 2416 | const multipleToggledState = new Set(); | ||
| 2417 | const multiple = isTrueBoolean(args?.multiple); | ||
| 2418 | |||
| 2390 | // Map custom buttons to results. Start at 2 because 1 and 0 are reserved for ok and cancel | 2419 | // Map custom buttons to results. Start at 2 because 1 and 0 are reserved for ok and cancel |
| 2391 | const resultToButtonMap = new Map(buttons.map((button, index) => [index + 2, button])); | 2420 | const resultToButtonMap = new Map(buttons.map((button, index) => [index + 2, button])); |
| 2392 | 2421 | ||
| @@ -2404,11 +2433,24 @@ async function buttonsCallback(args, text) { | |||
| 2404 | 2433 | ||
| 2405 | for (const [result, button] of resultToButtonMap) { | 2434 | for (const [result, button] of resultToButtonMap) { |
| 2406 | const buttonElement = document.createElement('div'); | 2435 | const buttonElement = document.createElement('div'); |
| 2407 | buttonElement.classList.add('menu_button', 'result-control', 'wide100p'); | 2436 | buttonElement.classList.add('menu_button', 'wide100p'); |
| 2408 | buttonElement.dataset.result = String(result); | 2437 | |
| 2438 | if (multiple) { | ||
| 2439 | buttonElement.classList.add('toggleable'); | ||
| 2440 | buttonElement.dataset.toggleValue = String(result); | ||
| 2409 | buttonElement.addEventListener('click', async () => { | 2441 | buttonElement.addEventListener('click', async () => { |
| 2410 | await popup.complete(result); | 2442 | buttonElement.classList.toggle('toggled'); |
| 2443 | if (buttonElement.classList.contains('toggled')) { | ||
| 2444 | multipleToggledState.add(result); | ||
| 2445 | } else { | ||
| 2446 | multipleToggledState.delete(result); | ||
| 2447 | } | ||
| 2411 | }); | 2448 | }); |
| 2449 | } else { | ||
| 2450 | buttonElement.classList.add('result-control'); | ||
| 2451 | buttonElement.dataset.result = String(result); | ||
| 2452 | } | ||
| 2453 | |||
| 2412 | buttonElement.innerText = button; | 2454 | buttonElement.innerText = button; |
| 2413 | buttonContainer.appendChild(buttonElement); | 2455 | buttonContainer.appendChild(buttonElement); |
| 2414 | } | 2456 | } |
| @@ -2424,10 +2466,18 @@ async function buttonsCallback(args, text) { | |||
| 2424 | popupContainer.style.flexDirection = 'column'; | 2466 | popupContainer.style.flexDirection = 'column'; |
| 2425 | popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup | 2467 | popupContainer.style.maxHeight = '80vh'; // Limit the overall height of the popup |
| 2426 | 2468 | ||
| 2427 | popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: 'Cancel', allowVerticalScrolling: true }); | 2469 | popup = new Popup(popupContainer, POPUP_TYPE.TEXT, '', { okButton: multiple ? t`Ok` : t`Cancel`, allowVerticalScrolling: true }); |
| 2428 | popup.show() | 2470 | popup.show() |
| 2429 | .then((result => resolve(typeof result === 'number' ? resultToButtonMap.get(result) ?? '' : ''))) | 2471 | .then((result => resolve(getResult(result)))) |
| 2430 | .catch(() => resolve('')); | 2472 | .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 | } | ||
| 2431 | }); | 2481 | }); |
| 2432 | } catch { | 2482 | } catch { |
| 2433 | return ''; | 2483 | return ''; |
| @@ -2911,6 +2911,35 @@ select option:not(:checked) { | |||
| 2911 | pointer-events: none; | 2911 | pointer-events: none; |
| 2912 | } | 2912 | } |
| 2913 | 2913 | ||
| 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 | |||
| 2914 | .fav_on { | 2943 | .fav_on { |
| 2915 | color: var(--golden) !important; | 2944 | color: var(--golden) !important; |
| 2916 | } | 2945 | } |
| @@ -2921,7 +2950,7 @@ select option:not(:checked) { | |||
| 2921 | } | 2950 | } |
| 2922 | 2951 | ||
| 2923 | .menu_button.togglable:not(.toggleEnabled) { | 2952 | .menu_button.togglable:not(.toggleEnabled) { |
| 2924 | color: red; | 2953 | color: var(--fullred); |
| 2925 | } | 2954 | } |
| 2926 | 2955 | ||
| 2927 | .displayBlock { | 2956 | .displayBlock { |