mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
dirty more detailed cmd defs
This commit is contained in:
@ -1,10 +1,17 @@
|
||||
import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
|
||||
|
||||
|
||||
|
||||
export class SlashCommand {
|
||||
/**@type {String}*/ name;
|
||||
/**@type {string}*/ name;
|
||||
/**@type {Function}*/ callback;
|
||||
/**@type {String}*/ helpString;
|
||||
/**@type {Boolean}*/ interruptsGeneration;
|
||||
/**@type {Boolean}*/ purgeFromMessage;
|
||||
/**@type {String[]}*/ aliases;
|
||||
/**@type {string}*/ helpString;
|
||||
/**@type {boolean}*/ interruptsGeneration = true;
|
||||
/**@type {boolean}*/ purgeFromMessage = true;
|
||||
/**@type {string[]}*/ aliases = [];
|
||||
/**@type {string}*/ returns;
|
||||
/**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
|
||||
/**@type {SlashCommandArgument[]}*/ unnamedArgumentList = [];
|
||||
|
||||
get helpStringFormatted() {
|
||||
let aliases = '';
|
||||
|
62
public/scripts/slash-commands/SlashCommandArgument.js
Normal file
62
public/scripts/slash-commands/SlashCommandArgument.js
Normal file
@ -0,0 +1,62 @@
|
||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||
|
||||
|
||||
|
||||
/**@readonly*/
|
||||
/**@enum {string}*/
|
||||
export const ARGUMENT_TYPE = {
|
||||
'STRING': 'string',
|
||||
'NUMBER': 'number',
|
||||
'BOOLEAN': 'bool',
|
||||
'VARIABLE_NAME': 'varname',
|
||||
'CLOSURE': 'closure',
|
||||
'SUBCOMMAND': 'subcommand',
|
||||
'LIST': 'list',
|
||||
'DICTIONARY': 'dictionary',
|
||||
};
|
||||
|
||||
|
||||
|
||||
export class SlashCommandArgument {
|
||||
/**@type {string}*/ description;
|
||||
/**@type {ARGUMENT_TYPE[]}*/ typeList = [];
|
||||
/**@type {boolean}*/ isRequired = false;
|
||||
/**@type {boolean}*/ acceptsMultiple = false;
|
||||
/**@type {string|SlashCommandClosure}*/ defaultValue;
|
||||
/**@type {string[]}*/ enumList = [];
|
||||
|
||||
/**
|
||||
* @param {string} description
|
||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
||||
* @param {string|SlashCommandClosure} defaultValue
|
||||
* @param {string|string[]} enums
|
||||
*/
|
||||
constructor(description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = []) {
|
||||
this.description = description;
|
||||
this.typeList = types ? Array.isArray(types) ? types : [types] : [];
|
||||
this.isRequired = isRequired ?? false;
|
||||
this.acceptsMultiple = acceptsMultiple ?? false;
|
||||
this.defaultValue = defaultValue;
|
||||
this.enumList = enums ? Array.isArray(enums) ? enums : [enums] : [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class SlashCommandNamedArgument extends SlashCommandArgument {
|
||||
/**@type {string}*/ name;
|
||||
/**@type {string[]}*/ aliasList = [];
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} description
|
||||
* @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types
|
||||
* @param {string|SlashCommandClosure} defaultValue
|
||||
* @param {string|string[]} enums
|
||||
*/
|
||||
constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = []) {
|
||||
super(name, description, types, isRequired, acceptsMultiple, defaultValue, enums);
|
||||
this.name = name;
|
||||
this.aliasList = aliases ? Array.isArray(aliases) ? aliases : [aliases] : [];
|
||||
}
|
||||
}
|
@ -45,4 +45,210 @@ export class SlashCommandAutoCompleteOption {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
renderDetails() {
|
||||
switch (this.type) {
|
||||
case OPTION_TYPE.COMMAND: {
|
||||
return this.renderCommandDetails();
|
||||
}
|
||||
case OPTION_TYPE.QUICK_REPLY: {
|
||||
return this.renderQuickReplyDetails();
|
||||
}
|
||||
case OPTION_TYPE.VARIABLE_NAME: {
|
||||
return this.renderVariableDetails();
|
||||
}
|
||||
default: {
|
||||
return this.renderBlankDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
renderBlankDetails() {
|
||||
return 'BLANK';
|
||||
}
|
||||
renderQuickReplyDetails() {
|
||||
const frag = document.createDocumentFragment();
|
||||
const specs = document.createElement('div'); {
|
||||
specs.classList.add('specs');
|
||||
const name = document.createElement('div'); {
|
||||
name.classList.add('name');
|
||||
name.classList.add('monospace');
|
||||
name.textContent = this.value.toString();
|
||||
specs.append(name);
|
||||
}
|
||||
frag.append(specs);
|
||||
}
|
||||
const help = document.createElement('span'); {
|
||||
help.classList.add('help');
|
||||
help.textContent = 'Quick Reply';
|
||||
frag.append(help);
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
renderVariableDetails() {
|
||||
const frag = document.createDocumentFragment();
|
||||
const specs = document.createElement('div'); {
|
||||
specs.classList.add('specs');
|
||||
const name = document.createElement('div'); {
|
||||
name.classList.add('name');
|
||||
name.classList.add('monospace');
|
||||
name.textContent = this.value.toString();
|
||||
specs.append(name);
|
||||
}
|
||||
frag.append(specs);
|
||||
}
|
||||
const help = document.createElement('span'); {
|
||||
help.classList.add('help');
|
||||
help.textContent = 'scoped variable';
|
||||
frag.append(help);
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
renderCommandDetails() {
|
||||
const frag = document.createDocumentFragment();
|
||||
const key = this.name;
|
||||
/**@type {SlashCommand} */
|
||||
// @ts-ignore
|
||||
const cmd = this.value;
|
||||
const namedArguments = cmd.namedArgumentList ?? [];
|
||||
const unnamedArguments = cmd.unnamedArgumentList ?? [];
|
||||
const returnType = cmd.returns ?? 'void';
|
||||
const helpString = cmd.helpString ?? 'NO DETAILS';
|
||||
const aliasList = cmd.aliases ?? [];
|
||||
const specs = document.createElement('div'); {
|
||||
specs.classList.add('specs');
|
||||
const name = document.createElement('div'); {
|
||||
name.classList.add('name');
|
||||
name.classList.add('monospace');
|
||||
name.textContent = `/${key}`;
|
||||
specs.append(name);
|
||||
}
|
||||
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 argItem = document.createElement('div'); {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
listItem.append(argItem);
|
||||
}
|
||||
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 argItem = document.createElement('div'); {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
listItem.append(argItem);
|
||||
}
|
||||
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.textContent = returnType ?? 'void';
|
||||
body.append(returns);
|
||||
}
|
||||
specs.append(body);
|
||||
}
|
||||
frag.append(specs);
|
||||
}
|
||||
const help = document.createElement('span'); {
|
||||
help.classList.add('help');
|
||||
help.innerHTML = helpString;
|
||||
frag.append(help);
|
||||
}
|
||||
if (aliasList.length > 0) {
|
||||
const aliases = document.createElement('span'); {
|
||||
aliases.classList.add('aliases');
|
||||
aliases.append(' (alias: ');
|
||||
for (const aliasName of aliasList) {
|
||||
const alias = document.createElement('span'); {
|
||||
alias.classList.add('monospace');
|
||||
alias.textContent = `/${aliasName}`;
|
||||
aliases.append(alias);
|
||||
}
|
||||
}
|
||||
aliases.append(')');
|
||||
frag.append(aliases);
|
||||
}
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { power_user } from '../power-user.js';
|
||||
import { isTrueBoolean, uuidv4 } from '../utils.js';
|
||||
import { SlashCommand } from './SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
|
||||
import { OPTION_TYPE, SlashCommandAutoCompleteOption } from './SlashCommandAutoCompleteOption.js';
|
||||
import { SlashCommandClosure } from './SlashCommandClosure.js';
|
||||
import { SlashCommandExecutor } from './SlashCommandExecutor.js';
|
||||
@ -18,7 +19,66 @@ export const PARSER_FLAG = {
|
||||
|
||||
export class SlashCommandParser {
|
||||
// @ts-ignore
|
||||
/**@type {Object.<string, SlashCommand>}*/ commands = {};
|
||||
/**@type {Object.<string, SlashCommand>}*/ static commands = {};
|
||||
static addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
|
||||
const reserved = ['/', '#', ':', 'parser-flag'];
|
||||
for (const start of reserved) {
|
||||
if (command.toLowerCase().startsWith(start) || (aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) {
|
||||
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
|
||||
}
|
||||
}
|
||||
this.addCommandUnsafe(command, callback, aliases, helpString, interruptsGeneration, purgeFromMessage);
|
||||
}
|
||||
static addCommandUnsafe(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
|
||||
const fnObj = Object.assign(new SlashCommand(), { name:command, callback, helpString, interruptsGeneration, purgeFromMessage, aliases });
|
||||
|
||||
if ([command, ...aliases].some(x => Object.hasOwn(this.commands, x))) {
|
||||
console.trace('WARN: Duplicate slash command registered!', [command, ...aliases]);
|
||||
}
|
||||
|
||||
this.commands[command] = fnObj;
|
||||
|
||||
if (Array.isArray(aliases)) {
|
||||
aliases.forEach((alias) => {
|
||||
this.commands[alias] = fnObj;
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {SlashCommand} command
|
||||
*/
|
||||
static addCommandObject(command) {
|
||||
const reserved = ['/', '#', ':', 'parser-flag'];
|
||||
for (const start of reserved) {
|
||||
if (command.name.toLowerCase().startsWith(start) || (command.aliases ?? []).find(a=>a.toLowerCase().startsWith(start))) {
|
||||
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
|
||||
}
|
||||
}
|
||||
this.addCommandObjectUnsafe(command);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {SlashCommand} command
|
||||
*/
|
||||
static addCommandObjectUnsafe(command) {
|
||||
if ([command.name, ...command.aliases].some(x => Object.hasOwn(this.commands, x))) {
|
||||
console.trace('WARN: Duplicate slash command registered!', [command.name, ...command.aliases]);
|
||||
}
|
||||
|
||||
this.commands[command.name] = command;
|
||||
|
||||
if (Array.isArray(command.aliases)) {
|
||||
command.aliases.forEach((alias) => {
|
||||
this.commands[alias] = command;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
get commands() {
|
||||
return SlashCommandParser.commands;
|
||||
}
|
||||
// @ts-ignore
|
||||
/**@type {Object.<string, string>}*/ helpStrings = {};
|
||||
/**@type {boolean}*/ verifyCommandNames = true;
|
||||
@ -54,14 +114,68 @@ export class SlashCommandParser {
|
||||
|
||||
constructor() {
|
||||
// add dummy commands for help strings / autocomplete
|
||||
this.addDummyCommand('parser-flag',
|
||||
[],
|
||||
`<span class="monospace">(${Object.keys(PARSER_FLAG).join('|')}) (on|off)</span> – Set a parser flag.`,
|
||||
);
|
||||
this.addDummyCommand('/',
|
||||
['#'],
|
||||
'<span class="monospace">(comment)</span> – Write a comment.',
|
||||
);
|
||||
const parserFlagCmd = new SlashCommand();
|
||||
parserFlagCmd.name = 'parser-flag';
|
||||
parserFlagCmd.unnamedArgumentList.push(new SlashCommandArgument(
|
||||
'flag',
|
||||
'The parser flag to modify.',
|
||||
ARGUMENT_TYPE.STRING,
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
Object.keys(PARSER_FLAG),
|
||||
));
|
||||
parserFlagCmd.unnamedArgumentList.push(new SlashCommandArgument(
|
||||
'state',
|
||||
'The state of the parser flag to set.',
|
||||
ARGUMENT_TYPE.BOOLEAN,
|
||||
false,
|
||||
false,
|
||||
'on',
|
||||
// ['on', 'off'],
|
||||
));
|
||||
parserFlagCmd.helpString = 'Set a parser flag.';
|
||||
SlashCommandParser.addCommandObjectUnsafe(parserFlagCmd);
|
||||
|
||||
const commentCmd = new SlashCommand();
|
||||
commentCmd.name = '/';
|
||||
commentCmd.aliases.push('#');
|
||||
commentCmd.unnamedArgumentList.push(new SlashCommandArgument(
|
||||
'comment',
|
||||
'Commentary',
|
||||
ARGUMENT_TYPE.STRING,
|
||||
));
|
||||
commentCmd.helpString = 'Write a comment.';
|
||||
SlashCommandParser.addCommandObjectUnsafe(commentCmd);
|
||||
|
||||
const dummyCmd = new SlashCommand();
|
||||
dummyCmd.name = 'foo';
|
||||
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'arg1', 'first argument', ARGUMENT_TYPE.STRING, true,
|
||||
));
|
||||
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'arg2', 'second argument', ARGUMENT_TYPE.STRING, true,
|
||||
));
|
||||
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
|
||||
));
|
||||
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
|
||||
));
|
||||
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
|
||||
));
|
||||
dummyCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'arg3', 'third argument', ARGUMENT_TYPE.STRING, false, false,
|
||||
));
|
||||
dummyCmd.unnamedArgumentList.push(new SlashCommandArgument(
|
||||
'foo', 'foo argument', [ARGUMENT_TYPE.SUBCOMMAND, ARGUMENT_TYPE.CLOSURE], true, true,
|
||||
));
|
||||
dummyCmd.callback = ()=>'foo';
|
||||
dummyCmd.returns = 'string';
|
||||
dummyCmd.helpString = 'Just a dummy command that does not do anything but return "foo".';
|
||||
SlashCommandParser.addCommandObjectUnsafe(dummyCmd);
|
||||
|
||||
this.registerLanguage();
|
||||
}
|
||||
registerLanguage() {
|
||||
@ -165,31 +279,10 @@ export class SlashCommandParser {
|
||||
}
|
||||
|
||||
addCommand(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
|
||||
const reserved = ['/', '#', ':', 'parser-flag'];
|
||||
for (const start of reserved) {
|
||||
if (command.toLowerCase().startsWith(start) || (aliases ?? []).find(a=>a.toLowerCase().startsWith(reserved))) {
|
||||
throw new Error(`Illegal Name. Slash command name cannot begin with "${start}".`);
|
||||
}
|
||||
}
|
||||
this.addCommandUnsafe(command, callback, aliases, helpString, interruptsGeneration, purgeFromMessage);
|
||||
}
|
||||
addCommandUnsafe(command, callback, aliases, helpString = '', interruptsGeneration = false, purgeFromMessage = true) {
|
||||
const fnObj = Object.assign(new SlashCommand(), { name:command, callback, helpString, interruptsGeneration, purgeFromMessage, aliases });
|
||||
|
||||
if ([command, ...aliases].some(x => Object.hasOwn(this.commands, x))) {
|
||||
console.trace('WARN: Duplicate slash command registered!', [command, ...aliases]);
|
||||
}
|
||||
|
||||
this.commands[command] = fnObj;
|
||||
|
||||
if (Array.isArray(aliases)) {
|
||||
aliases.forEach((alias) => {
|
||||
this.commands[alias] = fnObj;
|
||||
});
|
||||
}
|
||||
SlashCommandParser.addCommand(command, callback, aliases, helpString, interruptsGeneration, purgeFromMessage);
|
||||
}
|
||||
addDummyCommand(command, aliases, helpString) {
|
||||
this.addCommandUnsafe(command, null, aliases, helpString, true, true);
|
||||
SlashCommandParser.addCommandUnsafe(command, null, aliases, helpString, true, true);
|
||||
}
|
||||
|
||||
getHelpString() {
|
||||
@ -469,7 +562,7 @@ export class SlashCommandParser {
|
||||
return this.testCommandEnd();
|
||||
}
|
||||
parseComment() {
|
||||
const start = this.index + 2;
|
||||
const start = this.index + 1;
|
||||
const cmd = new SlashCommandExecutor(start);
|
||||
this.commandIndex.push(cmd);
|
||||
this.scopeIndex.push(this.scope.getCopy());
|
||||
|
Reference in New Issue
Block a user