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 | "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": "停止图像生成", |
| @@ -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 | }; |
| @@ -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: {}, |
| @@ -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> |
| @@ -1,18 +1,25 @@ | |||
| 1 | import { characters, substituteParams, substituteParamsExtended, this_chid } from '../../../script.js'; | 1 | import { characters, saveSettingsDebounced, substituteParams, substituteParamsExtended, this_chid } from '../../../script.js'; |
| 2 | import { extension_settings } from '../../extensions.js'; | 2 | import { extension_settings, writeExtensionField } from '../../extensions.js'; |
| 3 | import { getPresetManager } from '../../preset-manager.js'; | ||
| 3 | import { regexFromString } from '../../utils.js'; | 4 | import { regexFromString } from '../../utils.js'; |
| 5 | import { lodash } from '../../../lib.js'; | ||
| 4 | 6 | ||
| 5 | /** | 7 | /** |
| 6 | * @enum {number} Regex scripts types | 8 | * @enum {number} Regex scripts types |
| 7 | * @readonly | 9 | * @readonly |
| 8 | */ | 10 | */ |
| 9 | export const SCRIPT_TYPES = { | 11 | export const SCRIPT_TYPES = { |
| 10 | UNKNOWN: -1, | ||
| 11 | GLOBAL: 0, | 12 | GLOBAL: 0, |
| 12 | SCOPED: 1, | 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 | * @typedef {import('../../char-data.js').RegexScriptData} RegexScript | 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 | export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) { | 52 | export 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 | } |
| 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 | * @enum {number} Where the regex script should be applied | 222 | * @enum {number} Where the regex script should be applied |
| 66 | * @readonly | 223 | * @readonly |
| 67 | */ | 224 | */ |
| @@ -15,5 +15,11 @@ | |||
| 15 | Scoped Scripts | 15 | 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> |
| @@ -1,5 +1,5 @@ | |||
| 1 | import { characters, eventSource, event_types, getCurrentChatId, messageFormatting, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js'; | 1 | import { characters, eventSource, event_types, getCurrentChatId, messageFormatting, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js'; |
| 2 | import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js'; | 2 | import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { selected_group } from '../../group-chats.js'; | 3 | import { selected_group } from '../../group-chats.js'; |
| 4 | import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; | 4 | import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; |
| 5 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 5 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| @@ -8,9 +8,10 @@ import { commonEnumProviders, enumIcons } from '../../slash-commands/SlashComman | |||
| 8 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; | 8 | import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 9 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 9 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| 10 | import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js'; | 10 | import { download, equalsIgnoreCaseAndAccents, escapeHtml, getFileText, getSortableDelay, isFalseBoolean, isTrueBoolean, regexFromString, setInfoBlock, uuidv4 } from '../../utils.js'; |
| 11 | import { getRegexScripts, getScriptsByType, regex_placement, runRegexScript, SCRIPT_TYPES, substitute_find_regex } from './engine.js'; | 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 | import { t } from '../../i18n.js'; | 12 | import { t } from '../../i18n.js'; |
| 13 | import { accountStorage } from '../../util/AccountStorage.js'; | 13 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 14 | import { getPresetManager } from '../../preset-manager.js'; | ||
| 14 | 15 | ||
| 15 | // Re-exports for legacy extensions | 16 | // Re-exports for legacy extensions |
| 16 | export { getRegexScripts }; | 17 | export { getRegexScripts }; |
| @@ -33,12 +34,14 @@ const sanitizeFileName = name => name.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, ' | |||
| 33 | * @property {boolean} isSelected - Whether the preset is currently selected | 34 | * @property {boolean} isSelected - Whether the preset is currently selected |
| 34 | * @property {RegexPresetItem[]} global - The list of global preset items | 35 | * @property {RegexPresetItem[]} global - The list of global preset items |
| 35 | * @property {RegexPresetItem[]} scoped - The list of scoped preset items | 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 | * @typedef {object} RegexPresetState | 41 | * @typedef {object} RegexPresetState |
| 40 | * @property {string[]} global - List of enabled global regex script IDs | 42 | * @property {string[]} global - List of enabled global regex script IDs |
| 41 | * @property {string[]} scoped - List of enabled scoped regex script IDs | 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 | class RegexPresetManager { | 47 | class 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)); | ||
| 73 | 77 | ||
| 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 | } |
| 79 | 84 | ||
| @@ -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 || []; | ||
| 93 | 100 | ||
| 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 | } |
| 97 | 104 | ||
| 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 | } |
| 101 | 109 | ||
| 102 | /** | 110 | /** |
| @@ -365,17 +373,18 @@ class RegexPresetManager { | |||
| 365 | return; | 373 | return; |
| 366 | } | 374 | } |
| 367 | 375 | ||
| 368 | // Apply to both global and scoped lists | 376 | // Apply preset to all 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 | saveFunction: (scripts) => writeExtensionField(this_chid, 'regex_scripts', scripts), | 385 | saveFunction: scripts => saveScriptsByType(scripts, scriptType), |
| 378 | }); | 386 | }); |
| 387 | } | ||
| 379 | 388 | ||
| 380 | // Render the changes to the UI | 389 | // 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 | }; |
| 427 | 437 | ||
| 428 | if (isUpdate) { | 438 | if (isUpdate) { |
| @@ -485,15 +495,17 @@ function setToggleAllIcon(allAreChecked) { | |||
| 485 | function setMoveButtonsVisibility() { | 495 | function 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 | } |
| 491 | 503 | ||
| 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} regexScript | 506 | * @param {import('../../char-data.js').RegexScriptData} regexScript |
| 495 | * @param {number} existingScriptIndex Index of the existing script | 507 | * @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 immediately | 509 | * @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 | } |
| 530 | 542 | ||
| 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); |
| 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 | if (saveSettings) { | 553 | if (saveSettings) { |
| @@ -557,7 +570,7 @@ async function saveRegexScript(regexScript, existingScriptIndex, scriptType, sav | |||
| 557 | /** | 570 | /** |
| 558 | * Delete a regex script by ID | 571 | * Delete a regex script by ID |
| 559 | * @param {string} id ID of the script to delete | 572 | * @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 immediately | 574 | * @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); |
| 570 | 583 | ||
| 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 | |||
| 604 | async function loadRegexScripts() { | 627 | async 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); |
| 608 | 632 | ||
| 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 to | 637 | * @param {string} container Container to render the script to |
| 614 | * @param {import('../../char-data.js').RegexScriptData} script Script data | 638 | * @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 array | 640 | * @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 | } |
| 626 | 650 | ||
| 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 | }); | ||
| 685 | 743 | ||
| 686 | $(container).append(scriptHtml); | 744 | $(container).append(scriptHtml); |
| 687 | } | 745 | } |
| 688 | 746 | ||
| 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)); | ||
| 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 | $('#regex_scoped_toggle').prop('checked', isScopedAllowed); | 752 | $('#regex_preset_toggle').prop('checked', isPresetScriptsAllowed(getCurrentPresetAPI(), getCurrentPresetName())); |
| 694 | 753 | ||
| 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 ID | 759 | * @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 | */ |
| 704 | async function onRegexEditorOpenClick(existingId, scriptType) { | 763 | async function onRegexEditorOpenClick(existingId, scriptType) { |
| @@ -1006,23 +1065,30 @@ function populateDebuggerRuleList(container) { | |||
| 1006 | } | 1065 | } |
| 1007 | 1066 | ||
| 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 = []; | ||
| 1011 | 1073 | ||
| 1012 | allScripts.forEach(script => { | 1074 | allScripts.forEach(script => { |
| 1013 | const scriptCopy = structuredClone(script); // Use structuredClone for deep copy | 1075 | const scriptCopy = structuredClone(script); // Use structuredClone for deep copy |
| 1014 | if (globalScriptIds.has(script.id)) { | 1076 | if (globalScriptIds.has(script.id)) { |
| 1015 | // @ts-ignore | 1077 | // @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-ignore | 1081 | // @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 | }); |
| 1024 | 1090 | ||
| 1025 | container.data('allScripts', [...globalScripts, ...scopedScripts]); | 1091 | container.data('allScripts', [...globalScripts, ...scopedScripts, ...presetScripts]); |
| 1026 | 1092 | ||
| 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 | } |
| 1090 | 1164 | ||
| 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-ignore | 1179 | // @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(); | ||
| 1107 | 1183 | ||
| 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 | ]; |
| 1114 | 1191 | ||
| 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); | ||
| 1190 | 1268 | ||
| 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); | ||
| 1195 | 1274 | ||
| 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-ignore | 1283 | // @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 | }); |
| 1207 | 1288 | ||
| 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 | }); |
| 1330 | 1411 | ||
| 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) { | |||
| 1418 | 1494 | ||
| 1419 | /** | 1495 | /** |
| 1420 | * Performs the import of the regex object. | 1496 | * Performs the import of the regex object. |
| 1421 | * @param {Object} regexScript Input object | 1497 | * @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 | */ |
| 1424 | async function onRegexImportObjectChange(regexScript, scriptType) { | 1500 | async 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); |
| 1435 | 1511 | ||
| 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 | } |
| 1439 | 1525 | ||
| 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 file | 1538 | * @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 | */ |
| 1455 | async function onRegexImportFileChange(file, scriptType) { | 1541 | async 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 | */ |
| 1482 | function getScriptType(script) { | 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 | function getSelectedScripts() { | 1578 | function 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) { | 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 | async function checkCharEmbeddedRegexScripts() { | 1607 | async function checkCharEmbeddedRegexScripts() { |
| 1522 | const chid = this_chid; | 1608 | const chid = this_chid; |
| 1523 | 1609 | ||
| 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); |
| 1527 | 1613 | ||
| 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, ''); |
| 1536 | 1621 | ||
| 1537 | if (result) { | 1622 | if (result) { |
| 1538 | extension_settings.character_allowed_regex.push(avatar); | 1623 | allowScopedScripts(character); |
| 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 | await reloadCurrentChat(); | 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 | loadRegexScripts(); | 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 | // Workaround for loading in sequence with other extensions | 1709 | // Workaround for loading in sequence with other extensions |
| @@ -1584,11 +1743,15 @@ jQuery(async () => { | |||
| 1584 | 1743 | ||
| 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)); | ||
| 1592 | 1755 | ||
| 1593 | await callGenericPopup(template, POPUP_TYPE.TEXT); | 1756 | await callGenericPopup(template, POPUP_TYPE.TEXT); |
| 1594 | 1757 | ||
| @@ -1617,30 +1780,47 @@ jQuery(async () => { | |||
| 1617 | }); | 1780 | }); |
| 1618 | 1781 | ||
| 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 | }); |
| 1631 | 1785 | ||
| 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; | ||
| 1640 | } | 1809 | } |
| 1810 | for (const scriptType of scriptTypesToSave) { | ||
| 1811 | const scriptsOfType = getScriptsByType(scriptType); | ||
| 1812 | await saveScriptsByType(scriptsOfType, scriptType); | ||
| 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 | } | ||
| 1644 | 1824 | ||
| 1645 | /** | 1825 | /** |
| 1646 | * Bulk move regex scripts to the specified type | 1826 | * 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 | } |
| 1658 | 1838 | ||
| 1839 | saveSettingsDebounced(); | ||
| 1659 | await loadRegexScripts(); | 1840 | await loadRegexScripts(); |
| 1660 | 1841 | ||
| 1661 | // Reload the current chat to undo previous markdown | 1842 | // 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 | }); |
| 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 | $('#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-ignore | 1921 | // @ts-ignore |
| @@ -1760,17 +1954,26 @@ jQuery(async () => { | |||
| 1760 | } | 1954 | } |
| 1761 | 1955 | ||
| 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]; |
| 1764 | 1958 | ||
| 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) { | 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 | 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 | })); |
| 1897 | 2106 | ||
| 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); | ||
| 1900 | 2113 | ||
| 1901 | presetManager.setupEventListeners(); | 2114 | presetManager.setupEventListeners(); |
| 1902 | presetManager.registerSlashCommands(); | 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 | <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">☰</span> | 3 | <span class="drag-handle menu-handle">☰</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 | <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 | <div class="edit_existing_regex menu_button" data-i18n="[title]ext_regex_edit_script" title="Edit script"> | 29 | <div class="edit_existing_regex menu_button" data-i18n="[title]ext_regex_edit_script" title="Edit script"> |
| 12 | <i class="fa-solid fa-pencil"></i> | 30 | <i class="fa-solid fa-pencil"></i> |
| 13 | </div> | 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 | <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> |
| @@ -24,39 +24,33 @@ | |||
| 24 | text-align: center; | 24 | text-align: center; |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 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 | } |
| 31 | 32 | ||
| 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 | } |
| 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 | opacity: 0.5; | 41 | opacity: 0.5; |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 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 | } |
| 47 | 48 | ||
| 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 | } |
| 55 | 53 | ||
| 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 | } |
| 94 | 88 | ||
| 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 | } |
| 106 | 93 | ||
| 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 | } |
| 110 | 98 | ||
| @@ -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 | } | ||
| @@ -81,6 +81,9 @@ function autoSelectPreset() { | |||
| 81 | * @returns {PresetManager} Preset manager | 81 | * @returns {PresetManager} Preset manager |
| 82 | */ | 82 | */ |
| 83 | export function getPresetManager(apiId = '') { | 83 | export 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} options | 818 | * @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 name | 828 | // 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 | } |
| 830 | 833 | ||
| 831 | // Otherwise, read from the preset by name | 834 | // Otherwise, read from the preset by name |
| @@ -835,7 +838,7 @@ class PresetManager { | |||
| 835 | } | 838 | } |
| 836 | 839 | ||
| 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 | } |
| 841 | 844 | ||
| @@ -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} options | 847 | * @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 path | 860 | // 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 | } |
| 862 | 865 | ||
| @@ -868,7 +871,7 @@ class PresetManager { | |||
| 868 | 871 | ||
| 869 | // Set the value at the specified path | 872 | // 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); |
| 872 | 875 | ||
| 873 | // Save the updated preset | 876 | // 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 | } |
| 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 | await presetManager.renamePreset(newName); | 1041 | await presetManager.renamePreset(newName); |
| 1037 | 1042 | 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 }); |
| 1039 | 1044 | ||
| 1040 | if (apiId === 'openai') { | 1045 | if (apiId === 'openai') { |