Regex: Code clean-up (#4628) * Add confirmation for bulk move, add UNKNOWN type, improve JSDoc * Fix toggle all bulk move visibility * Improve function documentation * Refactor currentChatId check and improve JSDoc for getScriptDecorators function * Check against known values in moveRegexScript * Fix console warning spam * Simplify currentChatId check * Prevent moving scripts into the void * Make texts translatable
Signed| @@ -1,16 +1,13 @@ | ||
| 1 | 1 | import { characters, substituteParams, substituteParamsExtended, this_chid } from '../../../script.js'; |
| 2 | 2 | import { extension_settings } from '../../extensions.js'; |
| 3 | 3 | import { regexFromString } from '../../utils.js'; |
| 4 | -export { | |
| 5 | - regex_placement, | |
| 6 | - getRegexedString, | |
| 7 | - runRegexScript, | |
| 8 | -}; | |
| 9 | 4 | |
| 10 | 5 | /** |
| 11 | 6 | * @enum {number} Regex scripts types |
| 7 | + * @readonly | |
| 12 | 8 | */ |
| 13 | 9 | export const SCRIPT_TYPES = { |
| 10 | + UNKNOWN: -1, | |
| 14 | 11 | GLOBAL: 0, |
| 15 | 12 | SCOPED: 1, |
| 16 | 13 | }; |
| @@ -21,28 +18,34 @@ export const SCRIPT_TYPES = { | ||
| 21 | 18 | |
| 22 | 19 | /** |
| 23 | 20 | * @typedef {object} GetRegexScriptsOptions |
| 24 | 21 | * @property {boolean} allowedOnly onlyOnly return allowed scripts |
| 22 | + */ | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * @type {Readonly<GetRegexScriptsOptions>} | |
| 25 | 26 | */ |
| 26 | 27 | const DEFAULT_GET_REGEX_SCRIPTS_OPTIONS = Object.freeze({ allowedOnly: false }); |
| 27 | 28 | |
| 28 | 29 | /** |
| 29 | 30 | * Retrieves the list of regex scripts by combining the scripts from the extension settings and the character data |
| 30 | 31 | * |
| 31 | 32 | * @param {GetRegexScriptsOptions} optionoptions Options for retrieving the regex scripts |
| 32 | 33 | * @returns {RegexScript[]} An array of regex scripts, where each script is an object containing the necessary information. |
| 33 | 34 | */ |
| 34 | 35 | export function getRegexScripts(optionoptions = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) { |
| 35 | 36 | return [...Object.values(SCRIPT_TYPES).flatMap(type => getScriptsByType(type, optionoptions))]; |
| 36 | 37 | } |
| 37 | 38 | |
| 38 | 39 | /** |
| 39 | 40 | * Retrieves the regex scripts for a specific type. |
| 40 | 41 | * @param {SCRIPT_TYPES} scriptType The type of regex scripts to retrieve. |
| 41 | 42 | * @param {GetRegexScriptsOptions} optionoptions Options for retrieving the regex scripts |
| 42 | 43 | * @returns {RegexScript[]} An array of regex scripts for the specified type. |
| 43 | 44 | */ |
| 44 | 45 | export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) { |
| 45 | 46 | switch (scriptType) { |
| 47 | + case SCRIPT_TYPES.UNKNOWN: | |
| 48 | + return []; | |
| 46 | 49 | case SCRIPT_TYPES.GLOBAL: |
| 47 | 50 | return extension_settings.regex ?? []; |
| 48 | 51 | case SCRIPT_TYPES.SCOPED: { |
| @@ -53,14 +56,16 @@ export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX | ||
| 53 | 56 | return Array.isArray(scopedScripts) ? scopedScripts : []; |
| 54 | 57 | } |
| 55 | 58 | default: |
| 59 | + console.warn(`getScriptsByType: Invalid script type ${scriptType}`); | |
| 56 | 60 | return []; |
| 57 | 61 | } |
| 58 | 62 | } |
| 59 | 63 | |
| 60 | 64 | /** |
| 61 | 65 | * @enum {number} Where the regex script should be applied |
| 66 | + * @readonly | |
| 62 | 67 | */ |
| 63 | 68 | export const regex_placement = { |
| 64 | 69 | /** |
| 65 | 70 | * @deprecated MD Display is deprecated. Do not use. |
| 66 | 71 | */ |
| @@ -73,6 +78,10 @@ const regex_placement = { | ||
| 73 | 78 | REASONING: 6, |
| 74 | 79 | }; |
| 75 | 80 | |
| 81 | +/** | |
| 82 | + * @enum {number} How to substitute parameters in the find regex | |
| 83 | + * @readonly | |
| 84 | + */ | |
| 76 | 85 | export const substitute_find_regex = { |
| 77 | 86 | NONE: 0, |
| 78 | 87 | RAW: 1, |
| @@ -109,7 +118,7 @@ function sanitizeRegexMacro(x) { | ||
| 109 | 118 | * @returns {string} The regexed string |
| 110 | 119 | * @typedef {{characterOverride?: string, isMarkdown?: boolean, isPrompt?: boolean, isEdit?: boolean, depth?: number }} RegexParams The parameters to use for the regex script |
| 111 | 120 | */ |
| 112 | 121 | export function getRegexedString(rawString, placement, { characterOverride, isMarkdown, isPrompt, isEdit, depth } = {}) { |
| 113 | 122 | // WTF have you passed me? |
| 114 | 123 | if (typeof rawString !== 'string') { |
| 115 | 124 | console.warn('getRegexedString: rawString is not a string. Returning empty string.'); |
| @@ -166,7 +175,7 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown, | ||
| 166 | 175 | * @returns {string} The new string |
| 167 | 176 | * @typedef {{characterOverride?: string}} RegexScriptParams The parameters to use for the regex script |
| 168 | 177 | */ |
| 169 | 178 | export function runRegexScript(regexScript, rawString, { characterOverride } = {}) { |
| 170 | 179 | let newString = rawString; |
| 171 | 180 | if (!regexScript || !!(regexScript.disabled) || !regexScript?.findRegex || !rawString) { |
| 172 | 181 | return newString; |
| @@ -476,10 +476,12 @@ function setToggleAllIcon(allAreChecked) { | ||
| 476 | 476 | selectAllIcon.toggleClass('fa-minus', allAreChecked); |
| 477 | 477 | } |
| 478 | 478 | |
| 479 | +/** | |
| 480 | + * Sets the visibility of the bulk move buttons based on selected scripts. | |
| 481 | + */ | |
| 479 | 482 | function setMoveButtonsVisibility() { |
| 480 | 483 | const hasGlobalScripts = $('#saved_regex_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; |
| 481 | - const hasScopedScripts = | |
| 484 | + const hasScopedScripts = $('#saved_scoped_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; | |
| 482 | - $('#saved_scoped_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; | |
| 483 | 485 | $('#bulk_regex_move_to_global').toggle(hasScopedScripts); |
| 484 | 486 | $('#bulk_regex_move_to_scoped').toggle(hasGlobalScripts); |
| 485 | 487 | } |
| @@ -538,7 +540,7 @@ async function saveRegexScript(regexScript, existingScriptIndex, scriptType, sav | ||
| 538 | 540 | |
| 539 | 541 | // Reload the current chat to undo previous markdown |
| 540 | 542 | const currentChatId = getCurrentChatId(); |
| 541 | - if (currentChatId !== undefined && currentChatId !== null) { | |
| 543 | + if (currentChatId) { | |
| 542 | 544 | await reloadCurrentChat(); |
| 543 | 545 | } |
| 544 | 546 | } |
| @@ -550,10 +552,10 @@ async function saveRegexScript(regexScript, existingScriptIndex, scriptType, sav | ||
| 550 | 552 | } |
| 551 | 553 | |
| 552 | 554 | /** |
| 553 | 555 | * Delete a regex script by ID |
| 554 | 556 | * @param {string} id ID of the script to delete |
| 555 | 557 | * @param {SCRIPT_TYPES} scriptType global? scoped? |
| 556 | 558 | * @param {boolean} saveSettings Whether to save the settings immediately |
| 557 | 559 | * @returns {Promise<void>} |
| 558 | 560 | */ |
| 559 | 561 | async function deleteRegexScript(id, scriptType, saveSettings = true) { |
| @@ -575,17 +577,21 @@ async function deleteRegexScript(id, scriptType, saveSettings = true) { | ||
| 575 | 577 | |
| 576 | 578 | /** |
| 577 | 579 | * Move a regex script from one type to another |
| 578 | 580 | * @param {import('../../char-data.js').RegexScriptData} script The script to move |
| 579 | 581 | * @param {SCRIPT_TYPES} toType Target type |
| 580 | 582 | * @param {SCRIPT_TYPES|null} fromType Source type, if null it will be determined automatically |
| 581 | 583 | * @param {boolean} saveSettings Whether to save the settings immediately |
| 582 | 584 | * @returns {Promise<void>} |
| 583 | 585 | */ |
| 584 | 586 | async function moveRegexScript(script, toType, fromType = null, saveSettings = true) { |
| 585 | 587 | if (!fromTypeObject.values(SCRIPT_TYPES).includes(toType)) { |
| 588 | + console.warn(`moveRegexScript: Invalid target script type ${toType}`); | |
| 589 | + return; | |
| 590 | + } | |
| 591 | + if (!Object.values(SCRIPT_TYPES).includes(fromType)) { | |
| 586 | 592 | fromType = getScriptType(script); |
| 587 | 593 | } |
| 588 | 594 | if (fromType === toType || fromType === -1SCRIPT_TYPES.UNKNOWN || toType === SCRIPT_TYPES.UNKNOWN) { |
| 589 | 595 | return; |
| 590 | 596 | } |
| 591 | 597 | await deleteRegexScript(script.id, fromType, false); |
| @@ -1234,13 +1240,13 @@ async function onRegexDebuggerOpenClick() { | ||
| 1234 | 1240 | }); |
| 1235 | 1241 | |
| 1236 | 1242 | popupContainer.append(navPanel).append(contentPanel); |
| 1237 | 1243 | callGenericPopup(popupContainer, POPUP_TYPE.TEXT, 't`Step-by-step Transformation'`, { wide: true, allowVerticalScrolling: false }); |
| 1238 | 1244 | }); |
| 1239 | 1245 | |
| 1240 | 1246 | debuggerHtml.find('#regex_debugger_expand_final').on('click', function () { |
| 1241 | 1247 | const content = $('#regex_debugger_final_output').html(); |
| 1242 | 1248 | const popupContent = $('<div class="regex-popup-content"></div>').html(content); |
| 1243 | 1249 | callGenericPopup(popupContent, POPUP_TYPE.TEXT, 't`Final Output'`, { wide: true, large: true, allowVerticalScrolling: true }); |
| 1244 | 1250 | }); |
| 1245 | 1251 | |
| 1246 | 1252 | await callGenericPopup(debuggerHtml.children(), POPUP_TYPE.TEXT, '', { wide: true, allowVerticalScrolling: true }); |
| @@ -1465,10 +1471,23 @@ async function onRegexImportFileChange(file, scriptType) { | ||
| 1465 | 1471 | } |
| 1466 | 1472 | } |
| 1467 | 1473 | |
| 1474 | +/** | |
| 1475 | + * Determines the type of a given script. | |
| 1476 | + * @param {RegexScript} script The script to check | |
| 1477 | + * @returns {SCRIPT_TYPES} The script type. | |
| 1478 | + */ | |
| 1468 | 1479 | function getScriptType(script) { |
| 1469 | - return getScriptsByType(SCRIPT_TYPES.SCOPED).some(s => s.id === script.id) | |
| 1480 | + const scopedScripts = getScriptsByType(SCRIPT_TYPES.SCOPED); | |
| 1470 | - ? SCRIPT_TYPES.SCOPED | |
| 1481 | + if (scopedScripts.some(s => s.id === script.id)) { | |
| 1471 | 1482 | :return SCRIPT_TYPES.GLOBALSCOPED; |
| 1483 | + } | |
| 1484 | + | |
| 1485 | + const globalScripts = getScriptsByType(SCRIPT_TYPES.GLOBAL); | |
| 1486 | + if (globalScripts.some(s => s.id === script.id)) { | |
| 1487 | + return SCRIPT_TYPES.GLOBAL; | |
| 1488 | + } | |
| 1489 | + | |
| 1490 | + return SCRIPT_TYPES.UNKNOWN; | |
| 1472 | 1491 | } |
| 1473 | 1492 | |
| 1474 | 1493 | function getSelectedScripts() { |
| @@ -1591,6 +1610,7 @@ jQuery(async () => { | ||
| 1591 | 1610 | |
| 1592 | 1611 | checkboxes.prop('checked', newState); |
| 1593 | 1612 | setToggleAllIcon(newState); |
| 1613 | + setMoveButtonsVisibility(); | |
| 1594 | 1614 | }); |
| 1595 | 1615 | |
| 1596 | 1616 | $('#bulk_enable_regex').on('click', async function () { |
| @@ -1637,12 +1657,26 @@ jQuery(async () => { | ||
| 1637 | 1657 | |
| 1638 | 1658 | // Reload the current chat to undo previous markdown |
| 1639 | 1659 | const currentChatId = getCurrentChatId(); |
| 1640 | - if (currentChatId !== undefined && currentChatId !== null) { | |
| 1660 | + if (currentChatId) { | |
| 1641 | 1661 | await reloadCurrentChat(); |
| 1642 | 1662 | } |
| 1643 | 1663 | } |
| 1644 | - $('#bulk_regex_move_to_global').on('click', () => bulkMoveRegexScript(SCRIPT_TYPES.GLOBAL)); | |
| 1664 | + | |
| 1645 | 1665 | $('#bulk_regex_move_to_scopedbulk_regex_move_to_global').on('click', async () => bulkMoveRegexScript(SCRIPT_TYPES.SCOPED));{ |
| 1666 | + const confirm = await callGenericPopup(t`Are you sure you want to move the selected regex scripts to global?`, POPUP_TYPE.CONFIRM); | |
| 1667 | + if (!confirm) { | |
| 1668 | + return; | |
| 1669 | + } | |
| 1670 | + await bulkMoveRegexScript(SCRIPT_TYPES.GLOBAL); | |
| 1671 | + }); | |
| 1672 | + | |
| 1673 | + $('#bulk_regex_move_to_scoped').on('click', async () => { | |
| 1674 | + const confirm = await callGenericPopup(t`Are you sure you want to move the selected regex scripts to scoped?`, POPUP_TYPE.CONFIRM); | |
| 1675 | + if (!confirm) { | |
| 1676 | + return; | |
| 1677 | + } | |
| 1678 | + await bulkMoveRegexScript(SCRIPT_TYPES.SCOPED); | |
| 1679 | + }); | |
| 1646 | 1680 | |
| 1647 | 1681 | $('#bulk_delete_regex').on('click', async function () { |
| 1648 | 1682 | const scripts = getSelectedScripts(); |
| @@ -1650,7 +1684,7 @@ jQuery(async () => { | ||
| 1650 | 1684 | toastr.warning(t`No regex scripts selected for deletion.`); |
| 1651 | 1685 | return; |
| 1652 | 1686 | } |
| 1653 | 1687 | const confirm = await callGenericPopup('t`Are you sure you want to delete the selected regex scripts?'`, POPUP_TYPE.CONFIRM); |
| 1654 | 1688 | if (!confirm) { |
| 1655 | 1689 | return; |
| 1656 | 1690 | } |
| @@ -1750,9 +1784,10 @@ jQuery(async () => { | ||
| 1750 | 1784 | * @property {import('../../slash-commands/SlashCommandEnumValue.js').EnumType} color |
| 1751 | 1785 | * @property {string} icon |
| 1752 | 1786 | */ |
| 1787 | + | |
| 1753 | 1788 | /** |
| 1754 | 1789 | * @param {SCRIPT_TYPES} type The script type |
| 1755 | 1790 | * @returns {ScriptDecorators} The decorators for the script type |
| 1756 | 1791 | */ |
| 1757 | 1792 | function getScriptDecorators(type) { |
| 1758 | 1793 | switch (type) { |
| @@ -1768,13 +1803,15 @@ jQuery(async () => { | ||
| 1768 | 1803 | color: enumTypes.name, |
| 1769 | 1804 | icon: 'S', |
| 1770 | 1805 | }; |
| 1771 | - } | |
| 1806 | + default: | |
| 1772 | 1807 | return { |
| 1773 | 1808 | typename: 'Unknown', |
| 1774 | 1809 | color: enumTypes.variable, |
| 1775 | 1810 | icon: 'Unknown', |
| 1776 | 1811 | }; |
| 1777 | 1812 | } |
| 1813 | + } | |
| 1814 | + | |
| 1778 | 1815 | const localEnumProviders = { |
| 1779 | 1816 | regexScripts: () => |
| 1780 | 1817 | getRegexScripts().map(script => { |