import { callPopup, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced } from "../../../script.js"; import { extension_settings } from "../../extensions.js"; import { getSortableDelay, uuidv4 } from "../../utils.js"; import { regex_placement } from "./engine.js"; async function saveRegexScript(regexScript, existingScriptIndex) { // If not editing // Is the script name undefined or empty? if (!regexScript.scriptName) { toastr.error(`Could not save regex script: The script name was undefined or empty!`); return; } if (existingScriptIndex === -1) { // Does the script name already exist? if (extension_settings.regex.find((e) => e.scriptName === regexScript.scriptName)) { toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`); return; } } else { // Does the script name already exist somewhere else? // (If this fails, make it a .filter().map() to index array) const foundIndex = extension_settings.regex.findIndex((e) => e.scriptName === regexScript.scriptName); if (foundIndex !== existingScriptIndex && foundIndex !== -1) { toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`); return; } } // Is a find regex present? if (regexScript.findRegex.length === 0) { toastr.warning(`This regex script will not work, but was saved anyway: A find regex isn't present.`); } // Is there someplace to place results? if (regexScript.placement.length === 0) { toastr.warning(`This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!`); } if (existingScriptIndex !== -1) { extension_settings.regex[existingScriptIndex] = regexScript; } else { extension_settings.regex.push(regexScript); } saveSettingsDebounced(); await loadRegexScripts(); // Reload the current chat to undo previous markdown const currentChatId = getCurrentChatId(); if (currentChatId !== undefined && currentChatId !== null) { await reloadCurrentChat(); } } async function deleteRegexScript({ existingId }) { let scriptName = $(`#${existingId}`).find('.regex_script_name').text(); const existingScriptIndex = extension_settings.regex.findIndex((script) => script.scriptName === scriptName); if (!existingScriptIndex || existingScriptIndex !== -1) { extension_settings.regex.splice(existingScriptIndex, 1); saveSettingsDebounced(); await loadRegexScripts(); } } async function loadRegexScripts() { $("#saved_regex_scripts").empty(); const scriptTemplate = $(await $.get("scripts/extensions/regex/scriptTemplate.html")); extension_settings.regex.forEach((script) => { // Have to clone here const scriptHtml = scriptTemplate.clone(); scriptHtml.attr('id', uuidv4()); scriptHtml.find('.regex_script_name').text(script.scriptName); scriptHtml.find('.edit_existing_regex').on('click', async function() { await onRegexEditorOpenClick(scriptHtml.attr("id")); }); scriptHtml.find('.delete_regex').on('click', async function() { await deleteRegexScript({ existingId: scriptHtml.attr("id") }); }); $("#saved_regex_scripts").append(scriptHtml); }); } async function onRegexEditorOpenClick(existingId) { const editorHtml = $(await $.get("scripts/extensions/regex/editor.html")); // If an ID exists, fill in all the values let existingScriptIndex = -1; if (existingId) { const existingScriptName = $(`#${existingId}`).find('.regex_script_name').text(); existingScriptIndex = extension_settings.regex.findIndex((script) => script.scriptName === existingScriptName); if (existingScriptIndex !== -1) { const existingScript = extension_settings.regex[existingScriptIndex]; if (existingScript.scriptName) { editorHtml.find(`.regex_script_name`).val(existingScript.scriptName); } else { toastr.error("This script doesn't have a name! Please delete it.") return; } editorHtml.find(`.find_regex`).val(existingScript.findRegex || ""); editorHtml.find(`.regex_replace_string`).val(existingScript.replaceString || ""); editorHtml.find(`.regex_trim_strings`).val(existingScript.trimStrings?.join("\n") || []); editorHtml .find(`input[name="disabled"]`) .prop("checked", existingScript.disabled ?? false); editorHtml .find(`input[name="only_format_display"]`) .prop("checked", existingScript.markdownOnly ?? false); editorHtml .find(`input[name="run_on_edit"]`) .prop("checked", existingScript.runOnEdit ?? false); editorHtml .find(`input[name="substitute_regex"]`) .prop("checked", existingScript.substituteRegex ?? false); editorHtml .find(`select[name="replace_strategy_select"]`) .val(existingScript.replaceStrategy ?? 0); existingScript.placement.forEach((element) => { editorHtml .find(`input[name="replace_position"][value="${element}"]`) .prop("checked", true); }); } } else { editorHtml .find(`input[name="only_format_display"]`) .prop("checked", true); editorHtml .find(`input[name="run_on_edit"]`) .prop("checked", true); editorHtml .find(`input[name="replace_position"][value="1"]`) .prop("checked", true); } const popupResult = await callPopup(editorHtml, "confirm", undefined, { okButton: "Save" }); if (popupResult) { const newRegexScript = { scriptName: editorHtml.find(".regex_script_name").val(), findRegex: editorHtml.find(".find_regex").val(), replaceString: editorHtml.find(".regex_replace_string").val(), trimStrings: editorHtml.find(".regex_trim_strings").val().split("\n").filter((e) => e.length !== 0) || [], placement: editorHtml .find(`input[name="replace_position"]`) .filter(":checked") .map(function() { return parseInt($(this).val()) }) .get() .filter((e) => e !== NaN) || [], disabled: editorHtml .find(`input[name="disabled"]`) .prop("checked"), markdownOnly: editorHtml .find(`input[name="only_format_display"]`) .prop("checked"), runOnEdit: editorHtml .find(`input[name="run_on_edit"]`) .prop("checked"), substituteRegex: editorHtml .find(`input[name="substitute_regex"]`) .prop("checked"), replaceStrategy: parseInt(editorHtml .find(`select[name="replace_strategy_select"]`) .find(`:selected`) .val()) ?? 0 }; saveRegexScript(newRegexScript, existingScriptIndex); } } // Common settings migration function. Some parts will eventually be removed // TODO: Maybe migrate placement to strings? function migrateSettings() { let performSave = false; // Current: If MD Display is present in placement, remove it and add new placements/MD option extension_settings.regex.forEach((script) => { if (script.placement.includes(regex_placement.MD_DISPLAY)) { script.placement = script.placement.length === 1 ? Object.values(regex_placement).filter((e) => e !== regex_placement.MD_DISPLAY) : script.placement = script.placement.filter((e) => e !== regex_placement.MD_DISPLAY); script.markdownOnly = true performSave = true; } // Old system and sendas placement migration // 4 - sendAs if (script.placement.includes(4)) { script.placement = script.placement.length === 1 ? [regex_placement.SLASH_COMMAND] : script.placement = script.placement.filter((e) => e !== 4); performSave = true; } }); if (performSave) { saveSettingsDebounced(); } } // Workaround for loading in sequence with other extensions // NOTE: Always puts extension at the top of the list, but this is fine since it's static jQuery(async () => { if (extension_settings.regex) { migrateSettings(); } // Manually disable the extension since static imports auto-import the JS file if (extension_settings.disabledExtensions.includes("regex")) { return; } const settingsHtml = await $.get("scripts/extensions/regex/dropdown.html"); $("#extensions_settings2").append(settingsHtml); $("#open_regex_editor").on("click", function() { onRegexEditorOpenClick(false); }); $('#saved_regex_scripts').sortable({ delay: getSortableDelay(), stop: function () { let newScripts = []; $('#saved_regex_scripts').children().each(function () { const scriptName = $(this).find(".regex_script_name").text(); const existingScript = extension_settings.regex.find((e) => e.scriptName === scriptName); if (existingScript) { newScripts.push(existingScript); } }); extension_settings.regex = newScripts; saveSettingsDebounced(); console.debug("Regex scripts reordered"); // TODO: Maybe reload regex scripts after move }, }); await loadRegexScripts(); $("#saved_regex_scripts").sortable("enable"); });