Preset Regex: add preset type for script regexes (#4642) * feat: preset regex * refactor: unify `saveScriptsByType` * fix: default branch for `saveScriptsByType` * refactor: resolve some reviews * fix: lint * refactor: getPresetName -> getCurrentPresetName * refactor: unify regex allow operation * feat: notify whether to reload current chat when preset is changed * refacotr: set timeout for reload current chat notification * fix: _ -> lodash * i18n * Refactor API ID access * refactor: replace UNKNOWN type with a separate constant * refactor: consolidate CSS rules for scoped and preset script blocks * Extend toast timeout * Fix bug with moving, refactor * Refactor bulk toggle operations * Use for..of for preset application * refactor: enhance parameter documentation for scriptType in regex functions * fix: await loadRegexScripts in event handlers * fix: correct script type in saveScriptsByType for preset handling * fix: add null checks for preset manager and API ID in regex functions * feat: implement expandable regex script options in UI * Always show edit/delete buttons * Add title attribute to regex script name * fix: correct class name for regex script display * Remove extra paddings from script blocks * fix: ensure preset scripts are only disallowed if currently allowed during rename * fix: reload on bulk script toggle * feat: add PRESET_RENAMED_BEFORE event type and emit it during preset renaming * fix: always preserve preset extensions on rename * Handle horde in getPresetManager --------- Co-authored-by: ZoinkCN <zoinkcn@outlook.com> Co-authored-by: StageDog <aksanajw845@gmail.com>
Signed| @@ -1646,8 +1646,9 @@ | ||
| 1646 | 1646 | "macro for manual injection)": "宏用于手动注入)", |
| 1647 | 1647 | "Color": "颜色", |
| 1648 | 1648 | "Only apply color as accent": "仅应用颜色作为强调", |
| 1649 | 1649 | "ext_regex_new_global_script_desc": "新增「全局」正规表达式正则表达式", |
| 1650 | 1650 | "ext_regex_new_scoped_script_desc": "新增「局部」正规表达式正则表达式", |
| 1651 | + "ext_regex_new_preset_script_desc": "新增「预设」正则表达式", | |
| 1651 | 1652 | "ext_regex_debugger_active_rules": "激活的规则", |
| 1652 | 1653 | "ext_regex_debugger_save_order": "保存此顺序", |
| 1653 | 1654 | "ext_regex_debugger_testing_area": "测试区域", |
| @@ -1668,6 +1669,7 @@ | ||
| 1668 | 1669 | "ext_regex_preset_delete": "删除当前预设", |
| 1669 | 1670 | "ext_regex_new_global_script": "新建全局正则", |
| 1670 | 1671 | "ext_regex_new_scoped_script": "新建局部正则", |
| 1672 | + "ext_regex_new_preset_script": "新建预设正则", | |
| 1671 | 1673 | "ext_regex_import_script": "导入正则", |
| 1672 | 1674 | "ext_regex_bulk_edit": "批量编辑", |
| 1673 | 1675 | "ext_regex_debugger_desc": "高级正则调试工具", |
| @@ -1678,8 +1680,12 @@ | ||
| 1678 | 1680 | "No scripts found": "没有找到脚本", |
| 1679 | 1681 | "ext_regex_scoped_scripts": "局部正则脚本", |
| 1680 | 1682 | "ext_regex_scoped_scripts_desc": "只影响当前角色,保存在角色卡片中", |
| 1683 | + "ext_regex_preset_scripts": "预设正则脚本", | |
| 1684 | + "ext_regex_preset_scripts_desc": "只影响当前预设,保存在预设中", | |
| 1681 | 1685 | "ext_regex_disallow_scoped": "不允许使用局部正则", |
| 1682 | 1686 | "ext_regex_allow_scoped": "允许使用局部正则", |
| 1687 | + "ext_regex_disallow_preset": "不允许使用预设正则", | |
| 1688 | + "ext_regex_allow_preset": "允许使用预设正则", | |
| 1683 | 1689 | "Regex Editor": "正则表达式编辑器", |
| 1684 | 1690 | "Test Mode": "测试模式", |
| 1685 | 1691 | "ext_regex_desc": "“正则”是一个使用“正则表达式”来查找/替换字符串的工具。如果您想了解更多信息,请点击标题旁边的“?”。", |
| @@ -1730,8 +1736,14 @@ | ||
| 1730 | 1736 | "ext_regex_edit_script": "编辑脚本", |
| 1731 | 1737 | "ext_regex_move_to_global": "移至全局", |
| 1732 | 1738 | "ext_regex_move_to_scoped": "移至局部", |
| 1739 | + "ext_regex_move_to_preset": "移至预设", | |
| 1733 | 1740 | "ext_regex_export_script": "导出脚本", |
| 1734 | 1741 | "ext_regex_delete_script": "删除脚本", |
| 1742 | + "This preset has embedded regex script(s).": "此预设包含内置正则脚本。", | |
| 1743 | + "Preset '${0}' contains enabled regex scripts": "预设 '${0}' 包含被启用的正则脚本", | |
| 1744 | + "Reload the chat for regex to take effect": "重新加载聊天以使正则生效", | |
| 1745 | + "Click here to reload immediately": "点击此处立即重新加载", | |
| 1746 | + "If you want to do it later, select \"Regex\" from the extensions menu.": "您可稍后从扩展菜单中的(Regex)启用。", | |
| 1735 | 1747 | "Trigger Stable Diffusion": "触发Stable Diffusion", |
| 1736 | 1748 | "Abort current image generation task": "中止当前图像生成", |
| 1737 | 1749 | "Stop Image Generation": "停止图像生成", |
| @@ -88,6 +88,7 @@ export const event_types = { | ||
| 88 | 88 | PRESET_CHANGED: 'preset_changed', |
| 89 | 89 | PRESET_DELETED: 'preset_deleted', |
| 90 | 90 | PRESET_RENAMED: 'preset_renamed', |
| 91 | + PRESET_RENAMED_BEFORE: 'preset_renamed_before', | |
| 91 | 92 | MAIN_API_CHANGED: 'main_api_changed', |
| 92 | 93 | WORLDINFO_ENTRIES_LOADED: 'worldinfo_entries_loaded', |
| 93 | 94 | }; |
| @@ -190,7 +190,10 @@ export const extension_settings = { | ||
| 190 | 190 | regex: [], |
| 191 | 191 | /** @type {import('./extensions/regex/index.js').RegexPreset[]} */ |
| 192 | 192 | regex_presets: [], |
| 193 | + /** @type {string[]} */ | |
| 193 | 194 | character_allowed_regex: [], |
| 195 | + /** @type {Record<string, string[]>} */ | |
| 196 | + preset_allowed_regex: {}, | |
| 194 | 197 | tts: {}, |
| 195 | 198 | sd: { |
| 196 | 199 | prompts: {}, |
| @@ -16,6 +16,10 @@ | ||
| 16 | 16 | <i class="fa-solid fa-address-card"></i> |
| 17 | 17 | <small data-i18n="ext_regex_new_scoped_script">+ Scoped</small> |
| 18 | 18 | </div> |
| 19 | + <div id="open_preset_editor" class="menu_button menu_button_icon" data-i18n="[title]ext_regex_new_preset_script_desc" title="New preset regex script"> | |
| 20 | + <i class="fa-solid fa-sliders"></i> | |
| 21 | + <small data-i18n="ext_regex_new_preset_script">+ Preset</small> | |
| 22 | + </div> | |
| 19 | 23 | <div id="import_regex" class="menu_button menu_button_icon"> |
| 20 | 24 | <i class="fa-solid fa-file-import"></i> |
| 21 | 25 | <small data-i18n="ext_regex_import_script">Import</small> |
| @@ -52,6 +56,10 @@ | ||
| 52 | 56 | <i class="fa-solid fa-address-card"></i> |
| 53 | 57 | <small data-i18n="ext_regex_move_to_scoped">Move to scoped scripts</small> |
| 54 | 58 | </div> |
| 59 | + <div id="bulk_regex_move_to_preset" class="menu_button menu_button_icon" hidden> | |
| 60 | + <i class="fa-solid fa-sliders"></i> | |
| 61 | + <small data-i18n="ext_regex_move_to_preset">Move to preset scripts</small> | |
| 62 | + </div> | |
| 55 | 63 | <div id="bulk_export_regex" class="menu_button menu_button_icon"> |
| 56 | 64 | <i class="fa-solid fa-file-export"></i> |
| 57 | 65 | <small data-i18n="Export">Export</small> |
| @@ -78,7 +86,7 @@ | ||
| 78 | 86 | </div> |
| 79 | 87 | </div> |
| 80 | 88 | <hr /> |
| 81 | 89 | <div id="global_scripts_block" class="padding5"> |
| 82 | 90 | <div> |
| 83 | 91 | <strong data-i18n="ext_regex_global_scripts">Global Scripts</strong> |
| 84 | 92 | </div> |
| @@ -88,7 +96,7 @@ | ||
| 88 | 96 | <div id="saved_regex_scripts" no-scripts-text="No scripts found" data-i18n="[no-scripts-text]No scripts found" class="flex-container regex-script-container flexFlowColumn"></div> |
| 89 | 97 | </div> |
| 90 | 98 | <hr /> |
| 91 | 99 | <div id="scoped_scripts_block" class="padding5"> |
| 92 | 100 | <div class="flex-container alignItemsBaseline"> |
| 93 | 101 | <strong class="flex1" data-i18n="ext_regex_scoped_scripts">Scoped Scripts</strong> |
| 94 | 102 | <label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle"> |
| @@ -102,6 +110,21 @@ | ||
| 102 | 110 | </small> |
| 103 | 111 | <div id="saved_scoped_scripts" no-scripts-text="No scripts found" data-i18n="[no-scripts-text]No scripts found" class="flex-container regex-script-container flexFlowColumn"></div> |
| 104 | 112 | </div> |
| 113 | + <hr /> | |
| 114 | + <div id="preset_scripts_block"> | |
| 115 | + <div class="flex-container alignItemsBaseline"> | |
| 116 | + <strong class="flex1" data-i18n="ext_regex_preset_scripts">Preset Scripts</strong> | |
| 117 | + <label id="toggle_preset_regex" class="checkbox flex-container" for="regex_preset_toggle"> | |
| 118 | + <input type="checkbox" id="regex_preset_toggle" class="enable_scoped" /> | |
| 119 | + <span class="regex-toggle-on fa-solid fa-toggle-on fa-lg" data-i18n="[title]ext_regex_disallow_preset" title="Disallow using preset regex"></span> | |
| 120 | + <span class="regex-toggle-off fa-solid fa-toggle-off fa-lg" data-i18n="[title]ext_regex_allow_preset" title="Allow using preset regex"></span> | |
| 121 | + </label> | |
| 122 | + </div> | |
| 123 | + <small data-i18n="ext_regex_preset_scripts_desc"> | |
| 124 | + Only available for this preset. Saved to the preset data. | |
| 125 | + </small> | |
| 126 | + <div id="saved_preset_scripts" class="flex-container regex-script-container flexFlowColumn"></div> | |
| 127 | + </div> | |
| 105 | 128 | </div> |
| 106 | 129 | </div> |
| 107 | 130 | </div> |
| @@ -1,18 +1,25 @@ | ||
| 1 | 1 | import { characters, saveSettingsDebounced, substituteParams, substituteParamsExtended, this_chid } from '../../../script.js'; |
| 2 | 2 | import { extension_settings, writeExtensionField } from '../../extensions.js'; |
| 3 | +import { getPresetManager } from '../../preset-manager.js'; | |
| 3 | 4 | import { regexFromString } from '../../utils.js'; |
| 5 | +import { lodash } from '../../../lib.js'; | |
| 4 | 6 | |
| 5 | 7 | /** |
| 6 | 8 | * @enum {number} Regex scripts types |
| 7 | 9 | * @readonly |
| 8 | 10 | */ |
| 9 | 11 | export const SCRIPT_TYPES = { |
| 10 | - UNKNOWN: -1, | |
| 11 | 12 | GLOBAL: 0, |
| 12 | 13 | SCOPED: 1, |
| 14 | + PRESET: 2, | |
| 13 | 15 | }; |
| 14 | 16 | |
| 15 | 17 | /** |
| 18 | + * Special type for unknown/invalid script types. | |
| 19 | + */ | |
| 20 | +export const SCRIPT_TYPE_UNKNOWN = -1; | |
| 21 | + | |
| 22 | +/** | |
| 16 | 23 | * @typedef {import('../../char-data.js').RegexScriptData} RegexScript |
| 17 | 24 | */ |
| 18 | 25 | |
| @@ -44,7 +51,7 @@ export function getRegexScripts(options = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) { | ||
| 44 | 51 | */ |
| 45 | 52 | export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) { |
| 46 | 53 | switch (scriptType) { |
| 47 | 54 | case SCRIPT_TYPES.UNKNOWNSCRIPT_TYPE_UNKNOWN: |
| 48 | 55 | return []; |
| 49 | 56 | case SCRIPT_TYPES.GLOBAL: |
| 50 | 57 | return extension_settings.regex ?? []; |
| @@ -55,6 +62,14 @@ export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX | ||
| 55 | 62 | const scopedScripts = characters[this_chid]?.data?.extensions?.regex_scripts; |
| 56 | 63 | return Array.isArray(scopedScripts) ? scopedScripts : []; |
| 57 | 64 | } |
| 65 | + case SCRIPT_TYPES.PRESET: { | |
| 66 | + if (allowedOnly && !extension_settings?.preset_allowed_regex?.[getCurrentPresetAPI()]?.includes(getCurrentPresetName())) { | |
| 67 | + return []; | |
| 68 | + } | |
| 69 | + const presetManager = getPresetManager(); | |
| 70 | + const presetScripts = presetManager?.readPresetExtensionField({ path: 'regex_scripts' }); | |
| 71 | + return Array.isArray(presetScripts) ? presetScripts : []; | |
| 72 | + } | |
| 58 | 73 | default: |
| 59 | 74 | console.warn(`getScriptsByType: Invalid script type ${scriptType}`); |
| 60 | 75 | return []; |
| @@ -62,6 +77,148 @@ export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX | ||
| 62 | 77 | } |
| 63 | 78 | |
| 64 | 79 | /** |
| 80 | + * Saves an array of regex scripts for a specific type. | |
| 81 | + * @param {RegexScript[]} scripts An array of regex scripts to save. | |
| 82 | + * @param {SCRIPT_TYPES} scriptType The type of regex scripts to save. | |
| 83 | + * @returns {Promise<void>} | |
| 84 | + */ | |
| 85 | +export async function saveScriptsByType(scripts, scriptType) { | |
| 86 | + switch (scriptType) { | |
| 87 | + case SCRIPT_TYPES.GLOBAL: | |
| 88 | + extension_settings.regex = scripts; | |
| 89 | + saveSettingsDebounced(); | |
| 90 | + break; | |
| 91 | + case SCRIPT_TYPES.SCOPED: | |
| 92 | + await writeExtensionField(this_chid, 'regex_scripts', scripts); | |
| 93 | + break; | |
| 94 | + case SCRIPT_TYPES.PRESET: { | |
| 95 | + const presetManager = getPresetManager(); | |
| 96 | + await presetManager.writePresetExtensionField({ path: 'regex_scripts', value: scripts }); | |
| 97 | + break; | |
| 98 | + } | |
| 99 | + default: | |
| 100 | + console.warn(`saveScriptsByType: Invalid script type ${scriptType}`); | |
| 101 | + break; | |
| 102 | + } | |
| 103 | +} | |
| 104 | + | |
| 105 | +/** | |
| 106 | + * Check if character's regexes are allowed to be used; if character is undefined, returns false | |
| 107 | + * @param {import('../../char-data.js').v1CharData|undefined} character | |
| 108 | + * @returns {boolean} | |
| 109 | + */ | |
| 110 | +export function isScopedScriptsAllowed(character) { | |
| 111 | + return !!extension_settings?.character_allowed_regex?.includes(character?.avatar); | |
| 112 | +} | |
| 113 | + | |
| 114 | +/** | |
| 115 | + * Allow character's regexes to be used; if character is undefined, do nothing | |
| 116 | + * @param {import('../../char-data.js').v1CharData|undefined} character | |
| 117 | + * @returns {void} | |
| 118 | + */ | |
| 119 | +export function allowScopedScripts(character) { | |
| 120 | + const avatar = character?.avatar; | |
| 121 | + if (!avatar) { | |
| 122 | + return; | |
| 123 | + } | |
| 124 | + if (!Array.isArray(extension_settings?.character_allowed_regex)) { | |
| 125 | + extension_settings.character_allowed_regex = []; | |
| 126 | + } | |
| 127 | + if (!extension_settings.character_allowed_regex.includes(avatar)) { | |
| 128 | + extension_settings.character_allowed_regex.push(avatar); | |
| 129 | + saveSettingsDebounced(); | |
| 130 | + } | |
| 131 | +} | |
| 132 | + | |
| 133 | +/** | |
| 134 | + * Disallow character's regexes to be used; if character is undefined, do nothing | |
| 135 | + * @param {import('../../char-data.js').v1CharData|undefined} character | |
| 136 | + * @returns {void} | |
| 137 | + */ | |
| 138 | +export function disallowScopedScripts(character) { | |
| 139 | + const avatar = character?.avatar; | |
| 140 | + if (!avatar) { | |
| 141 | + return; | |
| 142 | + } | |
| 143 | + if (!Array.isArray(extension_settings?.character_allowed_regex)) { | |
| 144 | + return; | |
| 145 | + } | |
| 146 | + const index = extension_settings.character_allowed_regex.indexOf(avatar); | |
| 147 | + if (index !== -1) { | |
| 148 | + extension_settings.character_allowed_regex.splice(index, 1); | |
| 149 | + saveSettingsDebounced(); | |
| 150 | + } | |
| 151 | +} | |
| 152 | + | |
| 153 | +/** | |
| 154 | + * Check if preset's regexes are allowed to be used | |
| 155 | + * @param {string} apiId API ID | |
| 156 | + * @param {string} presetName Preset name | |
| 157 | + * @returns {boolean} True if allowed, false if not | |
| 158 | + */ | |
| 159 | +export function isPresetScriptsAllowed(apiId, presetName) { | |
| 160 | + if (!apiId || !presetName) { | |
| 161 | + return false; | |
| 162 | + } | |
| 163 | + return !!extension_settings?.preset_allowed_regex?.[apiId]?.includes(presetName); | |
| 164 | +} | |
| 165 | + | |
| 166 | +/** | |
| 167 | + * Allow preset's regexes to be used | |
| 168 | + * @param {string} apiId API ID | |
| 169 | + * @param {string} presetName Preset name | |
| 170 | + * @returns {void} | |
| 171 | + */ | |
| 172 | +export function allowPresetScripts(apiId, presetName) { | |
| 173 | + if (!apiId || !presetName) { | |
| 174 | + return; | |
| 175 | + } | |
| 176 | + if (!Array.isArray(extension_settings?.preset_allowed_regex?.[apiId])) { | |
| 177 | + lodash.set(extension_settings, ['preset_allowed_regex', apiId], []); | |
| 178 | + } | |
| 179 | + if (!extension_settings.preset_allowed_regex[apiId].includes(presetName)) { | |
| 180 | + extension_settings.preset_allowed_regex[apiId].push(presetName); | |
| 181 | + saveSettingsDebounced(); | |
| 182 | + } | |
| 183 | +} | |
| 184 | + | |
| 185 | +/** | |
| 186 | + * Disallow preset's regexes to be used | |
| 187 | + * @param {string} apiId API ID | |
| 188 | + * @param {string} presetName Preset name | |
| 189 | + * @returns {void} | |
| 190 | + */ | |
| 191 | +export function disallowPresetScripts(apiId, presetName) { | |
| 192 | + if (!apiId || !presetName) { | |
| 193 | + return; | |
| 194 | + } | |
| 195 | + if (!Array.isArray(extension_settings?.preset_allowed_regex?.[apiId])) { | |
| 196 | + return; | |
| 197 | + } | |
| 198 | + const index = extension_settings.preset_allowed_regex[apiId].indexOf(presetName); | |
| 199 | + if (index !== -1) { | |
| 200 | + extension_settings.preset_allowed_regex[apiId].splice(index, 1); | |
| 201 | + saveSettingsDebounced(); | |
| 202 | + } | |
| 203 | +} | |
| 204 | + | |
| 205 | +/** | |
| 206 | + * Gets the current API ID from the preset manager. | |
| 207 | + * @returns {string|null} Current API ID, or null if no preset manager | |
| 208 | + */ | |
| 209 | +export function getCurrentPresetAPI() { | |
| 210 | + return getPresetManager()?.apiId ?? null; | |
| 211 | +} | |
| 212 | + | |
| 213 | +/** | |
| 214 | + * Gets the name of the currently selected preset. | |
| 215 | + * @returns {string|null} The name of the currently selected preset, or null if no preset manager | |
| 216 | + */ | |
| 217 | +export function getCurrentPresetName() { | |
| 218 | + return getPresetManager()?.getSelectedPresetName() ?? null; | |
| 219 | +} | |
| 220 | + | |
| 221 | +/** | |
| 65 | 222 | * @enum {number} Where the regex script should be applied |
| 66 | 223 | * @readonly |
| 67 | 224 | */ |
| @@ -15,5 +15,11 @@ | ||
| 15 | 15 | Scoped Scripts |
| 16 | 16 | </span> |
| 17 | 17 | </label> |
| 18 | + <label for="regex_import_target_preset"> | |
| 19 | + <input type="radio" name="regex_import_target" id="regex_import_target_preset" value="preset" /> | |
| 20 | + <span data-i18n="ext_regex_preset_scripts"> | |
| 21 | + Preset Scripts | |
| 22 | + </span> | |
| 23 | + </label> | |
| 18 | 24 | </div> |
| 19 | 25 | </div> |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { characters, eventSource, event_types, getCurrentChatId, messageFormatting, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js'; |
| 2 | 2 | import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js'; |
| 3 | 3 | import { selected_group } from '../../group-chats.js'; |
| 4 | 4 | import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; |
| 5 | 5 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| @@ -8,9 +8,10 @@ import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashComman | ||
| 8 | 8 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 9 | 9 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| 10 | 10 | import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js'; |
| 11 | 11 | import { allowPresetScripts, allowScopedScripts, disallowPresetScripts, disallowScopedScripts, getCurrentPresetAPI, getCurrentPresetName, getRegexScripts, getScriptsByType, isPresetScriptsAllowed, isScopedScriptsAllowed, regex_placement, runRegexScript, saveScriptsByType, SCRIPT_TYPE_UNKNOWN, SCRIPT_TYPES, substitute_find_regex } from './engine.js'; |
| 12 | 12 | import { t } from '../../i18n.js'; |
| 13 | 13 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 14 | +import { getPresetManager } from '../../preset-manager.js'; | |
| 14 | 15 | |
| 15 | 16 | // Re-exports for legacy extensions |
| 16 | 17 | export { getRegexScripts }; |
| @@ -33,12 +34,14 @@ const sanitizeFileName = name => name.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, ' | ||
| 33 | 34 | * @property {boolean} isSelected - Whether the preset is currently selected |
| 34 | 35 | * @property {RegexPresetItem[]} global - The list of global preset items |
| 35 | 36 | * @property {RegexPresetItem[]} scoped - The list of scoped preset items |
| 37 | + * @property {RegexPresetItem[]} preset - The list of preset preset items | |
| 36 | 38 | */ |
| 37 | 39 | |
| 38 | 40 | /** |
| 39 | 41 | * @typedef {object} RegexPresetState |
| 40 | 42 | * @property {string[]} global - List of enabled global regex script IDs |
| 41 | 43 | * @property {string[]} scoped - List of enabled scoped regex script IDs |
| 44 | + * @property {string[]} preset - List of enabled preset regex script IDs | |
| 42 | 45 | */ |
| 43 | 46 | |
| 44 | 47 | class RegexPresetManager { |
| @@ -70,10 +73,12 @@ class RegexPresetManager { | ||
| 70 | 73 | captureCurrentState() { |
| 71 | 74 | const globalScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL)); |
| 72 | 75 | const scopedScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED)); |
| 76 | + const presetScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.PRESET)); | |
| 73 | 77 | |
| 74 | 78 | return { |
| 75 | 79 | global: globalScripts.map(item => item.id).sort(), |
| 76 | 80 | scoped: scopedScripts.map(item => item.id).sort(), |
| 81 | + preset: presetScripts.map(item => item.id).sort(), | |
| 77 | 82 | }; |
| 78 | 83 | } |
| 79 | 84 | |
| @@ -90,13 +95,16 @@ class RegexPresetManager { | ||
| 90 | 95 | const global2 = state2.global || []; |
| 91 | 96 | const scoped1 = state1.scoped || []; |
| 92 | 97 | const scoped2 = state2.scoped || []; |
| 98 | + const preset1 = state1.preset || []; | |
| 99 | + const preset2 = state2.preset || []; | |
| 93 | 100 | |
| 94 | 101 | if (global1.length !== global2.length || scoped1.length !== scoped2.length) { |
| 95 | 102 | return true; |
| 96 | 103 | } |
| 97 | 104 | |
| 98 | 105 | return !global1.every(id => global2.includes(id)) || |
| 99 | 106 | !scoped1.every(id => scoped2.includes(id)); || |
| 107 | + !preset1.every(id => preset2.includes(id)); | |
| 100 | 108 | } |
| 101 | 109 | |
| 102 | 110 | /** |
| @@ -365,17 +373,18 @@ class RegexPresetManager { | ||
| 365 | 373 | return; |
| 366 | 374 | } |
| 367 | 375 | |
| 368 | 376 | // Apply to both globalpreset andto scopedall lists |
| 369 | - await this.applyPresetList({ | |
| 377 | + for (const scriptType of Object.values(SCRIPT_TYPES)) { | |
| 370 | - presetList: preset.global, | |
| 378 | + await this.applyPresetList({ | |
| 371 | - targetList: extension_settings.regex, | |
| 379 | + presetList: { | |
| 372 | - saveFunction: () => saveSettingsDebounced(), | |
| 380 | + [SCRIPT_TYPES.GLOBAL]: preset.global, | |
| 373 | - }); | |
| 381 | + [SCRIPT_TYPES.SCOPED]: preset.scoped, | |
| 374 | - await this.applyPresetList({ | |
| 382 | + [SCRIPT_TYPES.PRESET]: preset.preset, | |
| 375 | - presetList: preset.scoped, | |
| 383 | + }[scriptType], | |
| 376 | - targetList: characters[this_chid]?.data?.extensions?.regex_scripts, | |
| 384 | + targetList: getScriptsByType(scriptType), | |
| 377 | 385 | saveFunction: (scripts) => writeExtensionFieldsaveScriptsByType(this_chid, 'regex_scripts'scripts, scriptsscriptType), |
| 378 | 386 | }); |
| 387 | + } | |
| 379 | 388 | |
| 380 | 389 | // Render the changes to the UI |
| 381 | 390 | await loadRegexScripts(); |
| @@ -423,6 +432,7 @@ class RegexPresetManager { | ||
| 423 | 432 | isSelected: false, |
| 424 | 433 | global: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL)), |
| 425 | 434 | scoped: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED)), |
| 435 | + preset: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.PRESET)), | |
| 426 | 436 | }; |
| 427 | 437 | |
| 428 | 438 | if (isUpdate) { |
| @@ -485,15 +495,17 @@ function setToggleAllIcon(allAreChecked) { | ||
| 485 | 495 | function setMoveButtonsVisibility() { |
| 486 | 496 | const hasGlobalScripts = $('#saved_regex_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; |
| 487 | 497 | const hasScopedScripts = $('#saved_scoped_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; |
| 488 | - $('#bulk_regex_move_to_global').toggle(hasScopedScripts); | |
| 498 | + const hasPresetScripts = $('#saved_preset_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0; | |
| 489 | 499 | $('#bulk_regex_move_to_scopedbulk_regex_move_to_global').toggle(hasGlobalScriptshasScopedScripts || hasPresetScripts); |
| 500 | + $('#bulk_regex_move_to_scoped').toggle(hasGlobalScripts || hasPresetScripts); | |
| 501 | + $('#bulk_regex_move_to_preset').toggle(hasGlobalScripts || hasScopedScripts); | |
| 490 | 502 | } |
| 491 | 503 | |
| 492 | 504 | /** |
| 493 | 505 | * Saves a regex script to the extension settings or character data. |
| 494 | 506 | * @param {import('../../char-data.js').RegexScriptData} regexScript |
| 495 | 507 | * @param {number} existingScriptIndex Index of the existing script |
| 496 | 508 | * @param {SCRIPT_TYPES} scriptType global?Type scoped?of the script |
| 497 | 509 | * @param {boolean} [saveSettings=true] Whether to save the settings immediately |
| 498 | 510 | * @returns {Promise<void>} |
| 499 | 511 | */ |
| @@ -529,12 +541,13 @@ async function saveRegexScript(regexScript, existingScriptIndex, scriptType, sav | ||
| 529 | 541 | } |
| 530 | 542 | |
| 531 | 543 | if (scriptType === SCRIPT_TYPES.SCOPED) { |
| 532 | 544 | await writeExtensionFieldsaveScriptsByType(this_chid, 'regex_scripts'array, arraySCRIPT_TYPES.SCOPED); |
| 545 | + allowScopedScripts(characters?.[this_chid]); | |
| 546 | + } | |
| 533 | 547 | |
| 534 | - // Add the character to the allowed list | |
| 548 | + if (scriptType === SCRIPT_TYPES.PRESET) { | |
| 535 | - if (!extension_settings.character_allowed_regex.includes(characters[this_chid].avatar)) { | |
| 549 | + await saveScriptsByType(array, SCRIPT_TYPES.PRESET); | |
| 536 | - extension_settings.character_allowed_regex.push(characters[this_chid].avatar); | |
| 550 | + allowPresetScripts(getCurrentPresetAPI(), getCurrentPresetName()); | |
| 537 | - } | |
| 538 | 551 | } |
| 539 | 552 | |
| 540 | 553 | if (saveSettings) { |
| @@ -557,7 +570,7 @@ async function saveRegexScript(regexScript, existingScriptIndex, scriptType, sav | ||
| 557 | 570 | /** |
| 558 | 571 | * Delete a regex script by ID |
| 559 | 572 | * @param {string} id ID of the script to delete |
| 560 | 573 | * @param {SCRIPT_TYPES} scriptType global?Type scoped?of the script |
| 561 | 574 | * @param {boolean} saveSettings Whether to save the settings immediately |
| 562 | 575 | * @returns {Promise<void>} |
| 563 | 576 | */ |
| @@ -568,8 +581,18 @@ async function deleteRegexScript(id, scriptType, saveSettings = true) { | ||
| 568 | 581 | if (existingScriptIndex !== -1) { |
| 569 | 582 | array.splice(existingScriptIndex, 1); |
| 570 | 583 | |
| 571 | 584 | ifswitch (scriptType === SCRIPT_TYPES.SCOPED) { |
| 572 | - await writeExtensionField(this_chid, 'regex_scripts', array); | |
| 585 | + case SCRIPT_TYPES.GLOBAL: | |
| 586 | + // will be handled by saveSettingsDebounced | |
| 587 | + break; | |
| 588 | + case SCRIPT_TYPES.SCOPED: | |
| 589 | + await saveScriptsByType(array, SCRIPT_TYPES.SCOPED); | |
| 590 | + break; | |
| 591 | + case SCRIPT_TYPES.PRESET: | |
| 592 | + await saveScriptsByType(array, SCRIPT_TYPES.PRESET); | |
| 593 | + break; | |
| 594 | + default: | |
| 595 | + break; | |
| 573 | 596 | } |
| 574 | 597 | if (saveSettings) { |
| 575 | 598 | saveSettingsDebounced(); |
| @@ -594,7 +617,7 @@ async function moveRegexScript(script, toType, fromType = null, saveSettings = t | ||
| 594 | 617 | if (!Object.values(SCRIPT_TYPES).includes(fromType)) { |
| 595 | 618 | fromType = getScriptType(script); |
| 596 | 619 | } |
| 597 | 620 | if (fromType === toType || fromType === SCRIPT_TYPES.UNKNOWNSCRIPT_TYPE_UNKNOWN || toType === SCRIPT_TYPES.UNKNOWNSCRIPT_TYPE_UNKNOWN) { |
| 598 | 621 | return; |
| 599 | 622 | } |
| 600 | 623 | await deleteRegexScript(script.id, fromType, false); |
| @@ -604,6 +627,7 @@ async function moveRegexScript(script, toType, fromType = null, saveSettings = t | ||
| 604 | 627 | async function loadRegexScripts() { |
| 605 | 628 | $('#saved_regex_scripts').empty(); |
| 606 | 629 | $('#saved_scoped_scripts').empty(); |
| 630 | + $('#saved_preset_scripts').empty(); | |
| 607 | 631 | setToggleAllIcon(false); |
| 608 | 632 | |
| 609 | 633 | const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate')); |
| @@ -612,7 +636,7 @@ async function loadRegexScripts() { | ||
| 612 | 636 | * Renders a script to the UI. |
| 613 | 637 | * @param {string} container Container to render the script to |
| 614 | 638 | * @param {import('../../char-data.js').RegexScriptData} script Script data |
| 615 | 639 | * @param {SCRIPT_TYPES} scriptType global?Type scoped?of the script |
| 616 | 640 | * @param {number} index Index of the script in the array |
| 617 | 641 | */ |
| 618 | 642 | function renderScript(container, script, scriptType, index) { |
| @@ -625,7 +649,7 @@ async function loadRegexScripts() { | ||
| 625 | 649 | } |
| 626 | 650 | |
| 627 | 651 | scriptHtml.attr('id', script.id); |
| 628 | 652 | scriptHtml.find('.regex_script_name').text(script.scriptName).attr('title', script.scriptName); |
| 629 | 653 | scriptHtml.find('.disable_regex').prop('checked', script.disabled ?? false) |
| 630 | 654 | .on('input', async function () { |
| 631 | 655 | script.disabled = !!$(this).prop('checked'); |
| @@ -663,6 +687,16 @@ async function loadRegexScripts() { | ||
| 663 | 687 | } |
| 664 | 688 | await moveRegexScript(script, SCRIPT_TYPES.SCOPED, scriptType); |
| 665 | 689 | }); |
| 690 | + scriptHtml.find('.move_to_preset').on('click', async function () { | |
| 691 | + const confirm = await callGenericPopup( | |
| 692 | + t`Are you sure you want to move this regex script to preset?`, | |
| 693 | + POPUP_TYPE.CONFIRM, | |
| 694 | + ); | |
| 695 | + if (!confirm) { | |
| 696 | + return; | |
| 697 | + } | |
| 698 | + await moveRegexScript(script, SCRIPT_TYPES.PRESET, scriptType); | |
| 699 | + }); | |
| 666 | 700 | scriptHtml.find('.export_regex').on('click', async function () { |
| 667 | 701 | const fileName = `regex-${sanitizeFileName(script.scriptName)}.json`; |
| 668 | 702 | const fileData = JSON.stringify(script, null, 4); |
| @@ -682,15 +716,40 @@ async function loadRegexScripts() { | ||
| 682 | 716 | const allAreChecked = checkboxes.length === checkboxes.filter(':checked').length; |
| 683 | 717 | setToggleAllIcon(allAreChecked); |
| 684 | 718 | }); |
| 719 | + scriptHtml.find('input[name="regex_expand"]').on('change', function () { | |
| 720 | + if (!(this instanceof HTMLInputElement)) { | |
| 721 | + return; | |
| 722 | + } | |
| 723 | + | |
| 724 | + if (!this.checked) { | |
| 725 | + return; | |
| 726 | + } | |
| 727 | + | |
| 728 | + const closeMenuHandler = (e) => { | |
| 729 | + if (e.target instanceof HTMLElement) { | |
| 730 | + if (e.target.closest('.regex-script-label')) { | |
| 731 | + return; | |
| 732 | + } | |
| 733 | + this.checked = false; | |
| 734 | + document.removeEventListener('click', closeMenuHandler); | |
| 735 | + } | |
| 736 | + }; | |
| 737 | + | |
| 738 | + // Use setTimeout to avoid closing immediately from the same click | |
| 739 | + setTimeout(() => { | |
| 740 | + document.addEventListener('click', closeMenuHandler, { passive: true, once: false }); | |
| 741 | + }, 0); | |
| 742 | + }); | |
| 685 | 743 | |
| 686 | 744 | $(container).append(scriptHtml); |
| 687 | 745 | } |
| 688 | 746 | |
| 689 | 747 | getScriptsByType(SCRIPT_TYPES.GLOBAL).forEach((script, index) => renderScript('#saved_regex_scripts', script, SCRIPT_TYPES.GLOBAL, index)); |
| 690 | 748 | getScriptsByType(SCRIPT_TYPES.SCOPED).forEach((script, index) => renderScript('#saved_scoped_scripts', script, SCRIPT_TYPES.SCOPED, index)); |
| 749 | + getScriptsByType(SCRIPT_TYPES.PRESET).forEach((script, index) => renderScript('#saved_preset_scripts', script, SCRIPT_TYPES.PRESET, index)); | |
| 691 | 750 | |
| 692 | - const isScopedAllowed = extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar); | |
| 751 | + $('#regex_scoped_toggle').prop('checked', isScopedScriptsAllowed(characters?.[this_chid])); | |
| 693 | 752 | $('#regex_scoped_toggleregex_preset_toggle').prop('checked', isScopedAllowedisPresetScriptsAllowed(getCurrentPresetAPI(), getCurrentPresetName())); |
| 694 | 753 | |
| 695 | 754 | setMoveButtonsVisibility(); |
| 696 | 755 | } |
| @@ -698,7 +757,7 @@ async function loadRegexScripts() { | ||
| 698 | 757 | /** |
| 699 | 758 | * Opens the regex editor. |
| 700 | 759 | * @param {string|boolean} existingId Existing ID |
| 701 | 760 | * @param {SCRIPT_TYPES} scriptType global?Type scoped?of the script |
| 702 | 761 | * @returns {Promise<void>} |
| 703 | 762 | */ |
| 704 | 763 | async function onRegexEditorOpenClick(existingId, scriptType) { |
| @@ -1006,23 +1065,30 @@ function populateDebuggerRuleList(container) { | ||
| 1006 | 1065 | } |
| 1007 | 1066 | |
| 1008 | 1067 | const globalScriptIds = new Set(getScriptsByType(SCRIPT_TYPES.GLOBAL).map(s => s.id)); |
| 1068 | + const scopedScriptIds = new Set(getScriptsByType(SCRIPT_TYPES.SCOPED).map(s => s.id)); | |
| 1069 | + const presetScriptIds = new Set(getScriptsByType(SCRIPT_TYPES.PRESET).map(s => s.id)); | |
| 1009 | 1070 | const globalScripts = []; |
| 1010 | 1071 | const scopedScripts = []; |
| 1072 | + const presetScripts = []; | |
| 1011 | 1073 | |
| 1012 | 1074 | allScripts.forEach(script => { |
| 1013 | 1075 | const scriptCopy = structuredClone(script); // Use structuredClone for deep copy |
| 1014 | 1076 | if (globalScriptIds.has(script.id)) { |
| 1015 | 1077 | // @ts-ignore |
| 1016 | 1078 | scriptCopy.type = SCRIPT_TYPES.SCOPEDGLOBAL; |
| 1017 | 1079 | globalScripts.push(scriptCopy); |
| 1018 | - } else { | |
| 1080 | + } else if (scopedScriptIds.has(script.id)) { | |
| 1019 | 1081 | // @ts-ignore |
| 1020 | 1082 | scriptCopy.type = SCRIPT_TYPES.SCOPED; |
| 1021 | 1083 | scopedScripts.push(scriptCopy); |
| 1084 | + } else if (presetScriptIds.has(script.id)) { | |
| 1085 | + // @ts-ignore | |
| 1086 | + scriptCopy.type = SCRIPT_TYPES.PRESET; | |
| 1087 | + presetScripts.push(scriptCopy); | |
| 1022 | 1088 | } |
| 1023 | 1089 | }); |
| 1024 | 1090 | |
| 1025 | 1091 | container.data('allScripts', [...globalScripts, ...scopedScripts, ...presetScripts]); |
| 1026 | 1092 | |
| 1027 | 1093 | const renderRule = (script) => { |
| 1028 | 1094 | if (!script.id) script.id = uuidv4(); |
| @@ -1040,6 +1106,7 @@ function populateDebuggerRuleList(container) { | ||
| 1040 | 1106 | { |
| 1041 | 1107 | [SCRIPT_TYPES.SCOPED]: t`Scoped`, |
| 1042 | 1108 | [SCRIPT_TYPES.GLOBAL]: t`Global`, |
| 1109 | + [SCRIPT_TYPES.PRESET]: t`Preset`, | |
| 1043 | 1110 | }[script.type], |
| 1044 | 1111 | ); |
| 1045 | 1112 | ruleElement.find('.rule-enabled').prop('checked', !script.disabled); |
| @@ -1086,6 +1153,13 @@ function populateDebuggerRuleList(container) { | ||
| 1086 | 1153 | scopedScripts.forEach(script => scopedList.append(renderRule(script))); |
| 1087 | 1154 | rulesContainer.append(scopedList); |
| 1088 | 1155 | } |
| 1156 | + | |
| 1157 | + if (presetScripts.length > 0) { | |
| 1158 | + rulesContainer.append('<div class="list-header regex-debugger-list-header">' + t`Preset Rules` + '</div>'); | |
| 1159 | + const presetList = $('<ul id="regex_debugger_rules_preset" class="sortable-list"></ul>'); | |
| 1160 | + presetScripts.forEach(script => presetList.append(renderRule(script))); | |
| 1161 | + rulesContainer.append(presetList); | |
| 1162 | + } | |
| 1089 | 1163 | } |
| 1090 | 1164 | |
| 1091 | 1165 | /** |
| @@ -1104,12 +1178,15 @@ async function onRegexDebuggerOpenClick() { | ||
| 1104 | 1178 | debuggerHtml.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection(); |
| 1105 | 1179 | // @ts-ignore |
| 1106 | 1180 | debuggerHtml.find('#regex_debugger_rules_scoped').sortable({ delay: getSortableDelay() }).disableSelection(); |
| 1181 | + // @ts-ignore | |
| 1182 | + debuggerHtml.find('#regex_debugger_rules_preset').sortable({ delay: getSortableDelay() }).disableSelection(); | |
| 1107 | 1183 | |
| 1108 | 1184 | debuggerHtml.find('#regex_debugger_run_test').on('click', function () { |
| 1109 | 1185 | const allScripts = debuggerHtml.data('allScripts'); |
| 1110 | 1186 | const orderedRuleIds = [ |
| 1111 | 1187 | ...$('#regex_debugger_rules_global').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(), |
| 1112 | 1188 | ...$('#regex_debugger_rules_scoped').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(), |
| 1189 | + ...$('#regex_debugger_rules_preset').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(), | |
| 1113 | 1190 | ]; |
| 1114 | 1191 | |
| 1115 | 1192 | const rawInput = String($('#regex_debugger_raw_input').val()); |
| @@ -1187,11 +1264,13 @@ async function onRegexDebuggerOpenClick() { | ||
| 1187 | 1264 | const allKnownScripts = getRegexScripts(); |
| 1188 | 1265 | const newGlobalScripts = $('#regex_debugger_rules_global').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean); |
| 1189 | 1266 | const newScopedScripts = $('#regex_debugger_rules_scoped').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean); |
| 1267 | + const newPresetScripts = $('#regex_debugger_rules_preset').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean); | |
| 1190 | 1268 | |
| 1191 | 1269 | extension_settings.regex = newGlobalScripts; |
| 1192 | 1270 | if (this_chid !== undefined) { |
| 1193 | 1271 | await writeExtensionFieldsaveScriptsByType(this_chid, 'regex_scripts'newScopedScripts, newScopedScriptsSCRIPT_TYPES.SCOPED); |
| 1194 | 1272 | } |
| 1273 | + await saveScriptsByType(newPresetScripts, SCRIPT_TYPES.PRESET); | |
| 1195 | 1274 | |
| 1196 | 1275 | saveSettingsDebounced(); |
| 1197 | 1276 | await loadRegexScripts(); |
| @@ -1203,6 +1282,8 @@ async function onRegexDebuggerOpenClick() { | ||
| 1203 | 1282 | currentPopupContent.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection(); |
| 1204 | 1283 | // @ts-ignore |
| 1205 | 1284 | currentPopupContent.find('#regex_debugger_rules_scoped').sortable({ delay: getSortableDelay() }).disableSelection(); |
| 1285 | + // @ts-ignore | |
| 1286 | + currentPopupContent.find('#regex_debugger_rules_preset').sortable({ delay: getSortableDelay() }).disableSelection(); | |
| 1206 | 1287 | }); |
| 1207 | 1288 | |
| 1208 | 1289 | debuggerHtml.find('#regex_debugger_expand_steps').on('click', function () { |
| @@ -1328,11 +1409,6 @@ function migrateSettings() { | ||
| 1328 | 1409 | } |
| 1329 | 1410 | }); |
| 1330 | 1411 | |
| 1331 | - if (!extension_settings.character_allowed_regex) { | |
| 1332 | - extension_settings.character_allowed_regex = []; | |
| 1333 | - performSave = true; | |
| 1334 | - } | |
| 1335 | - | |
| 1336 | 1412 | if (performSave) { |
| 1337 | 1413 | saveSettingsDebounced(); |
| 1338 | 1414 | } |
| @@ -1418,8 +1494,8 @@ async function toggleRegexCallback(args, scriptName) { | ||
| 1418 | 1494 | |
| 1419 | 1495 | /** |
| 1420 | 1496 | * Performs the import of the regex object. |
| 1421 | 1497 | * @param {ObjectRegexScript} regexScript Input object |
| 1422 | 1498 | * @param {SCRIPT_TYPES} scriptType global?The scoped?type of script to import as |
| 1423 | 1499 | */ |
| 1424 | 1500 | async function onRegexImportObjectChange(regexScript, scriptType) { |
| 1425 | 1501 | try { |
| @@ -1433,8 +1509,18 @@ async function onRegexImportObjectChange(regexScript, scriptType) { | ||
| 1433 | 1509 | const array = getScriptsByType(scriptType); |
| 1434 | 1510 | array.push(regexScript); |
| 1435 | 1511 | |
| 1436 | 1512 | ifswitch (scriptType === SCRIPT_TYPES.SCOPED) { |
| 1437 | - await writeExtensionField(this_chid, 'regex_scripts', array); | |
| 1513 | + case SCRIPT_TYPES.GLOBAL: | |
| 1514 | + // will be handled by saveSettingsDebounced | |
| 1515 | + break; | |
| 1516 | + case SCRIPT_TYPES.SCOPED: | |
| 1517 | + await saveScriptsByType(array, SCRIPT_TYPES.SCOPED); | |
| 1518 | + break; | |
| 1519 | + case SCRIPT_TYPES.PRESET: | |
| 1520 | + await saveScriptsByType(array, SCRIPT_TYPES.PRESET); | |
| 1521 | + break; | |
| 1522 | + default: | |
| 1523 | + break; | |
| 1438 | 1524 | } |
| 1439 | 1525 | |
| 1440 | 1526 | saveSettingsDebounced(); |
| @@ -1450,7 +1536,7 @@ async function onRegexImportObjectChange(regexScript, scriptType) { | ||
| 1450 | 1536 | /** |
| 1451 | 1537 | * Performs the import of the regex file. |
| 1452 | 1538 | * @param {File} file Input file |
| 1453 | 1539 | * @param {SCRIPT_TYPES} scriptType global?The scoped?type of script to import as |
| 1454 | 1540 | */ |
| 1455 | 1541 | async function onRegexImportFileChange(file, scriptType) { |
| 1456 | 1542 | if (!file) { |
| @@ -1480,17 +1566,13 @@ async function onRegexImportFileChange(file, scriptType) { | ||
| 1480 | 1566 | * @returns {SCRIPT_TYPES} The script type. |
| 1481 | 1567 | */ |
| 1482 | 1568 | function getScriptType(script) { |
| 1483 | - const scopedScripts = getScriptsByType(SCRIPT_TYPES.SCOPED); | |
| 1569 | + for (const scriptType of Object.values(SCRIPT_TYPES)) { | |
| 1484 | - if (scopedScripts.some(s => s.id === script.id)) { | |
| 1570 | + const scripts = getScriptsByType(scriptType); | |
| 1485 | - return SCRIPT_TYPES.SCOPED; | |
| 1571 | + if (scripts.some(s => s.id === script.id)) { | |
| 1486 | - } | |
| 1572 | + return scriptType; | |
| 1487 | - | |
| 1573 | + } | |
| 1488 | - const globalScripts = getScriptsByType(SCRIPT_TYPES.GLOBAL); | |
| 1489 | - if (globalScripts.some(s => s.id === script.id)) { | |
| 1490 | - return SCRIPT_TYPES.GLOBAL; | |
| 1491 | 1574 | } |
| 1492 | - | |
| 1575 | + return SCRIPT_TYPE_UNKNOWN; | |
| 1493 | - return SCRIPT_TYPES.UNKNOWN; | |
| 1494 | 1576 | } |
| 1495 | 1577 | |
| 1496 | 1578 | function getSelectedScripts() { |
| @@ -1507,44 +1589,121 @@ function purgeEmbeddedRegexScripts({ character }) { | ||
| 1507 | 1589 | if (!avatar) { |
| 1508 | 1590 | return; |
| 1509 | 1591 | } |
| 1510 | 1592 | const checkKey = `AlertRegex_${characters[this_chid].avatar}`; |
| 1511 | 1593 | if (accountStorage.getItem(checkKey)) { |
| 1512 | 1594 | accountStorage.removeItem(checkKey); |
| 1513 | 1595 | } |
| 1514 | - const index = extension_settings.character_allowed_regex.indexOf(avatar); | |
| 1596 | + disallowScopedScripts(characters?.[this_chid]); | |
| 1515 | - if (index !== -1) { | |
| 1597 | +} | |
| 1516 | - extension_settings.character_allowed_regex.splice(index, 1); | |
| 1598 | + | |
| 1517 | - saveSettingsDebounced(); | |
| 1599 | +function purgePresetEmbeddedRegexScripts({ apiId, name }) { | |
| 1600 | + const checkKey = `AlertRegex_${apiId}_${name}`; | |
| 1601 | + if (accountStorage.getItem(checkKey)) { | |
| 1602 | + accountStorage.removeItem(checkKey); | |
| 1518 | 1603 | } |
| 1604 | + disallowPresetScripts(apiId, name); | |
| 1519 | 1605 | } |
| 1520 | 1606 | |
| 1521 | 1607 | async function checkCharEmbeddedRegexScripts() { |
| 1522 | 1608 | const chid = this_chid; |
| 1523 | 1609 | |
| 1524 | 1610 | if (chid !== undefined && !selected_group) { |
| 1525 | 1611 | const avatarcharacter = characters[chid]?.avatar; |
| 1526 | 1612 | const scripts = getScriptsByType(SCRIPT_TYPES.SCOPED); |
| 1527 | 1613 | |
| 1528 | 1614 | if (Array.isArray(scripts) && scripts.length > 0) { |
| 1529 | 1615 | if (avatar && !extension_settings.character_allowed_regex.includesisScopedScriptsAllowed(avatarcharacter)) { |
| 1530 | 1616 | const checkKey = `AlertRegex_${characters[chid]character.avatar}`; |
| 1531 | - | |
| 1532 | 1617 | if (!accountStorage.getItem(checkKey)) { |
| 1533 | 1618 | accountStorage.setItem(checkKey, 'true'); |
| 1534 | 1619 | const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {}); |
| 1535 | 1620 | const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' }); |
| 1536 | 1621 | |
| 1537 | 1622 | if (result) { |
| 1538 | 1623 | extension_settings.character_allowed_regex.pushallowScopedScripts(avatarcharacter); |
| 1624 | + await reloadCurrentChat(); | |
| 1625 | + } | |
| 1626 | + } | |
| 1627 | + } | |
| 1628 | + } | |
| 1629 | + } | |
| 1630 | + | |
| 1631 | + await loadRegexScripts(); | |
| 1632 | +} | |
| 1633 | + | |
| 1634 | +/** | |
| 1635 | + * Notify whether to reload current chat when preset is changed | |
| 1636 | + * @param {string} presetName The name of the preset | |
| 1637 | + */ | |
| 1638 | +function notifyReloadCurrentChat(presetName) { | |
| 1639 | + toastr.info( | |
| 1640 | + t`Reload the chat for regex to take effect` + '<br><u>' + t`Click here to reload immediately` + '</u>', | |
| 1641 | + t`Preset '${presetName}' contains enabled regex scripts`, | |
| 1642 | + { | |
| 1643 | + timeOut: 5000, | |
| 1644 | + escapeHtml: false, | |
| 1645 | + onclick: reloadCurrentChat, | |
| 1646 | + }); | |
| 1647 | +} | |
| 1648 | + | |
| 1649 | +async function checkPresetEmbeddedRegexScripts() { | |
| 1650 | + const apiId = getCurrentPresetAPI(); | |
| 1651 | + const name = getCurrentPresetName(); | |
| 1652 | + const scripts = getScriptsByType(SCRIPT_TYPES.PRESET); | |
| 1653 | + | |
| 1654 | + if (Array.isArray(scripts) && scripts.length > 0) { | |
| 1655 | + if (!isPresetScriptsAllowed(apiId, name)) { | |
| 1656 | + const checkKey = `AlertRegex_${apiId}_${name}`; | |
| 1657 | + | |
| 1658 | + if (!accountStorage.getItem(checkKey)) { | |
| 1659 | + accountStorage.setItem(checkKey, 'true'); | |
| 1660 | + const template = await renderExtensionTemplateAsync('regex', 'presetEmbeddedScripts', {}); | |
| 1661 | + const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, ''); | |
| 1662 | + | |
| 1663 | + if (result) { | |
| 1664 | + allowPresetScripts(apiId, name); | |
| 1665 | + if (getCurrentChatId()) { | |
| 1539 | 1666 | await reloadCurrentChat(); |
| 1540 | - saveSettingsDebounced(); | |
| 1541 | 1667 | } |
| 1542 | 1668 | } |
| 1543 | 1669 | } |
| 1670 | + } else if (getCurrentChatId() && scripts.filter(script => !script.disabled).length > 0) { | |
| 1671 | + notifyReloadCurrentChat(name); | |
| 1544 | 1672 | } |
| 1545 | 1673 | } |
| 1546 | 1674 | |
| 1547 | 1675 | await loadRegexScripts(); |
| 1676 | +} | |
| 1677 | + | |
| 1678 | +async function onMainApiChanged({ apiId }) { | |
| 1679 | + const presetManager = getPresetManager(apiId); | |
| 1680 | + if (!presetManager) { | |
| 1681 | + return; | |
| 1682 | + } | |
| 1683 | + const presetName = presetManager.getSelectedPresetName(); | |
| 1684 | + const presetScripts = presetManager.readPresetExtensionField({ path: 'regex_scripts' }) ?? []; | |
| 1685 | + if (getCurrentChatId() && | |
| 1686 | + isPresetScriptsAllowed(apiId, presetName) && | |
| 1687 | + Array.isArray(presetScripts) && | |
| 1688 | + presetScripts.filter(script => !script.disabled).length > 0) { | |
| 1689 | + notifyReloadCurrentChat(presetName); | |
| 1690 | + } | |
| 1691 | + | |
| 1692 | + await loadRegexScripts(); | |
| 1693 | +} | |
| 1694 | + | |
| 1695 | +function onPresetRenamed({ apiId, oldName, newName }) { | |
| 1696 | + const oldCheckKey = `AlertRegex_${apiId}_${oldName}`; | |
| 1697 | + const checkKey = `AlertRegex_${apiId}_${newName}`; | |
| 1698 | + const value = accountStorage.getItem(oldCheckKey); | |
| 1699 | + if (value) { | |
| 1700 | + accountStorage.setItem(checkKey, value); | |
| 1701 | + accountStorage.removeItem(oldCheckKey); | |
| 1702 | + } | |
| 1703 | + if (isPresetScriptsAllowed(apiId, oldName)) { | |
| 1704 | + disallowPresetScripts(apiId, oldName); | |
| 1705 | + allowPresetScripts(apiId, newName); | |
| 1706 | + } | |
| 1548 | 1707 | } |
| 1549 | 1708 | |
| 1550 | 1709 | // Workaround for loading in sequence with other extensions |
| @@ -1584,11 +1743,15 @@ jQuery(async () => { | ||
| 1584 | 1743 | |
| 1585 | 1744 | onRegexEditorOpenClick(false, SCRIPT_TYPES.SCOPED); |
| 1586 | 1745 | }); |
| 1746 | + $('#open_preset_editor').on('click', function () { | |
| 1747 | + onRegexEditorOpenClick(false, SCRIPT_TYPES.PRESET); | |
| 1748 | + }); | |
| 1587 | 1749 | $('#import_regex_file').on('change', async function () { |
| 1588 | 1750 | let target = SCRIPT_TYPES.GLOBAL; |
| 1589 | 1751 | const template = $(await renderExtensionTemplateAsync('regex', 'importTarget')); |
| 1590 | 1752 | template.find('#regex_import_target_global').on('input', () => (target = SCRIPT_TYPES.GLOBAL)); |
| 1591 | 1753 | template.find('#regex_import_target_scoped').on('input', () => (target = SCRIPT_TYPES.SCOPED)); |
| 1754 | + template.find('#regex_import_target_preset').on('input', () => (target = SCRIPT_TYPES.PRESET)); | |
| 1592 | 1755 | |
| 1593 | 1756 | await callGenericPopup(template, POPUP_TYPE.TEXT); |
| 1594 | 1757 | |
| @@ -1617,30 +1780,47 @@ jQuery(async () => { | ||
| 1617 | 1780 | }); |
| 1618 | 1781 | |
| 1619 | 1782 | $('#bulk_enable_regex').on('click', async function () { |
| 1620 | - const scripts = getSelectedScripts().filter(script => script.disabled); | |
| 1783 | + await bulkToggleRegexScripts(true); | |
| 1621 | - if (scripts.length === 0) { | |
| 1622 | - toastr.warning(t`No regex scripts selected for enabling.`); | |
| 1623 | - return; | |
| 1624 | - } | |
| 1625 | - for (const script of scripts) { | |
| 1626 | - script.disabled = false; | |
| 1627 | - } | |
| 1628 | - saveSettingsDebounced(); | |
| 1629 | - await loadRegexScripts(); | |
| 1630 | 1784 | }); |
| 1631 | 1785 | |
| 1632 | 1786 | $('#bulk_disable_regex').on('click', async function () { |
| 1633 | - const scripts = getSelectedScripts().filter(script => !script.disabled); | |
| 1787 | + await bulkToggleRegexScripts(false); | |
| 1788 | + }); | |
| 1789 | + | |
| 1790 | + /** | |
| 1791 | + * Bulk enable or disable regex scripts | |
| 1792 | + * @param {boolean} newState New state to set (true = enable, false = disable) | |
| 1793 | + * @returns {Promise<void>} | |
| 1794 | + */ | |
| 1795 | + async function bulkToggleRegexScripts(newState) { | |
| 1796 | + const scripts = getSelectedScripts().filter(script => script.disabled === newState); | |
| 1634 | 1797 | if (scripts.length === 0) { |
| 1635 | - toastr.warning(t`No regex scripts selected for disabling.`); | |
| 1798 | + toastr.warning(newState | |
| 1799 | + ? t`No regex scripts selected for enabling.` | |
| 1800 | + : t`No regex scripts selected for disabling.`, | |
| 1801 | + ); | |
| 1636 | 1802 | return; |
| 1637 | 1803 | } |
| 1804 | + const scriptTypesToSave = new Set(); | |
| 1638 | 1805 | for (const script of scripts) { |
| 1639 | - script.disabled = true; | |
| 1806 | + const scriptType = getScriptType(script); | |
| 1807 | + scriptTypesToSave.add(scriptType); | |
| 1808 | + script.disabled = !newState; | |
| 1640 | 1809 | } |
| 1810 | + for (const scriptType of scriptTypesToSave) { | |
| 1811 | + const scriptsOfType = getScriptsByType(scriptType); | |
| 1812 | + await saveScriptsByType(scriptsOfType, scriptType); | |
| 1813 | + } | |
| 1814 | + | |
| 1641 | 1815 | saveSettingsDebounced(); |
| 1642 | 1816 | await loadRegexScripts(); |
| 1643 | - }); | |
| 1817 | + | |
| 1818 | + // Reload the current chat to undo previous markdown | |
| 1819 | + const currentChatId = getCurrentChatId(); | |
| 1820 | + if (currentChatId) { | |
| 1821 | + await reloadCurrentChat(); | |
| 1822 | + } | |
| 1823 | + } | |
| 1644 | 1824 | |
| 1645 | 1825 | /** |
| 1646 | 1826 | * Bulk move regex scripts to the specified type |
| @@ -1656,6 +1836,7 @@ jQuery(async () => { | ||
| 1656 | 1836 | await moveRegexScript(script, toType, getScriptType(script), false); |
| 1657 | 1837 | } |
| 1658 | 1838 | |
| 1839 | + saveSettingsDebounced(); | |
| 1659 | 1840 | await loadRegexScripts(); |
| 1660 | 1841 | |
| 1661 | 1842 | // Reload the current chat to undo previous markdown |
| @@ -1681,6 +1862,14 @@ jQuery(async () => { | ||
| 1681 | 1862 | await bulkMoveRegexScript(SCRIPT_TYPES.SCOPED); |
| 1682 | 1863 | }); |
| 1683 | 1864 | |
| 1865 | + $('#bulk_regex_move_to_preset').on('click', async function () { | |
| 1866 | + const confirm = await callGenericPopup(t`Are you sure you want to move the selected regex scripts to preset?`, POPUP_TYPE.CONFIRM); | |
| 1867 | + if (!confirm) { | |
| 1868 | + return; | |
| 1869 | + } | |
| 1870 | + await bulkMoveRegexScript(SCRIPT_TYPES.PRESET); | |
| 1871 | + }); | |
| 1872 | + | |
| 1684 | 1873 | $('#bulk_delete_regex').on('click', async function () { |
| 1685 | 1874 | const scripts = getSelectedScripts(); |
| 1686 | 1875 | if (scripts.length === 0) { |
| @@ -1714,14 +1903,19 @@ jQuery(async () => { | ||
| 1714 | 1903 | let sortableDatas = [ |
| 1715 | 1904 | { |
| 1716 | 1905 | selector: '#saved_regex_scripts', |
| 1717 | 1906 | setter: xscripts => extension_settings.regex =saveScriptsByType(scripts, xSCRIPT_TYPES.GLOBAL), |
| 1718 | 1907 | getter: () => getScriptsByType(SCRIPT_TYPES.GLOBAL), |
| 1719 | 1908 | }, |
| 1720 | 1909 | { |
| 1721 | 1910 | selector: '#saved_scoped_scripts', |
| 1722 | 1911 | setter: xscripts => writeExtensionFieldsaveScriptsByType(this_chid, 'regex_scripts'scripts, xSCRIPT_TYPES.SCOPED), |
| 1723 | 1912 | getter: () => getScriptsByType(SCRIPT_TYPES.SCOPED), |
| 1724 | 1913 | }, |
| 1914 | + { | |
| 1915 | + selector: '#saved_preset_scripts', | |
| 1916 | + setter: scripts => saveScriptsByType(scripts, SCRIPT_TYPES.PRESET), | |
| 1917 | + getter: () => getScriptsByType(SCRIPT_TYPES.PRESET), | |
| 1918 | + }, | |
| 1725 | 1919 | ]; |
| 1726 | 1920 | for (const { selector, setter, getter } of sortableDatas) { |
| 1727 | 1921 | // @ts-ignore |
| @@ -1760,17 +1954,26 @@ jQuery(async () => { | ||
| 1760 | 1954 | } |
| 1761 | 1955 | |
| 1762 | 1956 | const isEnable = !!$(this).prop('checked'); |
| 1763 | 1957 | const avatarcharacter = characters[this_chid].avatar; |
| 1764 | 1958 | |
| 1765 | 1959 | if (isEnable) { |
| 1766 | - if (!extension_settings.character_allowed_regex.includes(avatar)) { | |
| 1960 | + allowScopedScripts(character); | |
| 1767 | - extension_settings.character_allowed_regex.push(avatar); | |
| 1768 | - } | |
| 1769 | 1961 | } else { |
| 1770 | - const index = extension_settings.character_allowed_regex.indexOf(avatar); | |
| 1962 | + disallowScopedScripts(character); | |
| 1771 | - if (index !== -1) { | |
| 1963 | + } | |
| 1772 | - extension_settings.character_allowed_regex.splice(index, 1); | |
| 1964 | + | |
| 1773 | - } | |
| 1965 | + saveSettingsDebounced(); | |
| 1966 | + reloadCurrentChat(); | |
| 1967 | + }); | |
| 1968 | + | |
| 1969 | + $('#regex_preset_toggle').on('input', function () { | |
| 1970 | + const isEnable = !!$(this).prop('checked'); | |
| 1971 | + const name = getCurrentPresetName(); | |
| 1972 | + | |
| 1973 | + if (isEnable) { | |
| 1974 | + allowPresetScripts(getCurrentPresetAPI(), name); | |
| 1975 | + } else { | |
| 1976 | + disallowPresetScripts(getCurrentPresetAPI(), name); | |
| 1774 | 1977 | } |
| 1775 | 1978 | |
| 1776 | 1979 | saveSettingsDebounced(); |
| @@ -1806,6 +2009,12 @@ jQuery(async () => { | ||
| 1806 | 2009 | color: enumTypes.name, |
| 1807 | 2010 | icon: 'S', |
| 1808 | 2011 | }; |
| 2012 | + case SCRIPT_TYPES.PRESET: | |
| 2013 | + return { | |
| 2014 | + typename: 'preset', | |
| 2015 | + color: enumTypes.name, | |
| 2016 | + icon: 'P', | |
| 2017 | + }; | |
| 1809 | 2018 | default: |
| 1810 | 2019 | return { |
| 1811 | 2020 | typename: 'Unknown', |
| @@ -1895,8 +2104,12 @@ jQuery(async () => { | ||
| 1895 | 2104 | `, |
| 1896 | 2105 | })); |
| 1897 | 2106 | |
| 2107 | + eventSource.on(event_types.MAIN_API_CHANGED, onMainApiChanged); | |
| 1898 | 2108 | eventSource.on(event_types.CHAT_CHANGED, checkCharEmbeddedRegexScripts); |
| 1899 | 2109 | eventSource.on(event_types.CHARACTER_DELETED, purgeEmbeddedRegexScripts); |
| 2110 | + eventSource.on(event_types.PRESET_RENAMED_BEFORE, onPresetRenamed); | |
| 2111 | + eventSource.on(event_types.PRESET_CHANGED, checkPresetEmbeddedRegexScripts); | |
| 2112 | + eventSource.on(event_types.PRESET_DELETED, purgePresetEmbeddedRegexScripts); | |
| 1900 | 2113 | |
| 1901 | 2114 | presetManager.setupEventListeners(); |
| 1902 | 2115 | presetManager.registerSlashCommands(); |
| @@ -0,0 +1,5 @@ | ||
| 1 | +<div> | |
| 2 | + <h3 data-i18n="This preset has embedded regex script(s).">This preset has embedded regex script(s).</h3> | |
| 3 | + <h3 data-i18n="Would you like to allow using them?">Would you like to allow using them?</h3> | |
| 4 | + <div class="m-b-1" data-i18n="If you want to do it later, select 'Regex' from the extensions menu.">If you want to do it later, select "Regex" from the extensions menu.</div> | |
| 5 | +</div> | |
| @@ -1,25 +1,34 @@ | ||
| 1 | 1 | <div class="regex-script-label flex-container flexnowrap"> |
| 2 | 2 | <input type="checkbox" class="regex_bulk_checkbox" /> |
| 3 | 3 | <span class="drag-handle menu-handle">☰</span> |
| 4 | 4 | <div class="regex_script_name flexGrowflex1 overflow-hidden"></div> |
| 5 | 5 | <div class="flex-container flexnowrap"> |
| 6 | 6 | <label class="checkbox flex-container margin-r5" for="regex_disable"> |
| 7 | 7 | <input type="checkbox" name="regex_disable" class="disable_regex" /> |
| 8 | 8 | <span class="regex-toggle-on fa-solid fa-toggle-on" data-i18n="[title]ext_regex_disable_script" title="Disable script"></span> |
| 9 | 9 | <span class="regex-toggle-off fa-solid fa-toggle-off" data-i18n="[title]ext_regex_enable_script" title="Enable script"></span> |
| 10 | 10 | </label> |
| 11 | + <label class="menu_button regex_script_expand" title="Show more options" data-i18n="[title]Show more options"> | |
| 12 | + <input type="checkbox" name="regex_expand" /> | |
| 13 | + <span class="fa-solid fa-ellipsis"></span> | |
| 14 | + </label> | |
| 15 | + <div class="flex-container regex_script_buttons"> | |
| 16 | + <div class="move_to_global menu_button" data-i18n="[title]ext_regex_move_to_global" title="Move to global scripts"> | |
| 17 | + <i class="fa-solid fa-globe"></i> | |
| 18 | + </div> | |
| 19 | + <div class="move_to_scoped menu_button" data-i18n="[title]ext_regex_move_to_scoped" title="Move to scoped scripts"> | |
| 20 | + <i class="fa-solid fa-address-card"></i> | |
| 21 | + </div> | |
| 22 | + <div class="move_to_preset menu_button" data-i18n="[title]ext_regex_move_to_preset" title="Move to preset scripts"> | |
| 23 | + <i class="fa-solid fa-sliders"></i> | |
| 24 | + </div> | |
| 25 | + <div class="export_regex menu_button" data-i18n="[title]ext_regex_export_script" title="Export script"> | |
| 26 | + <i class="fa-solid fa-file-export"></i> | |
| 27 | + </div> | |
| 28 | + </div> | |
| 11 | 29 | <div class="edit_existing_regex menu_button" data-i18n="[title]ext_regex_edit_script" title="Edit script"> |
| 12 | 30 | <i class="fa-solid fa-pencil"></i> |
| 13 | 31 | </div> |
| 14 | - <div class="move_to_global menu_button" data-i18n="[title]ext_regex_move_to_global" title="Move to global scripts"> | |
| 15 | - <i class="fa-solid fa-globe"></i> | |
| 16 | - </div> | |
| 17 | - <div class="move_to_scoped menu_button" data-i18n="[title]ext_regex_move_to_scoped" title="Move to scoped scripts"> | |
| 18 | - <i class="fa-solid fa-address-card"></i> | |
| 19 | - </div> | |
| 20 | - <div class="export_regex menu_button" data-i18n="[title]ext_regex_export_script" title="Export script"> | |
| 21 | - <i class="fa-solid fa-file-export"></i> | |
| 22 | - </div> | |
| 23 | 32 | <div class="delete_regex menu_button" data-i18n="[title]ext_regex_delete_script" title="Delete script"> |
| 24 | 33 | <i class="fa-solid fa-trash"></i> |
| 25 | 34 | </div> |
| @@ -24,39 +24,33 @@ | ||
| 24 | 24 | text-align: center; |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | 27 | #scoped_scripts_block {, |
| 28 | +#preset_scripts_block { | |
| 28 | 29 | opacity: 1; |
| 29 | 30 | transition: opacity var(--animation-duration-2x) ease-in-out; |
| 30 | 31 | } |
| 31 | 32 | |
| 32 | 33 | #scoped_scripts_block .move_to_scoped {, |
| 34 | +#global_scripts_block .move_to_global, | |
| 35 | +#preset_scripts_block .move_to_preset { | |
| 33 | 36 | display: none; |
| 34 | 37 | } |
| 35 | 38 | |
| 36 | -#global_scripts_block .move_to_global { | |
| 39 | +#scoped_scripts_block:not(:has(#regex_scoped_toggle:checked)), | |
| 37 | - display: none; | |
| 40 | +#preset_scripts_block:not(:has(#regex_preset_toggle:checked)) { | |
| 38 | -} | |
| 39 | - | |
| 40 | -#scoped_scripts_block:not(:has(#regex_scoped_toggle:checked)) { | |
| 41 | 41 | opacity: 0.5; |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | 44 | .enable_scoped:checked~.regex-toggle-on {, |
| 45 | +.enable_scoped:not(:checked)~.regex-toggle-off { | |
| 45 | 46 | display: block; |
| 46 | 47 | } |
| 47 | 48 | |
| 48 | 49 | .enable_scoped:checked~.regex-toggle-off {, |
| 49 | - display: none; | |
| 50 | -} | |
| 51 | - | |
| 52 | 50 | .enable_scoped:not(:checked)~.regex-toggle-on { |
| 53 | 51 | display: none; |
| 54 | 52 | } |
| 55 | 53 | |
| 56 | -.enable_scoped:not(:checked)~.regex-toggle-off { | |
| 57 | - display: block; | |
| 58 | -} | |
| 59 | - | |
| 60 | 54 | .regex-script-label { |
| 61 | 55 | align-items: baseline; |
| 62 | 56 | border: 1px solid var(--SmartThemeBorderColor); |
| @@ -92,19 +86,13 @@ input.enable_scoped { | ||
| 92 | 86 | cursor: pointer; |
| 93 | 87 | } |
| 94 | 88 | |
| 95 | 89 | .disable_regex:checked~.regex-toggle-off {on, |
| 96 | - display: block; | |
| 97 | -} | |
| 98 | - | |
| 99 | -.disable_regex:checked~.regex-toggle-on { | |
| 100 | - display: none; | |
| 101 | -} | |
| 102 | - | |
| 103 | 90 | .disable_regex:not(:checked)~.regex-toggle-off { |
| 104 | 91 | display: none; |
| 105 | 92 | } |
| 106 | 93 | |
| 107 | 94 | .disable_regex:not(:checked)~.regex-toggle-on {, |
| 95 | +.disable_regex:checked~.regex-toggle-off { | |
| 108 | 96 | display: block; |
| 109 | 97 | } |
| 110 | 98 | |
| @@ -155,3 +143,19 @@ input.enable_scoped { | ||
| 155 | 143 | .regex_settings:has(#regex_bulk_edit:checked) .regex_bulk_checkbox { |
| 156 | 144 | display: inline-grid; |
| 157 | 145 | } |
| 146 | + | |
| 147 | +@supports not selector(:has(*)) { | |
| 148 | + .regex-script-label label.regex_script_expand { | |
| 149 | + display: none; | |
| 150 | + } | |
| 151 | + | |
| 152 | + .regex-script-label .regex_script_buttons { | |
| 153 | + display: flex; | |
| 154 | + } | |
| 155 | +} | |
| 156 | + | |
| 157 | +.regex-script-label label.regex_script_expand input[name="regex_expand"], | |
| 158 | +.regex-script-label:has(input[name="regex_expand"]:checked) label.regex_script_expand, | |
| 159 | +.regex-script-label:not(:has(input[name="regex_expand"]:checked)) .regex_script_buttons { | |
| 160 | + display: none; | |
| 161 | +} | |
| @@ -81,6 +81,9 @@ function autoSelectPreset() { | ||
| 81 | 81 | * @returns {PresetManager} Preset manager |
| 82 | 82 | */ |
| 83 | 83 | export function getPresetManager(apiId = '') { |
| 84 | + if (apiId === 'koboldhorde') { | |
| 85 | + apiId = 'kobold'; | |
| 86 | + } | |
| 84 | 87 | if (!apiId) { |
| 85 | 88 | apiId = main_api == 'koboldhorde' ? 'kobold' : main_api; |
| 86 | 89 | } |
| @@ -814,7 +817,7 @@ class PresetManager { | ||
| 814 | 817 | * Reads a preset extension field from the preset. |
| 815 | 818 | * @param {object} options |
| 816 | 819 | * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name. |
| 817 | 820 | * @param {string} options.path Path to the preset extension field, e.g. 'myextension.data'. If empty, reads the entire extensions object. |
| 818 | 821 | * @return {any} The value of the preset extension field, or null if not found. |
| 819 | 822 | */ |
| 820 | 823 | readPresetExtensionField({ name, path }) { |
| @@ -825,7 +828,7 @@ class PresetManager { | ||
| 825 | 828 | // Read from settings if the selected preset is the same as the provided name |
| 826 | 829 | if (settings && selectedName === presetName) { |
| 827 | 830 | const settingsExtensions = ensurePlainObject(settings.extensions || {}); |
| 828 | 831 | return path ? lodash.get(settingsExtensions, path, null) : settingsExtensions; |
| 829 | 832 | } |
| 830 | 833 | |
| 831 | 834 | // Otherwise, read from the preset by name |
| @@ -835,7 +838,7 @@ class PresetManager { | ||
| 835 | 838 | } |
| 836 | 839 | |
| 837 | 840 | const presetExtensions = ensurePlainObject(preset.extensions || {}); |
| 838 | 841 | const value = path ? lodash.get(presetExtensions, path, null) : presetExtensions; |
| 839 | 842 | return value; |
| 840 | 843 | } |
| 841 | 844 | |
| @@ -843,7 +846,7 @@ class PresetManager { | ||
| 843 | 846 | * Writes a value to a preset extension field. |
| 844 | 847 | * @param {object} options |
| 845 | 848 | * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name. |
| 846 | 849 | * @param {string} options.path Path to the preset extension field, e.g. 'myextension.data'. If empty, writes to the root of the extensions object. |
| 847 | 850 | * @param {any} options.value Value to write to the preset extension field. |
| 848 | 851 | * @return {Promise<void>} Resolves when the preset is saved. |
| 849 | 852 | */ |
| @@ -856,7 +859,7 @@ class PresetManager { | ||
| 856 | 859 | if (settings && selectedName === presetName) { |
| 857 | 860 | // Set the value at the specified path |
| 858 | 861 | settings.extensions = ensurePlainObject(settings.extensions || {}); |
| 859 | 862 | path ? lodash.set(settings.extensions, path, value) : (settings.extensions = value); |
| 860 | 863 | await saveSettings(); |
| 861 | 864 | } |
| 862 | 865 | |
| @@ -868,7 +871,7 @@ class PresetManager { | ||
| 868 | 871 | |
| 869 | 872 | // Set the value at the specified path |
| 870 | 873 | preset.extensions = ensurePlainObject(preset.extensions || {}); |
| 871 | 874 | path ? lodash.set(preset.extensions, path, value) : (preset.extensions = value); |
| 872 | 875 | |
| 873 | 876 | // Save the updated preset |
| 874 | 877 | await this.savePreset(presetName, preset, { skipUpdate: true }); |
| @@ -1033,8 +1036,10 @@ export async function initPresetManager() { | ||
| 1033 | 1036 | return; |
| 1034 | 1037 | } |
| 1035 | 1038 | |
| 1039 | + await eventSource.emit(event_types.PRESET_RENAMED_BEFORE, { apiId: apiId, oldName: oldName, newName: newName }); | |
| 1040 | + const extensions = presetManager.readPresetExtensionField({ name: oldName, path: '' }); | |
| 1036 | 1041 | await presetManager.renamePreset(newName); |
| 1037 | - | |
| 1042 | + await presetManager.writePresetExtensionField({ name: newName, path: '', value: extensions }); | |
| 1038 | 1043 | await eventSource.emit(event_types.PRESET_RENAMED, { apiId: apiId, oldName: oldName, newName: newName }); |
| 1039 | 1044 | |
| 1040 | 1045 | if (apiId === 'openai') { |