Merge pull request #2795 from SillyTavern/expand-echo-command Expand `/echo` command with `cssClass`, `color`, `escapeHtml` and `onClick`
Signed| @@ -797,6 +797,27 @@ export function initDefaultSlashCommands() { | |||
| 797 | defaultValue: 'false', | 797 | defaultValue: 'false', |
| 798 | enumList: commonEnumProviders.boolean('trueFalse')(), | 798 | enumList: commonEnumProviders.boolean('trueFalse')(), |
| 799 | }), | 799 | }), |
| 800 | SlashCommandNamedArgument.fromProps({ | ||
| 801 | name: 'cssClass', | ||
| 802 | description: 'additional CSS class to add to the toast message (e.g. for custom styling)', | ||
| 803 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 804 | }), | ||
| 805 | SlashCommandNamedArgument.fromProps({ | ||
| 806 | name: 'color', | ||
| 807 | description: 'custom CSS color of the toast message. Accepts all valid CSS color values (e.g. \'red\', \'#FF0000\', \'rgb(255, 0, 0)\').<br />>Can be more customizable with the \'cssClass\' argument and custom classes.', | ||
| 808 | }), | ||
| 809 | SlashCommandNamedArgument.fromProps({ | ||
| 810 | name: 'escapeHtml', | ||
| 811 | description: 'whether to escape HTML in the toast message.', | ||
| 812 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 813 | defaultValue: 'true', | ||
| 814 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 815 | }), | ||
| 816 | SlashCommandNamedArgument.fromProps({ | ||
| 817 | name: 'onClick', | ||
| 818 | description: 'a closure to call when the toast is clicked. This executed closure receives scope as provided in the script. Careful about possible side effects when manipulating variables and more.', | ||
| 819 | typeList: [ARGUMENT_TYPE.CLOSURE], | ||
| 820 | }), | ||
| 800 | ], | 821 | ], |
| 801 | unnamedArgumentList: [ | 822 | unnamedArgumentList: [ |
| 802 | new SlashCommandArgument( | 823 | new SlashCommandArgument( |
| @@ -805,13 +826,19 @@ export function initDefaultSlashCommands() { | |||
| 805 | ], | 826 | ], |
| 806 | helpString: ` | 827 | helpString: ` |
| 807 | <div> | 828 | <div> |
| 808 | Echoes the provided text to a toast message. Useful for pipes debugging. | 829 | Echoes the provided text to a toast message. Can be used to display informational messages or for pipes debugging. |
| 809 | </div> | 830 | </div> |
| 810 | <div> | 831 | <div> |
| 811 | <strong>Example:</strong> | 832 | <strong>Example:</strong> |
| 812 | <ul> | 833 | <ul> |
| 813 | <li> | 834 | <li> |
| 814 | <pre><code>/echo title="My Message" severity=info This is an info message</code></pre> | 835 | <pre><code>/echo title="My Message" severity=warning This is a warning message</code></pre> |
| 836 | </li> | ||
| 837 | <li> | ||
| 838 | <pre><code>/echo color=purple This message is purple</code></pre> | ||
| 839 | </li> | ||
| 840 | <li> | ||
| 841 | <pre><code>/echo onClick={: /echo escapeHtml=false color=transparent cssClass=wider_dialogue_popup <img src="/img/five.png" /> :} timeout=5000 Clicking on this message within 5 seconds will open the image.</code></pre> | ||
| 815 | </li> | 842 | </li> |
| 816 | </ul> | 843 | </ul> |
| 817 | </div> | 844 | </div> |
| @@ -2181,7 +2208,7 @@ async function generateCallback(args, value) { | |||
| 2181 | 2208 | ||
| 2182 | /** | 2209 | /** |
| 2183 | * | 2210 | * |
| 2184 | * @param {{title?: string, severity?: string, timeout?: string, extendedTimeout?: string, preventDuplicates?: string, awaitDismissal?: string}} args - named arguments from the slash command | 2211 | * @param {{title?: string, severity?: string, timeout?: string, extendedTimeout?: string, preventDuplicates?: string, awaitDismissal?: string, cssClass?: string, color?: string, escapeHtml?: string, onClick?: SlashCommandClosure}} args - named arguments from the slash command |
| 2185 | * @param {string} value - The string to echo (unnamed argument from the slash command) | 2212 | * @param {string} value - The string to echo (unnamed argument from the slash command) |
| 2186 | * @returns {Promise<string>} The text that was echoed | 2213 | * @returns {Promise<string>} The text that was echoed |
| 2187 | */ | 2214 | */ |
| @@ -2200,7 +2227,7 @@ async function echoCallback(args, value) { | |||
| 2200 | // Make sure that the value is a string | 2227 | // Make sure that the value is a string |
| 2201 | value = String(value); | 2228 | value = String(value); |
| 2202 | 2229 | ||
| 2203 | const title = args.title ? args.title : undefined; | 2230 | let title = args.title ? args.title : undefined; |
| 2204 | const severity = args.severity ? args.severity : 'info'; | 2231 | const severity = args.severity ? args.severity : 'info'; |
| 2205 | 2232 | ||
| 2206 | /** @type {ToastrOptions} */ | 2233 | /** @type {ToastrOptions} */ |
| @@ -2208,6 +2235,8 @@ async function echoCallback(args, value) { | |||
| 2208 | if (args.timeout && !isNaN(parseInt(args.timeout))) options.timeOut = parseInt(args.timeout); | 2235 | if (args.timeout && !isNaN(parseInt(args.timeout))) options.timeOut = parseInt(args.timeout); |
| 2209 | if (args.extendedTimeout && !isNaN(parseInt(args.extendedTimeout))) options.extendedTimeOut = parseInt(args.extendedTimeout); | 2236 | if (args.extendedTimeout && !isNaN(parseInt(args.extendedTimeout))) options.extendedTimeOut = parseInt(args.extendedTimeout); |
| 2210 | if (isTrueBoolean(args.preventDuplicates)) options.preventDuplicates = true; | 2237 | if (isTrueBoolean(args.preventDuplicates)) options.preventDuplicates = true; |
| 2238 | if (args.cssClass) options.toastClass = args.cssClass; | ||
| 2239 | if (args.escapeHtml !== undefined) options.escapeHtml = isTrueBoolean(args.escapeHtml); | ||
| 2211 | 2240 | ||
| 2212 | // Prepare possible await handling | 2241 | // Prepare possible await handling |
| 2213 | let awaitDismissal = isTrueBoolean(args.awaitDismissal); | 2242 | let awaitDismissal = isTrueBoolean(args.awaitDismissal); |
| @@ -2216,23 +2245,45 @@ async function echoCallback(args, value) { | |||
| 2216 | if (awaitDismissal) { | 2245 | if (awaitDismissal) { |
| 2217 | options.onHidden = () => resolveToastDismissal(value); | 2246 | options.onHidden = () => resolveToastDismissal(value); |
| 2218 | } | 2247 | } |
| 2248 | if (args.onClick) { | ||
| 2249 | if (args.onClick instanceof SlashCommandClosure) { | ||
| 2250 | options.onclick = async () => { | ||
| 2251 | // Execute the slash command directly, with its internal scope and everything. Clear progress handler so it doesn't interfere with command execution progress. | ||
| 2252 | args.onClick.onProgress = null; | ||
| 2253 | await args.onClick.execute(); | ||
| 2254 | }; | ||
| 2255 | } else { | ||
| 2256 | toastr.warning('Invalid onClick provided for /echo command. This is not a closure'); | ||
| 2257 | } | ||
| 2258 | } | ||
| 2259 | |||
| 2260 | // If we allow HTML, we need to sanitize it to prevent security risks | ||
| 2261 | if (!options.escapeHtml) { | ||
| 2262 | if (title) title = DOMPurify.sanitize(title, { FORBID_TAGS: ['style'] }); | ||
| 2263 | value = DOMPurify.sanitize(value, { FORBID_TAGS: ['style'] }); | ||
| 2264 | } | ||
| 2219 | 2265 | ||
| 2266 | let toast; | ||
| 2220 | switch (severity) { | 2267 | switch (severity) { |
| 2221 | case 'error': | 2268 | case 'error': |
| 2222 | toastr.error(value, title, options); | 2269 | toast = toastr.error(value, title, options); |
| 2223 | break; | 2270 | break; |
| 2224 | case 'warning': | 2271 | case 'warning': |
| 2225 | toastr.warning(value, title, options); | 2272 | toast = toastr.warning(value, title, options); |
| 2226 | break; | 2273 | break; |
| 2227 | case 'success': | 2274 | case 'success': |
| 2228 | toastr.success(value, title, options); | 2275 | toast = toastr.success(value, title, options); |
| 2229 | break; | 2276 | break; |
| 2230 | case 'info': | 2277 | case 'info': |
| 2231 | default: | 2278 | default: |
| 2232 | toastr.info(value, title, options); | 2279 | toast = toastr.info(value, title, options); |
| 2233 | break; | 2280 | break; |
| 2234 | } | 2281 | } |
| 2235 | 2282 | ||
| 2283 | if (args.color) { | ||
| 2284 | toast.css('background-color', args.color); | ||
| 2285 | } | ||
| 2286 | |||
| 2236 | if (awaitDismissal) { | 2287 | if (awaitDismissal) { |
| 2237 | return new Promise((resolve) => { | 2288 | return new Promise((resolve) => { |
| 2238 | resolveToastDismissal = resolve; | 2289 | resolveToastDismissal = resolve; |