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:
@ -58,6 +58,7 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||
import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js';
|
||||
import { NAME_RESULT_TYPE, SlashCommandParserNameResult } from './slash-commands/SlashCommandParserNameResult.js';
|
||||
import { OPTION_TYPE, SlashCommandAutoCompleteOption, SlashCommandFuzzyScore } from './slash-commands/SlashCommandAutoCompleteOption.js';
|
||||
import { SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
export {
|
||||
executeSlashCommands, getSlashCommandsHelp, registerSlashCommand,
|
||||
};
|
||||
@ -1654,11 +1655,20 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
const dom = document.createElement('ul'); {
|
||||
dom.classList.add('slashCommandAutoComplete');
|
||||
}
|
||||
const detailsWrap = document.createElement('div'); {
|
||||
detailsWrap.classList.add('slashCommandAutoComplete-detailsWrap');
|
||||
}
|
||||
const detailsDom = document.createElement('div'); {
|
||||
detailsDom.classList.add('slashCommandAutoComplete-details');
|
||||
detailsWrap.append(detailsDom);
|
||||
}
|
||||
let isReplacable = false;
|
||||
/**@type {SlashCommandAutoCompleteOption[]} */
|
||||
let result = [];
|
||||
/**@type {SlashCommandAutoCompleteOption} */
|
||||
let selectedItem = null;
|
||||
let isActive = false;
|
||||
let isShowingDetails = false;
|
||||
let text;
|
||||
/**@type {SlashCommandParserNameResult}*/
|
||||
let parserResult;
|
||||
@ -1669,7 +1679,18 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
const items = {};
|
||||
let hasCache = false;
|
||||
let selectionStart;
|
||||
const makeItem = (key, typeIcon, noSlash, helpString = '', aliasList = []) => {
|
||||
/**
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} typeIcon
|
||||
* @param {boolean} noSlash
|
||||
* @param {SlashCommandNamedArgument[]} namedArguments
|
||||
* @param {SlashCommandArgument[]} unnamedArguments
|
||||
* @param {string} helpString
|
||||
* @param {string[]} aliasList
|
||||
* @returns
|
||||
*/
|
||||
const makeItem = (key, typeIcon, noSlash, namedArguments = [], unnamedArguments = [], returnType = 'void', helpString = '', aliasList = []) => {
|
||||
const li = document.createElement('li'); {
|
||||
li.classList.add('item');
|
||||
const type = document.createElement('span'); {
|
||||
@ -1678,6 +1699,8 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
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');
|
||||
@ -1688,12 +1711,106 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
name.append(span);
|
||||
}
|
||||
});
|
||||
li.append(name);
|
||||
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 namedArguments) {
|
||||
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;
|
||||
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 unnamedArguments) {
|
||||
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;
|
||||
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 = returnType ?? 'void';
|
||||
body.append(returns);
|
||||
}
|
||||
specs.append(body);
|
||||
}
|
||||
li.append(specs);
|
||||
}
|
||||
li.append(' ');
|
||||
const help = document.createElement('span'); {
|
||||
help.classList.add('help');
|
||||
help.innerHTML = helpString;
|
||||
const content = document.createElement('span'); {
|
||||
content.classList.add('helpContent');
|
||||
content.innerHTML = helpString;
|
||||
const text = content.textContent;
|
||||
content.innerHTML = '';
|
||||
content.textContent = text;
|
||||
help.append(content);
|
||||
}
|
||||
li.append(help);
|
||||
}
|
||||
if (aliasList.length > 0) {
|
||||
@ -1708,7 +1825,7 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
}
|
||||
}
|
||||
aliases.append(')');
|
||||
li.append(aliases);
|
||||
// li.append(aliases);
|
||||
}
|
||||
}
|
||||
// gotta listen to pointerdown (happens before textarea-blur)
|
||||
@ -1728,7 +1845,9 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
};
|
||||
const hide = () => {
|
||||
dom?.remove();
|
||||
detailsWrap?.remove();
|
||||
isActive = false;
|
||||
isShowingDetails = false;
|
||||
};
|
||||
const show = async(isInput = false, isForced = false) => {
|
||||
//TODO check if isInput and isForced are both required
|
||||
@ -1743,7 +1862,16 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
// init by appending all command options
|
||||
Object.keys(parser.commands).forEach(key=>{
|
||||
const cmd = parser.commands[key];
|
||||
items[key] = makeItem(key, '/', false, cmd.helpString, [cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key));
|
||||
items[key] = makeItem(
|
||||
key,
|
||||
'/',
|
||||
false,
|
||||
cmd.namedArgumentList,
|
||||
cmd.unnamedArgumentList,
|
||||
cmd.returns,
|
||||
cmd.helpString,
|
||||
[cmd.name, ...(cmd.aliases ?? [])].filter(it=>it != key),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1991,6 +2119,7 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
} else if (result.length == 1 && parserResult && result[0].name == parserResult.name) {
|
||||
// only one result that is exactly the current value? just show hint, no autocomplete
|
||||
isReplacable = false;
|
||||
isShowingDetails = false;
|
||||
} else if (!isReplacable && result.length > 1) {
|
||||
return hide();
|
||||
}
|
||||
@ -2000,22 +2129,29 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
};
|
||||
const render = ()=>{
|
||||
// render autocomplete list
|
||||
if (isReplacable) {
|
||||
dom.innerHTML = '';
|
||||
dom.classList.remove('defaultDark');
|
||||
dom.classList.remove('defaultLight');
|
||||
dom.classList.remove('defaultThemed');
|
||||
detailsDom.classList.remove('defaultDark');
|
||||
detailsDom.classList.remove('defaultLight');
|
||||
detailsDom.classList.remove('defaultThemed');
|
||||
switch (power_user.stscript.autocomplete_style ?? 'theme') {
|
||||
case 'dark': {
|
||||
dom.classList.add('defaultDark');
|
||||
detailsDom.classList.add('defaultDark');
|
||||
break;
|
||||
}
|
||||
case 'light': {
|
||||
dom.classList.add('defaultLight');
|
||||
detailsDom.classList.add('defaultLight');
|
||||
break;
|
||||
}
|
||||
case 'theme':
|
||||
default: {
|
||||
dom.classList.add('defaultThemed');
|
||||
detailsDom.classList.add('defaultThemed');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2031,8 +2167,20 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
dom.append(frag);
|
||||
updatePosition();
|
||||
document.body.append(dom);
|
||||
} else {
|
||||
dom.remove();
|
||||
}
|
||||
renderDetailsDebounced();
|
||||
};
|
||||
const renderDetails = ()=>{
|
||||
if (!isShowingDetails && isReplacable) return detailsWrap.remove();
|
||||
detailsDom.innerHTML = '';
|
||||
detailsDom.append(selectedItem?.renderDetails() ?? 'NO ITEM');
|
||||
document.body.append(detailsWrap);
|
||||
updateDetailsPositionDebounced();
|
||||
};
|
||||
const renderDebounced = debounce(render, 10);
|
||||
const renderDetailsDebounced = debounce(renderDetails, 10);
|
||||
const updatePosition = () => {
|
||||
if (isFloating) {
|
||||
updateFloatingPosition();
|
||||
@ -2040,10 +2188,35 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
const rect = textarea.getBoundingClientRect();
|
||||
dom.style.setProperty('--bottom', `${window.innerHeight - rect.top}px`);
|
||||
dom.style.bottom = `${window.innerHeight - rect.top}px`;
|
||||
if (isShowingDetails) {
|
||||
dom.style.left = '1vw';
|
||||
} else {
|
||||
dom.style.left = `${rect.left}px`;
|
||||
dom.style.right = `${window.innerWidth - rect.right}px`;
|
||||
}
|
||||
dom.style.right = `calc(1vw + ${isShowingDetails ? 25 : 0}vw)`;
|
||||
updateDetailsPosition();
|
||||
}
|
||||
};
|
||||
const updateDetailsPosition = () => {
|
||||
if (isShowingDetails || !isReplacable) {
|
||||
const rect = textarea.getBoundingClientRect();
|
||||
if (isReplacable) {
|
||||
const selRect = selectedItem.dom.children[0].getBoundingClientRect();
|
||||
detailsWrap.style.setProperty('--targetTop', `${selRect.top}`);
|
||||
detailsWrap.style.bottom = dom.style.bottom;
|
||||
detailsWrap.style.left = `calc(100vw - ${dom.style.right})`;
|
||||
detailsWrap.style.right = '1vw';
|
||||
detailsWrap.style.top = '5vh';
|
||||
} else {
|
||||
detailsWrap.style.setProperty('--targetTop', `${rect.top}`);
|
||||
detailsWrap.style.bottom = dom.style.bottom;
|
||||
detailsWrap.style.left = `${rect.left}px`;
|
||||
detailsWrap.style.right = `calc(100vw - ${rect.right}px)`;
|
||||
detailsWrap.style.top = '5vh';
|
||||
}
|
||||
}
|
||||
};
|
||||
const updateDetailsPositionDebounced = debounce(updateDetailsPosition, 10);
|
||||
const updateFloatingPosition = () => {
|
||||
const location = getCursorPosition();
|
||||
const rect = textarea.getBoundingClientRect();
|
||||
@ -2131,6 +2304,11 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
textarea.selectionDirection = selectionEnd;
|
||||
}
|
||||
};
|
||||
const toggleDetails = async() => {
|
||||
isShowingDetails = !isShowingDetails;
|
||||
renderDetailsDebounced();
|
||||
updatePosition();
|
||||
};
|
||||
const showAutoCompleteDebounced = show;
|
||||
textarea.addEventListener('input', ()=>showAutoCompleteDebounced(true));
|
||||
textarea.addEventListener('click', ()=>showAutoCompleteDebounced());
|
||||
@ -2151,11 +2329,13 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
selectedItem.dom.classList.remove('selected');
|
||||
selectedItem = result[newIdx];
|
||||
selectedItem.dom.classList.add('selected');
|
||||
const rect = selectedItem.dom.getBoundingClientRect();
|
||||
const rect = selectedItem.dom.children[0].getBoundingClientRect();
|
||||
const rectParent = dom.getBoundingClientRect();
|
||||
if (rect.top < rectParent.top || rect.bottom > rectParent.bottom ) {
|
||||
selectedItem.dom.scrollIntoView();
|
||||
// selectedItem.dom.children[0].scrollIntoView();
|
||||
dom.scrollTop += rect.top < rectParent.top ? rect.top - rectParent.top : rect.bottom - rectParent.bottom;
|
||||
}
|
||||
renderDetailsDebounced();
|
||||
return;
|
||||
}
|
||||
case 'ArrowDown': {
|
||||
@ -2168,11 +2348,13 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
selectedItem.dom.classList.remove('selected');
|
||||
selectedItem = result[newIdx];
|
||||
selectedItem.dom.classList.add('selected');
|
||||
const rect = selectedItem.dom.getBoundingClientRect();
|
||||
const rect = selectedItem.dom.children[0].getBoundingClientRect();
|
||||
const rectParent = dom.getBoundingClientRect();
|
||||
if (rect.top < rectParent.top || rect.bottom > rectParent.bottom ) {
|
||||
selectedItem.dom.scrollIntoView();
|
||||
// selectedItem.dom.children[0].scrollIntoView();
|
||||
dom.scrollTop += rect.top < rectParent.top ? rect.top - rectParent.top : rect.bottom - rectParent.bottom;
|
||||
}
|
||||
renderDetailsDebounced();
|
||||
return;
|
||||
}
|
||||
case 'Enter':
|
||||
@ -2211,8 +2393,13 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
switch (evt.key) {
|
||||
case ' ': {
|
||||
if (evt.ctrlKey) {
|
||||
if (isActive) {
|
||||
// ctrl-space to toggle details for selected item
|
||||
toggleDetails();
|
||||
} else {
|
||||
// ctrl-space to force show autocomplete
|
||||
showAutoCompleteDebounced(true, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@ -2244,7 +2431,7 @@ export async function setSlashCommandAutoComplete(textarea, isFloating = false)
|
||||
}
|
||||
}
|
||||
});
|
||||
textarea.addEventListener('blur', ()=>hide());
|
||||
// textarea.addEventListener('blur', ()=>hide());
|
||||
if (isFloating) {
|
||||
textarea.addEventListener('scroll', debounce(updateFloatingPosition, 100));
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js';
|
||||
import { extension_settings, saveMetadataDebounced } from './extensions.js';
|
||||
import { executeSlashCommands, registerSlashCommand } from './slash-commands.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
|
||||
import { isFalseBoolean } from './utils.js';
|
||||
|
||||
@ -753,7 +756,59 @@ export function registerVariableCommands() {
|
||||
registerSlashCommand('decvar', (_, value) => decrementLocalVariable(value), [], '<span class="monospace">(key)</span> – decrement a local variable by 1 and pass the result down the pipe, e.g. <tt>/decvar score</tt>', true, true);
|
||||
registerSlashCommand('incglobalvar', (_, value) => incrementGlobalVariable(value), [], '<span class="monospace">(key)</span> – increment a global variable by 1 and pass the result down the pipe, e.g. <tt>/incglobalvar score</tt>', true, true);
|
||||
registerSlashCommand('decglobalvar', (_, value) => decrementGlobalVariable(value), [], '<span class="monospace">(key)</span> – decrement a global variable by 1 and pass the result down the pipe, e.g. <tt>/decglobalvar score</tt>', true, true);
|
||||
registerSlashCommand('if', ifCallback, [], '<span class="monospace">left=varname1 right=varname2 rule=comparison else="(alt.command)" "(command)"</span> – compare the value of the left operand "a" with the value of the right operand "b", and if the condition yields true, then execute any valid slash command enclosed in quotes and pass the result of the command execution down the pipe. Numeric values and string literals for left and right operands supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. <tt>/if left=score right=10 rule=gte "/speak You win"</tt> triggers a /speak command if the value of "score" is greater or equals 10.', true, true);
|
||||
// registerSlashCommand('if', ifCallback, [], '<span class="monospace">left=varname1 right=varname2 rule=comparison else="(alt.command)" "(command)"</span> – compare the value of the left operand "a" with the value of the right operand "b", and if the condition yields true, then execute any valid slash command enclosed in quotes and pass the result of the command execution down the pipe. Numeric values and string literals for left and right operands supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. <tt>/if left=score right=10 rule=gte "/speak You win"</tt> triggers a /speak command if the value of "score" is greater or equals 10.', true, true);
|
||||
const ifCmd = new SlashCommand();
|
||||
ifCmd.name = 'if';
|
||||
ifCmd.callback = ifCallback;
|
||||
ifCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'left', 'left operand', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], true,
|
||||
));
|
||||
ifCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'right', 'right operand', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], true,
|
||||
));
|
||||
ifCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, ['gt', 'gte', 'lt', 'lte', 'eq', 'neq', 'not', 'in', 'nin'],
|
||||
));
|
||||
ifCmd.namedArgumentList.push(new SlashCommandNamedArgument(
|
||||
'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false,
|
||||
));
|
||||
ifCmd.unnamedArgumentList.push(new SlashCommandArgument(
|
||||
'then', 'command to execute if true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], true,
|
||||
));
|
||||
ifCmd.returns = 'result of the executed command ("then" or "else")';
|
||||
ifCmd.helpString = `
|
||||
<p>
|
||||
Compares the value of the left operand <code>a</code> with the value of the right operand <code>b</code>,
|
||||
and if the condition yields true, then execute any valid slash command enclosed in quotes and pass the
|
||||
result of the command execution down the pipe.
|
||||
</p>
|
||||
<p>
|
||||
Numeric values and string literals for left and right operands supported.
|
||||
</p>
|
||||
<div>
|
||||
<strong>Available rules:</strong>
|
||||
<ul>
|
||||
<li>gt => a > b</li>
|
||||
<li>gte => a >= b</li>
|
||||
<li>lt => a < b</li>
|
||||
<li>lte => a <= b</li>
|
||||
<li>eq => a == b</li>
|
||||
<li>neq => a != b</li>
|
||||
<li>not => !a</li>
|
||||
<li>in (strings) => a includes b</li>
|
||||
<li>nin (strings) => a not includes b</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Examples:</strong>
|
||||
<ul>
|
||||
<li>
|
||||
<pre><code>/if left=score right=10 rule=gte "/speak You win"</code></pre>
|
||||
triggers a /speak command if the value of "score" is greater or equals 10.
|
||||
</li>
|
||||
</ul>
|
||||
</div>`;
|
||||
SlashCommandParser.addCommandObject(ifCmd);
|
||||
registerSlashCommand('while', whileCallback, [], '<span class="monospace">left=varname1 right=varname2 rule=comparison "(command)"</span> – compare the value of the left operand "a" with the value of the right operand "b", and if the condition yields true, then execute any valid slash command enclosed in quotes. Numeric values and string literals for left and right operands supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. <tt>/setvar key=i 0 | /while left=i right=10 rule=let "/addvar key=i 1"</tt> adds 1 to the value of "i" until it reaches 10. Loops are limited to 100 iterations by default, pass guard=off to disable.', true, true);
|
||||
registerSlashCommand('times', (args, value) => timesCallback(args, value), [], '<span class="monospace">(repeats) "(command)"</span> – execute any valid slash command enclosed in quotes <tt>repeats</tt> number of times, e.g. <tt>/setvar key=i 1 | /times 5 "/addvar key=i 1"</tt> adds 1 to the value of "i" 5 times. <tt>{{timesIndex}}</tt> is replaced with the iteration number (zero-based), e.g. <tt>/times 4 "/echo {{timesIndex}}"</tt> echos the numbers 0 through 4. Loops are limited to 100 iterations by default, pass guard=off to disable.', true, true);
|
||||
registerSlashCommand('flushvar', (_, value) => deleteLocalVariable(value), [], '<span class="monospace">(key)</span> – delete a local variable, e.g. <tt>/flushvar score</tt>', true, true);
|
||||
|
262
public/style.css
262
public/style.css
@ -76,6 +76,7 @@
|
||||
/*base variable calculated in rems*/
|
||||
--fontScale: 1;
|
||||
--mainFontSize: calc(var(--fontScale) * 15px);
|
||||
--mainFontFamily: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
|
||||
/* base variable for blur strength slider calculations */
|
||||
--blurStrength: 10;
|
||||
@ -133,7 +134,7 @@ body {
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
font-family: var(--mainFontFamily);
|
||||
font-size: var(--mainFontSize);
|
||||
color: var(--SmartThemeBodyColor);
|
||||
overflow: hidden;
|
||||
@ -1096,16 +1097,45 @@ select {
|
||||
--ac-color-hovered-background: color-mix(in srgb, rgb(128 128 128) 30%, var(--SmartThemeChatTintColor));
|
||||
--ac-color-hovered-text: var(--SmartThemeEmColor);
|
||||
}
|
||||
.slashCommandAutoComplete {
|
||||
--ac-color-border: rgb(69, 69, 69);
|
||||
--ac-color-background: rgb(32, 32, 32);
|
||||
.slashCommandAutoComplete-detailsWrap {
|
||||
--targetTop: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
flex: 0 1 calc(var(--targetTop) * 1px - 5vh);
|
||||
display: block;
|
||||
}
|
||||
.slashCommandAutoComplete-details {
|
||||
flex: 0 0 auto;
|
||||
max-height: 80vh;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
flex: 1 1 0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.slashCommandAutoComplete, .slashCommandAutoComplete-details {
|
||||
--ac-color-border: rgb(69 69 69);
|
||||
--ac-color-background: rgb(32 32 32);
|
||||
--ac-color-text: rgb(204 204 204);
|
||||
--ac-color-matched-background: transparent;
|
||||
--ac-color-matched-text: rgb(108, 171, 251);
|
||||
--ac-color-selected-background: rgb(32, 57, 92);
|
||||
--ac-color-selected-text: rgb(255, 255, 255);
|
||||
--ac-color-matched-text: rgb(108 171 251);
|
||||
--ac-color-selected-background: rgb(32 57 92);
|
||||
--ac-color-selected-text: rgb(255 255 255);
|
||||
--ac-color-hovered-background: rgb(43 45 46);
|
||||
--ac-color-hovered-text: rgb(204 204 204);
|
||||
--ac-color-arg-name: rgb(171 209 239);
|
||||
--ac-color-type: rgb(131 199 177);
|
||||
--ac-color-cmd: rgb(219 219 173);
|
||||
--ac-color-symbol: rgb(115 156 211);
|
||||
--ac-color-string: rgb(190 146 122);
|
||||
--ac-color-variable: rgb(131 193 252);
|
||||
--ac-color-current-parenthesis: rgb(195 118 210);
|
||||
--bottom: 50vh;
|
||||
background: var(--ac-color-background);
|
||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||
@ -1117,36 +1147,240 @@ select {
|
||||
margin: 0px;
|
||||
overflow: auto;
|
||||
padding: 0px;
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
|
||||
}
|
||||
.slashCommandAutoComplete {
|
||||
padding-bottom: 1px;
|
||||
position: absolute;
|
||||
display: grid;
|
||||
grid-template-columns: 0fr auto minmax(50%, 1fr);
|
||||
align-items: baseline;
|
||||
/* gap: 0.5em; */
|
||||
> .item {
|
||||
cursor: pointer;
|
||||
padding: 3px;
|
||||
text-shadow: none;
|
||||
&:hover {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
font-size: 0.8em;
|
||||
display: contents;
|
||||
&:hover > * {
|
||||
background-color: var(--ac-color-hovered-background);
|
||||
color: var(--ac-color-hovered-text);
|
||||
}
|
||||
&.selected {
|
||||
&.selected > * {
|
||||
background-color: var(--ac-color-selected-background);
|
||||
color: var(--ac-color-selected-text);
|
||||
}
|
||||
> * {
|
||||
height: 100%;
|
||||
}
|
||||
> *+* {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
> .type {
|
||||
flex: 0 0 auto;
|
||||
display: inline-block;
|
||||
width: 2.75em;
|
||||
font-size: 0.8em;
|
||||
width: 2.25em;
|
||||
/* font-size: 0.8em; */
|
||||
text-align: center;
|
||||
opacity: 0.6;
|
||||
white-space: nowrap;
|
||||
font-family: monospace;
|
||||
&:before { content: "["; }
|
||||
&:after { content: "]"; }
|
||||
}
|
||||
.matched {
|
||||
> .specs {
|
||||
align-items: flex-start;
|
||||
> .name {
|
||||
> .matched {
|
||||
background-color: var(--ac-color-matched-background);
|
||||
color: var(--ac-color-matched-text);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
> .body {
|
||||
flex-wrap: wrap;
|
||||
column-gap: 0.5em;
|
||||
> .arguments {
|
||||
display: contents;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .help {
|
||||
height: 100%;
|
||||
> .helpContent {
|
||||
&:before { content: "– "; }
|
||||
/* display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1; */
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
font-size: 0.9em;white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
height: 1.2em;
|
||||
display: block;
|
||||
> * {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.slashCommandAutoComplete-details {
|
||||
font-size: 0.8em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
> .specs {
|
||||
flex-direction: column;
|
||||
padding: 0.25em 0.25em 0.5em 0.25em;
|
||||
border-bottom: 1px solid var(--ac-color-border);
|
||||
> .name {
|
||||
font-weight: bold;
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
> .body {
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
> .arguments {
|
||||
margin: 0;
|
||||
padding-left: 1.25em;
|
||||
> .argumentItem::marker {
|
||||
color: color-mix(in srgb, var(--ac-color-text), var(--ac-color-background));
|
||||
}
|
||||
.argument.optional + .argument-description:before {
|
||||
content: "(optional) ";
|
||||
color: var(--ac-color-text);
|
||||
opacity: 0.5;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.argument-description {
|
||||
margin-left: 0.5em;
|
||||
font-family: var(--mainFontFamily);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .help {
|
||||
padding: 0 0.5em 0.5em 0.5em;
|
||||
div {
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
> code {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slashCommandAutoComplete > .item, .slashCommandAutoComplete-details {
|
||||
> .specs {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
> .name {
|
||||
font-family: monospace;
|
||||
white-space: nowrap;
|
||||
/* color: var(--ac-color-text); */
|
||||
}
|
||||
> .body {
|
||||
display: flex;
|
||||
> .arguments {
|
||||
font-family: monospace;
|
||||
.argument {
|
||||
white-space: nowrap;
|
||||
&.namedArgument {
|
||||
&:before {
|
||||
content: "[";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
&:after {
|
||||
content: "]";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
&.optional:after {
|
||||
content: "]?";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
> .argument-name {
|
||||
color: var(--ac-color-arg-name);
|
||||
}
|
||||
}
|
||||
&.unnamedArgument {
|
||||
&:before {
|
||||
content: "(";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
&.multiple:before {
|
||||
content: "...(";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
&:after {
|
||||
content: ")";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
&.optional:after {
|
||||
content: ")?";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
}
|
||||
> .argument-name + .argument-types:before {
|
||||
content: "=";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
> .argument-types {
|
||||
word-break: break-all;
|
||||
white-space: break-spaces;
|
||||
> .argument-type + .argument-type:before {
|
||||
content: "|";
|
||||
color: var(--ac-color-text);
|
||||
};
|
||||
> .argument-type {
|
||||
color: var(--ac-color-type);
|
||||
}
|
||||
}
|
||||
> .argument-types + .argument-enums,
|
||||
> .argument-name + .argument-enums
|
||||
{
|
||||
&:before {
|
||||
content: "=";
|
||||
color: var(--ac-color-text);
|
||||
}
|
||||
}
|
||||
> .argument-enums {
|
||||
word-break: break-all;
|
||||
white-space: break-spaces;
|
||||
> .argument-enum + .argument-enum:before {
|
||||
content: "|";
|
||||
color: var(--ac-color-text);
|
||||
};
|
||||
> .argument-enum {
|
||||
color: var(--ac-color-string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .returns {
|
||||
font-family: monospace;
|
||||
white-space: nowrap;
|
||||
color: var(--ac-color-text);
|
||||
&:before {
|
||||
content: "=> ";
|
||||
color: var(--ac-color-symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#character_popup .editor_maximize {
|
||||
|
Reference in New Issue
Block a user