| import { characters, eventSource, event_types, getCurrentChatId, messageFormatting, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js'; |
| import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; |
| import { selected_group } from '../../group-chats.js'; |
| import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; |
| import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; |
| import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js'; |
| import { allowPresetScripts, allowScopedScripts, disallowPresetScripts, disallowScopedScripts, getCurrentPresetAPI, getCurrentPresetName, getRegexScripts, getScriptsByType, isPresetScriptsAllowed, isScopedScriptsAllowed, regex_placement, RegexProvider, runRegexScript, saveScriptsByType, SCRIPT_TYPE_UNKNOWN, SCRIPT_TYPES, substitute_find_regex } from './engine.js'; |
| import { t } from '../../i18n.js'; |
| import { accountStorage } from '../../util/AccountStorage.js'; |
| import { getPresetManager } from '../../preset-manager.js'; |
|
|
| |
| export { getRegexScripts }; |
|
|
| const sanitizeFileName = name => name.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, '_').toLowerCase(); |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| class RegexPresetManager { |
| |
| presetSelect = null; |
|
|
| |
| presetCreateButton = null; |
|
|
| |
| presetUpdateButton = null; |
|
|
| |
| presetApplyButton = null; |
|
|
| |
| presetDeleteButton = null; |
|
|
| |
| currentPresetId = null; |
|
|
| |
| lastKnownState = null; |
|
|
| |
| |
| |
| |
| captureCurrentState() { |
| const globalScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL)); |
| const scopedScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED)); |
| const presetScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.PRESET)); |
|
|
| return { |
| global: globalScripts.map(item => item.id).sort(), |
| scoped: scopedScripts.map(item => item.id).sort(), |
| preset: presetScripts.map(item => item.id).sort(), |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| hasStateChanged(state1, state2) { |
| if (!state1 || !state2) return false; |
|
|
| const global1 = state1.global || []; |
| const global2 = state2.global || []; |
| const scoped1 = state1.scoped || []; |
| const scoped2 = state2.scoped || []; |
| const preset1 = state1.preset || []; |
| const preset2 = state2.preset || []; |
|
|
| if (global1.length !== global2.length || scoped1.length !== scoped2.length) { |
| return true; |
| } |
|
|
| return !global1.every(id => global2.includes(id)) || |
| !scoped1.every(id => scoped2.includes(id)) || |
| !preset1.every(id => preset2.includes(id)); |
| } |
|
|
| |
| |
| |
| |
| updateStoredState(presetId) { |
| this.currentPresetId = presetId; |
| this.lastKnownState = this.captureCurrentState(); |
| } |
|
|
| |
| |
| |
| |
| async checkUnsavedChanges() { |
| if (!this.currentPresetId || !this.lastKnownState) { |
| return true; |
| } |
|
|
| const currentState = this.captureCurrentState(); |
| if (!this.hasStateChanged(this.lastKnownState, currentState)) { |
| return true; |
| } |
|
|
| const currentPreset = extension_settings.regex_presets.find(p => p.id === this.currentPresetId); |
| const presetName = currentPreset ? currentPreset.name : t`Unknown Preset`; |
|
|
| const choice = await Popup.show.confirm( |
| t`You have unsaved changes to the "${presetName}" preset.`, |
| t`Do you want to save them before switching?`, |
| { |
| okButton: t`Save Changes`, |
| cancelButton: t`Discard Changes`, |
| }, |
| ); |
|
|
| if (choice) { |
| |
| await this.savePreset(this.currentPresetId, true); |
| this.renderPresetList(); |
| return true; |
| } |
|
|
| |
| return true; |
| } |
|
|
| |
| |
| |
| |
| setupEventListeners() { |
| this.presetSelect = (document.getElementById('regex_presets')); |
| if (!this.presetSelect) { |
| console.error('RegexPresetManager: Could not find preset select element in the DOM.'); |
| return; |
| } |
|
|
| this.presetSelect.addEventListener('change', async (event) => { |
| const selectedPresetId = this.presetSelect.value; |
| const fromSlashCommand = event instanceof CustomEvent && event?.detail?.fromSlashCommand === true; |
|
|
| |
| if (!fromSlashCommand) { |
| const canProceed = await this.checkUnsavedChanges(); |
| if (!canProceed) { |
| |
| event.preventDefault(); |
| const currentPreset = extension_settings.regex_presets.find(p => p.id === this.currentPresetId); |
| if (currentPreset) { |
| this.presetSelect.value = currentPreset.id; |
| } |
| return; |
| } |
| } |
|
|
| await this.applyPreset(selectedPresetId); |
| extension_settings.regex_presets.forEach(p => { p.isSelected = p.id === selectedPresetId; }); |
| saveSettingsDebounced(); |
| this.updateStoredState(selectedPresetId); |
| }); |
|
|
| this.presetCreateButton = document.getElementById('regex_preset_create'); |
| if (!this.presetCreateButton) { |
| console.error('RegexPresetManager: Could not find preset create button in the DOM.'); |
| return; |
| } |
|
|
| this.presetCreateButton.addEventListener('click', async () => { |
| const newId = uuidv4(); |
| await this.savePreset(newId, false); |
| this.renderPresetList(); |
| this.updateStoredState(newId); |
| }); |
|
|
| this.presetUpdateButton = document.getElementById('regex_preset_update'); |
| if (!this.presetUpdateButton) { |
| console.error('RegexPresetManager: Could not find preset update button in the DOM.'); |
| return; |
| } |
|
|
| this.presetUpdateButton.addEventListener('click', async () => { |
| const selectedPresetId = this.presetSelect.value; |
| await this.savePreset(selectedPresetId, true); |
| this.renderPresetList(); |
| this.updateStoredState(selectedPresetId); |
| }); |
|
|
| this.presetApplyButton = document.getElementById('regex_preset_apply'); |
| if (!this.presetApplyButton) { |
| console.error('RegexPresetManager: Could not find preset apply button in the DOM.'); |
| return; |
| } |
|
|
| this.presetApplyButton.addEventListener('click', async () => { |
| const selectedPresetId = this.presetSelect.value; |
| await this.applyPreset(selectedPresetId); |
| this.updateStoredState(selectedPresetId); |
| }); |
|
|
| this.presetDeleteButton = document.getElementById('regex_preset_delete'); |
| if (!this.presetDeleteButton) { |
| console.error('RegexPresetManager: Could not find preset delete button in the DOM.'); |
| return; |
| } |
|
|
| this.presetDeleteButton.addEventListener('click', async () => { |
| const selectedPresetId = this.presetSelect.value; |
| await this.deletePreset(selectedPresetId); |
| this.renderPresetList(); |
|
|
| const newSelectedPresetId = extension_settings.regex_presets.find(p => p.isSelected)?.id; |
| if (newSelectedPresetId) { |
| await this.applyPreset(newSelectedPresetId); |
| this.presetSelect.value = newSelectedPresetId; |
| this.updateStoredState(newSelectedPresetId); |
| } else { |
| this.currentPresetId = null; |
| this.lastKnownState = null; |
| } |
| }); |
|
|
| this.renderPresetList(); |
|
|
| |
| const selectedPreset = extension_settings.regex_presets?.find(p => p.isSelected); |
| if (selectedPreset) { |
| this.updateStoredState(selectedPreset.id); |
| } |
| } |
|
|
| |
| |
| |
| |
| registerSlashCommands() { |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| name: 'regex-preset', |
| helpString: t`Selects a regex preset by name or ID. Gets the current regex preset ID if no argument is provided.`, |
| callback: (args, name) => { |
| if (!this.presetSelect) { |
| return ''; |
| } |
|
|
| name = String(name ?? '').trim(); |
|
|
| if (name) { |
| const quiet = isTrueBoolean(args?.quiet?.toString()); |
| const foundId = extension_settings.regex_presets.find(p => equalsIgnoreCaseAndAccents(p.id, name) || equalsIgnoreCaseAndAccents(p.name, name))?.id; |
|
|
| if (foundId) { |
| this.presetSelect.value = foundId; |
| this.presetSelect.dispatchEvent(new CustomEvent('change', { detail: { fromSlashCommand: true } })); |
| return foundId; |
| } |
|
|
| !quiet && toastr.warning(`Regex preset "${name}" not found`); |
| return ''; |
| } |
|
|
| return this.presetSelect.value; |
| }, |
| returns: 'current preset ID', |
| namedArgumentList: [ |
| SlashCommandNamedArgument.fromProps({ |
| name: 'quiet', |
| description: 'Suppress the toast message on preset change', |
| typeList: [ARGUMENT_TYPE.BOOLEAN], |
| defaultValue: 'false', |
| enumList: commonEnumProviders.boolean('trueFalse')(), |
| }), |
| ], |
| unnamedArgumentList: [ |
| SlashCommandArgument.fromProps({ |
| description: 'regex preset name or ID', |
| typeList: [ARGUMENT_TYPE.STRING], |
| enumProvider: () => extension_settings.regex_presets.map(x => new SlashCommandEnumValue(x.id, x.name, enumTypes.enum, enumIcons.preset)), |
| }), |
| ], |
| })); |
| } |
|
|
| |
| |
| |
| |
| renderPresetList() { |
| if (!this.presetSelect) { |
| return; |
| } |
|
|
| this.presetSelect.innerHTML = ''; |
|
|
| if (!Array.isArray(extension_settings.regex_presets) || extension_settings.regex_presets.length === 0) { |
| const fallbackOption = new Option(t`[No presets saved]`, '', true, true); |
| this.presetSelect.appendChild(fallbackOption); |
| this.presetSelect.disabled = true; |
| return; |
| } |
|
|
| extension_settings.regex_presets.forEach(preset => { |
| const option = new Option(preset.name, preset.id, preset.isSelected, preset.isSelected); |
| this.presetSelect.appendChild(option); |
| }); |
|
|
| this.presetSelect.disabled = false; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async applyPresetList({ presetList, targetList, saveFunction }) { |
| if (!Array.isArray(targetList) || !Array.isArray(presetList)) { |
| return; |
| } |
|
|
| |
| targetList.forEach((script => { |
| script.disabled = !presetList.some(p => p.id === script.id); |
| })); |
|
|
| |
| targetList.sort((a, b) => { |
| const aIndex = presetList.findIndex(p => p.id === a.id); |
| const bIndex = presetList.findIndex(p => p.id === b.id); |
| return aIndex - bIndex || targetList.indexOf(a) - targetList.indexOf(b); |
| }); |
|
|
| await saveFunction(targetList); |
| } |
|
|
| |
| |
| |
| |
| |
| async applyPreset(presetId) { |
| const preset = extension_settings.regex_presets.find(p => p.id === presetId); |
| if (!preset) { |
| toastr.error(t`Could not find the selected preset.`); |
| return; |
| } |
|
|
| |
| for (const scriptType of Object.values(SCRIPT_TYPES)) { |
| await this.applyPresetList({ |
| presetList: { |
| [SCRIPT_TYPES.GLOBAL]: preset.global, |
| [SCRIPT_TYPES.SCOPED]: preset.scoped, |
| [SCRIPT_TYPES.PRESET]: preset.preset, |
| }[scriptType], |
| targetList: getScriptsByType(scriptType), |
| saveFunction: scripts => saveScriptsByType(scripts, scriptType), |
| }); |
| } |
|
|
| |
| await loadRegexScripts(); |
| |
| await reloadCurrentChat(); |
| } |
|
|
| |
| |
| |
| |
| |
| regexListToPresetItems(list) { |
| if (!Array.isArray(list)) { |
| return null; |
| } |
|
|
| return list.filter(x => !x.disabled).map(s => ({ id: s.id })); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async savePreset(presetId, isUpdate) { |
| const existingPreset = isUpdate ? extension_settings.regex_presets.find(p => p.id === presetId) : null; |
|
|
| if (isUpdate && !existingPreset) { |
| toastr.error(t`Could not find the preset to update.`); |
| return; |
| } |
|
|
| const name = isUpdate ? existingPreset.name : await Popup.show.input(t`Enter a name for the new regex preset:`, ''); |
| const id = isUpdate ? existingPreset.id : presetId; |
|
|
| if (!name || !name.trim().length) { |
| return; |
| } |
|
|
| const preset = { |
| id: id, |
| name: name, |
| isSelected: false, |
| global: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL)), |
| scoped: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED)), |
| preset: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.PRESET)), |
| }; |
|
|
| if (isUpdate) { |
| Object.assign(existingPreset, preset); |
| } else { |
| extension_settings.regex_presets.push(preset); |
| } |
|
|
| extension_settings.regex_presets.forEach(p => { p.isSelected = p.id === id; }); |
| saveSettingsDebounced(); |
|
|
| toastr.success(isUpdate ? t`Regex preset updated` : t`Regex preset saved`); |
| } |
|
|
| |
| |
| |
| |
| |
| async deletePreset(presetId) { |
| const presetIndex = extension_settings.regex_presets.findIndex(p => p.id === presetId); |
| if (presetIndex === -1) { |
| toastr.error(t`Could not find the preset to delete.`); |
| return; |
| } |
|
|
| const presetName = extension_settings.regex_presets[presetIndex].name; |
| const confirm = await Popup.show.confirm(t`Are you sure you want to delete this regex preset?`, presetName); |
| if (!confirm) { |
| return; |
| } |
|
|
| extension_settings.regex_presets.splice(presetIndex, 1); |
|
|
| |
| extension_settings.regex_presets.forEach((p, i) => { p.isSelected = i === 0; }); |
| saveSettingsDebounced(); |
|
|
| toastr.success(t`Regex preset deleted`); |
| } |
| } |
|
|
| const presetManager = new RegexPresetManager(); |
|
|
| |
| |
| |
| |
| |
| |
| function setToggleAllIcon(allAreChecked) { |
| const selectAllIcon = $('#bulk_select_all_toggle').find('i'); |
| selectAllIcon.toggleClass('fa-check-double', !allAreChecked); |
| selectAllIcon.toggleClass('fa-minus', allAreChecked); |
| } |
|
|
| |
| |
| |
| function setMoveButtonsVisibility() { |
| const hasGlobalScripts = $('#saved_regex_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; |
| const hasScopedScripts = $('#saved_scoped_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; |
| const hasPresetScripts = $('#saved_preset_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; |
| $('#bulk_regex_move_to_global').toggle(hasScopedScripts || hasPresetScripts); |
| $('#bulk_regex_move_to_scoped').toggle(hasGlobalScripts || hasPresetScripts); |
| $('#bulk_regex_move_to_preset').toggle(hasGlobalScripts || hasScopedScripts); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async function saveRegexScript(regexScript, existingScriptIndex, scriptType, saveSettings = true) { |
| |
| const array = getScriptsByType(scriptType); |
|
|
| |
| if (!regexScript.id) { |
| regexScript.id = uuidv4(); |
| } |
|
|
| |
| if (!regexScript.scriptName) { |
| toastr.error(t`Could not save regex script: The script name was undefined or empty!`); |
| return; |
| } |
|
|
| |
| if (regexScript.findRegex.length === 0) { |
| toastr.warning(t`This regex script will not work, but was saved anyway: A find regex isn't present.`); |
| } |
|
|
| |
| if (regexScript.placement.length === 0) { |
| toastr.warning(t`This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!`); |
| } |
|
|
| if (existingScriptIndex !== -1) { |
| array[existingScriptIndex] = regexScript; |
| } else { |
| array.push(regexScript); |
| } |
|
|
| if (scriptType === SCRIPT_TYPES.SCOPED) { |
| await saveScriptsByType(array, SCRIPT_TYPES.SCOPED); |
| allowScopedScripts(characters?.[this_chid]); |
| } |
|
|
| if (scriptType === SCRIPT_TYPES.PRESET) { |
| await saveScriptsByType(array, SCRIPT_TYPES.PRESET); |
| allowPresetScripts(getCurrentPresetAPI(), getCurrentPresetName()); |
| } |
|
|
| if (saveSettings) { |
| saveSettingsDebounced(); |
| await loadRegexScripts(); |
|
|
| |
| const currentChatId = getCurrentChatId(); |
| if (currentChatId) { |
| await reloadCurrentChat(); |
| } |
| } |
|
|
| const debuggerPopup = $('#regex_debugger_popup'); |
| if (debuggerPopup.length) { |
| populateDebuggerRuleList(debuggerPopup.parent()); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function deleteRegexScript(id, scriptType, saveSettings = true) { |
| const array = getScriptsByType(scriptType); |
|
|
| const existingScriptIndex = array.findIndex(script => script.id === id); |
| if (existingScriptIndex !== -1) { |
| array.splice(existingScriptIndex, 1); |
|
|
| switch (scriptType) { |
| case SCRIPT_TYPES.GLOBAL: |
| |
| break; |
| case SCRIPT_TYPES.SCOPED: |
| await saveScriptsByType(array, SCRIPT_TYPES.SCOPED); |
| break; |
| case SCRIPT_TYPES.PRESET: |
| await saveScriptsByType(array, SCRIPT_TYPES.PRESET); |
| break; |
| default: |
| break; |
| } |
| if (saveSettings) { |
| saveSettingsDebounced(); |
| await loadRegexScripts(); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async function moveRegexScript(script, toType, fromType = null, saveSettings = true) { |
| if (!Object.values(SCRIPT_TYPES).includes(toType)) { |
| console.warn(`moveRegexScript: Invalid target script type ${toType}`); |
| return; |
| } |
| if (!Object.values(SCRIPT_TYPES).includes(fromType)) { |
| fromType = getScriptType(script); |
| } |
| if (fromType === toType || fromType === SCRIPT_TYPE_UNKNOWN || toType === SCRIPT_TYPE_UNKNOWN) { |
| return; |
| } |
| await deleteRegexScript(script.id, fromType, false); |
| await saveRegexScript(script, -1, toType, saveSettings); |
| } |
|
|
| async function loadRegexScripts() { |
| $('#saved_regex_scripts').empty(); |
| $('#saved_scoped_scripts').empty(); |
| $('#saved_preset_scripts').empty(); |
| setToggleAllIcon(false); |
|
|
| const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate')); |
|
|
| |
| |
| |
| |
| |
| |
| |
| function renderScript(container, script, scriptType, index) { |
| |
| const scriptHtml = scriptTemplate.clone(); |
| const save = () => saveRegexScript(script, index, scriptType); |
|
|
| if (!script.id) { |
| script.id = uuidv4(); |
| } |
|
|
| scriptHtml.attr('id', script.id); |
| scriptHtml.find('.regex_script_name').text(script.scriptName).attr('title', script.scriptName); |
| scriptHtml.find('.disable_regex').prop('checked', script.disabled ?? false) |
| .on('input', async function () { |
| script.disabled = !!$(this).prop('checked'); |
| await save(); |
| }); |
| scriptHtml.find('.regex-toggle-on').on('click', function () { |
| scriptHtml.find('.disable_regex').prop('checked', true).trigger('input'); |
| }); |
| scriptHtml.find('.regex-toggle-off').on('click', function () { |
| scriptHtml.find('.disable_regex').prop('checked', false).trigger('input'); |
| }); |
| scriptHtml.find('.edit_existing_regex').on('click', async function () { |
| await onRegexEditorOpenClick(scriptHtml.attr('id'), scriptType); |
| }); |
| scriptHtml.find('.move_to_global').on('click', async function () { |
| const confirm = await callGenericPopup(t`Are you sure you want to move this regex script to global?`, POPUP_TYPE.CONFIRM); |
|
|
| if (!confirm) { |
| return; |
| } |
| await moveRegexScript(script, SCRIPT_TYPES.GLOBAL, scriptType); |
| }); |
| scriptHtml.find('.move_to_scoped').on('click', async function () { |
| if (this_chid === undefined) { |
| toastr.error(t`No character selected.`); |
| return; |
| } |
| if (selected_group) { |
| toastr.error(t`Cannot edit scoped scripts in group chats.`); |
| return; |
| } |
| const confirm = await callGenericPopup(t`Are you sure you want to move this regex script to scoped?`, POPUP_TYPE.CONFIRM); |
| if (!confirm) { |
| return; |
| } |
| await moveRegexScript(script, SCRIPT_TYPES.SCOPED, scriptType); |
| }); |
| scriptHtml.find('.move_to_preset').on('click', async function () { |
| const confirm = await callGenericPopup( |
| t`Are you sure you want to move this regex script to preset?`, |
| POPUP_TYPE.CONFIRM, |
| ); |
| if (!confirm) { |
| return; |
| } |
| await moveRegexScript(script, SCRIPT_TYPES.PRESET, scriptType); |
| }); |
| scriptHtml.find('.export_regex').on('click', async function () { |
| const fileName = `regex-${sanitizeFileName(script.scriptName)}.json`; |
| const fileData = JSON.stringify(script, null, 4); |
| download(fileData, fileName, 'application/json'); |
| }); |
| scriptHtml.find('.delete_regex').on('click', async function () { |
| const confirm = await callGenericPopup(t`Are you sure you want to delete this regex script?`, POPUP_TYPE.CONFIRM); |
| if (!confirm) { |
| return; |
| } |
| await deleteRegexScript(script.id, scriptType); |
| await reloadCurrentChat(); |
| }); |
| scriptHtml.find('.regex_bulk_checkbox').on('change', function () { |
| setMoveButtonsVisibility(); |
| const checkboxes = $('#regex_container .regex_bulk_checkbox'); |
| const allAreChecked = checkboxes.length === checkboxes.filter(':checked').length; |
| setToggleAllIcon(allAreChecked); |
| }); |
| scriptHtml.find('input[name="regex_expand"]').on('change', function () { |
| if (!(this instanceof HTMLInputElement)) { |
| return; |
| } |
|
|
| if (!this.checked) { |
| return; |
| } |
|
|
| const closeMenuHandler = (e) => { |
| if (e.target instanceof HTMLElement) { |
| if (e.target.closest('.regex-script-label')) { |
| return; |
| } |
| this.checked = false; |
| document.removeEventListener('click', closeMenuHandler); |
| } |
| }; |
|
|
| |
| setTimeout(() => { |
| document.addEventListener('click', closeMenuHandler, { passive: true, once: false }); |
| }, 0); |
| }); |
|
|
| $(container).append(scriptHtml); |
| } |
|
|
| getScriptsByType(SCRIPT_TYPES.GLOBAL).forEach((script, index) => renderScript('#saved_regex_scripts', script, SCRIPT_TYPES.GLOBAL, index)); |
| getScriptsByType(SCRIPT_TYPES.SCOPED).forEach((script, index) => renderScript('#saved_scoped_scripts', script, SCRIPT_TYPES.SCOPED, index)); |
| getScriptsByType(SCRIPT_TYPES.PRESET).forEach((script, index) => renderScript('#saved_preset_scripts', script, SCRIPT_TYPES.PRESET, index)); |
|
|
| $('#regex_scoped_toggle').prop('checked', isScopedScriptsAllowed(characters?.[this_chid])); |
| $('#regex_preset_toggle').prop('checked', isPresetScriptsAllowed(getCurrentPresetAPI(), getCurrentPresetName())); |
|
|
| setMoveButtonsVisibility(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function onRegexEditorOpenClick(existingId, scriptType) { |
| const editorHtml = $(await renderExtensionTemplateAsync('regex', 'editor')); |
| const array = getScriptsByType(scriptType); |
|
|
| |
| let existingScriptIndex = -1; |
| if (existingId) { |
| existingScriptIndex = array.findIndex((script) => script.id === existingId); |
| if (existingScriptIndex !== -1) { |
| const existingScript = array[existingScriptIndex]; |
| if (existingScript.scriptName) { |
| editorHtml.find('.regex_script_name').val(existingScript.scriptName); |
| } else { |
| toastr.error('This script doesn\'t have a name! Please delete it.'); |
| return; |
| } |
|
|
| editorHtml.find('.find_regex').val(existingScript.findRegex || ''); |
| editorHtml.find('.regex_replace_string').val(existingScript.replaceString || ''); |
| editorHtml.find('.regex_trim_strings').val(existingScript.trimStrings?.join('\n') || []); |
| editorHtml.find('input[name="disabled"]').prop('checked', existingScript.disabled ?? false); |
| editorHtml.find('input[name="only_format_display"]').prop('checked', existingScript.markdownOnly ?? false); |
| editorHtml.find('input[name="only_format_prompt"]').prop('checked', existingScript.promptOnly ?? false); |
| editorHtml.find('input[name="run_on_edit"]').prop('checked', existingScript.runOnEdit ?? false); |
| editorHtml.find('select[name="substitute_regex"]').val(existingScript.substituteRegex ?? substitute_find_regex.NONE); |
| editorHtml.find('input[name="min_depth"]').val(existingScript.minDepth ?? ''); |
| editorHtml.find('input[name="max_depth"]').val(existingScript.maxDepth ?? ''); |
|
|
| existingScript.placement.forEach((element) => { |
| editorHtml |
| .find(`input[name="replace_position"][value="${element}"]`) |
| .prop('checked', true); |
| }); |
| } |
| } else { |
| editorHtml |
| .find('input[name="only_format_display"]') |
| .prop('checked', true); |
|
|
| editorHtml |
| .find('input[name="run_on_edit"]') |
| .prop('checked', true); |
|
|
| editorHtml |
| .find('input[name="replace_position"][value="1"]') |
| .prop('checked', true); |
| } |
|
|
| editorHtml.find('#regex_test_mode_toggle').on('click', function () { |
| editorHtml.find('#regex_test_mode').toggleClass('displayNone'); |
| updateTestResult(); |
| }); |
|
|
| function updateTestResult() { |
| updateInfoBlock(editorHtml); |
|
|
| if (!editorHtml.find('#regex_test_mode').is(':visible')) { |
| return; |
| } |
|
|
| const testScript = { |
| id: uuidv4(), |
| scriptName: editorHtml.find('.regex_script_name').val().toString(), |
| findRegex: editorHtml.find('.find_regex').val().toString(), |
| replaceString: editorHtml.find('.regex_replace_string').val().toString(), |
| trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [], |
| substituteRegex: Number(editorHtml.find('select[name="substitute_regex"]').val()), |
| disabled: false, |
| promptOnly: false, |
| markdownOnly: false, |
| runOnEdit: false, |
| minDepth: null, |
| maxDepth: null, |
| placement: null, |
| }; |
| const rawTestString = String(editorHtml.find('#regex_test_input').val()); |
| const result = runRegexScript(testScript, rawTestString); |
| editorHtml.find('#regex_test_output').text(result); |
| } |
|
|
| editorHtml.find('input, textarea, select').on('input', updateTestResult); |
| updateInfoBlock(editorHtml); |
|
|
| const popupResult = await callGenericPopup(editorHtml, POPUP_TYPE.CONFIRM, '', { okButton: t`Save`, cancelButton: t`Cancel`, allowVerticalScrolling: true }); |
| if (popupResult) { |
| const newRegexScript = { |
| id: existingId ? String(existingId) : uuidv4(), |
| scriptName: String(editorHtml.find('.regex_script_name').val()), |
| findRegex: String(editorHtml.find('.find_regex').val()), |
| replaceString: String(editorHtml.find('.regex_replace_string').val()), |
| trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [], |
| placement: |
| editorHtml |
| .find('input[name="replace_position"]') |
| .filter(':checked') |
| .map(function () { return parseInt($(this).val().toString()); }) |
| .get() |
| .filter((e) => !isNaN(e)) || [], |
| disabled: editorHtml.find('input[name="disabled"]').prop('checked'), |
| markdownOnly: editorHtml.find('input[name="only_format_display"]').prop('checked'), |
| promptOnly: editorHtml.find('input[name="only_format_prompt"]').prop('checked'), |
| runOnEdit: editorHtml.find('input[name="run_on_edit"]').prop('checked'), |
| substituteRegex: Number(editorHtml.find('select[name="substitute_regex"]').val()), |
| minDepth: parseInt(String(editorHtml.find('input[name="min_depth"]').val())), |
| maxDepth: parseInt(String(editorHtml.find('input[name="max_depth"]').val())), |
| }; |
|
|
| saveRegexScript(newRegexScript, existingScriptIndex, scriptType); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function buildReplacementHtml(match, pattern) { |
| const container = document.createDocumentFragment(); |
| let lastIndex = 0; |
| const backrefRegex = /\$\$|\$&|\$`|\$'|\$(\d{1,2})/g; |
|
|
| let reMatch; |
| while ((reMatch = backrefRegex.exec(pattern)) !== null) { |
| |
| const literalPart = pattern.substring(lastIndex, reMatch.index); |
| if (literalPart) { |
| const mark = document.createElement('mark'); |
| mark.className = 'green_hl'; |
| mark.innerText = literalPart; |
| container.appendChild(mark); |
| } |
|
|
| const backref = reMatch[0]; |
| if (backref === '$$') { |
| container.appendChild(document.createTextNode('$')); |
| } else if (backref === '$&') { |
| const mark = document.createElement('mark'); |
| mark.className = 'yellow_hl'; |
| mark.innerText = match[0]; |
| container.appendChild(mark); |
| } else if (backref === '$`') { |
| container.appendChild(document.createTextNode(match.input.substring(0, match.index))); |
| } else if (backref === '$\'') { |
| container.appendChild(document.createTextNode(match.input.substring(match.index + match[0].length))); |
| } else { |
| const groupIndex = parseInt(reMatch[1], 10); |
| if (groupIndex > 0 && groupIndex < match.length && match[groupIndex] !== undefined) { |
| const mark = document.createElement('mark'); |
| mark.className = 'yellow_hl'; |
| mark.innerText = match[groupIndex]; |
| container.appendChild(mark); |
| } else { |
| |
| const mark = document.createElement('mark'); |
| mark.className = 'green_hl'; |
| mark.innerText = backref; |
| container.appendChild(mark); |
| } |
| } |
| lastIndex = backrefRegex.lastIndex; |
| } |
|
|
| |
| const finalLiteralPart = pattern.substring(lastIndex); |
| if (finalLiteralPart) { |
| const mark = document.createElement('mark'); |
| mark.className = 'green_hl'; |
| mark.innerText = finalLiteralPart; |
| container.appendChild(mark); |
| } |
|
|
| |
| const tempDiv = document.createElement('div'); |
| tempDiv.appendChild(container); |
| return tempDiv.innerHTML; |
| } |
|
|
| function executeRegexScriptForDebugging(script, text) { |
| let err; |
| let originalRegex; |
|
|
| try { |
| originalRegex = regexFromString(script.findRegex); |
| if (!originalRegex) throw new Error('Invalid regex string'); |
| } catch (e) { |
| err = `Compile error: ${e.message}`; |
| return { output: text, highlightedOutput: text, error: err, charsCaptured: 0, charsAdded: 0, charsRemoved: 0 }; |
| } |
|
|
| const globalRegex = new RegExp(originalRegex.source, originalRegex.flags.includes('g') ? originalRegex.flags : originalRegex.flags + 'g'); |
| const matches = [...text.matchAll(globalRegex)]; |
|
|
| if (matches.length === 0) { |
| return { output: text, highlightedOutput: escapeHtml(text), error: null, charsCaptured: 0, charsAdded: 0, charsRemoved: 0 }; |
| } |
|
|
| let outputText = ''; |
| let highlightedOutput = ''; |
| let lastIndex = 0; |
| let totalCharsCaptured = 0; |
| let totalCharsAdded = 0; |
| let totalCharsRemoved = 0; |
|
|
| try { |
| for (const match of matches) { |
| const originalMatchText = match[0]; |
| totalCharsCaptured += originalMatchText.length; |
|
|
| |
| const precedingText = text.substring(lastIndex, match.index); |
| outputText += precedingText; |
| highlightedOutput += escapeHtml(precedingText); |
|
|
| |
| let charsAddedInMatch = 0; |
| let charsKeptFromMatch = 0; |
| const backrefRegex = /\$\$|\$&|\$`|\$'|\$(\d{1,2})/g; |
| let lastPatternIndex = 0; |
| let reMatch; |
| let replacementForPlainText = ''; |
|
|
| |
| while ((reMatch = backrefRegex.exec(script.replaceString)) !== null) { |
| const literalPart = script.replaceString.substring(lastPatternIndex, reMatch.index); |
| charsAddedInMatch += literalPart.length; |
| replacementForPlainText += literalPart; |
| const backref = reMatch[0]; |
| if (backref === '$$') { |
| replacementForPlainText += '$'; |
| } else if (backref === '$&') { |
| charsKeptFromMatch += (match[0] || '').length; replacementForPlainText += (match[0] || ''); |
| } else if (backref === '$`') { |
| const part = match.input.substring(0, match.index); charsKeptFromMatch += part.length; replacementForPlainText += part; |
| } else if (backref === '$\'') { |
| const part = match.input.substring(match.index + match[0].length); charsKeptFromMatch += part.length; replacementForPlainText += part; |
| } else { |
| const groupIndex = parseInt(reMatch[1], 10); |
| if (groupIndex > 0 && groupIndex < match.length && match[groupIndex] !== undefined) { |
| charsKeptFromMatch += match[groupIndex].length; |
| replacementForPlainText += match[groupIndex]; |
| } |
| } |
| lastPatternIndex = backrefRegex.lastIndex; |
| } |
| const finalLiteralPart = script.replaceString.substring(lastPatternIndex); |
| charsAddedInMatch += finalLiteralPart.length; |
| replacementForPlainText += finalLiteralPart; |
|
|
| totalCharsAdded += charsAddedInMatch; |
| totalCharsRemoved += (originalMatchText.length - charsKeptFromMatch); |
|
|
| outputText += replacementForPlainText; |
| |
|
|
| |
| |
| highlightedOutput += `<mark class='red_hl'>${escapeHtml(originalMatchText)}</mark>`; |
| |
| highlightedOutput += ' → '; |
| |
| highlightedOutput += buildReplacementHtml(match, script.replaceString); |
|
|
| lastIndex = match.index + originalMatchText.length; |
| } |
|
|
| |
| const trailingText = text.substring(lastIndex); |
| outputText += trailingText; |
| highlightedOutput += escapeHtml(trailingText); |
|
|
| } catch (e) { |
| err = (err ? err + '; ' : '') + `Replace error: ${e.message}`; |
| outputText = text; |
| highlightedOutput = escapeHtml(text); |
| } |
|
|
| return { |
| output: outputText, |
| highlightedOutput: highlightedOutput, |
| error: err, |
| charsCaptured: totalCharsCaptured, |
| charsAdded: totalCharsAdded, |
| charsRemoved: totalCharsRemoved, |
| }; |
| } |
|
|
| function populateDebuggerRuleList(container) { |
| const rulesContainer = container.find('#regex_debugger_rules'); |
| const ruleTemplate = container.find('#regex_debugger_rule_template'); |
| if (!rulesContainer.length || !ruleTemplate.length) { |
| console.error('Regex Debugger: Could not find rule list or template in the DOM.'); |
| return; |
| } |
|
|
| rulesContainer.empty(); |
|
|
| const allScripts = getRegexScripts(); |
| if (!allScripts || allScripts.length === 0) { |
| rulesContainer.append('<div class="regex-debugger-no-rules">' + t`No regex rules found.` + '</div>'); |
| return; |
| } |
|
|
| const globalScriptIds = new Set(getScriptsByType(SCRIPT_TYPES.GLOBAL).map(s => s.id)); |
| const scopedScriptIds = new Set(getScriptsByType(SCRIPT_TYPES.SCOPED).map(s => s.id)); |
| const presetScriptIds = new Set(getScriptsByType(SCRIPT_TYPES.PRESET).map(s => s.id)); |
| const globalScripts = []; |
| const scopedScripts = []; |
| const presetScripts = []; |
|
|
| allScripts.forEach(script => { |
| const scriptCopy = structuredClone(script); |
| if (globalScriptIds.has(script.id)) { |
| |
| scriptCopy.type = SCRIPT_TYPES.GLOBAL; |
| globalScripts.push(scriptCopy); |
| } else if (scopedScriptIds.has(script.id)) { |
| |
| scriptCopy.type = SCRIPT_TYPES.SCOPED; |
| scopedScripts.push(scriptCopy); |
| } else if (presetScriptIds.has(script.id)) { |
| |
| scriptCopy.type = SCRIPT_TYPES.PRESET; |
| presetScripts.push(scriptCopy); |
| } |
| }); |
|
|
| container.data('allScripts', [...globalScripts, ...presetScripts, ...scopedScripts]); |
|
|
| const renderRule = (script) => { |
| if (!script.id) script.id = uuidv4(); |
| const ruleElementContent = $(ruleTemplate.prop('content')).clone(); |
| const ruleElement = ruleElementContent.find('.regex-debugger-rule'); |
|
|
| ruleElement.attr('data-id', script.id); |
| |
| ruleElement.find('.rule-name').text(script.scriptName); |
| ruleElement.find('.rule-regex').text(script.findRegex); |
| |
| ruleElement |
| .find('.rule-scope') |
| .text( |
| { |
| [SCRIPT_TYPES.SCOPED]: t`Scoped`, |
| [SCRIPT_TYPES.GLOBAL]: t`Global`, |
| [SCRIPT_TYPES.PRESET]: t`Preset`, |
| }[script.type], |
| ); |
| ruleElement.find('.rule-enabled').prop('checked', !script.disabled); |
| |
| ruleElement.find('.edit_rule').on('click', () => onRegexEditorOpenClick(script.id, script.type)); |
|
|
| ruleElement.on('click', function (event) { |
| if ($(event.target).is('input, .menu_button, .menu_button i')) { |
| return; |
| } |
| const scriptId = $(this).data('id'); |
| const stepElement = $(`#step-result-${scriptId}`); |
| const container = $('#regex_debugger_steps_output'); |
|
|
| if (stepElement.length && container.length) { |
| |
| const targetTop = stepElement.position().top; |
| const containerScrollTop = container.scrollTop(); |
| const containerHeight = container.height(); |
|
|
| |
| let scrollTo = containerScrollTop + targetTop - (containerHeight / 2) + (stepElement.height() / 2); |
|
|
| container.animate({ scrollTop: scrollTo }, 300); |
|
|
| stepElement.css('transition', 'background-color 0.5s').css('background-color', 'var(--highlight_color)'); |
| setTimeout(() => stepElement.css('background-color', ''), 1000); |
| } |
| }); |
|
|
| return ruleElementContent; |
| }; |
|
|
| if (globalScripts.length > 0) { |
| rulesContainer.append('<div class="list-header regex-debugger-list-header">' + t`Global Rules` + '</div>'); |
| const globalList = $('<ul id="regex_debugger_rules_global" class="sortable-list"></ul>'); |
| globalScripts.forEach(script => globalList.append(renderRule(script))); |
| rulesContainer.append(globalList); |
| } |
|
|
| if (presetScripts.length > 0) { |
| rulesContainer.append('<div class="list-header regex-debugger-list-header">' + t`Preset Rules` + '</div>'); |
| const presetList = $('<ul id="regex_debugger_rules_preset" class="sortable-list"></ul>'); |
| presetScripts.forEach(script => presetList.append(renderRule(script))); |
| rulesContainer.append(presetList); |
| } |
|
|
| if (scopedScripts.length > 0) { |
| rulesContainer.append('<div class="list-header regex-debugger-list-header">' + t`Scoped Rules` + '</div>'); |
| const scopedList = $('<ul id="regex_debugger_rules_scoped" class="sortable-list"></ul>'); |
| scopedScripts.forEach(script => scopedList.append(renderRule(script))); |
| rulesContainer.append(scopedList); |
| } |
| } |
|
|
| |
| |
| |
| |
| async function onRegexDebuggerOpenClick() { |
| const templateContent = await renderExtensionTemplateAsync('regex', 'debugger'); |
| const debuggerHtml = $('<div>').html(templateContent); |
|
|
| const stepTemplate = debuggerHtml.find('#regex_debugger_step_template'); |
|
|
| populateDebuggerRuleList(debuggerHtml); |
|
|
| |
| debuggerHtml.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection(); |
| |
| debuggerHtml.find('#regex_debugger_rules_scoped').sortable({ delay: getSortableDelay() }).disableSelection(); |
| |
| debuggerHtml.find('#regex_debugger_rules_preset').sortable({ delay: getSortableDelay() }).disableSelection(); |
|
|
| debuggerHtml.find('#regex_debugger_run_test').on('click', function () { |
| const allScripts = debuggerHtml.data('allScripts'); |
| const orderedRuleIds = [ |
| ...$('#regex_debugger_rules_global').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(), |
| ...$('#regex_debugger_rules_scoped').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(), |
| ...$('#regex_debugger_rules_preset').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(), |
| ]; |
|
|
| const rawInput = String($('#regex_debugger_raw_input').val()); |
| const stepsOutput = $('#regex_debugger_steps_output'); |
| const finalOutput = $('#regex_debugger_final_output'); |
|
|
| if (!stepsOutput.length || !finalOutput.length) return; |
|
|
| const displayMode = $('input[name="display_mode"]:checked').val(); |
| stepsOutput.empty(); |
| finalOutput.empty(); |
| $('#regex_debugger_final_summary').remove(); |
|
|
| if (!allScripts) return; |
| let textForNextStep = rawInput; |
| let totalCharsCaptured = 0; |
| let totalCharsAdded = 0; |
| let totalCharsRemoved = 0; |
|
|
| orderedRuleIds.forEach(scriptId => { |
| const ruleElement = $(`#regex_debugger_rules [data-id="${scriptId}"]`); |
| if (!ruleElement.find('.rule-enabled').is(':checked')) return; |
|
|
| const script = allScripts.find(s => s.id === scriptId); |
|
|
| if (script) { |
| const result = executeRegexScriptForDebugging(script, textForNextStep); |
| totalCharsCaptured += result.charsCaptured; |
| totalCharsAdded += result.charsAdded; |
| totalCharsRemoved += result.charsRemoved; |
|
|
| const stepElement = $(stepTemplate.prop('content')).clone(); |
| |
| stepElement.find('>:first-child').attr('id', `step-result-${script.id}`); |
| const stepHeader = stepElement.find('.step-header'); |
| stepHeader.find('strong').text(t`After:` + ` ${script.scriptName}`); |
|
|
| const metricsHtml = '<span class="step-metrics">' + t`Captured:` + ` ${result.charsCaptured}, ` + t`Added:` + ` +${result.charsAdded}, ` + t`Removed:` + ` -${result.charsRemoved}</span>`; |
| stepHeader.append(metricsHtml); |
|
|
| if (displayMode === 'highlight') { |
| stepElement.find('.step-output').html(result.highlightedOutput); |
| } else { |
| stepElement.find('.step-output').text(result.output); |
| } |
|
|
| if (result.error) { |
| stepHeader.append($(`<div class='warning_text text_rose-500'>${result.error}</div>`)); |
| } |
|
|
| stepsOutput.append(stepElement); |
| textForNextStep = result.output; |
| } |
| }); |
|
|
| const summaryHtml = ` |
| <div id="regex_debugger_final_summary" class="regex-debugger-summary"> |
| <strong>` + t`Total Captured:` + `</strong> ${totalCharsCaptured} | <strong>` + t`Total Added:` + `</strong> +${totalCharsAdded} | <strong>` + t`Total Removed:` + `</strong> -${totalCharsRemoved} |
| </div> |
| `; |
| finalOutput.before(summaryHtml); |
|
|
| const renderMode = $('#regex_debugger_render_mode').val(); |
| if (renderMode === 'message') { |
| const formattedHtml = messageFormatting(textForNextStep, 'Debugger', true, false, null); |
| const messageBlock = $('<div class="mes"><div class="mes_text"></div></div>'); |
| messageBlock.find('.mes_text').html(formattedHtml); |
| finalOutput.append(messageBlock); |
| } else { |
| finalOutput.text(textForNextStep); |
| } |
| }); |
|
|
| debuggerHtml.find('#regex_debugger_save_order').on('click', async function () { |
| const allKnownScripts = getRegexScripts(); |
| const newGlobalScripts = $('#regex_debugger_rules_global').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean); |
| const newScopedScripts = $('#regex_debugger_rules_scoped').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean); |
| const newPresetScripts = $('#regex_debugger_rules_preset').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean); |
|
|
| extension_settings.regex = newGlobalScripts; |
| if (this_chid !== undefined) { |
| await saveScriptsByType(newScopedScripts, SCRIPT_TYPES.SCOPED); |
| } |
| await saveScriptsByType(newPresetScripts, SCRIPT_TYPES.PRESET); |
|
|
| saveSettingsDebounced(); |
| await loadRegexScripts(); |
| toastr.success(t`Regex script order saved!`); |
|
|
| const currentPopupContent = $('div:has(> #regex_debugger_rules)'); |
| populateDebuggerRuleList(currentPopupContent); |
| |
| currentPopupContent.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection(); |
| |
| currentPopupContent.find('#regex_debugger_rules_scoped').sortable({ delay: getSortableDelay() }).disableSelection(); |
| |
| currentPopupContent.find('#regex_debugger_rules_preset').sortable({ delay: getSortableDelay() }).disableSelection(); |
| }); |
|
|
| debuggerHtml.find('#regex_debugger_expand_steps').on('click', function () { |
| const popupContainer = $('<div class="expanded-regex-container"></div>'); |
| const navPanel = $('<div class="expanded-regex-nav"><h4>Steps</h4></div>'); |
| const contentPanel = $('<div class="expanded-regex-content"></div>'); |
|
|
| const content = $('#regex_debugger_steps_output').clone().html(); |
| contentPanel.html(content); |
|
|
| $('#regex_debugger_rules .regex-debugger-rule').each(function () { |
| const ruleElement = $(this); |
| const scriptId = ruleElement.data('id'); |
| const scriptName = ruleElement.find('.rule-name').text(); |
|
|
| const link = $(`<a href="#">${escapeHtml(scriptName)}</a>`); |
| link.data('target-id', `step-result-${scriptId}`); |
|
|
| link.on('click', function (e) { |
| e.preventDefault(); |
| navPanel.find('a').removeClass('active'); |
| $(this).addClass('active'); |
|
|
| const targetId = $(this).data('target-id'); |
| |
| const targetElement = contentPanel.find(`#${targetId}`); |
|
|
| if (targetElement.length) { |
| const scrollTo = contentPanel.scrollTop() + targetElement.position().top; |
| contentPanel.animate({ scrollTop: scrollTo }, 300); |
|
|
| targetElement.css('transition', 'background-color 0.5s').css('background-color', 'var(--highlight_color)'); |
| setTimeout(() => targetElement.css('background-color', ''), 1000); |
| } |
| }); |
|
|
| navPanel.append(link); |
| }); |
|
|
| popupContainer.append(navPanel).append(contentPanel); |
| callGenericPopup(popupContainer, POPUP_TYPE.TEXT, t`Step-by-step Transformation`, { wide: true, allowVerticalScrolling: false }); |
| }); |
|
|
| debuggerHtml.find('#regex_debugger_expand_final').on('click', function () { |
| const content = $('#regex_debugger_final_output').html(); |
| const popupContent = $('<div class="regex-popup-content"></div>').html(content); |
| callGenericPopup(popupContent, POPUP_TYPE.TEXT, t`Final Output`, { wide: true, large: true, allowVerticalScrolling: true }); |
| }); |
|
|
| await callGenericPopup(debuggerHtml.children(), POPUP_TYPE.TEXT, '', { wide: true, allowVerticalScrolling: true }); |
| } |
|
|
| |
| |
| |
| |
| function updateInfoBlock(editorHtml) { |
| const infoBlock = editorHtml.find('.info-block').get(0); |
| const infoBlockFlagsHint = editorHtml.find('#regex_info_block_flags_hint'); |
| const findRegex = String(editorHtml.find('.find_regex').val()); |
|
|
| infoBlockFlagsHint.hide(); |
|
|
| |
| if (!findRegex) { |
| setInfoBlock(infoBlock, t`Find Regex is empty`, 'info'); |
| return; |
| } |
|
|
| try { |
| const regex = regexFromString(findRegex); |
| if (!regex) { |
| throw new Error(t`Invalid Find Regex`); |
| } |
|
|
| const flagInfo = []; |
| flagInfo.push(regex.flags.includes('g') ? t`Applies to all matches` : t`Applies to the first match`); |
| flagInfo.push(regex.flags.includes('i') ? t`Case insensitive` : t`Case sensitive`); |
|
|
| setInfoBlock(infoBlock, flagInfo.join('. '), 'hint'); |
| infoBlockFlagsHint.show(); |
| } catch (error) { |
| setInfoBlock(infoBlock, error.message, 'error'); |
| } |
| } |
|
|
| |
| |
| function migrateSettings() { |
| let performSave = false; |
|
|
| |
| extension_settings.regex.forEach((script) => { |
| if (!script.id) { |
| script.id = uuidv4(); |
| performSave = true; |
| } |
|
|
| if (!Array.isArray(script.placement)) { |
| script.placement = []; |
| performSave = true; |
| } |
|
|
| if (script.placement.includes(regex_placement.MD_DISPLAY)) { |
| script.placement = script.placement.length === 1 ? |
| Object.values(regex_placement).filter((e) => e !== regex_placement.MD_DISPLAY) : |
| script.placement = script.placement.filter((e) => e !== regex_placement.MD_DISPLAY); |
|
|
| script.markdownOnly = true; |
| script.promptOnly = true; |
|
|
| performSave = true; |
| } |
|
|
| |
| |
| if (script.placement.includes(4)) { |
| script.placement = script.placement.length === 1 ? |
| [regex_placement.SLASH_COMMAND] : |
| script.placement = script.placement.filter((e) => e !== 4); |
|
|
| performSave = true; |
| } |
| }); |
|
|
| if (performSave) { |
| saveSettingsDebounced(); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function runRegexCallback(args, value) { |
| if (!args.name) { |
| toastr.warning('No regex script name provided.'); |
| return value; |
| } |
|
|
| const scriptName = args.name; |
| const scripts = getRegexScripts(); |
|
|
| for (const script of scripts) { |
| if (script.scriptName.toLowerCase() === scriptName.toLowerCase()) { |
| if (script.disabled) { |
| toastr.warning(t`Regex script "${scriptName}" is disabled.`); |
| return value; |
| } |
|
|
| console.debug(`Running regex callback for ${scriptName}`); |
| return runRegexScript(script, value); |
| } |
| } |
|
|
| toastr.warning(`Regex script "${scriptName}" not found.`); |
| return value; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function toggleRegexCallback(args, scriptName) { |
| if (typeof scriptName !== 'string') throw new Error('Script name must be a string.'); |
|
|
| const quiet = isTrueBoolean(args?.quiet); |
| const action = isTrueBoolean(args?.state) ? 'enable' : |
| isFalseBoolean(args?.state) ? 'disable' : |
| 'toggle'; |
|
|
| const scripts = getRegexScripts(); |
| const script = scripts.find(s => equalsIgnoreCaseAndAccents(s.scriptName, scriptName)); |
|
|
| if (!script) { |
| toastr.warning(t`Regex script '${scriptName}' not found.`); |
| return ''; |
| } |
|
|
| switch (action) { |
| case 'enable': |
| script.disabled = false; |
| break; |
| case 'disable': |
| script.disabled = true; |
| break; |
| default: |
| script.disabled = !script.disabled; |
| break; |
| } |
|
|
| const scriptType = getScriptType(script); |
| const index = getScriptsByType(scriptType).indexOf(script); |
|
|
| await saveRegexScript(script, index, scriptType); |
| if (script.disabled) { |
| !quiet && toastr.success(t`Regex script '${scriptName}' has been disabled.`); |
| } else { |
| !quiet && toastr.success(t`Regex script '${scriptName}' has been enabled.`); |
| } |
|
|
| return script.scriptName || ''; |
| } |
|
|
| |
| |
| |
| |
| |
| async function onRegexImportObjectChange(regexScript, scriptType) { |
| try { |
| if (!regexScript.scriptName) { |
| throw new Error('No script name provided.'); |
| } |
|
|
| |
| regexScript.id = uuidv4(); |
|
|
| const array = getScriptsByType(scriptType); |
| array.push(regexScript); |
|
|
| switch (scriptType) { |
| case SCRIPT_TYPES.GLOBAL: |
| |
| break; |
| case SCRIPT_TYPES.SCOPED: |
| await saveScriptsByType(array, SCRIPT_TYPES.SCOPED); |
| break; |
| case SCRIPT_TYPES.PRESET: |
| await saveScriptsByType(array, SCRIPT_TYPES.PRESET); |
| break; |
| default: |
| break; |
| } |
|
|
| saveSettingsDebounced(); |
| await loadRegexScripts(); |
| toastr.success(t`Regex script "${regexScript.scriptName}" imported.`); |
| } catch (error) { |
| console.log(error); |
| toastr.error(t`Invalid regex object.`); |
| return; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| async function onRegexImportFileChange(file, scriptType) { |
| if (!file) { |
| toastr.error('No file provided.'); |
| return; |
| } |
|
|
| try { |
| const regexScripts = JSON.parse(await getFileText(file)); |
| if (Array.isArray(regexScripts)) { |
| for (const regexScript of regexScripts) { |
| await onRegexImportObjectChange(regexScript, scriptType); |
| } |
| } else { |
| await onRegexImportObjectChange(regexScripts, scriptType); |
| } |
| } catch (error) { |
| console.log(error); |
| toastr.error('Invalid JSON file.'); |
| return; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function getScriptType(script) { |
| for (const scriptType of Object.values(SCRIPT_TYPES)) { |
| const scripts = getScriptsByType(scriptType); |
| if (scripts.some(s => s.id === script.id)) { |
| return scriptType; |
| } |
| } |
| return SCRIPT_TYPE_UNKNOWN; |
| } |
|
|
| function getSelectedScripts() { |
| const scripts = getRegexScripts(); |
| const selector = '#regex_container .regex-script-label:has(.regex_bulk_checkbox:checked)'; |
| const selectedIds = Array.from(document.querySelectorAll(selector)) |
| .map(e => e.getAttribute('id')) |
| .filter(id => id); |
| return scripts.filter(script => selectedIds.includes(script.id)); |
| } |
|
|
| function purgeEmbeddedRegexScripts({ character }) { |
| const avatar = character?.avatar; |
| if (!avatar) { |
| return; |
| } |
| const checkKey = `AlertRegex_${avatar}`; |
| if (accountStorage.getItem(checkKey)) { |
| accountStorage.removeItem(checkKey); |
| } |
| disallowScopedScripts(characters?.[this_chid]); |
| } |
|
|
| function purgePresetEmbeddedRegexScripts({ apiId, name }) { |
| const checkKey = `AlertRegex_${apiId}_${name}`; |
| if (accountStorage.getItem(checkKey)) { |
| accountStorage.removeItem(checkKey); |
| } |
| disallowPresetScripts(apiId, name); |
| } |
|
|
| async function checkCharEmbeddedRegexScripts() { |
| const chid = this_chid; |
|
|
| if (chid !== undefined && !selected_group) { |
| const character = characters[chid]; |
| const scripts = getScriptsByType(SCRIPT_TYPES.SCOPED); |
|
|
| if (Array.isArray(scripts) && scripts.length > 0) { |
| if (!isScopedScriptsAllowed(character)) { |
| const checkKey = `AlertRegex_${character.avatar}`; |
| if (!accountStorage.getItem(checkKey)) { |
| accountStorage.setItem(checkKey, 'true'); |
| const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {}); |
| const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, ''); |
|
|
| if (result) { |
| allowScopedScripts(character); |
| await reloadCurrentChat(); |
| } |
| } |
| } |
| } |
| } |
|
|
| |
| RegexProvider.instance.clear(); |
| await loadRegexScripts(); |
| } |
|
|
| |
| |
| |
| |
| function notifyReloadCurrentChat(presetName) { |
| toastr.info( |
| t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>', |
| t`Preset '${presetName}' contains enabled regex scripts`, |
| { |
| timeOut: 5000, |
| escapeHtml: false, |
| onclick: reloadCurrentChat, |
| }); |
| } |
|
|
| async function checkPresetEmbeddedRegexScripts() { |
| const apiId = getCurrentPresetAPI(); |
| const name = getCurrentPresetName(); |
| const scripts = getScriptsByType(SCRIPT_TYPES.PRESET); |
|
|
| if (Array.isArray(scripts) && scripts.length > 0) { |
| if (!isPresetScriptsAllowed(apiId, name)) { |
| const checkKey = `AlertRegex_${apiId}_${name}`; |
|
|
| if (!accountStorage.getItem(checkKey)) { |
| accountStorage.setItem(checkKey, 'true'); |
| const template = await renderExtensionTemplateAsync('regex', 'presetEmbeddedScripts', {}); |
| const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, ''); |
|
|
| if (result) { |
| allowPresetScripts(apiId, name); |
| if (getCurrentChatId()) { |
| await reloadCurrentChat(); |
| } |
| } |
| } |
| } else if (getCurrentChatId() && scripts.filter(script => !script.disabled).length > 0) { |
| notifyReloadCurrentChat(name); |
| } |
| } |
|
|
| await loadRegexScripts(); |
| } |
|
|
| async function onMainApiChanged({ apiId }) { |
| const presetManager = getPresetManager(apiId); |
| if (!presetManager) { |
| return; |
| } |
| const presetName = presetManager.getSelectedPresetName(); |
| const presetScripts = presetManager.readPresetExtensionField({ path: 'regex_scripts' }) ?? []; |
| if (getCurrentChatId() && |
| isPresetScriptsAllowed(apiId, presetName) && |
| Array.isArray(presetScripts) && |
| presetScripts.filter(script => !script.disabled).length > 0) { |
| notifyReloadCurrentChat(presetName); |
| } |
|
|
| await loadRegexScripts(); |
| } |
|
|
| function onPresetRenamed({ apiId, oldName, newName }) { |
| const oldCheckKey = `AlertRegex_${apiId}_${oldName}`; |
| const checkKey = `AlertRegex_${apiId}_${newName}`; |
| const value = accountStorage.getItem(oldCheckKey); |
| if (value) { |
| accountStorage.setItem(checkKey, value); |
| accountStorage.removeItem(oldCheckKey); |
| } |
| if (isPresetScriptsAllowed(apiId, oldName)) { |
| disallowPresetScripts(apiId, oldName); |
| allowPresetScripts(apiId, newName); |
| } |
| } |
|
|
| |
| |
| jQuery(async () => { |
| if (!Array.isArray(extension_settings.regex)) { |
| extension_settings.regex = []; |
| } |
|
|
| if (!Array.isArray(extension_settings.regex_presets)) { |
| extension_settings.regex_presets = []; |
| } |
|
|
| |
| if (extension_settings.disabledExtensions.includes('regex')) { |
| return; |
| } |
|
|
| migrateSettings(); |
|
|
| const settingsHtml = $(await renderExtensionTemplateAsync('regex', 'dropdown')); |
| $('#regex_container').append(settingsHtml); |
| $('#open_regex_editor').on('click', function () { |
| onRegexEditorOpenClick(false, SCRIPT_TYPES.GLOBAL); |
| }); |
| $('#open_regex_debugger').on('click', onRegexDebuggerOpenClick); |
| $('#open_scoped_editor').on('click', function () { |
| if (this_chid === undefined) { |
| toastr.error(t`No character selected.`); |
| return; |
| } |
|
|
| if (selected_group) { |
| toastr.error(t`Cannot edit scoped scripts in group chats.`); |
| return; |
| } |
|
|
| onRegexEditorOpenClick(false, SCRIPT_TYPES.SCOPED); |
| }); |
| $('#open_preset_editor').on('click', function () { |
| onRegexEditorOpenClick(false, SCRIPT_TYPES.PRESET); |
| }); |
| $('#import_regex_file').on('change', async function () { |
| let target = SCRIPT_TYPES.GLOBAL; |
| const template = $(await renderExtensionTemplateAsync('regex', 'importTarget')); |
| template.find('#regex_import_target_global').on('input', () => (target = SCRIPT_TYPES.GLOBAL)); |
| template.find('#regex_import_target_scoped').on('input', () => (target = SCRIPT_TYPES.SCOPED)); |
| template.find('#regex_import_target_preset').on('input', () => (target = SCRIPT_TYPES.PRESET)); |
|
|
| await callGenericPopup(template, POPUP_TYPE.TEXT); |
|
|
| const inputElement = this instanceof HTMLInputElement && this; |
| for (const file of inputElement.files) { |
| await onRegexImportFileChange(file, target); |
| } |
| inputElement.value = ''; |
| }); |
| $('#import_regex').on('click', function () { |
| $('#import_regex_file').trigger('click'); |
| }); |
|
|
| $('#bulk_select_all_toggle').on('click', async function () { |
| const checkboxes = $('#regex_container .regex_bulk_checkbox'); |
| if (checkboxes.length === 0) { |
| return; |
| } |
|
|
| const allAreChecked = checkboxes.length === checkboxes.filter(':checked').length; |
| const newState = !allAreChecked; |
|
|
| checkboxes.prop('checked', newState); |
| setToggleAllIcon(newState); |
| setMoveButtonsVisibility(); |
| }); |
|
|
| $('#bulk_enable_regex').on('click', async function () { |
| await bulkToggleRegexScripts(true); |
| }); |
|
|
| $('#bulk_disable_regex').on('click', async function () { |
| await bulkToggleRegexScripts(false); |
| }); |
|
|
| |
| |
| |
| |
| |
| async function bulkToggleRegexScripts(newState) { |
| const scripts = getSelectedScripts().filter(script => script.disabled === newState); |
| if (scripts.length === 0) { |
| toastr.warning(newState |
| ? t`No regex scripts selected for enabling.` |
| : t`No regex scripts selected for disabling.`, |
| ); |
| return; |
| } |
| const scriptTypesToSave = new Set(); |
| for (const script of scripts) { |
| const scriptType = getScriptType(script); |
| scriptTypesToSave.add(scriptType); |
| script.disabled = !newState; |
| } |
| for (const scriptType of scriptTypesToSave) { |
| const scriptsOfType = getScriptsByType(scriptType); |
| await saveScriptsByType(scriptsOfType, scriptType); |
| } |
|
|
| saveSettingsDebounced(); |
| await loadRegexScripts(); |
|
|
| |
| const currentChatId = getCurrentChatId(); |
| if (currentChatId) { |
| await reloadCurrentChat(); |
| } |
| } |
|
|
| |
| |
| |
| |
| async function bulkMoveRegexScript(toType) { |
| const scripts = getSelectedScripts(); |
| if (scripts.length === 0) { |
| toastr.warning(t`No regex scripts selected for moving.`); |
| return; |
| } |
| for (const script of scripts) { |
| await moveRegexScript(script, toType, getScriptType(script), false); |
| } |
|
|
| saveSettingsDebounced(); |
| await loadRegexScripts(); |
|
|
| |
| const currentChatId = getCurrentChatId(); |
| if (currentChatId) { |
| await reloadCurrentChat(); |
| } |
| } |
|
|
| $('#bulk_regex_move_to_global').on('click', async () => { |
| const confirm = await callGenericPopup(t`Are you sure you want to move the selected regex scripts to global?`, POPUP_TYPE.CONFIRM); |
| if (!confirm) { |
| return; |
| } |
| await bulkMoveRegexScript(SCRIPT_TYPES.GLOBAL); |
| }); |
|
|
| $('#bulk_regex_move_to_scoped').on('click', async () => { |
| if (this_chid === undefined) { |
| toastr.error(t`No character selected.`); |
| return; |
| } |
| if (selected_group) { |
| toastr.error(t`Cannot edit scoped scripts in group chats.`); |
| return; |
| } |
| const confirm = await callGenericPopup(t`Are you sure you want to move the selected regex scripts to scoped?`, POPUP_TYPE.CONFIRM); |
| if (!confirm) { |
| return; |
| } |
| await bulkMoveRegexScript(SCRIPT_TYPES.SCOPED); |
| }); |
|
|
| $('#bulk_regex_move_to_preset').on('click', async function () { |
| const confirm = await callGenericPopup(t`Are you sure you want to move the selected regex scripts to preset?`, POPUP_TYPE.CONFIRM); |
| if (!confirm) { |
| return; |
| } |
| await bulkMoveRegexScript(SCRIPT_TYPES.PRESET); |
| }); |
|
|
| $('#bulk_delete_regex').on('click', async function () { |
| const scripts = getSelectedScripts(); |
| if (scripts.length === 0) { |
| toastr.warning(t`No regex scripts selected for deletion.`); |
| return; |
| } |
| const confirm = await callGenericPopup(t`Are you sure you want to delete the selected regex scripts?`, POPUP_TYPE.CONFIRM); |
| if (!confirm) { |
| return; |
| } |
| for (const script of scripts) { |
| await deleteRegexScript(script.id, getScriptType(script), false); |
| } |
| saveSettingsDebounced(); |
| await loadRegexScripts(); |
| await reloadCurrentChat(); |
| }); |
|
|
| $('#bulk_export_regex').on('click', async function () { |
| const scripts = getSelectedScripts(); |
| if (scripts.length === 0) { |
| toastr.warning(t`No regex scripts selected for export.`); |
| return; |
| } |
| const fileName = `regex-${new Date().toISOString()}.json`; |
| const fileData = JSON.stringify(scripts, null, 4); |
| download(fileData, fileName, 'application/json'); |
| await loadRegexScripts(); |
| }); |
|
|
| let sortableDatas = [ |
| { |
| selector: '#saved_regex_scripts', |
| setter: scripts => saveScriptsByType(scripts, SCRIPT_TYPES.GLOBAL), |
| getter: () => getScriptsByType(SCRIPT_TYPES.GLOBAL), |
| }, |
| { |
| selector: '#saved_scoped_scripts', |
| setter: scripts => saveScriptsByType(scripts, SCRIPT_TYPES.SCOPED), |
| getter: () => getScriptsByType(SCRIPT_TYPES.SCOPED), |
| }, |
| { |
| selector: '#saved_preset_scripts', |
| setter: scripts => saveScriptsByType(scripts, SCRIPT_TYPES.PRESET), |
| getter: () => getScriptsByType(SCRIPT_TYPES.PRESET), |
| }, |
| ]; |
| for (const { selector, setter, getter } of sortableDatas) { |
| |
| $(selector).sortable({ |
| delay: getSortableDelay(), |
| handle: '.drag-handle', |
| stop: async function () { |
| const oldScripts = getter(); |
| const newScripts = []; |
| $(selector).children().each(function () { |
| const id = $(this).attr('id'); |
| const existingScript = oldScripts.find((e) => e.id === id); |
| if (existingScript) { |
| newScripts.push(existingScript); |
| } |
| }); |
|
|
| await setter(newScripts); |
| saveSettingsDebounced(); |
|
|
| console.debug(`Regex scripts in ${selector} reordered`); |
| await reloadCurrentChat(); |
| await loadRegexScripts(); |
| }, |
| }); |
| } |
|
|
| $('#regex_scoped_toggle').on('input', function () { |
| if (this_chid === undefined) { |
| toastr.error(t`No character selected.`); |
| return; |
| } |
|
|
| if (selected_group) { |
| toastr.error(t`Cannot edit scoped scripts in group chats.`); |
| return; |
| } |
|
|
| const isEnable = !!$(this).prop('checked'); |
| const character = characters[this_chid]; |
|
|
| if (isEnable) { |
| allowScopedScripts(character); |
| } else { |
| disallowScopedScripts(character); |
| } |
|
|
| saveSettingsDebounced(); |
| reloadCurrentChat(); |
| }); |
|
|
| $('#regex_preset_toggle').on('input', function () { |
| const isEnable = !!$(this).prop('checked'); |
| const name = getCurrentPresetName(); |
|
|
| if (isEnable) { |
| allowPresetScripts(getCurrentPresetAPI(), name); |
| } else { |
| disallowPresetScripts(getCurrentPresetAPI(), name); |
| } |
|
|
| saveSettingsDebounced(); |
| reloadCurrentChat(); |
| }); |
|
|
| await loadRegexScripts(); |
| |
| $('#saved_regex_scripts').sortable('enable'); |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| function getScriptDecorators(type) { |
| switch (type) { |
| case SCRIPT_TYPES.GLOBAL: |
| return { |
| typename: 'global', |
| color: enumTypes.enum, |
| icon: 'G', |
| }; |
| case SCRIPT_TYPES.SCOPED: |
| return { |
| typename: 'scoped', |
| color: enumTypes.name, |
| icon: 'S', |
| }; |
| case SCRIPT_TYPES.PRESET: |
| return { |
| typename: 'preset', |
| color: enumTypes.name, |
| icon: 'P', |
| }; |
| default: |
| return { |
| typename: 'Unknown', |
| color: enumTypes.variable, |
| icon: 'Unknown', |
| }; |
| } |
| } |
|
|
| const localEnumProviders = { |
| regexScripts: () => |
| getRegexScripts().map(script => { |
| const type = getScriptType(script); |
| const { typename, color, icon } = getScriptDecorators(type); |
| return new SlashCommandEnumValue( |
| script.scriptName, |
| `${enumIcons.getStateIcon(!script.disabled)} [${typename}] ${script.findRegex}`, |
| color, |
| icon, |
| ); |
| }), |
| }; |
|
|
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| name: 'regex', |
| callback: runRegexCallback, |
| returns: 'replaced text', |
| namedArgumentList: [ |
| SlashCommandNamedArgument.fromProps({ |
| name: 'name', |
| description: 'script name', |
| typeList: [ARGUMENT_TYPE.STRING], |
| isRequired: true, |
| enumProvider: localEnumProviders.regexScripts, |
| }), |
| ], |
| unnamedArgumentList: [ |
| new SlashCommandArgument( |
| 'input', [ARGUMENT_TYPE.STRING], false, |
| ), |
| ], |
| helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.', |
| })); |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| name: 'regex-toggle', |
| callback: toggleRegexCallback, |
| returns: 'The name of the script that was toggled', |
| namedArgumentList: [ |
| SlashCommandNamedArgument.fromProps({ |
| name: 'state', |
| description: 'Explicitly set the state of the script (\'on\' to enable, \'off\' to disable). If not provided, the state will be toggled to the opposite of the current state.', |
| typeList: [ARGUMENT_TYPE.BOOLEAN], |
| defaultValue: 'toggle', |
| enumList: commonEnumProviders.boolean('onOffToggle')(), |
| }), |
| SlashCommandNamedArgument.fromProps({ |
| name: 'quiet', |
| description: 'Suppress the toast message script toggled', |
| typeList: [ARGUMENT_TYPE.BOOLEAN], |
| defaultValue: 'false', |
| enumList: commonEnumProviders.boolean('trueFalse')(), |
| }), |
| ], |
| unnamedArgumentList: [ |
| SlashCommandArgument.fromProps({ |
| description: 'script name', |
| typeList: [ARGUMENT_TYPE.STRING], |
| isRequired: true, |
| enumProvider: localEnumProviders.regexScripts, |
| }), |
| ], |
| helpString: ` |
| <div> |
| Toggles the state of a specified regex script. |
| </div> |
| <div> |
| <strong>Example:</strong> |
| <ul> |
| <li> |
| <pre><code class="language-stscript">/regex-toggle MyScript</code></pre> |
| </li> |
| <li> |
| <pre><code class="language-stscript">/regex-toggle state=off Character-specific Script</code></pre> |
| </li> |
| </ul> |
| </div> |
| `, |
| })); |
|
|
| eventSource.on(event_types.MAIN_API_CHANGED, onMainApiChanged); |
| eventSource.on(event_types.CHAT_CHANGED, checkCharEmbeddedRegexScripts); |
| eventSource.on(event_types.CHARACTER_DELETED, purgeEmbeddedRegexScripts); |
| eventSource.on(event_types.PRESET_RENAMED_BEFORE, onPresetRenamed); |
| eventSource.on(event_types.PRESET_CHANGED, checkPresetEmbeddedRegexScripts); |
| eventSource.on(event_types.PRESET_DELETED, purgePresetEmbeddedRegexScripts); |
|
|
| presetManager.setupEventListeners(); |
| presetManager.registerSlashCommands(); |
| }); |
|
|