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>

df7f0469129ebaaddd307e0c06cb29fbbb2e1097

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
11 files changed, +571 -133Showing whitespace changes
public/locales/zh-cn.json+14 -2
@@ -1646,8 +1646,9 @@
1646 "macro for manual injection)": "宏用于手动注入)",1646 "macro for manual injection)": "宏用于手动注入)",
1647 "Color": "颜色",1647 "Color": "颜色",
1648 "Only apply color as accent": "仅应用颜色作为强调",1648 "Only apply color as accent": "仅应用颜色作为强调",
1649 "ext_regex_new_global_script_desc": "新增「全局」正规表达式",1649 "ext_regex_new_global_script_desc": "新增「全局」正则表达式",
1650 "ext_regex_new_scoped_script_desc": "新增「局部」正规表达式",1650 "ext_regex_new_scoped_script_desc": "新增「局部」正则表达式",
1651 "ext_regex_new_preset_script_desc": "新增「预设」正则表达式",
1651 "ext_regex_debugger_active_rules": "激活的规则",1652 "ext_regex_debugger_active_rules": "激活的规则",
1652 "ext_regex_debugger_save_order": "保存此顺序",1653 "ext_regex_debugger_save_order": "保存此顺序",
1653 "ext_regex_debugger_testing_area": "测试区域",1654 "ext_regex_debugger_testing_area": "测试区域",
@@ -1668,6 +1669,7 @@
1668 "ext_regex_preset_delete": "删除当前预设",1669 "ext_regex_preset_delete": "删除当前预设",
1669 "ext_regex_new_global_script": "新建全局正则",1670 "ext_regex_new_global_script": "新建全局正则",
1670 "ext_regex_new_scoped_script": "新建局部正则",1671 "ext_regex_new_scoped_script": "新建局部正则",
1672 "ext_regex_new_preset_script": "新建预设正则",
1671 "ext_regex_import_script": "导入正则",1673 "ext_regex_import_script": "导入正则",
1672 "ext_regex_bulk_edit": "批量编辑",1674 "ext_regex_bulk_edit": "批量编辑",
1673 "ext_regex_debugger_desc": "高级正则调试工具",1675 "ext_regex_debugger_desc": "高级正则调试工具",
@@ -1678,8 +1680,12 @@
1678 "No scripts found": "没有找到脚本",1680 "No scripts found": "没有找到脚本",
1679 "ext_regex_scoped_scripts": "局部正则脚本",1681 "ext_regex_scoped_scripts": "局部正则脚本",
1680 "ext_regex_scoped_scripts_desc": "只影响当前角色,保存在角色卡片中",1682 "ext_regex_scoped_scripts_desc": "只影响当前角色,保存在角色卡片中",
1683 "ext_regex_preset_scripts": "预设正则脚本",
1684 "ext_regex_preset_scripts_desc": "只影响当前预设,保存在预设中",
1681 "ext_regex_disallow_scoped": "不允许使用局部正则",1685 "ext_regex_disallow_scoped": "不允许使用局部正则",
1682 "ext_regex_allow_scoped": "允许使用局部正则",1686 "ext_regex_allow_scoped": "允许使用局部正则",
1687 "ext_regex_disallow_preset": "不允许使用预设正则",
1688 "ext_regex_allow_preset": "允许使用预设正则",
1683 "Regex Editor": "正则表达式编辑器",1689 "Regex Editor": "正则表达式编辑器",
1684 "Test Mode": "测试模式",1690 "Test Mode": "测试模式",
1685 "ext_regex_desc": "“正则”是一个使用“正则表达式”来查找/替换字符串的工具。如果您想了解更多信息,请点击标题旁边的“?”。",1691 "ext_regex_desc": "“正则”是一个使用“正则表达式”来查找/替换字符串的工具。如果您想了解更多信息,请点击标题旁边的“?”。",
@@ -1730,8 +1736,14 @@
1730 "ext_regex_edit_script": "编辑脚本",1736 "ext_regex_edit_script": "编辑脚本",
1731 "ext_regex_move_to_global": "移至全局",1737 "ext_regex_move_to_global": "移至全局",
1732 "ext_regex_move_to_scoped": "移至局部",1738 "ext_regex_move_to_scoped": "移至局部",
1739 "ext_regex_move_to_preset": "移至预设",
1733 "ext_regex_export_script": "导出脚本",1740 "ext_regex_export_script": "导出脚本",
1734 "ext_regex_delete_script": "删除脚本",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 "Trigger Stable Diffusion": "触发Stable Diffusion",1747 "Trigger Stable Diffusion": "触发Stable Diffusion",
1736 "Abort current image generation task": "中止当前图像生成",1748 "Abort current image generation task": "中止当前图像生成",
1737 "Stop Image Generation": "停止图像生成",1749 "Stop Image Generation": "停止图像生成",
public/scripts/events.js+1 -0
@@ -88,6 +88,7 @@ export const event_types = {
88 PRESET_CHANGED: 'preset_changed',88 PRESET_CHANGED: 'preset_changed',
89 PRESET_DELETED: 'preset_deleted',89 PRESET_DELETED: 'preset_deleted',
90 PRESET_RENAMED: 'preset_renamed',90 PRESET_RENAMED: 'preset_renamed',
91 PRESET_RENAMED_BEFORE: 'preset_renamed_before',
91 MAIN_API_CHANGED: 'main_api_changed',92 MAIN_API_CHANGED: 'main_api_changed',
92 WORLDINFO_ENTRIES_LOADED: 'worldinfo_entries_loaded',93 WORLDINFO_ENTRIES_LOADED: 'worldinfo_entries_loaded',
93};94};
public/scripts/extensions.js+3 -0
@@ -190,7 +190,10 @@ export const extension_settings = {
190 regex: [],190 regex: [],
191 /** @type {import('./extensions/regex/index.js').RegexPreset[]} */191 /** @type {import('./extensions/regex/index.js').RegexPreset[]} */
192 regex_presets: [],192 regex_presets: [],
193 /** @type {string[]} */
193 character_allowed_regex: [],194 character_allowed_regex: [],
195 /** @type {Record<string, string[]>} */
196 preset_allowed_regex: {},
194 tts: {},197 tts: {},
195 sd: {198 sd: {
196 prompts: {},199 prompts: {},
public/scripts/extensions/regex/dropdown.html+25 -2
@@ -16,6 +16,10 @@
16 <i class="fa-solid fa-address-card"></i>16 <i class="fa-solid fa-address-card"></i>
17 <small data-i18n="ext_regex_new_scoped_script">+ Scoped</small>17 <small data-i18n="ext_regex_new_scoped_script">+ Scoped</small>
18 </div>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 <div id="import_regex" class="menu_button menu_button_icon">23 <div id="import_regex" class="menu_button menu_button_icon">
20 <i class="fa-solid fa-file-import"></i>24 <i class="fa-solid fa-file-import"></i>
21 <small data-i18n="ext_regex_import_script">Import</small>25 <small data-i18n="ext_regex_import_script">Import</small>
@@ -52,6 +56,10 @@
52 <i class="fa-solid fa-address-card"></i>56 <i class="fa-solid fa-address-card"></i>
53 <small data-i18n="ext_regex_move_to_scoped">Move to scoped scripts</small>57 <small data-i18n="ext_regex_move_to_scoped">Move to scoped scripts</small>
54 </div>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 <div id="bulk_export_regex" class="menu_button menu_button_icon">63 <div id="bulk_export_regex" class="menu_button menu_button_icon">
56 <i class="fa-solid fa-file-export"></i>64 <i class="fa-solid fa-file-export"></i>
57 <small data-i18n="Export">Export</small>65 <small data-i18n="Export">Export</small>
@@ -78,7 +86,7 @@
78 </div>86 </div>
79 </div>87 </div>
80 <hr />88 <hr />
81 <div id="global_scripts_block" class="padding5">89 <div id="global_scripts_block">
82 <div>90 <div>
83 <strong data-i18n="ext_regex_global_scripts">Global Scripts</strong>91 <strong data-i18n="ext_regex_global_scripts">Global Scripts</strong>
84 </div>92 </div>
@@ -88,7 +96,7 @@
88 <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>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 </div>97 </div>
90 <hr />98 <hr />
91 <div id="scoped_scripts_block" class="padding5">99 <div id="scoped_scripts_block">
92 <div class="flex-container alignItemsBaseline">100 <div class="flex-container alignItemsBaseline">
93 <strong class="flex1" data-i18n="ext_regex_scoped_scripts">Scoped Scripts</strong>101 <strong class="flex1" data-i18n="ext_regex_scoped_scripts">Scoped Scripts</strong>
94 <label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle">102 <label id="toggle_scoped_regex" class="checkbox flex-container" for="regex_scoped_toggle">
@@ -102,6 +110,21 @@
102 </small>110 </small>
103 <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>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 </div>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 </div>128 </div>
106 </div>129 </div>
107</div>130</div>
public/scripts/extensions/regex/engine.js+161 -4
@@ -1,18 +1,25 @@
1import { characters, substituteParams, substituteParamsExtended, this_chid } from '../../../script.js';1import { characters, saveSettingsDebounced, substituteParams, substituteParamsExtended, this_chid } from '../../../script.js';
2import { extension_settings } from '../../extensions.js';2import { extension_settings, writeExtensionField } from '../../extensions.js';
3import { getPresetManager } from '../../preset-manager.js';
3import { regexFromString } from '../../utils.js';4import { regexFromString } from '../../utils.js';
5import { lodash } from '../../../lib.js';
46
5/**7/**
6 * @enum {number} Regex scripts types8 * @enum {number} Regex scripts types
7 * @readonly9 * @readonly
8 */10 */
9export const SCRIPT_TYPES = {11export const SCRIPT_TYPES = {
10 UNKNOWN: -1,
11 GLOBAL: 0,12 GLOBAL: 0,
12 SCOPED: 1,13 SCOPED: 1,
14 PRESET: 2,
13};15};
1416
15/**17/**
18 * Special type for unknown/invalid script types.
19 */
20export const SCRIPT_TYPE_UNKNOWN = -1;
21
22/**
16 * @typedef {import('../../char-data.js').RegexScriptData} RegexScript23 * @typedef {import('../../char-data.js').RegexScriptData} RegexScript
17 */24 */
1825
@@ -44,7 +51,7 @@ export function getRegexScripts(options = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) {
44 */51 */
45export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) {52export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) {
46 switch (scriptType) {53 switch (scriptType) {
47 case SCRIPT_TYPES.UNKNOWN:54 case SCRIPT_TYPE_UNKNOWN:
48 return [];55 return [];
49 case SCRIPT_TYPES.GLOBAL:56 case SCRIPT_TYPES.GLOBAL:
50 return extension_settings.regex ?? [];57 return extension_settings.regex ?? [];
@@ -55,6 +62,14 @@ export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX
55 const scopedScripts = characters[this_chid]?.data?.extensions?.regex_scripts;62 const scopedScripts = characters[this_chid]?.data?.extensions?.regex_scripts;
56 return Array.isArray(scopedScripts) ? scopedScripts : [];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 default:73 default:
59 console.warn(`getScriptsByType: Invalid script type ${scriptType}`);74 console.warn(`getScriptsByType: Invalid script type ${scriptType}`);
60 return [];75 return [];
@@ -62,6 +77,148 @@ export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX
62}77}
6378
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 */
85export 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 */
110export 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 */
119export 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 */
138export 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 */
159export 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 */
172export 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 */
191export 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 */
209export 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 */
217export function getCurrentPresetName() {
218 return getPresetManager()?.getSelectedPresetName() ?? null;
219}
220
221/**
65 * @enum {number} Where the regex script should be applied222 * @enum {number} Where the regex script should be applied
66 * @readonly223 * @readonly
67 */224 */
public/scripts/extensions/regex/importTarget.html+6 -0
@@ -15,5 +15,11 @@
15 Scoped Scripts15 Scoped Scripts
16 </span>16 </span>
17 </label>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 </div>24 </div>
19</div>25</div>
public/scripts/extensions/regex/index.js+301 -88
@@ -1,5 +1,5 @@
1import { characters, eventSource, event_types, getCurrentChatId, messageFormatting, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js';1import { characters, eventSource, event_types, getCurrentChatId, messageFormatting, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js';
2import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';2import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
3import { selected_group } from '../../group-chats.js';3import { selected_group } from '../../group-chats.js';
4import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';4import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js';
5import { SlashCommand } from '../../slash-commands/SlashCommand.js';5import { SlashCommand } from '../../slash-commands/SlashCommand.js';
@@ -8,9 +8,10 @@ import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashComman
8import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';8import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
9import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';9import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
10import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js';10import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js';
11import { getRegexScripts, getScriptsByType, regex_placement, runRegexScript, SCRIPT_TYPES, substitute_find_regex } from './engine.js';11import { 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';
12import { t } from '../../i18n.js';12import { t } from '../../i18n.js';
13import { accountStorage } from '../../util/AccountStorage.js';13import { accountStorage } from '../../util/AccountStorage.js';
14import { getPresetManager } from '../../preset-manager.js';
1415
15// Re-exports for legacy extensions16// Re-exports for legacy extensions
16export { getRegexScripts };17export { getRegexScripts };
@@ -33,12 +34,14 @@ const sanitizeFileName = name => name.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, '
33 * @property {boolean} isSelected - Whether the preset is currently selected34 * @property {boolean} isSelected - Whether the preset is currently selected
34 * @property {RegexPresetItem[]} global - The list of global preset items35 * @property {RegexPresetItem[]} global - The list of global preset items
35 * @property {RegexPresetItem[]} scoped - The list of scoped preset items36 * @property {RegexPresetItem[]} scoped - The list of scoped preset items
37 * @property {RegexPresetItem[]} preset - The list of preset preset items
36 */38 */
3739
38/**40/**
39 * @typedef {object} RegexPresetState41 * @typedef {object} RegexPresetState
40 * @property {string[]} global - List of enabled global regex script IDs42 * @property {string[]} global - List of enabled global regex script IDs
41 * @property {string[]} scoped - List of enabled scoped regex script IDs43 * @property {string[]} scoped - List of enabled scoped regex script IDs
44 * @property {string[]} preset - List of enabled preset regex script IDs
42 */45 */
4346
44class RegexPresetManager {47class RegexPresetManager {
@@ -70,10 +73,12 @@ class RegexPresetManager {
70 captureCurrentState() {73 captureCurrentState() {
71 const globalScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL));74 const globalScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL));
72 const scopedScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED));75 const scopedScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED));
76 const presetScripts = this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.PRESET));
7377
74 return {78 return {
75 global: globalScripts.map(item => item.id).sort(),79 global: globalScripts.map(item => item.id).sort(),
76 scoped: scopedScripts.map(item => item.id).sort(),80 scoped: scopedScripts.map(item => item.id).sort(),
81 preset: presetScripts.map(item => item.id).sort(),
77 };82 };
78 }83 }
7984
@@ -90,13 +95,16 @@ class RegexPresetManager {
90 const global2 = state2.global || [];95 const global2 = state2.global || [];
91 const scoped1 = state1.scoped || [];96 const scoped1 = state1.scoped || [];
92 const scoped2 = state2.scoped || [];97 const scoped2 = state2.scoped || [];
98 const preset1 = state1.preset || [];
99 const preset2 = state2.preset || [];
93100
94 if (global1.length !== global2.length || scoped1.length !== scoped2.length) {101 if (global1.length !== global2.length || scoped1.length !== scoped2.length) {
95 return true;102 return true;
96 }103 }
97104
98 return !global1.every(id => global2.includes(id)) ||105 return !global1.every(id => global2.includes(id)) ||
99 !scoped1.every(id => scoped2.includes(id));106 !scoped1.every(id => scoped2.includes(id)) ||
107 !preset1.every(id => preset2.includes(id));
100 }108 }
101109
102 /**110 /**
@@ -365,17 +373,18 @@ class RegexPresetManager {
365 return;373 return;
366 }374 }
367375
368 // Apply to both global and scoped lists376 // Apply preset to all lists
377 for (const scriptType of Object.values(SCRIPT_TYPES)) {
369 await this.applyPresetList({378 await this.applyPresetList({
370 presetList: preset.global,379 presetList: {
371 targetList: extension_settings.regex,380 [SCRIPT_TYPES.GLOBAL]: preset.global,
372 saveFunction: () => saveSettingsDebounced(),381 [SCRIPT_TYPES.SCOPED]: preset.scoped,
373 });382 [SCRIPT_TYPES.PRESET]: preset.preset,
374 await this.applyPresetList({383 }[scriptType],
375 presetList: preset.scoped,384 targetList: getScriptsByType(scriptType),
376 targetList: characters[this_chid]?.data?.extensions?.regex_scripts,385 saveFunction: scripts => saveScriptsByType(scripts, scriptType),
377 saveFunction: (scripts) => writeExtensionField(this_chid, 'regex_scripts', scripts),
378 });386 });
387 }
379388
380 // Render the changes to the UI389 // Render the changes to the UI
381 await loadRegexScripts();390 await loadRegexScripts();
@@ -423,6 +432,7 @@ class RegexPresetManager {
423 isSelected: false,432 isSelected: false,
424 global: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL)),433 global: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.GLOBAL)),
425 scoped: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED)),434 scoped: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.SCOPED)),
435 preset: this.regexListToPresetItems(getScriptsByType(SCRIPT_TYPES.PRESET)),
426 };436 };
427437
428 if (isUpdate) {438 if (isUpdate) {
@@ -485,15 +495,17 @@ function setToggleAllIcon(allAreChecked) {
485function setMoveButtonsVisibility() {495function setMoveButtonsVisibility() {
486 const hasGlobalScripts = $('#saved_regex_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0;496 const hasGlobalScripts = $('#saved_regex_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0;
487 const hasScopedScripts = $('#saved_scoped_scripts .regex-script-label:has(.regex_bulk_checkbox:checked)').length > 0;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 $('#bulk_regex_move_to_scoped').toggle(hasGlobalScripts);499 $('#bulk_regex_move_to_global').toggle(hasScopedScripts || hasPresetScripts);
500 $('#bulk_regex_move_to_scoped').toggle(hasGlobalScripts || hasPresetScripts);
501 $('#bulk_regex_move_to_preset').toggle(hasGlobalScripts || hasScopedScripts);
490}502}
491503
492/**504/**
493 * Saves a regex script to the extension settings or character data.505 * Saves a regex script to the extension settings or character data.
494 * @param {import('../../char-data.js').RegexScriptData} regexScript506 * @param {import('../../char-data.js').RegexScriptData} regexScript
495 * @param {number} existingScriptIndex Index of the existing script507 * @param {number} existingScriptIndex Index of the existing script
496 * @param {SCRIPT_TYPES} scriptType global? scoped?508 * @param {SCRIPT_TYPES} scriptType Type of the script
497 * @param {boolean} [saveSettings=true] Whether to save the settings immediately509 * @param {boolean} [saveSettings=true] Whether to save the settings immediately
498 * @returns {Promise<void>}510 * @returns {Promise<void>}
499 */511 */
@@ -529,12 +541,13 @@ async function saveRegexScript(regexScript, existingScriptIndex, scriptType, sav
529 }541 }
530542
531 if (scriptType === SCRIPT_TYPES.SCOPED) {543 if (scriptType === SCRIPT_TYPES.SCOPED) {
532 await writeExtensionField(this_chid, 'regex_scripts', array);544 await saveScriptsByType(array, SCRIPT_TYPES.SCOPED);
533545 allowScopedScripts(characters?.[this_chid]);
534 // Add the character to the allowed list
535 if (!extension_settings.character_allowed_regex.includes(characters[this_chid].avatar)) {
536 extension_settings.character_allowed_regex.push(characters[this_chid].avatar);
537 }546 }
547
548 if (scriptType === SCRIPT_TYPES.PRESET) {
549 await saveScriptsByType(array, SCRIPT_TYPES.PRESET);
550 allowPresetScripts(getCurrentPresetAPI(), getCurrentPresetName());
538 }551 }
539552
540 if (saveSettings) {553 if (saveSettings) {
@@ -557,7 +570,7 @@ async function saveRegexScript(regexScript, existingScriptIndex, scriptType, sav
557/**570/**
558 * Delete a regex script by ID571 * Delete a regex script by ID
559 * @param {string} id ID of the script to delete572 * @param {string} id ID of the script to delete
560 * @param {SCRIPT_TYPES} scriptType global? scoped?573 * @param {SCRIPT_TYPES} scriptType Type of the script
561 * @param {boolean} saveSettings Whether to save the settings immediately574 * @param {boolean} saveSettings Whether to save the settings immediately
562 * @returns {Promise<void>}575 * @returns {Promise<void>}
563 */576 */
@@ -568,8 +581,18 @@ async function deleteRegexScript(id, scriptType, saveSettings = true) {
568 if (existingScriptIndex !== -1) {581 if (existingScriptIndex !== -1) {
569 array.splice(existingScriptIndex, 1);582 array.splice(existingScriptIndex, 1);
570583
571 if (scriptType === SCRIPT_TYPES.SCOPED) {584 switch (scriptType) {
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 if (saveSettings) {597 if (saveSettings) {
575 saveSettingsDebounced();598 saveSettingsDebounced();
@@ -594,7 +617,7 @@ async function moveRegexScript(script, toType, fromType = null, saveSettings = t
594 if (!Object.values(SCRIPT_TYPES).includes(fromType)) {617 if (!Object.values(SCRIPT_TYPES).includes(fromType)) {
595 fromType = getScriptType(script);618 fromType = getScriptType(script);
596 }619 }
597 if (fromType === toType || fromType === SCRIPT_TYPES.UNKNOWN || toType === SCRIPT_TYPES.UNKNOWN) {620 if (fromType === toType || fromType === SCRIPT_TYPE_UNKNOWN || toType === SCRIPT_TYPE_UNKNOWN) {
598 return;621 return;
599 }622 }
600 await deleteRegexScript(script.id, fromType, false);623 await deleteRegexScript(script.id, fromType, false);
@@ -604,6 +627,7 @@ async function moveRegexScript(script, toType, fromType = null, saveSettings = t
604async function loadRegexScripts() {627async function loadRegexScripts() {
605 $('#saved_regex_scripts').empty();628 $('#saved_regex_scripts').empty();
606 $('#saved_scoped_scripts').empty();629 $('#saved_scoped_scripts').empty();
630 $('#saved_preset_scripts').empty();
607 setToggleAllIcon(false);631 setToggleAllIcon(false);
608632
609 const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate'));633 const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate'));
@@ -612,7 +636,7 @@ async function loadRegexScripts() {
612 * Renders a script to the UI.636 * Renders a script to the UI.
613 * @param {string} container Container to render the script to637 * @param {string} container Container to render the script to
614 * @param {import('../../char-data.js').RegexScriptData} script Script data638 * @param {import('../../char-data.js').RegexScriptData} script Script data
615 * @param {SCRIPT_TYPES} scriptType global? scoped?639 * @param {SCRIPT_TYPES} scriptType Type of the script
616 * @param {number} index Index of the script in the array640 * @param {number} index Index of the script in the array
617 */641 */
618 function renderScript(container, script, scriptType, index) {642 function renderScript(container, script, scriptType, index) {
@@ -625,7 +649,7 @@ async function loadRegexScripts() {
625 }649 }
626650
627 scriptHtml.attr('id', script.id);651 scriptHtml.attr('id', script.id);
628 scriptHtml.find('.regex_script_name').text(script.scriptName);652 scriptHtml.find('.regex_script_name').text(script.scriptName).attr('title', script.scriptName);
629 scriptHtml.find('.disable_regex').prop('checked', script.disabled ?? false)653 scriptHtml.find('.disable_regex').prop('checked', script.disabled ?? false)
630 .on('input', async function () {654 .on('input', async function () {
631 script.disabled = !!$(this).prop('checked');655 script.disabled = !!$(this).prop('checked');
@@ -663,6 +687,16 @@ async function loadRegexScripts() {
663 }687 }
664 await moveRegexScript(script, SCRIPT_TYPES.SCOPED, scriptType);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 scriptHtml.find('.export_regex').on('click', async function () {700 scriptHtml.find('.export_regex').on('click', async function () {
667 const fileName = `regex-${sanitizeFileName(script.scriptName)}.json`;701 const fileName = `regex-${sanitizeFileName(script.scriptName)}.json`;
668 const fileData = JSON.stringify(script, null, 4);702 const fileData = JSON.stringify(script, null, 4);
@@ -682,15 +716,40 @@ async function loadRegexScripts() {
682 const allAreChecked = checkboxes.length === checkboxes.filter(':checked').length;716 const allAreChecked = checkboxes.length === checkboxes.filter(':checked').length;
683 setToggleAllIcon(allAreChecked);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 });
685743
686 $(container).append(scriptHtml);744 $(container).append(scriptHtml);
687 }745 }
688746
689 getScriptsByType(SCRIPT_TYPES.GLOBAL).forEach((script, index) => renderScript('#saved_regex_scripts', script, SCRIPT_TYPES.GLOBAL, index));747 getScriptsByType(SCRIPT_TYPES.GLOBAL).forEach((script, index) => renderScript('#saved_regex_scripts', script, SCRIPT_TYPES.GLOBAL, index));
690 getScriptsByType(SCRIPT_TYPES.SCOPED).forEach((script, index) => renderScript('#saved_scoped_scripts', script, SCRIPT_TYPES.SCOPED, index));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));
691750
692 const isScopedAllowed = extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar);751 $('#regex_scoped_toggle').prop('checked', isScopedScriptsAllowed(characters?.[this_chid]));
693 $('#regex_scoped_toggle').prop('checked', isScopedAllowed);752 $('#regex_preset_toggle').prop('checked', isPresetScriptsAllowed(getCurrentPresetAPI(), getCurrentPresetName()));
694753
695 setMoveButtonsVisibility();754 setMoveButtonsVisibility();
696}755}
@@ -698,7 +757,7 @@ async function loadRegexScripts() {
698/**757/**
699 * Opens the regex editor.758 * Opens the regex editor.
700 * @param {string|boolean} existingId Existing ID759 * @param {string|boolean} existingId Existing ID
701 * @param {SCRIPT_TYPES} scriptType global? scoped?760 * @param {SCRIPT_TYPES} scriptType Type of the script
702 * @returns {Promise<void>}761 * @returns {Promise<void>}
703 */762 */
704async function onRegexEditorOpenClick(existingId, scriptType) {763async function onRegexEditorOpenClick(existingId, scriptType) {
@@ -1006,23 +1065,30 @@ function populateDebuggerRuleList(container) {
1006 }1065 }
10071066
1008 const globalScriptIds = new Set(getScriptsByType(SCRIPT_TYPES.GLOBAL).map(s => s.id));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 const globalScripts = [];1070 const globalScripts = [];
1010 const scopedScripts = [];1071 const scopedScripts = [];
1072 const presetScripts = [];
10111073
1012 allScripts.forEach(script => {1074 allScripts.forEach(script => {
1013 const scriptCopy = structuredClone(script); // Use structuredClone for deep copy1075 const scriptCopy = structuredClone(script); // Use structuredClone for deep copy
1014 if (globalScriptIds.has(script.id)) {1076 if (globalScriptIds.has(script.id)) {
1015 // @ts-ignore1077 // @ts-ignore
1016 scriptCopy.type = SCRIPT_TYPES.SCOPED;1078 scriptCopy.type = SCRIPT_TYPES.GLOBAL;
1017 globalScripts.push(scriptCopy);1079 globalScripts.push(scriptCopy);
1018 } else {1080 } else if (scopedScriptIds.has(script.id)) {
1019 // @ts-ignore1081 // @ts-ignore
1020 scriptCopy.type = SCRIPT_TYPES.SCOPED;1082 scriptCopy.type = SCRIPT_TYPES.SCOPED;
1021 scopedScripts.push(scriptCopy);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 });
10241090
1025 container.data('allScripts', [...globalScripts, ...scopedScripts]);1091 container.data('allScripts', [...globalScripts, ...scopedScripts, ...presetScripts]);
10261092
1027 const renderRule = (script) => {1093 const renderRule = (script) => {
1028 if (!script.id) script.id = uuidv4();1094 if (!script.id) script.id = uuidv4();
@@ -1040,6 +1106,7 @@ function populateDebuggerRuleList(container) {
1040 {1106 {
1041 [SCRIPT_TYPES.SCOPED]: t`Scoped`,1107 [SCRIPT_TYPES.SCOPED]: t`Scoped`,
1042 [SCRIPT_TYPES.GLOBAL]: t`Global`,1108 [SCRIPT_TYPES.GLOBAL]: t`Global`,
1109 [SCRIPT_TYPES.PRESET]: t`Preset`,
1043 }[script.type],1110 }[script.type],
1044 );1111 );
1045 ruleElement.find('.rule-enabled').prop('checked', !script.disabled);1112 ruleElement.find('.rule-enabled').prop('checked', !script.disabled);
@@ -1086,6 +1153,13 @@ function populateDebuggerRuleList(container) {
1086 scopedScripts.forEach(script => scopedList.append(renderRule(script)));1153 scopedScripts.forEach(script => scopedList.append(renderRule(script)));
1087 rulesContainer.append(scopedList);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}
10901164
1091/**1165/**
@@ -1104,12 +1178,15 @@ async function onRegexDebuggerOpenClick() {
1104 debuggerHtml.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection();1178 debuggerHtml.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection();
1105 // @ts-ignore1179 // @ts-ignore
1106 debuggerHtml.find('#regex_debugger_rules_scoped').sortable({ delay: getSortableDelay() }).disableSelection();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();
11071183
1108 debuggerHtml.find('#regex_debugger_run_test').on('click', function () {1184 debuggerHtml.find('#regex_debugger_run_test').on('click', function () {
1109 const allScripts = debuggerHtml.data('allScripts');1185 const allScripts = debuggerHtml.data('allScripts');
1110 const orderedRuleIds = [1186 const orderedRuleIds = [
1111 ...$('#regex_debugger_rules_global').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(),1187 ...$('#regex_debugger_rules_global').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(),
1112 ...$('#regex_debugger_rules_scoped').find('li.regex-debugger-rule').map((i, el) => $(el).data('id')).get(),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 ];
11141191
1115 const rawInput = String($('#regex_debugger_raw_input').val());1192 const rawInput = String($('#regex_debugger_raw_input').val());
@@ -1187,11 +1264,13 @@ async function onRegexDebuggerOpenClick() {
1187 const allKnownScripts = getRegexScripts();1264 const allKnownScripts = getRegexScripts();
1188 const newGlobalScripts = $('#regex_debugger_rules_global').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean);1265 const newGlobalScripts = $('#regex_debugger_rules_global').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean);
1189 const newScopedScripts = $('#regex_debugger_rules_scoped').children('li').map((_, el) => allKnownScripts.find(s => s.id === $(el).data('id'))).get().filter(Boolean);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);
11901268
1191 extension_settings.regex = newGlobalScripts;1269 extension_settings.regex = newGlobalScripts;
1192 if (this_chid !== undefined) {1270 if (this_chid !== undefined) {
1193 await writeExtensionField(this_chid, 'regex_scripts', newScopedScripts);1271 await saveScriptsByType(newScopedScripts, SCRIPT_TYPES.SCOPED);
1194 }1272 }
1273 await saveScriptsByType(newPresetScripts, SCRIPT_TYPES.PRESET);
11951274
1196 saveSettingsDebounced();1275 saveSettingsDebounced();
1197 await loadRegexScripts();1276 await loadRegexScripts();
@@ -1203,6 +1282,8 @@ async function onRegexDebuggerOpenClick() {
1203 currentPopupContent.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection();1282 currentPopupContent.find('#regex_debugger_rules_global').sortable({ delay: getSortableDelay() }).disableSelection();
1204 // @ts-ignore1283 // @ts-ignore
1205 currentPopupContent.find('#regex_debugger_rules_scoped').sortable({ delay: getSortableDelay() }).disableSelection();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 });
12071288
1208 debuggerHtml.find('#regex_debugger_expand_steps').on('click', function () {1289 debuggerHtml.find('#regex_debugger_expand_steps').on('click', function () {
@@ -1328,11 +1409,6 @@ function migrateSettings() {
1328 }1409 }
1329 });1410 });
13301411
1331 if (!extension_settings.character_allowed_regex) {
1332 extension_settings.character_allowed_regex = [];
1333 performSave = true;
1334 }
1335
1336 if (performSave) {1412 if (performSave) {
1337 saveSettingsDebounced();1413 saveSettingsDebounced();
1338 }1414 }
@@ -1418,8 +1494,8 @@ async function toggleRegexCallback(args, scriptName) {
14181494
1419/**1495/**
1420 * Performs the import of the regex object.1496 * Performs the import of the regex object.
1421 * @param {Object} regexScript Input object1497 * @param {RegexScript} regexScript Input object
1422 * @param {SCRIPT_TYPES} scriptType global? scoped?1498 * @param {SCRIPT_TYPES} scriptType The type of script to import as
1423 */1499 */
1424async function onRegexImportObjectChange(regexScript, scriptType) {1500async function onRegexImportObjectChange(regexScript, scriptType) {
1425 try {1501 try {
@@ -1433,8 +1509,18 @@ async function onRegexImportObjectChange(regexScript, scriptType) {
1433 const array = getScriptsByType(scriptType);1509 const array = getScriptsByType(scriptType);
1434 array.push(regexScript);1510 array.push(regexScript);
14351511
1436 if (scriptType === SCRIPT_TYPES.SCOPED) {1512 switch (scriptType) {
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 }
14391525
1440 saveSettingsDebounced();1526 saveSettingsDebounced();
@@ -1450,7 +1536,7 @@ async function onRegexImportObjectChange(regexScript, scriptType) {
1450/**1536/**
1451 * Performs the import of the regex file.1537 * Performs the import of the regex file.
1452 * @param {File} file Input file1538 * @param {File} file Input file
1453 * @param {SCRIPT_TYPES} scriptType global? scoped?1539 * @param {SCRIPT_TYPES} scriptType The type of script to import as
1454 */1540 */
1455async function onRegexImportFileChange(file, scriptType) {1541async function onRegexImportFileChange(file, scriptType) {
1456 if (!file) {1542 if (!file) {
@@ -1480,17 +1566,13 @@ async function onRegexImportFileChange(file, scriptType) {
1480 * @returns {SCRIPT_TYPES} The script type.1566 * @returns {SCRIPT_TYPES} The script type.
1481 */1567 */
1482function getScriptType(script) {1568function 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)) {
1572 return scriptType;
1486 }1573 }
1487
1488 const globalScripts = getScriptsByType(SCRIPT_TYPES.GLOBAL);
1489 if (globalScripts.some(s => s.id === script.id)) {
1490 return SCRIPT_TYPES.GLOBAL;
1491 }1574 }
14921575 return SCRIPT_TYPE_UNKNOWN;
1493 return SCRIPT_TYPES.UNKNOWN;
1494}1576}
14951577
1496function getSelectedScripts() {1578function getSelectedScripts() {
@@ -1507,44 +1589,121 @@ function purgeEmbeddedRegexScripts({ character }) {
1507 if (!avatar) {1589 if (!avatar) {
1508 return;1590 return;
1509 }1591 }
1510 const checkKey = `AlertRegex_${characters[this_chid].avatar}`;1592 const checkKey = `AlertRegex_${avatar}`;
1511 if (accountStorage.getItem(checkKey)) {1593 if (accountStorage.getItem(checkKey)) {
1512 accountStorage.removeItem(checkKey);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) {
1516 extension_settings.character_allowed_regex.splice(index, 1);
1517 saveSettingsDebounced();
1518}1597}
1598
1599function purgePresetEmbeddedRegexScripts({ apiId, name }) {
1600 const checkKey = `AlertRegex_${apiId}_${name}`;
1601 if (accountStorage.getItem(checkKey)) {
1602 accountStorage.removeItem(checkKey);
1603 }
1604 disallowPresetScripts(apiId, name);
1519}1605}
15201606
1521async function checkCharEmbeddedRegexScripts() {1607async function checkCharEmbeddedRegexScripts() {
1522 const chid = this_chid;1608 const chid = this_chid;
15231609
1524 if (chid !== undefined && !selected_group) {1610 if (chid !== undefined && !selected_group) {
1525 const avatar = characters[chid]?.avatar;1611 const character = characters[chid];
1526 const scripts = getScriptsByType(SCRIPT_TYPES.SCOPED);1612 const scripts = getScriptsByType(SCRIPT_TYPES.SCOPED);
15271613
1528 if (Array.isArray(scripts) && scripts.length > 0) {1614 if (Array.isArray(scripts) && scripts.length > 0) {
1529 if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) {1615 if (!isScopedScriptsAllowed(character)) {
1530 const checkKey = `AlertRegex_${characters[chid].avatar}`;1616 const checkKey = `AlertRegex_${character.avatar}`;
1531
1532 if (!accountStorage.getItem(checkKey)) {1617 if (!accountStorage.getItem(checkKey)) {
1533 accountStorage.setItem(checkKey, 'true');1618 accountStorage.setItem(checkKey, 'true');
1534 const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {});1619 const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {});
1535 const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { okButton: 'Yes' });1620 const result = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '');
15361621
1537 if (result) {1622 if (result) {
1538 extension_settings.character_allowed_regex.push(avatar);1623 allowScopedScripts(character);
1539 await reloadCurrentChat();1624 await reloadCurrentChat();
1540 saveSettingsDebounced();
1541 }1625 }
1542 }1626 }
1543 }1627 }
1544 }1628 }
1545 }1629 }
15461630
1547 loadRegexScripts();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 */
1638function 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
1649async 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()) {
1666 await reloadCurrentChat();
1667 }
1668 }
1669 }
1670 } else if (getCurrentChatId() && scripts.filter(script => !script.disabled).length > 0) {
1671 notifyReloadCurrentChat(name);
1672 }
1673 }
1674
1675 await loadRegexScripts();
1676}
1677
1678async 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
1695function 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}
15491708
1550// Workaround for loading in sequence with other extensions1709// Workaround for loading in sequence with other extensions
@@ -1584,11 +1743,15 @@ jQuery(async () => {
15841743
1585 onRegexEditorOpenClick(false, SCRIPT_TYPES.SCOPED);1744 onRegexEditorOpenClick(false, SCRIPT_TYPES.SCOPED);
1586 });1745 });
1746 $('#open_preset_editor').on('click', function () {
1747 onRegexEditorOpenClick(false, SCRIPT_TYPES.PRESET);
1748 });
1587 $('#import_regex_file').on('change', async function () {1749 $('#import_regex_file').on('change', async function () {
1588 let target = SCRIPT_TYPES.GLOBAL;1750 let target = SCRIPT_TYPES.GLOBAL;
1589 const template = $(await renderExtensionTemplateAsync('regex', 'importTarget'));1751 const template = $(await renderExtensionTemplateAsync('regex', 'importTarget'));
1590 template.find('#regex_import_target_global').on('input', () => (target = SCRIPT_TYPES.GLOBAL));1752 template.find('#regex_import_target_global').on('input', () => (target = SCRIPT_TYPES.GLOBAL));
1591 template.find('#regex_import_target_scoped').on('input', () => (target = SCRIPT_TYPES.SCOPED));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));
15921755
1593 await callGenericPopup(template, POPUP_TYPE.TEXT);1756 await callGenericPopup(template, POPUP_TYPE.TEXT);
15941757
@@ -1617,30 +1780,47 @@ jQuery(async () => {
1617 });1780 });
16181781
1619 $('#bulk_enable_regex').on('click', async function () {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 });
16311785
1632 $('#bulk_disable_regex').on('click', async function () {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 if (scripts.length === 0) {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 return;1802 return;
1637 }1803 }
1804 const scriptTypesToSave = new Set();
1638 for (const script of scripts) {1805 for (const script of scripts) {
1639 script.disabled = true;1806 const scriptType = getScriptType(script);
1807 scriptTypesToSave.add(scriptType);
1808 script.disabled = !newState;
1809 }
1810 for (const scriptType of scriptTypesToSave) {
1811 const scriptsOfType = getScriptsByType(scriptType);
1812 await saveScriptsByType(scriptsOfType, scriptType);
1640 }1813 }
1814
1641 saveSettingsDebounced();1815 saveSettingsDebounced();
1642 await loadRegexScripts();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 }
16441824
1645 /**1825 /**
1646 * Bulk move regex scripts to the specified type1826 * Bulk move regex scripts to the specified type
@@ -1656,6 +1836,7 @@ jQuery(async () => {
1656 await moveRegexScript(script, toType, getScriptType(script), false);1836 await moveRegexScript(script, toType, getScriptType(script), false);
1657 }1837 }
16581838
1839 saveSettingsDebounced();
1659 await loadRegexScripts();1840 await loadRegexScripts();
16601841
1661 // Reload the current chat to undo previous markdown1842 // Reload the current chat to undo previous markdown
@@ -1681,6 +1862,14 @@ jQuery(async () => {
1681 await bulkMoveRegexScript(SCRIPT_TYPES.SCOPED);1862 await bulkMoveRegexScript(SCRIPT_TYPES.SCOPED);
1682 });1863 });
16831864
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 $('#bulk_delete_regex').on('click', async function () {1873 $('#bulk_delete_regex').on('click', async function () {
1685 const scripts = getSelectedScripts();1874 const scripts = getSelectedScripts();
1686 if (scripts.length === 0) {1875 if (scripts.length === 0) {
@@ -1714,14 +1903,19 @@ jQuery(async () => {
1714 let sortableDatas = [1903 let sortableDatas = [
1715 {1904 {
1716 selector: '#saved_regex_scripts',1905 selector: '#saved_regex_scripts',
1717 setter: x => extension_settings.regex = x,1906 setter: scripts => saveScriptsByType(scripts, SCRIPT_TYPES.GLOBAL),
1718 getter: () => getScriptsByType(SCRIPT_TYPES.GLOBAL),1907 getter: () => getScriptsByType(SCRIPT_TYPES.GLOBAL),
1719 },1908 },
1720 {1909 {
1721 selector: '#saved_scoped_scripts',1910 selector: '#saved_scoped_scripts',
1722 setter: x => writeExtensionField(this_chid, 'regex_scripts', x),1911 setter: scripts => saveScriptsByType(scripts, SCRIPT_TYPES.SCOPED),
1723 getter: () => getScriptsByType(SCRIPT_TYPES.SCOPED),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 for (const { selector, setter, getter } of sortableDatas) {1920 for (const { selector, setter, getter } of sortableDatas) {
1727 // @ts-ignore1921 // @ts-ignore
@@ -1760,17 +1954,26 @@ jQuery(async () => {
1760 }1954 }
17611955
1762 const isEnable = !!$(this).prop('checked');1956 const isEnable = !!$(this).prop('checked');
1763 const avatar = characters[this_chid].avatar;1957 const character = characters[this_chid];
17641958
1765 if (isEnable) {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 } else {1961 } else {
1770 const index = extension_settings.character_allowed_regex.indexOf(avatar);1962 disallowScopedScripts(character);
1771 if (index !== -1) {
1772 extension_settings.character_allowed_regex.splice(index, 1);
1773 }1963 }
1964
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 }
17751978
1776 saveSettingsDebounced();1979 saveSettingsDebounced();
@@ -1806,6 +2009,12 @@ jQuery(async () => {
1806 color: enumTypes.name,2009 color: enumTypes.name,
1807 icon: 'S',2010 icon: 'S',
1808 };2011 };
2012 case SCRIPT_TYPES.PRESET:
2013 return {
2014 typename: 'preset',
2015 color: enumTypes.name,
2016 icon: 'P',
2017 };
1809 default:2018 default:
1810 return {2019 return {
1811 typename: 'Unknown',2020 typename: 'Unknown',
@@ -1895,8 +2104,12 @@ jQuery(async () => {
1895 `,2104 `,
1896 }));2105 }));
18972106
2107 eventSource.on(event_types.MAIN_API_CHANGED, onMainApiChanged);
1898 eventSource.on(event_types.CHAT_CHANGED, checkCharEmbeddedRegexScripts);2108 eventSource.on(event_types.CHAT_CHANGED, checkCharEmbeddedRegexScripts);
1899 eventSource.on(event_types.CHARACTER_DELETED, purgeEmbeddedRegexScripts);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);
19002113
1901 presetManager.setupEventListeners();2114 presetManager.setupEventListeners();
1902 presetManager.registerSlashCommands();2115 presetManager.registerSlashCommands();
public/scripts/extensions/regex/presetEmbeddedScripts.html+5 -0
@@ -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>
public/scripts/extensions/regex/scriptTemplate.html+14 -5
@@ -1,25 +1,34 @@
1<div class="regex-script-label flex-container flexnowrap">1<div class="regex-script-label flex-container flexnowrap">
2 <input type="checkbox" class="regex_bulk_checkbox" />2 <input type="checkbox" class="regex_bulk_checkbox" />
3 <span class="drag-handle menu-handle">&#9776;</span>3 <span class="drag-handle menu-handle">&#9776;</span>
4 <div class="regex_script_name flexGrow overflow-hidden"></div>4 <div class="regex_script_name flex1 overflow-hidden"></div>
5 <div class="flex-container flexnowrap">5 <div class="flex-container flexnowrap">
6 <label class="checkbox flex-container" for="regex_disable">6 <label class="checkbox flex-container margin-r5" for="regex_disable">
7 <input type="checkbox" name="regex_disable" class="disable_regex" />7 <input type="checkbox" name="regex_disable" class="disable_regex" />
8 <span class="regex-toggle-on fa-solid fa-toggle-on" data-i18n="[title]ext_regex_disable_script" title="Disable script"></span>8 <span class="regex-toggle-on fa-solid fa-toggle-on" data-i18n="[title]ext_regex_disable_script" title="Disable script"></span>
9 <span class="regex-toggle-off fa-solid fa-toggle-off" data-i18n="[title]ext_regex_enable_script" title="Enable script"></span>9 <span class="regex-toggle-off fa-solid fa-toggle-off" data-i18n="[title]ext_regex_enable_script" title="Enable script"></span>
10 </label>10 </label>
11 <div class="edit_existing_regex menu_button" data-i18n="[title]ext_regex_edit_script" title="Edit script">11 <label class="menu_button regex_script_expand" title="Show more options" data-i18n="[title]Show more options">
12 <i class="fa-solid fa-pencil"></i>12 <input type="checkbox" name="regex_expand" />
13 </div>13 <span class="fa-solid fa-ellipsis"></span>
14 </label>
15 <div class="flex-container regex_script_buttons">
14 <div class="move_to_global menu_button" data-i18n="[title]ext_regex_move_to_global" title="Move to global scripts">16 <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>17 <i class="fa-solid fa-globe"></i>
16 </div>18 </div>
17 <div class="move_to_scoped menu_button" data-i18n="[title]ext_regex_move_to_scoped" title="Move to scoped scripts">19 <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>20 <i class="fa-solid fa-address-card"></i>
19 </div>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>
20 <div class="export_regex menu_button" data-i18n="[title]ext_regex_export_script" title="Export script">25 <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>26 <i class="fa-solid fa-file-export"></i>
22 </div>27 </div>
28 </div>
29 <div class="edit_existing_regex menu_button" data-i18n="[title]ext_regex_edit_script" title="Edit script">
30 <i class="fa-solid fa-pencil"></i>
31 </div>
23 <div class="delete_regex menu_button" data-i18n="[title]ext_regex_delete_script" title="Delete script">32 <div class="delete_regex menu_button" data-i18n="[title]ext_regex_delete_script" title="Delete script">
24 <i class="fa-solid fa-trash"></i>33 <i class="fa-solid fa-trash"></i>
25 </div>34 </div>
public/scripts/extensions/regex/style.css+29 -25
@@ -24,39 +24,33 @@
24 text-align: center;24 text-align: center;
25}25}
2626
27#scoped_scripts_block {27#scoped_scripts_block,
28#preset_scripts_block {
28 opacity: 1;29 opacity: 1;
29 transition: opacity var(--animation-duration-2x) ease-in-out;30 transition: opacity var(--animation-duration-2x) ease-in-out;
30}31}
3132
32#scoped_scripts_block .move_to_scoped {33#scoped_scripts_block .move_to_scoped,
34#global_scripts_block .move_to_global,
35#preset_scripts_block .move_to_preset {
33 display: none;36 display: none;
34}37}
3538
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 opacity: 0.5;41 opacity: 0.5;
42}42}
4343
44.enable_scoped:checked~.regex-toggle-on {44.enable_scoped:checked~.regex-toggle-on,
45.enable_scoped:not(:checked)~.regex-toggle-off {
45 display: block;46 display: block;
46}47}
4748
48.enable_scoped:checked~.regex-toggle-off {49.enable_scoped:checked~.regex-toggle-off,
49 display: none;
50}
51
52.enable_scoped:not(:checked)~.regex-toggle-on {50.enable_scoped:not(:checked)~.regex-toggle-on {
53 display: none;51 display: none;
54}52}
5553
56.enable_scoped:not(:checked)~.regex-toggle-off {
57 display: block;
58}
59
60.regex-script-label {54.regex-script-label {
61 align-items: baseline;55 align-items: baseline;
62 border: 1px solid var(--SmartThemeBorderColor);56 border: 1px solid var(--SmartThemeBorderColor);
@@ -92,19 +86,13 @@ input.enable_scoped {
92 cursor: pointer;86 cursor: pointer;
93}87}
9488
95.disable_regex:checked~.regex-toggle-off {89.disable_regex:checked~.regex-toggle-on,
96 display: block;
97}
98
99.disable_regex:checked~.regex-toggle-on {
100 display: none;
101}
102
103.disable_regex:not(:checked)~.regex-toggle-off {90.disable_regex:not(:checked)~.regex-toggle-off {
104 display: none;91 display: none;
105}92}
10693
107.disable_regex:not(:checked)~.regex-toggle-on {94.disable_regex:not(:checked)~.regex-toggle-on,
95.disable_regex:checked~.regex-toggle-off {
108 display: block;96 display: block;
109}97}
11098
@@ -155,3 +143,19 @@ input.enable_scoped {
155.regex_settings:has(#regex_bulk_edit:checked) .regex_bulk_checkbox {143.regex_settings:has(#regex_bulk_edit:checked) .regex_bulk_checkbox {
156 display: inline-grid;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}
public/scripts/preset-manager.js+12 -7
@@ -81,6 +81,9 @@ function autoSelectPreset() {
81 * @returns {PresetManager} Preset manager81 * @returns {PresetManager} Preset manager
82 */82 */
83export function getPresetManager(apiId = '') {83export function getPresetManager(apiId = '') {
84 if (apiId === 'koboldhorde') {
85 apiId = 'kobold';
86 }
84 if (!apiId) {87 if (!apiId) {
85 apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;88 apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
86 }89 }
@@ -814,7 +817,7 @@ class PresetManager {
814 * Reads a preset extension field from the preset.817 * Reads a preset extension field from the preset.
815 * @param {object} options818 * @param {object} options
816 * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name.819 * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name.
817 * @param {string} options.path Path to the preset extension field, e.g. 'myextension.data'.820 * @param {string} options.path Path to the preset extension field, e.g. 'myextension.data'. If empty, reads the entire extensions object.
818 * @return {any} The value of the preset extension field, or null if not found.821 * @return {any} The value of the preset extension field, or null if not found.
819 */822 */
820 readPresetExtensionField({ name, path }) {823 readPresetExtensionField({ name, path }) {
@@ -825,7 +828,7 @@ class PresetManager {
825 // Read from settings if the selected preset is the same as the provided name828 // Read from settings if the selected preset is the same as the provided name
826 if (settings && selectedName === presetName) {829 if (settings && selectedName === presetName) {
827 const settingsExtensions = ensurePlainObject(settings.extensions || {});830 const settingsExtensions = ensurePlainObject(settings.extensions || {});
828 return lodash.get(settingsExtensions, path, null);831 return path ? lodash.get(settingsExtensions, path, null) : settingsExtensions;
829 }832 }
830833
831 // Otherwise, read from the preset by name834 // Otherwise, read from the preset by name
@@ -835,7 +838,7 @@ class PresetManager {
835 }838 }
836839
837 const presetExtensions = ensurePlainObject(preset.extensions || {});840 const presetExtensions = ensurePlainObject(preset.extensions || {});
838 const value = lodash.get(presetExtensions, path, null);841 const value = path ? lodash.get(presetExtensions, path, null) : presetExtensions;
839 return value;842 return value;
840 }843 }
841844
@@ -843,7 +846,7 @@ class PresetManager {
843 * Writes a value to a preset extension field.846 * Writes a value to a preset extension field.
844 * @param {object} options847 * @param {object} options
845 * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name.848 * @param {string} [options.name] Name of the preset. If not provided, uses the currently selected preset name.
846 * @param {string} options.path Path to the preset extension field, e.g. 'myextension.data'.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 * @param {any} options.value Value to write to the preset extension field.850 * @param {any} options.value Value to write to the preset extension field.
848 * @return {Promise<void>} Resolves when the preset is saved.851 * @return {Promise<void>} Resolves when the preset is saved.
849 */852 */
@@ -856,7 +859,7 @@ class PresetManager {
856 if (settings && selectedName === presetName) {859 if (settings && selectedName === presetName) {
857 // Set the value at the specified path860 // Set the value at the specified path
858 settings.extensions = ensurePlainObject(settings.extensions || {});861 settings.extensions = ensurePlainObject(settings.extensions || {});
859 lodash.set(settings.extensions, path, value);862 path ? lodash.set(settings.extensions, path, value) : (settings.extensions = value);
860 await saveSettings();863 await saveSettings();
861 }864 }
862865
@@ -868,7 +871,7 @@ class PresetManager {
868871
869 // Set the value at the specified path872 // Set the value at the specified path
870 preset.extensions = ensurePlainObject(preset.extensions || {});873 preset.extensions = ensurePlainObject(preset.extensions || {});
871 lodash.set(preset.extensions, path, value);874 path ? lodash.set(preset.extensions, path, value) : (preset.extensions = value);
872875
873 // Save the updated preset876 // Save the updated preset
874 await this.savePreset(presetName, preset, { skipUpdate: true });877 await this.savePreset(presetName, preset, { skipUpdate: true });
@@ -1033,8 +1036,10 @@ export async function initPresetManager() {
1033 return;1036 return;
1034 }1037 }
10351038
1039 await eventSource.emit(event_types.PRESET_RENAMED_BEFORE, { apiId: apiId, oldName: oldName, newName: newName });
1040 const extensions = presetManager.readPresetExtensionField({ name: oldName, path: '' });
1036 await presetManager.renamePreset(newName);1041 await presetManager.renamePreset(newName);
10371042 await presetManager.writePresetExtensionField({ name: newName, path: '', value: extensions });
1038 await eventSource.emit(event_types.PRESET_RENAMED, { apiId: apiId, oldName: oldName, newName: newName });1043 await eventSource.emit(event_types.PRESET_RENAMED, { apiId: apiId, oldName: oldName, newName: newName });
10391044
1040 if (apiId === 'openai') {1045 if (apiId === 'openai') {