mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			442 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			442 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { hljs } from '../../lib.js';
 | |
| import { t } from '../i18n.js';
 | |
| import { SlashCommandAbortController } from './SlashCommandAbortController.js';
 | |
| import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
 | |
| import { SlashCommandClosure } from './SlashCommandClosure.js';
 | |
| import { SlashCommandDebugController } from './SlashCommandDebugController.js';
 | |
| import { SlashCommandScope } from './SlashCommandScope.js';
 | |
| 
 | |
| /**
 | |
|  * @typedef {{
 | |
|  * _scope:SlashCommandScope,
 | |
|  * _parserFlags:import('./SlashCommandParser.js').ParserFlags,
 | |
|  * _abortController:SlashCommandAbortController,
 | |
|  * _debugController:SlashCommandDebugController,
 | |
|  * _hasUnnamedArgument:boolean,
 | |
|  * [id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined,
 | |
|  * }} NamedArguments
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Alternative object for local JSDocs, where you don't need existing pipe, scope, etc. arguments
 | |
|  * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined}} NamedArgumentsCapture
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @typedef {string|SlashCommandClosure|(string|SlashCommandClosure)[]} UnnamedArguments
 | |
| */
 | |
| 
 | |
| 
 | |
| 
 | |
| export class SlashCommand {
 | |
|     /**
 | |
|      * Creates a SlashCommand from a properties object.
 | |
|      * @param {Object} props
 | |
|      * @param {string} [props.name]
 | |
|      * @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
 | |
|      * @param {string} [props.helpString]
 | |
|      * @param {boolean} [props.splitUnnamedArgument]
 | |
|      * @param {Number} [props.splitUnnamedArgumentCount]
 | |
|      * @param {boolean} [props.rawQuotes] If set to true, does not remove wrapping quotes from the unnamed argument.
 | |
|      * @param {string[]} [props.aliases]
 | |
|      * @param {string} [props.returns]
 | |
|      * @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
 | |
|      * @param {SlashCommandArgument[]} [props.unnamedArgumentList]
 | |
|      */
 | |
|     static fromProps(props) {
 | |
|         const instance = Object.assign(new this(), props);
 | |
|         return instance;
 | |
|     }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
|     /**@type {string}*/ name;
 | |
|     /**@type {(namedArguments:{_scope:SlashCommandScope, _abortController:SlashCommandAbortController, [id:string]:string|SlashCommandClosure}, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
 | |
|     /**@type {string}*/ helpString;
 | |
|     /**@type {boolean}*/ splitUnnamedArgument = false;
 | |
|     /**@type {Number}*/ splitUnnamedArgumentCount;
 | |
|     /** @type {boolean} */ rawQuotes = false;
 | |
|     /**@type {string[]}*/ aliases = [];
 | |
|     /**@type {string}*/ returns;
 | |
|     /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
 | |
|     /**@type {SlashCommandArgument[]}*/ unnamedArgumentList = [];
 | |
| 
 | |
|     /**@type {Object.<string, HTMLElement>}*/ helpCache = {};
 | |
|     /**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};
 | |
| 
 | |
|     /**@type {boolean}*/ isExtension = false;
 | |
|     /**@type {boolean}*/ isThirdParty = false;
 | |
|     /**@type {string}*/ source;
 | |
| 
 | |
|     renderHelpItem(key = null) {
 | |
|         key = key ?? this.name;
 | |
|         if (!this.helpCache[key]) {
 | |
|             const typeIcon = '[/]';
 | |
|             const li = document.createElement('li'); {
 | |
|                 li.classList.add('item');
 | |
|                 const type = document.createElement('span'); {
 | |
|                     type.classList.add('type');
 | |
|                     type.classList.add('monospace');
 | |
|                     type.textContent = typeIcon;
 | |
|                     li.append(type);
 | |
|                 }
 | |
|                 const specs = document.createElement('span'); {
 | |
|                     specs.classList.add('specs');
 | |
|                     const name = document.createElement('span'); {
 | |
|                         name.classList.add('name');
 | |
|                         name.classList.add('monospace');
 | |
|                         name.textContent = '/';
 | |
|                         key.split('').forEach(char=>{
 | |
|                             const span = document.createElement('span'); {
 | |
|                                 span.textContent = char;
 | |
|                                 name.append(span);
 | |
|                             }
 | |
|                         });
 | |
|                         specs.append(name);
 | |
|                     }
 | |
|                     const body = document.createElement('span'); {
 | |
|                         body.classList.add('body');
 | |
|                         const args = document.createElement('span'); {
 | |
|                             args.classList.add('arguments');
 | |
|                             for (const arg of this.namedArgumentList) {
 | |
|                                 const argItem = document.createElement('span'); {
 | |
|                                     argItem.classList.add('argument');
 | |
|                                     argItem.classList.add('namedArgument');
 | |
|                                     if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
 | |
|                                     if (arg.acceptsMultiple) argItem.classList.add('multiple');
 | |
|                                     const name = document.createElement('span'); {
 | |
|                                         name.classList.add('argument-name');
 | |
|                                         name.textContent = arg.name;
 | |
|                                         argItem.append(name);
 | |
|                                     }
 | |
|                                     if (arg.enumList.length > 0) {
 | |
|                                         const enums = document.createElement('span'); {
 | |
|                                             enums.classList.add('argument-enums');
 | |
|                                             for (const e of arg.enumList) {
 | |
|                                                 const enumItem = document.createElement('span'); {
 | |
|                                                     enumItem.classList.add('argument-enum');
 | |
|                                                     enumItem.textContent = e.value;
 | |
|                                                     enums.append(enumItem);
 | |
|                                                 }
 | |
|                                             }
 | |
|                                             argItem.append(enums);
 | |
|                                         }
 | |
|                                     } else {
 | |
|                                         const types = document.createElement('span'); {
 | |
|                                             types.classList.add('argument-types');
 | |
|                                             for (const t of arg.typeList) {
 | |
|                                                 const type = document.createElement('span'); {
 | |
|                                                     type.classList.add('argument-type');
 | |
|                                                     type.textContent = t;
 | |
|                                                     types.append(type);
 | |
|                                                 }
 | |
|                                             }
 | |
|                                             argItem.append(types);
 | |
|                                         }
 | |
|                                     }
 | |
|                                     args.append(argItem);
 | |
|                                 }
 | |
|                             }
 | |
|                             for (const arg of this.unnamedArgumentList) {
 | |
|                                 const argItem = document.createElement('span'); {
 | |
|                                     argItem.classList.add('argument');
 | |
|                                     argItem.classList.add('unnamedArgument');
 | |
|                                     if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
 | |
|                                     if (arg.acceptsMultiple) argItem.classList.add('multiple');
 | |
|                                     if (arg.enumList.length > 0) {
 | |
|                                         const enums = document.createElement('span'); {
 | |
|                                             enums.classList.add('argument-enums');
 | |
|                                             for (const e of arg.enumList) {
 | |
|                                                 const enumItem = document.createElement('span'); {
 | |
|                                                     enumItem.classList.add('argument-enum');
 | |
|                                                     enumItem.textContent = e.value;
 | |
|                                                     enums.append(enumItem);
 | |
|                                                 }
 | |
|                                             }
 | |
|                                             argItem.append(enums);
 | |
|                                         }
 | |
|                                     } else {
 | |
|                                         const types = document.createElement('span'); {
 | |
|                                             types.classList.add('argument-types');
 | |
|                                             for (const t of arg.typeList) {
 | |
|                                                 const type = document.createElement('span'); {
 | |
|                                                     type.classList.add('argument-type');
 | |
|                                                     type.textContent = t;
 | |
|                                                     types.append(type);
 | |
|                                                 }
 | |
|                                             }
 | |
|                                             argItem.append(types);
 | |
|                                         }
 | |
|                                     }
 | |
|                                     args.append(argItem);
 | |
|                                 }
 | |
|                             }
 | |
|                             body.append(args);
 | |
|                         }
 | |
|                         const returns = document.createElement('span'); {
 | |
|                             returns.classList.add('returns');
 | |
|                             returns.textContent = this.returns ?? 'void';
 | |
|                             body.append(returns);
 | |
|                         }
 | |
|                         specs.append(body);
 | |
|                     }
 | |
|                     li.append(specs);
 | |
|                 }
 | |
|                 const stopgap = document.createElement('span'); {
 | |
|                     stopgap.classList.add('stopgap');
 | |
|                     stopgap.textContent = '';
 | |
|                     li.append(stopgap);
 | |
|                 }
 | |
|                 const help = document.createElement('span'); {
 | |
|                     help.classList.add('help');
 | |
|                     const content = document.createElement('span'); {
 | |
|                         content.classList.add('helpContent');
 | |
|                         content.innerHTML = this.helpString;
 | |
|                         const text = content.textContent;
 | |
|                         content.innerHTML = '';
 | |
|                         content.textContent = text;
 | |
|                         help.append(content);
 | |
|                     }
 | |
|                     li.append(help);
 | |
|                 }
 | |
|                 if (this.aliases.length > 0) {
 | |
|                     const aliases = document.createElement('span'); {
 | |
|                         aliases.classList.add('aliases');
 | |
|                         aliases.append(' (alias: ');
 | |
|                         for (const aliasName of this.aliases) {
 | |
|                             const alias = document.createElement('span'); {
 | |
|                                 alias.classList.add('monospace');
 | |
|                                 alias.textContent = `/${aliasName}`;
 | |
|                                 aliases.append(alias);
 | |
|                             }
 | |
|                         }
 | |
|                         aliases.append(')');
 | |
|                         // li.append(aliases);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             this.helpCache[key] = li;
 | |
|         }
 | |
|         return /**@type {HTMLElement}*/(this.helpCache[key].cloneNode(true));
 | |
|     }
 | |
| 
 | |
|     renderHelpDetails(key = null) {
 | |
|         key = key ?? this.name;
 | |
|         if (!this.helpDetailsCache[key]) {
 | |
|             const frag = document.createDocumentFragment();
 | |
|             const cmd = this;
 | |
|             const namedArguments = cmd.namedArgumentList ?? [];
 | |
|             const unnamedArguments = cmd.unnamedArgumentList ?? [];
 | |
|             const returnType = cmd.returns ?? 'void';
 | |
|             const helpString = cmd.helpString ?? 'NO DETAILS';
 | |
|             const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key);
 | |
|             const specs = document.createElement('div'); {
 | |
|                 specs.classList.add('specs');
 | |
|                 const head = document.createElement('div'); {
 | |
|                     head.classList.add('head');
 | |
|                     const name = document.createElement('div'); {
 | |
|                         name.classList.add('name');
 | |
|                         name.classList.add('monospace');
 | |
|                         name.title = 'command name';
 | |
|                         name.textContent = `/${key}`;
 | |
|                         head.append(name);
 | |
|                     }
 | |
|                     const src = document.createElement('div'); {
 | |
|                         src.classList.add('source');
 | |
|                         src.classList.add('fa-solid');
 | |
|                         if (this.isExtension) {
 | |
|                             src.classList.add('isExtension');
 | |
|                             src.classList.add('fa-cubes');
 | |
|                             if (this.isThirdParty) src.classList.add('isThirdParty');
 | |
|                             else src.classList.add('isCore');
 | |
|                         } else {
 | |
|                             src.classList.add('isCore');
 | |
|                             src.classList.add('fa-star-of-life');
 | |
|                         }
 | |
|                         src.title = [
 | |
|                             this.isExtension ? 'Extension' : 'Core',
 | |
|                             this.isThirdParty ? 'Third Party' : (this.isExtension ? 'Core' : null),
 | |
|                             this.source,
 | |
|                         ].filter(it=>it).join('\n');
 | |
|                         head.append(src);
 | |
|                     }
 | |
|                     if (this.rawQuotes) {
 | |
|                         const rawQuotes = document.createElement('div'); {
 | |
|                             rawQuotes.classList.add('rawQuotes');
 | |
|                             rawQuotes.classList.add('fa-solid');
 | |
|                             rawQuotes.classList.add('fa-quote-left');
 | |
|                             rawQuotes.title = t`Does not alter quoted literal unnamed arguments. Pass raw=false argument to override.`;
 | |
|                             head.append(rawQuotes);
 | |
|                         }
 | |
|                     }
 | |
|                     specs.append(head);
 | |
|                 }
 | |
|                 const body = document.createElement('div'); {
 | |
|                     body.classList.add('body');
 | |
|                     const args = document.createElement('ul'); {
 | |
|                         args.classList.add('arguments');
 | |
|                         for (const arg of namedArguments) {
 | |
|                             const listItem = document.createElement('li'); {
 | |
|                                 listItem.classList.add('argumentItem');
 | |
|                                 const argSpec = document.createElement('div'); {
 | |
|                                     argSpec.classList.add('argumentSpec');
 | |
|                                     const argItem = document.createElement('div'); {
 | |
|                                         argItem.classList.add('argument');
 | |
|                                         argItem.classList.add('namedArgument');
 | |
|                                         argItem.title = `${arg.isRequired ? '' : 'optional '}named argument`;
 | |
|                                         if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
 | |
|                                         if (arg.acceptsMultiple) argItem.classList.add('multiple');
 | |
|                                         const name = document.createElement('span'); {
 | |
|                                             name.classList.add('argument-name');
 | |
|                                             name.title = `${argItem.title} - name`;
 | |
|                                             name.textContent = arg.name;
 | |
|                                             argItem.append(name);
 | |
|                                         }
 | |
|                                         if (arg.enumList.length > 0) {
 | |
|                                             const enums = document.createElement('span'); {
 | |
|                                                 enums.classList.add('argument-enums');
 | |
|                                                 enums.title = `${argItem.title} - accepted values`;
 | |
|                                                 for (const e of arg.enumList) {
 | |
|                                                     const enumItem = document.createElement('span'); {
 | |
|                                                         enumItem.classList.add('argument-enum');
 | |
|                                                         enumItem.textContent = e.value;
 | |
|                                                         enums.append(enumItem);
 | |
|                                                     }
 | |
|                                                 }
 | |
|                                                 argItem.append(enums);
 | |
|                                             }
 | |
|                                         } else {
 | |
|                                             const types = document.createElement('span'); {
 | |
|                                                 types.classList.add('argument-types');
 | |
|                                                 types.title = `${argItem.title} - accepted types`;
 | |
|                                                 for (const t of arg.typeList) {
 | |
|                                                     const type = document.createElement('span'); {
 | |
|                                                         type.classList.add('argument-type');
 | |
|                                                         type.textContent = t;
 | |
|                                                         types.append(type);
 | |
|                                                     }
 | |
|                                                 }
 | |
|                                                 argItem.append(types);
 | |
|                                             }
 | |
|                                         }
 | |
|                                         argSpec.append(argItem);
 | |
|                                     }
 | |
|                                     if (arg.defaultValue !== null) {
 | |
|                                         const argDefault = document.createElement('div'); {
 | |
|                                             argDefault.classList.add('argument-default');
 | |
|                                             argDefault.title = 'default value';
 | |
|                                             argDefault.textContent = arg.defaultValue.toString();
 | |
|                                             argSpec.append(argDefault);
 | |
|                                         }
 | |
|                                     }
 | |
|                                     listItem.append(argSpec);
 | |
|                                 }
 | |
|                                 const desc = document.createElement('div'); {
 | |
|                                     desc.classList.add('argument-description');
 | |
|                                     desc.innerHTML = arg.description;
 | |
|                                     listItem.append(desc);
 | |
|                                 }
 | |
|                                 args.append(listItem);
 | |
|                             }
 | |
|                         }
 | |
|                         for (const arg of unnamedArguments) {
 | |
|                             const listItem = document.createElement('li'); {
 | |
|                                 listItem.classList.add('argumentItem');
 | |
|                                 const argSpec = document.createElement('div'); {
 | |
|                                     argSpec.classList.add('argumentSpec');
 | |
|                                     const argItem = document.createElement('div'); {
 | |
|                                         argItem.classList.add('argument');
 | |
|                                         argItem.classList.add('unnamedArgument');
 | |
|                                         argItem.title = `${arg.isRequired ? '' : 'optional '}unnamed argument`;
 | |
|                                         if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
 | |
|                                         if (arg.acceptsMultiple) argItem.classList.add('multiple');
 | |
|                                         if (arg.enumList.length > 0) {
 | |
|                                             const enums = document.createElement('span'); {
 | |
|                                                 enums.classList.add('argument-enums');
 | |
|                                                 enums.title = `${argItem.title} - accepted values`;
 | |
|                                                 for (const e of arg.enumList) {
 | |
|                                                     const enumItem = document.createElement('span'); {
 | |
|                                                         enumItem.classList.add('argument-enum');
 | |
|                                                         enumItem.textContent = e.value;
 | |
|                                                         enums.append(enumItem);
 | |
|                                                     }
 | |
|                                                 }
 | |
|                                                 argItem.append(enums);
 | |
|                                             }
 | |
|                                         } else {
 | |
|                                             const types = document.createElement('span'); {
 | |
|                                                 types.classList.add('argument-types');
 | |
|                                                 types.title = `${argItem.title} - accepted types`;
 | |
|                                                 for (const t of arg.typeList) {
 | |
|                                                     const type = document.createElement('span'); {
 | |
|                                                         type.classList.add('argument-type');
 | |
|                                                         type.textContent = t;
 | |
|                                                         types.append(type);
 | |
|                                                     }
 | |
|                                                 }
 | |
|                                                 argItem.append(types);
 | |
|                                             }
 | |
|                                         }
 | |
|                                         argSpec.append(argItem);
 | |
|                                     }
 | |
|                                     if (arg.defaultValue !== null) {
 | |
|                                         const argDefault = document.createElement('div'); {
 | |
|                                             argDefault.classList.add('argument-default');
 | |
|                                             argDefault.title = 'default value';
 | |
|                                             argDefault.textContent = arg.defaultValue.toString();
 | |
|                                             argSpec.append(argDefault);
 | |
|                                         }
 | |
|                                     }
 | |
|                                     listItem.append(argSpec);
 | |
|                                 }
 | |
|                                 const desc = document.createElement('div'); {
 | |
|                                     desc.classList.add('argument-description');
 | |
|                                     desc.innerHTML = arg.description;
 | |
|                                     listItem.append(desc);
 | |
|                                 }
 | |
|                                 args.append(listItem);
 | |
|                             }
 | |
|                         }
 | |
|                         body.append(args);
 | |
|                     }
 | |
|                     const returns = document.createElement('span'); {
 | |
|                         returns.classList.add('returns');
 | |
|                         returns.title = [null, undefined, 'void'].includes(returnType) ? 'command does not return anything' : 'return value';
 | |
|                         returns.textContent = returnType ?? 'void';
 | |
|                         body.append(returns);
 | |
|                     }
 | |
|                     specs.append(body);
 | |
|                 }
 | |
|                 frag.append(specs);
 | |
|             }
 | |
|             const help = document.createElement('span'); {
 | |
|                 help.classList.add('help');
 | |
|                 help.innerHTML = helpString;
 | |
|                 for (const code of help.querySelectorAll('pre > code')) {
 | |
|                     code.classList.add('language-stscript');
 | |
|                     hljs.highlightElement(code);
 | |
|                 }
 | |
|                 frag.append(help);
 | |
|             }
 | |
|             if (aliasList.length > 0) {
 | |
|                 const aliases = document.createElement('span'); {
 | |
|                     aliases.classList.add('aliases');
 | |
|                     for (const aliasName of aliasList) {
 | |
|                         const alias = document.createElement('span'); {
 | |
|                             alias.classList.add('alias');
 | |
|                             alias.textContent = `/${aliasName}`;
 | |
|                             aliases.append(alias);
 | |
|                         }
 | |
|                     }
 | |
|                     frag.append(aliases);
 | |
|                 }
 | |
|             }
 | |
|             this.helpDetailsCache[key] = frag;
 | |
|         }
 | |
|         const frag = document.createDocumentFragment();
 | |
|         frag.append(this.helpDetailsCache[key].cloneNode(true));
 | |
|         return frag;
 | |
|     }
 | |
| }
 |