Merge branch 'staging' into connection-profile-omit
| @@ -6090,7 +6090,10 @@ | |||
| 6090 | <div class="alternate_grettings flexFlowColumn flex-container"> | 6090 | <div class="alternate_grettings flexFlowColumn flex-container"> |
| 6091 | <div class="title_restorable"> | 6091 | <div class="title_restorable"> |
| 6092 | <h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3> | 6092 | <h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3> |
| 6093 | <div title="Add" class="menu_button fa-solid fa-plus add_alternate_greeting" data-i18n="[title]Add"></div> | 6093 | <div class="menu_button menu_button_icon add_alternate_greeting"> |
| 6094 | <i class="fa-solid fa-plus"></i> | ||
| 6095 | <span data-i18n="Add">Add</span> | ||
| 6096 | </div> | ||
| 6094 | </div> | 6097 | </div> |
| 6095 | <small class="justifyLeft" data-i18n="Alternate_Greetings_desc"> | 6098 | <small class="justifyLeft" data-i18n="Alternate_Greetings_desc"> |
| 6096 | These will be displayed as swipes on the first message when starting a new chat. | 6099 | These will be displayed as swipes on the first message when starting a new chat. |
| @@ -6106,11 +6109,18 @@ | |||
| 6106 | </div> | 6109 | </div> |
| 6107 | <div id="alternate_greeting_form_template" class="template_element"> | 6110 | <div id="alternate_greeting_form_template" class="template_element"> |
| 6108 | <div class="alternate_greeting"> | 6111 | <div class="alternate_greeting"> |
| 6112 | <details open> | ||
| 6113 | <summary> | ||
| 6109 | <div class="title_restorable"> | 6114 | <div class="title_restorable"> |
| 6110 | <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong> | 6115 | <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong> |
| 6111 | <div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div> | 6116 | <div class="menu_button menu_button_icon delete_alternate_greeting"> |
| 6117 | <i class="fa-solid fa-trash-alt"></i> | ||
| 6118 | <span data-i18n="Delete">Delete</span> | ||
| 6112 | </div> | 6119 | </div> |
| 6113 | <textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text mdHotkeys" value="" autocomplete="off" rows="16"></textarea> | 6120 | </div> |
| 6121 | </summary> | ||
| 6122 | <textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text mdHotkeys" value="" autocomplete="off" rows="12"></textarea> | ||
| 6123 | </details> | ||
| 6114 | </div> | 6124 | </div> |
| 6115 | </div> | 6125 | </div> |
| 6116 | 6126 | ||
| @@ -7390,7 +7390,7 @@ function onScenarioOverrideRemoveClick() { | |||
| 7390 | */ | 7390 | */ |
| 7391 | export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) { | 7391 | export function callPopup(text, type, inputValue = '', { okButton, rows, wide, wider, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) { |
| 7392 | function getOkButtonText() { | 7392 | function getOkButtonText() { |
| 7393 | if (['text', 'alternate_greeting', 'char_not_selected'].includes(popup_type)) { | 7393 | if (['text', 'char_not_selected'].includes(popup_type)) { |
| 7394 | $dialoguePopupCancel.css('display', 'none'); | 7394 | $dialoguePopupCancel.css('display', 'none'); |
| 7395 | return okButton ?? 'Ok'; | 7395 | return okButton ?? 'Ok'; |
| 7396 | } else if (['delete_extension'].includes(popup_type)) { | 7396 | } else if (['delete_extension'].includes(popup_type)) { |
| @@ -7827,24 +7827,42 @@ function openAlternateGreetings() { | |||
| 7827 | 7827 | ||
| 7828 | const template = $('#alternate_greetings_template .alternate_grettings').clone(); | 7828 | const template = $('#alternate_greetings_template .alternate_grettings').clone(); |
| 7829 | const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings; | 7829 | const getArray = () => menu_type == 'create' ? create_save.alternate_greetings : characters[chid].data.alternate_greetings; |
| 7830 | const popup = new Popup(template, POPUP_TYPE.TEXT, '', { | ||
| 7831 | wide: true, | ||
| 7832 | large: true, | ||
| 7833 | allowVerticalScrolling: true, | ||
| 7834 | onClose: async () => { | ||
| 7835 | if (menu_type !== 'create') { | ||
| 7836 | await createOrEditCharacter(); | ||
| 7837 | } | ||
| 7838 | } | ||
| 7839 | }); | ||
| 7830 | 7840 | ||
| 7831 | for (let index = 0; index < getArray().length; index++) { | 7841 | for (let index = 0; index < getArray().length; index++) { |
| 7832 | addAlternateGreeting(template, getArray()[index], index, getArray); | 7842 | addAlternateGreeting(template, getArray()[index], index, getArray, popup); |
| 7833 | } | 7843 | } |
| 7834 | 7844 | ||
| 7835 | template.find('.add_alternate_greeting').on('click', function () { | 7845 | template.find('.add_alternate_greeting').on('click', function () { |
| 7836 | const array = getArray(); | 7846 | const array = getArray(); |
| 7837 | const index = array.length; | 7847 | const index = array.length; |
| 7838 | array.push(''); | 7848 | array.push(''); |
| 7839 | addAlternateGreeting(template, '', index, getArray); | 7849 | addAlternateGreeting(template, '', index, getArray, popup); |
| 7840 | updateAlternateGreetingsHintVisibility(template); | 7850 | updateAlternateGreetingsHintVisibility(template); |
| 7841 | }); | 7851 | }); |
| 7842 | 7852 | ||
| 7843 | updateAlternateGreetingsHintVisibility(template); | 7853 | updateAlternateGreetingsHintVisibility(template); |
| 7844 | callPopup(template, 'alternate_greeting', '', { wide: true, large: true }); | 7854 | popup.show(); |
| 7845 | } | 7855 | } |
| 7846 | 7856 | ||
| 7847 | function addAlternateGreeting(template, greeting, index, getArray) { | 7857 | /** |
| 7858 | * Adds an alternate greeting to the template. | ||
| 7859 | * @param {JQuery<HTMLElement>} template | ||
| 7860 | * @param {string} greeting | ||
| 7861 | * @param {number} index | ||
| 7862 | * @param {() => any[]} getArray | ||
| 7863 | * @param {Popup} popup | ||
| 7864 | */ | ||
| 7865 | function addAlternateGreeting(template, greeting, index, getArray, popup) { | ||
| 7848 | const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone(); | 7866 | const greetingBlock = $('#alternate_greeting_form_template .alternate_greeting').clone(); |
| 7849 | greetingBlock.find('.alternate_greeting_text').on('input', async function () { | 7867 | greetingBlock.find('.alternate_greeting_text').on('input', async function () { |
| 7850 | const value = $(this).val(); | 7868 | const value = $(this).val(); |
| @@ -7852,11 +7870,16 @@ function addAlternateGreeting(template, greeting, index, getArray) { | |||
| 7852 | array[index] = value; | 7870 | array[index] = value; |
| 7853 | }).val(greeting); | 7871 | }).val(greeting); |
| 7854 | greetingBlock.find('.greeting_index').text(index + 1); | 7872 | greetingBlock.find('.greeting_index').text(index + 1); |
| 7855 | greetingBlock.find('.delete_alternate_greeting').on('click', async function () { | 7873 | greetingBlock.find('.delete_alternate_greeting').on('click', async function (event) { |
| 7874 | event.preventDefault(); | ||
| 7875 | event.stopPropagation(); | ||
| 7876 | |||
| 7856 | if (confirm('Are you sure you want to delete this alternate greeting?')) { | 7877 | if (confirm('Are you sure you want to delete this alternate greeting?')) { |
| 7857 | const array = getArray(); | 7878 | const array = getArray(); |
| 7858 | array.splice(index, 1); | 7879 | array.splice(index, 1); |
| 7880 | |||
| 7859 | // We need to reopen the popup to update the index numbers | 7881 | // We need to reopen the popup to update the index numbers |
| 7882 | await popup.complete(POPUP_RESULT.AFFIRMATIVE); | ||
| 7860 | openAlternateGreetings(); | 7883 | openAlternateGreetings(); |
| 7861 | } | 7884 | } |
| 7862 | }); | 7885 | }); |
| @@ -9574,9 +9597,6 @@ jQuery(async function () { | |||
| 9574 | }, 2000); | 9597 | }, 2000); |
| 9575 | } | 9598 | } |
| 9576 | } | 9599 | } |
| 9577 | if (popup_type == 'alternate_greeting' && menu_type !== 'create') { | ||
| 9578 | createOrEditCharacter(); | ||
| 9579 | } | ||
| 9580 | 9600 | ||
| 9581 | if (dialogueResolve) { | 9601 | if (dialogueResolve) { |
| 9582 | if (popup_type == 'input') { | 9602 | if (popup_type == 'input') { |
| @@ -3469,7 +3469,7 @@ function getModelOptions(quiet) { | |||
| 3469 | case 'openai': | 3469 | case 'openai': |
| 3470 | return oai_settings.chat_completion_source; | 3470 | return oai_settings.chat_completion_source; |
| 3471 | default: | 3471 | default: |
| 3472 | return nullResult; | 3472 | return null; |
| 3473 | } | 3473 | } |
| 3474 | } | 3474 | } |
| 3475 | 3475 | ||
| @@ -12,7 +12,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 12 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | 12 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 13 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 13 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 14 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; | 14 | import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; |
| 15 | import { isFalseBoolean, convertValueType } from './utils.js'; | 15 | import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js'; |
| 16 | 16 | ||
| 17 | /** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ | 17 | /** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ |
| 18 | /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ | 18 | /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ |
| @@ -488,7 +488,7 @@ function existsGlobalVariable(name) { | |||
| 488 | /** | 488 | /** |
| 489 | * Parses boolean operands from command arguments. | 489 | * Parses boolean operands from command arguments. |
| 490 | * @param {object} args Command arguments | 490 | * @param {object} args Command arguments |
| 491 | * @returns {{a: string | number, b: string | number, rule: string}} Boolean operands | 491 | * @returns {{a: string | number, b: string | number?, rule: string}} Boolean operands |
| 492 | */ | 492 | */ |
| 493 | export function parseBooleanOperands(args) { | 493 | export function parseBooleanOperands(args) { |
| 494 | // Resolution order: numeric literal, local variable, global variable, string literal | 494 | // Resolution order: numeric literal, local variable, global variable, string literal |
| @@ -497,6 +497,9 @@ export function parseBooleanOperands(args) { | |||
| 497 | */ | 497 | */ |
| 498 | function getOperand(operand) { | 498 | function getOperand(operand) { |
| 499 | if (operand === undefined) { | 499 | if (operand === undefined) { |
| 500 | return undefined; | ||
| 501 | } | ||
| 502 | if (operand === '') { | ||
| 500 | return ''; | 503 | return ''; |
| 501 | } | 504 | } |
| 502 | 505 | ||
| @@ -525,8 +528,8 @@ export function parseBooleanOperands(args) { | |||
| 525 | return stringLiteral || ''; | 528 | return stringLiteral || ''; |
| 526 | } | 529 | } |
| 527 | 530 | ||
| 528 | const left = getOperand(args.a || args.left || args.first || args.x); | 531 | const left = getOperand(args.a ?? args.left ?? args.first ?? args.x); |
| 529 | const right = getOperand(args.b || args.right || args.second || args.y); | 532 | const right = getOperand(args.b ?? args.right ?? args.second ?? args.y); |
| 530 | const rule = args.rule; | 533 | const rule = args.rule; |
| 531 | 534 | ||
| 532 | return { a: left, b: right, rule }; | 535 | return { a: left, b: right, rule }; |
| @@ -534,86 +537,81 @@ export function parseBooleanOperands(args) { | |||
| 534 | 537 | ||
| 535 | /** | 538 | /** |
| 536 | * Evaluates a boolean comparison rule. | 539 | * Evaluates a boolean comparison rule. |
| 537 | * @param {string} rule Boolean comparison rule | 540 | * |
| 541 | * @param {string?} rule Boolean comparison rule | ||
| 538 | * @param {string|number} a The left operand | 542 | * @param {string|number} a The left operand |
| 539 | * @param {string|number} b The right operand | 543 | * @param {string|number?} b The right operand |
| 540 | * @returns {boolean} True if the rule yields true, false otherwise | 544 | * @returns {boolean} True if the rule yields true, false otherwise |
| 541 | */ | 545 | */ |
| 542 | export function evalBoolean(rule, a, b) { | 546 | export function evalBoolean(rule, a, b) { |
| 543 | if (!rule) { | 547 | if (a === undefined) { |
| 544 | toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command'); | 548 | throw new Error('Left operand is not provided'); |
| 545 | throw new Error('Invalid command.'); | 549 | } |
| 550 | |||
| 551 | // If right-hand side was not provided, whe just check if the left side is truthy | ||
| 552 | if (b === undefined) { | ||
| 553 | switch (rule) { | ||
| 554 | case undefined: | ||
| 555 | case 'not': { | ||
| 556 | const resultOnTruthy = rule !== 'not'; | ||
| 557 | if (isTrueBoolean(String(a))) return resultOnTruthy; | ||
| 558 | if (isFalseBoolean(String(a))) return !resultOnTruthy; | ||
| 559 | return a ? resultOnTruthy : !resultOnTruthy; | ||
| 546 | } | 560 | } |
| 561 | default: | ||
| 562 | throw new Error(`Unknown boolean comparison rule for truthy check. If right operand is not provided, the rule must not provided or be 'not'. Provided: ${rule}`); | ||
| 563 | } | ||
| 564 | } | ||
| 565 | |||
| 566 | // If no rule was provided, we are implicitly using 'eq', as defined for the slash commands | ||
| 567 | rule ??= 'eq'; | ||
| 547 | 568 | ||
| 548 | let result = false; | ||
| 549 | if (typeof a === 'number' && typeof b === 'number') { | 569 | if (typeof a === 'number' && typeof b === 'number') { |
| 550 | // only do numeric comparison if both operands are numbers | 570 | // only do numeric comparison if both operands are numbers |
| 551 | const aNumber = Number(a); | 571 | const aNumber = Number(a); |
| 552 | const bNumber = Number(b); | 572 | const bNumber = Number(b); |
| 553 | 573 | ||
| 554 | switch (rule) { | 574 | switch (rule) { |
| 555 | case 'not': | ||
| 556 | result = !aNumber; | ||
| 557 | break; | ||
| 558 | case 'gt': | 575 | case 'gt': |
| 559 | result = aNumber > bNumber; | 576 | return aNumber > bNumber; |
| 560 | break; | ||
| 561 | case 'gte': | 577 | case 'gte': |
| 562 | result = aNumber >= bNumber; | 578 | return aNumber >= bNumber; |
| 563 | break; | ||
| 564 | case 'lt': | 579 | case 'lt': |
| 565 | result = aNumber < bNumber; | 580 | return aNumber < bNumber; |
| 566 | break; | ||
| 567 | case 'lte': | 581 | case 'lte': |
| 568 | result = aNumber <= bNumber; | 582 | return aNumber <= bNumber; |
| 569 | break; | ||
| 570 | case 'eq': | 583 | case 'eq': |
| 571 | result = aNumber === bNumber; | 584 | return aNumber === bNumber; |
| 572 | break; | ||
| 573 | case 'neq': | 585 | case 'neq': |
| 574 | result = aNumber !== bNumber; | 586 | return aNumber !== bNumber; |
| 587 | case 'in': | ||
| 588 | case 'nin': | ||
| 589 | // Fall through to string comparison. Otherwise you could not check if 12345 contains 45 for example. | ||
| 590 | console.debug(`Boolean comparison rule '${rule}' is not supported for type number. Falling back to string comparison.`); | ||
| 575 | break; | 591 | break; |
| 576 | default: | 592 | default: |
| 577 | toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command'); | 593 | throw new Error(`Unknown boolean comparison rule for type number. Accepted: gt, gte, lt, lte, eq, neq. Provided: ${rule}`); |
| 578 | throw new Error('Invalid command.'); | ||
| 579 | } | ||
| 580 | } else { | ||
| 581 | // otherwise do case-insensitive string comparsion, stringify non-strings | ||
| 582 | let aString; | ||
| 583 | let bString; | ||
| 584 | if (typeof a == 'string') { | ||
| 585 | aString = a.toLowerCase(); | ||
| 586 | } else { | ||
| 587 | aString = JSON.stringify(a).toLowerCase(); | ||
| 588 | } | 594 | } |
| 589 | if (typeof b == 'string') { | ||
| 590 | bString = b.toLowerCase(); | ||
| 591 | } else { | ||
| 592 | bString = JSON.stringify(b).toLowerCase(); | ||
| 593 | } | 595 | } |
| 594 | 596 | ||
| 597 | // otherwise do case-insensitive string comparsion, stringify non-strings | ||
| 598 | let aString = (typeof a === 'string') ? a.toLowerCase() : JSON.stringify(a).toLowerCase(); | ||
| 599 | let bString = (typeof b === 'string') ? b.toLowerCase() : JSON.stringify(b).toLowerCase(); | ||
| 600 | |||
| 595 | switch (rule) { | 601 | switch (rule) { |
| 596 | case 'in': | 602 | case 'in': |
| 597 | result = aString.includes(bString); | 603 | return aString.includes(bString); |
| 598 | break; | ||
| 599 | case 'nin': | 604 | case 'nin': |
| 600 | result = !aString.includes(bString); | 605 | return !aString.includes(bString); |
| 601 | break; | ||
| 602 | case 'eq': | 606 | case 'eq': |
| 603 | result = aString === bString; | 607 | return aString === bString; |
| 604 | break; | ||
| 605 | case 'neq': | 608 | case 'neq': |
| 606 | result = aString !== bString; | 609 | return aString !== bString; |
| 607 | break; | ||
| 608 | default: | 610 | default: |
| 609 | toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command'); | 611 | throw new Error(`Unknown boolean comparison rule for type number. Accepted: in, nin, eq, neq. Provided: ${rule}`); |
| 610 | throw new Error('Invalid command.'); | ||
| 611 | } | 612 | } |
| 612 | } | 613 | } |
| 613 | 614 | ||
| 614 | return result; | ||
| 615 | } | ||
| 616 | |||
| 617 | /** | 615 | /** |
| 618 | * Executes a slash command from a string (may be enclosed in quotes) and returns the result. | 616 | * Executes a slash command from a string (may be enclosed in quotes) and returns the result. |
| 619 | * @param {string} command Command to execute. May contain escaped macro and batch separators. | 617 | * @param {string} command Command to execute. May contain escaped macro and batch separators. |
| @@ -1317,32 +1315,36 @@ export function registerVariableCommands() { | |||
| 1317 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1315 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1318 | isRequired: true, | 1316 | isRequired: true, |
| 1319 | enumProvider: commonEnumProviders.variables('all'), | 1317 | enumProvider: commonEnumProviders.variables('all'), |
| 1320 | forceEnum: false, | ||
| 1321 | }), | 1318 | }), |
| 1322 | SlashCommandNamedArgument.fromProps({ | 1319 | SlashCommandNamedArgument.fromProps({ |
| 1323 | name: 'right', | 1320 | name: 'right', |
| 1324 | description: 'right operand', | 1321 | description: 'right operand', |
| 1325 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1322 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1326 | isRequired: true, | ||
| 1327 | enumProvider: commonEnumProviders.variables('all'), | 1323 | enumProvider: commonEnumProviders.variables('all'), |
| 1328 | forceEnum: false, | ||
| 1329 | }), | 1324 | }), |
| 1330 | new SlashCommandNamedArgument( | 1325 | SlashCommandNamedArgument.fromProps({ |
| 1331 | 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ | 1326 | name: 'rule', |
| 1332 | new SlashCommandEnumValue('gt', 'a > b'), | 1327 | description: 'comparison rule', |
| 1333 | new SlashCommandEnumValue('gte', 'a >= b'), | 1328 | typeList: [ARGUMENT_TYPE.STRING], |
| 1334 | new SlashCommandEnumValue('lt', 'a < b'), | 1329 | defaultValue: 'eq', |
| 1335 | new SlashCommandEnumValue('lte', 'a <= b'), | 1330 | enumList: [ |
| 1336 | new SlashCommandEnumValue('eq', 'a == b'), | 1331 | new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'), |
| 1337 | new SlashCommandEnumValue('neq', 'a !== b'), | 1332 | new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'), |
| 1338 | new SlashCommandEnumValue('not', '!a'), | 1333 | new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'), |
| 1339 | new SlashCommandEnumValue('in', 'a includes b'), | 1334 | new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'), |
| 1340 | new SlashCommandEnumValue('nin', 'a not includes b'), | 1335 | new SlashCommandEnumValue('gt', 'a > b (numbers)'), |
| 1336 | new SlashCommandEnumValue('gte', 'a >= b (numbers)'), | ||
| 1337 | new SlashCommandEnumValue('lt', 'a < b (numbers)'), | ||
| 1338 | new SlashCommandEnumValue('lte', 'a <= b (numbers)'), | ||
| 1339 | new SlashCommandEnumValue('not', '!a (truthy)'), | ||
| 1341 | ], | 1340 | ], |
| 1342 | ), | 1341 | forceEnum: true, |
| 1343 | new SlashCommandNamedArgument( | 1342 | }), |
| 1344 | 'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false, | 1343 | SlashCommandNamedArgument.fromProps({ |
| 1345 | ), | 1344 | name: 'else', |
| 1345 | description: 'command to execute if not true', | ||
| 1346 | typeList: [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], | ||
| 1347 | }), | ||
| 1346 | ], | 1348 | ], |
| 1347 | unnamedArgumentList: [ | 1349 | unnamedArgumentList: [ |
| 1348 | new SlashCommandArgument( | 1350 | new SlashCommandArgument( |
| @@ -1360,17 +1362,25 @@ export function registerVariableCommands() { | |||
| 1360 | Numeric values and string literals for left and right operands supported. | 1362 | Numeric values and string literals for left and right operands supported. |
| 1361 | </div> | 1363 | </div> |
| 1362 | <div> | 1364 | <div> |
| 1365 | If the rule is not provided, it defaults to <code>eq</code>. | ||
| 1366 | </div> | ||
| 1367 | <div> | ||
| 1368 | If no right operand is provided, it defaults to checking the <code>left</code> value to be truthy. | ||
| 1369 | A non-empty string or non-zero number is considered truthy, as is the value <code>true</code> or <code>on</code>.<br /> | ||
| 1370 | Only acceptable rules for no provided right operand are <code>not</code>, and no provided rule - which default to returning whether it is not or is truthy. | ||
| 1371 | </div> | ||
| 1372 | <div> | ||
| 1363 | <strong>Available rules:</strong> | 1373 | <strong>Available rules:</strong> |
| 1364 | <ul> | 1374 | <ul> |
| 1365 | <li>gt => a > b</li> | 1375 | <li><code>eq</code> => a == b <small>(strings & numbers)</small></li> |
| 1366 | <li>gte => a >= b</li> | 1376 | <li><code>neq</code> => a !== b <small>(strings & numbers)</small></li> |
| 1367 | <li>lt => a < b</li> | 1377 | <li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li> |
| 1368 | <li>lte => a <= b</li> | 1378 | <li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li> |
| 1369 | <li>eq => a == b</li> | 1379 | <li><code>gt</code> => a > b <small>(numbers)</small></li> |
| 1370 | <li>neq => a != b</li> | 1380 | <li><code>gte</code> => a >= b <small>(numbers)</small></li> |
| 1371 | <li>not => !a</li> | 1381 | <li><code>lt</code> => a < b <small>(numbers)</small></li> |
| 1372 | <li>in (strings) => a includes b</li> | 1382 | <li><code>lte</code> => a <= b <small>(numbers)</small></li> |
| 1373 | <li>nin (strings) => a not includes b</li> | 1383 | <li><code>not</code> => !a <small>(truthy)</small></li> |
| 1374 | </ul> | 1384 | </ul> |
| 1375 | </div> | 1385 | </div> |
| 1376 | <div> | 1386 | <div> |
| @@ -1380,6 +1390,17 @@ export function registerVariableCommands() { | |||
| 1380 | <pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre> | 1390 | <pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre> |
| 1381 | triggers a /speak command if the value of "score" is greater or equals 10. | 1391 | triggers a /speak command if the value of "score" is greater or equals 10. |
| 1382 | </li> | 1392 | </li> |
| 1393 | <li> | ||
| 1394 | <pre><code class="language-stscript">/if left={{lastMessage}} rule=in right=surprise {: /echo SURPISE! :}</code></pre> | ||
| 1395 | executes a subcommand defined as a closure if the given value contains a specified word. | ||
| 1396 | <li> | ||
| 1397 | <pre><code class="language-stscript">/if left=myContent {: /echo My content had some content. :}</code></pre> | ||
| 1398 | executes the defined subcommand, if the provided value of left is truthy (contains some kind of contant that is not empty or false) | ||
| 1399 | </li> | ||
| 1400 | <li> | ||
| 1401 | <pre><code class="language-stscript">/if left=tree right={{getvar::object}} {: /echo The object is a tree! :}</code></pre> | ||
| 1402 | executes the defined subcommand, if the left and right values are equals. | ||
| 1403 | </li> | ||
| 1383 | </ul> | 1404 | </ul> |
| 1384 | </div> | 1405 | </div> |
| 1385 | `, | 1406 | `, |
| @@ -1395,32 +1416,38 @@ export function registerVariableCommands() { | |||
| 1395 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1416 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1396 | isRequired: true, | 1417 | isRequired: true, |
| 1397 | enumProvider: commonEnumProviders.variables('all'), | 1418 | enumProvider: commonEnumProviders.variables('all'), |
| 1398 | forceEnum: false, | ||
| 1399 | }), | 1419 | }), |
| 1400 | SlashCommandNamedArgument.fromProps({ | 1420 | SlashCommandNamedArgument.fromProps({ |
| 1401 | name: 'right', | 1421 | name: 'right', |
| 1402 | description: 'right operand', | 1422 | description: 'right operand', |
| 1403 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], | 1423 | typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], |
| 1404 | isRequired: true, | ||
| 1405 | enumProvider: commonEnumProviders.variables('all'), | 1424 | enumProvider: commonEnumProviders.variables('all'), |
| 1406 | forceEnum: false, | ||
| 1407 | }), | 1425 | }), |
| 1408 | new SlashCommandNamedArgument( | 1426 | SlashCommandNamedArgument.fromProps({ |
| 1409 | 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ | 1427 | name: 'rule', |
| 1410 | new SlashCommandEnumValue('gt', 'a > b'), | 1428 | description: 'comparison rule', |
| 1411 | new SlashCommandEnumValue('gte', 'a >= b'), | 1429 | typeList: [ARGUMENT_TYPE.STRING], |
| 1412 | new SlashCommandEnumValue('lt', 'a < b'), | 1430 | defaultValue: 'eq', |
| 1413 | new SlashCommandEnumValue('lte', 'a <= b'), | 1431 | enumList: [ |
| 1414 | new SlashCommandEnumValue('eq', 'a == b'), | 1432 | new SlashCommandEnumValue('eq', 'a == b (strings & numbers)'), |
| 1415 | new SlashCommandEnumValue('neq', 'a !== b'), | 1433 | new SlashCommandEnumValue('neq', 'a !== b (strings & numbers)'), |
| 1416 | new SlashCommandEnumValue('not', '!a'), | 1434 | new SlashCommandEnumValue('in', 'a includes b (strings & numbers as strings)'), |
| 1417 | new SlashCommandEnumValue('in', 'a includes b'), | 1435 | new SlashCommandEnumValue('nin', 'a not includes b (strings & numbers as strings)'), |
| 1418 | new SlashCommandEnumValue('nin', 'a not includes b'), | 1436 | new SlashCommandEnumValue('gt', 'a > b (numbers)'), |
| 1437 | new SlashCommandEnumValue('gte', 'a >= b (numbers)'), | ||
| 1438 | new SlashCommandEnumValue('lt', 'a < b (numbers)'), | ||
| 1439 | new SlashCommandEnumValue('lte', 'a <= b (numbers)'), | ||
| 1440 | new SlashCommandEnumValue('not', '!a (truthy)'), | ||
| 1419 | ], | 1441 | ], |
| 1420 | ), | 1442 | forceEnum: true, |
| 1421 | new SlashCommandNamedArgument( | 1443 | }), |
| 1422 | 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(), | 1444 | SlashCommandNamedArgument.fromProps({ |
| 1423 | ), | 1445 | name: 'guard', |
| 1446 | description: 'disable loop iteration limit', | ||
| 1447 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1448 | defaultValue: 'off', | ||
| 1449 | enumList: commonEnumProviders.boolean('onOff')(), | ||
| 1450 | }), | ||
| 1424 | ], | 1451 | ], |
| 1425 | unnamedArgumentList: [ | 1452 | unnamedArgumentList: [ |
| 1426 | new SlashCommandArgument( | 1453 | new SlashCommandArgument( |
| @@ -1439,15 +1466,15 @@ export function registerVariableCommands() { | |||
| 1439 | <div> | 1466 | <div> |
| 1440 | <strong>Available rules:</strong> | 1467 | <strong>Available rules:</strong> |
| 1441 | <ul> | 1468 | <ul> |
| 1442 | <li>gt => a > b</li> | 1469 | <li><code>eq</code> => a == b <small>(strings & numbers)</small></li> |
| 1443 | <li>gte => a >= b</li> | 1470 | <li><code>neq</code> => a !== b <small>(strings & numbers)</small></li> |
| 1444 | <li>lt => a < b</li> | 1471 | <li><code>in</code> => a includes b <small>(strings & numbers as strings)</small></li> |
| 1445 | <li>lte => a <= b</li> | 1472 | <li><code>nin</code> => a not includes b <small>(strings & numbers as strings)</small></li> |
| 1446 | <li>eq => a == b</li> | 1473 | <li><code>gt</code> => a > b <small>(numbers)</small></li> |
| 1447 | <li>neq => a != b</li> | 1474 | <li><code>gte</code> => a >= b <small>(numbers)</small></li> |
| 1448 | <li>not => !a</li> | 1475 | <li><code>lt</code> => a < b <small>(numbers)</small></li> |
| 1449 | <li>in (strings) => a includes b</li> | 1476 | <li><code>lte</code> => a <= b <small>(numbers)</small></li> |
| 1450 | <li>nin (strings) => a not includes b</li> | 1477 | <li><code>not</code> => !a <small>(truthy)</small></li> |
| 1451 | </ul> | 1478 | </ul> |
| 1452 | </div> | 1479 | </div> |
| 1453 | <div> | 1480 | <div> |
| @@ -1457,7 +1484,11 @@ export function registerVariableCommands() { | |||
| 1457 | <pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre> | 1484 | <pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre> |
| 1458 | adds 1 to the value of "i" until it reaches 10. | 1485 | adds 1 to the value of "i" until it reaches 10. |
| 1459 | </li> | 1486 | </li> |
| 1487 | <li> | ||
| 1488 | <pre><code class="language-stscript">/while left={{getvar::currentword}} {: /setvar key=currentword {: /do-something-and-return :}() | /echo The current work is "{{getvar::currentword}}" :}</code></pre> | ||
| 1489 | executes the defined subcommand as long as the "currentword" variable is truthy (has any content that is not false/empty) | ||
| 1460 | </ul> | 1490 | </ul> |
| 1491 | </li> | ||
| 1461 | </div> | 1492 | </div> |
| 1462 | <div> | 1493 | <div> |
| 1463 | Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. | 1494 | Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. |
| @@ -3154,6 +3154,26 @@ grammarly-extension { | |||
| 3154 | align-items: center; | 3154 | align-items: center; |
| 3155 | } | 3155 | } |
| 3156 | 3156 | ||
| 3157 | .alternate_greeting details { | ||
| 3158 | padding: 2px; | ||
| 3159 | } | ||
| 3160 | |||
| 3161 | .alternate_greeting summary { | ||
| 3162 | list-style-position: outside; | ||
| 3163 | margin-left: 1em; | ||
| 3164 | padding-left: 1em; | ||
| 3165 | } | ||
| 3166 | |||
| 3167 | .alternate_greeting textarea { | ||
| 3168 | field-sizing: content; | ||
| 3169 | max-height: 50dvh; | ||
| 3170 | } | ||
| 3171 | |||
| 3172 | .alternate_greeting summary::marker, | ||
| 3173 | .alternate_greeting summary strong { | ||
| 3174 | cursor: pointer; | ||
| 3175 | } | ||
| 3176 | |||
| 3157 | #rm_characters_block .form_create_bottom_buttons_block { | 3177 | #rm_characters_block .form_create_bottom_buttons_block { |
| 3158 | justify-content: space-evenly !important; | 3178 | justify-content: space-evenly !important; |
| 3159 | flex-grow: 0; | 3179 | flex-grow: 0; |