2023-12-02 19:04:51 +01:00
import { callPopup , getCurrentChatId , reloadCurrentChat , saveSettingsDebounced } from '../../../script.js' ;
import { extension _settings } from '../../extensions.js' ;
import { registerSlashCommand } from '../../slash-commands.js' ;
import { getSortableDelay , uuidv4 } from '../../utils.js' ;
import { resolveVariable } from '../../variables.js' ;
import { regex _placement , runRegexScript } from './engine.js' ;
2023-07-20 19:32:15 +02:00
async function saveRegexScript ( regexScript , existingScriptIndex ) {
// If not editing
2023-07-25 05:21:54 +02:00
// Is the script name undefined or empty?
if ( ! regexScript . scriptName ) {
2023-12-02 19:04:51 +01:00
toastr . error ( 'Could not save regex script: The script name was undefined or empty!' ) ;
2023-07-25 05:21:54 +02:00
return ;
}
if ( existingScriptIndex === - 1 ) {
2023-07-20 19:32:15 +02:00
// 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 ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'This regex script will not work, but was saved anyway: A find regex isn\'t present.' ) ;
2023-07-20 19:32:15 +02:00
}
// Is there someplace to place results?
if ( regexScript . placement . length === 0 ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!' ) ;
2023-07-20 19:32:15 +02:00
}
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 ( ) {
2023-12-02 19:04:51 +01:00
$ ( '#saved_regex_scripts' ) . empty ( ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
const scriptTemplate = $ ( await $ . get ( 'scripts/extensions/regex/scriptTemplate.html' ) ) ;
2023-07-20 19:32:15 +02:00
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 ) ;
2023-12-02 19:04:51 +01:00
scriptHtml . find ( '.disable_regex' ) . prop ( 'checked' , script . disabled ? ? false )
2023-11-05 22:40:43 +01:00
. on ( 'input' , function ( ) {
2023-12-02 19:04:51 +01:00
script . disabled = ! ! $ ( this ) . prop ( 'checked' ) ;
2024-01-24 21:48:58 +01:00
reloadCurrentChat ( ) ;
2023-11-05 22:40:43 +01:00
saveSettingsDebounced ( ) ;
2023-10-26 04:48:19 +02:00
} ) ;
2023-11-05 22:40:43 +01:00
scriptHtml . find ( '.regex-toggle-on' ) . on ( 'click' , function ( ) {
2023-12-02 19:04:51 +01:00
scriptHtml . find ( '.disable_regex' ) . prop ( 'checked' , true ) . trigger ( 'input' ) ;
2023-11-05 22:40:43 +01:00
} ) ;
scriptHtml . find ( '.regex-toggle-off' ) . on ( 'click' , function ( ) {
2023-12-02 19:04:51 +01:00
scriptHtml . find ( '.disable_regex' ) . prop ( 'checked' , false ) . trigger ( 'input' ) ;
2023-11-05 22:40:43 +01:00
} ) ;
scriptHtml . find ( '.edit_existing_regex' ) . on ( 'click' , async function ( ) {
2023-12-02 19:04:51 +01:00
await onRegexEditorOpenClick ( scriptHtml . attr ( 'id' ) ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-11-05 22:40:43 +01:00
scriptHtml . find ( '.delete_regex' ) . on ( 'click' , async function ( ) {
2023-12-02 19:04:51 +01:00
const confirm = await callPopup ( 'Are you sure you want to delete this regex script?' , 'confirm' ) ;
2023-11-05 22:44:28 +01:00
if ( ! confirm ) {
return ;
}
2023-12-02 19:04:51 +01:00
await deleteRegexScript ( { existingId : scriptHtml . attr ( 'id' ) } ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-12-02 19:04:51 +01:00
$ ( '#saved_regex_scripts' ) . append ( scriptHtml ) ;
2023-07-20 19:32:15 +02:00
} ) ;
}
async function onRegexEditorOpenClick ( existingId ) {
2023-12-02 19:04:51 +01:00
const editorHtml = $ ( await $ . get ( 'scripts/extensions/regex/editor.html' ) ) ;
2023-07-20 19:32:15 +02:00
// 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 ) {
2023-12-02 19:04:51 +01:00
editorHtml . find ( '.regex_script_name' ) . val ( existingScript . scriptName ) ;
2023-07-20 19:32:15 +02:00
} else {
2023-12-02 20:11:06 +01:00
toastr . error ( 'This script doesn\'t have a name! Please delete it.' ) ;
2023-07-20 19:32:15 +02:00
return ;
}
2023-12-02 19:04:51 +01:00
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' ) || [ ] ) ;
2024-01-24 21:48:58 +01:00
editorHtml . find ( 'input[name="disabled"]' ) . prop ( 'checked' , existingScript . disabled ? ? false ) ;
editorHtml . find ( 'input[name="only_format_display"]' ) . prop ( 'checked' , existingScript . markdownOnly ? ? false ) ;
editorHtml . find ( 'input[name="only_format_prompt"]' ) . prop ( 'checked' , existingScript . promptOnly ? ? false ) ;
editorHtml . find ( 'input[name="run_on_edit"]' ) . prop ( 'checked' , existingScript . runOnEdit ? ? false ) ;
editorHtml . find ( 'input[name="substitute_regex"]' ) . prop ( 'checked' , existingScript . substituteRegex ? ? false ) ;
editorHtml . find ( 'input[name="min_depth"]' ) . val ( existingScript . minDepth ? ? '' ) ;
editorHtml . find ( 'input[name="max_depth"]' ) . val ( existingScript . maxDepth ? ? '' ) ;
2023-07-20 19:32:15 +02:00
existingScript . placement . forEach ( ( element ) => {
editorHtml
. find ( ` input[name="replace_position"][value=" ${ element } "] ` )
2023-12-02 19:04:51 +01:00
. prop ( 'checked' , true ) ;
2023-07-20 19:32:15 +02:00
} ) ;
}
} else {
editorHtml
2023-12-02 19:04:51 +01:00
. find ( 'input[name="only_format_display"]' )
. prop ( 'checked' , true ) ;
2023-07-20 19:32:15 +02:00
editorHtml
2023-12-02 19:04:51 +01:00
. find ( 'input[name="run_on_edit"]' )
. prop ( 'checked' , true ) ;
2023-07-20 19:32:15 +02:00
editorHtml
2023-12-02 19:04:51 +01:00
. find ( 'input[name="replace_position"][value="1"]' )
. prop ( 'checked' , true ) ;
2023-07-20 19:32:15 +02:00
}
2024-01-04 03:34:38 +01:00
editorHtml . find ( '#regex_test_mode_toggle' ) . on ( 'click' , function ( ) {
editorHtml . find ( '#regex_test_mode' ) . toggleClass ( 'displayNone' ) ;
updateTestResult ( ) ;
} ) ;
function updateTestResult ( ) {
if ( ! editorHtml . find ( '#regex_test_mode' ) . is ( ':visible' ) ) {
return ;
}
const testScript = {
scriptName : editorHtml . find ( '.regex_script_name' ) . val ( ) ,
findRegex : editorHtml . find ( '.find_regex' ) . val ( ) ,
replaceString : editorHtml . find ( '.regex_replace_string' ) . val ( ) ,
trimStrings : String ( editorHtml . find ( '.regex_trim_strings' ) . val ( ) ) . split ( '\n' ) . filter ( ( e ) => e . length !== 0 ) || [ ] ,
substituteRegex : editorHtml . find ( 'input[name="substitute_regex"]' ) . prop ( 'checked' ) ,
} ;
const rawTestString = String ( editorHtml . find ( '#regex_test_input' ) . val ( ) ) ;
const result = runRegexScript ( testScript , rawTestString ) ;
editorHtml . find ( '#regex_test_output' ) . text ( result ) ;
}
editorHtml . find ( 'input, textarea, select' ) . on ( 'input' , updateTestResult ) ;
2023-12-02 19:04:51 +01:00
const popupResult = await callPopup ( editorHtml , 'confirm' , undefined , { okButton : 'Save' } ) ;
2023-07-20 19:32:15 +02:00
if ( popupResult ) {
const newRegexScript = {
2023-12-02 19:04:51 +01:00
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 ) || [ ] ,
2023-07-20 19:32:15 +02:00
placement :
editorHtml
2023-12-02 19:04:51 +01:00
. find ( 'input[name="replace_position"]' )
. filter ( ':checked' )
2023-12-02 20:11:06 +01:00
. map ( function ( ) { return parseInt ( $ ( this ) . val ( ) ) ; } )
2023-07-20 19:32:15 +02:00
. get ( )
2023-12-02 15:19:35 +01:00
. filter ( ( e ) => ! isNaN ( e ) ) || [ ] ,
2024-01-24 21:48:58 +01:00
disabled : editorHtml . find ( 'input[name="disabled"]' ) . prop ( 'checked' ) ,
markdownOnly : editorHtml . find ( 'input[name="only_format_display"]' ) . prop ( 'checked' ) ,
promptOnly : editorHtml . find ( 'input[name="only_format_prompt"]' ) . prop ( 'checked' ) ,
runOnEdit : editorHtml . find ( 'input[name="run_on_edit"]' ) . prop ( 'checked' ) ,
substituteRegex : editorHtml . find ( 'input[name="substitute_regex"]' ) . prop ( 'checked' ) ,
minDepth : parseInt ( String ( editorHtml . find ( 'input[name="min_depth"]' ) . val ( ) ) ) ,
maxDepth : parseInt ( String ( editorHtml . find ( 'input[name="max_depth"]' ) . val ( ) ) ) ,
2023-07-20 19:32:15 +02:00
} ;
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 ) ;
2023-12-02 20:11:06 +01:00
script . markdownOnly = true ;
script . promptOnly = true ;
2023-07-20 19:32:15 +02:00
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 ( ) ;
}
}
2023-11-30 21:59:04 +01:00
/ * *
* / r e g e x s l a s h c o m m a n d c a l l b a c k
* @ param { object } args Named arguments
* @ param { string } value Unnamed argument
* @ returns { string } The regexed string
* /
function runRegexCallback ( args , value ) {
if ( ! args . name ) {
2023-12-02 19:04:51 +01:00
toastr . warning ( 'No regex script name provided.' ) ;
2023-11-30 21:59:04 +01:00
return value ;
}
const scriptName = String ( resolveVariable ( args . name ) ) ;
for ( const script of extension _settings . regex ) {
if ( String ( script . scriptName ) . toLowerCase ( ) === String ( scriptName ) . toLowerCase ( ) ) {
if ( script . disabled ) {
toastr . warning ( ` Regex script " ${ scriptName } " is disabled. ` ) ;
return value ;
}
console . debug ( ` Running regex callback for ${ scriptName } ` ) ;
return runRegexScript ( script , value ) ;
}
}
toastr . warning ( ` Regex script " ${ scriptName } " not found. ` ) ;
return value ;
}
2023-07-20 19:32:15 +02:00
// 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
2023-12-02 19:04:51 +01:00
if ( extension _settings . disabledExtensions . includes ( 'regex' ) ) {
2023-07-20 19:32:15 +02:00
return ;
}
2023-12-02 19:04:51 +01:00
const settingsHtml = await $ . get ( 'scripts/extensions/regex/dropdown.html' ) ;
$ ( '#extensions_settings2' ) . append ( settingsHtml ) ;
$ ( '#open_regex_editor' ) . on ( 'click' , function ( ) {
2023-07-20 19:32:15 +02:00
onRegexEditorOpenClick ( false ) ;
} ) ;
$ ( '#saved_regex_scripts' ) . sortable ( {
2023-08-18 12:41:46 +02:00
delay : getSortableDelay ( ) ,
2023-07-20 19:32:15 +02:00
stop : function ( ) {
let newScripts = [ ] ;
$ ( '#saved_regex_scripts' ) . children ( ) . each ( function ( ) {
2023-12-02 19:04:51 +01:00
const scriptName = $ ( this ) . find ( '.regex_script_name' ) . text ( ) ;
2023-07-20 19:32:15 +02:00
const existingScript = extension _settings . regex . find ( ( e ) => e . scriptName === scriptName ) ;
if ( existingScript ) {
newScripts . push ( existingScript ) ;
}
} ) ;
extension _settings . regex = newScripts ;
saveSettingsDebounced ( ) ;
2023-12-02 19:04:51 +01:00
console . debug ( 'Regex scripts reordered' ) ;
2023-07-20 19:32:15 +02:00
// TODO: Maybe reload regex scripts after move
} ,
} ) ;
await loadRegexScripts ( ) ;
2023-12-02 19:04:51 +01:00
$ ( '#saved_regex_scripts' ) . sortable ( 'enable' ) ;
2023-11-30 21:59:04 +01:00
registerSlashCommand ( 'regex' , runRegexCallback , [ ] , '(name=scriptName [input]) – runs a Regex extension script by name on the provided string. The script must be enabled.' , true , true ) ;
2023-07-20 19:32:15 +02:00
} ) ;