2023-07-20 19:32:15 +02:00
import {
characters ,
online _status ,
main _api ,
api _server ,
is _send _press ,
max _context ,
saveSettingsDebounced ,
2023-07-30 01:48:08 +02:00
active _group ,
active _character ,
setActiveGroup ,
setActiveCharacter ,
2023-08-18 22:13:15 +02:00
getEntitiesList ,
2024-03-06 04:59:39 +01:00
buildAvatarList ,
2023-08-18 22:13:15 +02:00
selectCharacterById ,
2023-08-28 06:49:20 +02:00
eventSource ,
2023-10-21 13:39:01 +02:00
menu _type ,
substituteParams ,
2023-12-17 20:27:33 +01:00
callPopup ,
2023-12-25 09:29:14 +01:00
sendTextareaMessage ,
2023-12-02 19:04:51 +01:00
} from '../script.js' ;
2023-07-20 19:32:15 +02:00
import {
power _user ,
send _on _enter _options ,
2023-12-02 19:04:51 +01:00
} from './power-user.js' ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
import { LoadLocal , SaveLocal , LoadLocalBool } from './f-localStorage.js' ;
2024-03-07 23:48:50 +01:00
import { selected _group , is _group _generating , openGroupById } from './group-chats.js' ;
2024-03-27 04:28:24 +01:00
import { getTagKeyForEntity , applyTagsOnCharacterSelect } from './tags.js' ;
2023-07-20 19:32:15 +02:00
import {
SECRET _KEYS ,
secret _state ,
2023-12-02 19:04:51 +01:00
} from './secrets.js' ;
import { debounce , delay , getStringHash , isValidUrl } from './utils.js' ;
import { chat _completion _sources , oai _settings } from './openai.js' ;
import { getTokenCount } from './tokenizers.js' ;
2023-12-19 15:38:11 +01:00
import { textgen _types , textgenerationwebui _settings as textgen _settings , getTextGenServer } from './textgen-settings.js' ;
2023-07-20 19:32:15 +02:00
2023-12-15 00:37:17 +01:00
import Bowser from '../lib/bowser.min.js' ;
2023-08-28 06:49:20 +02:00
2023-12-02 19:04:51 +01:00
var RPanelPin = document . getElementById ( 'rm_button_panel_pin' ) ;
var LPanelPin = document . getElementById ( 'lm_button_panel_pin' ) ;
var WIPanelPin = document . getElementById ( 'WI_panel_pin' ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
var RightNavPanel = document . getElementById ( 'right-nav-panel' ) ;
var LeftNavPanel = document . getElementById ( 'left-nav-panel' ) ;
var WorldInfo = document . getElementById ( 'WorldInfo' ) ;
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
var SelectedCharacterTab = document . getElementById ( 'rm_button_selected_ch' ) ;
2023-07-20 19:32:15 +02:00
var connection _made = false ;
var retry _delay = 500 ;
const observerConfig = { childList : true , subtree : true } ;
const countTokensDebounced = debounce ( RA _CountCharTokens , 1000 ) ;
const observer = new MutationObserver ( function ( mutations ) {
mutations . forEach ( function ( mutation ) {
2023-12-02 19:04:51 +01:00
if ( mutation . target . classList . contains ( 'online_status_text' ) ) {
2023-07-20 19:32:15 +02:00
RA _checkOnlineStatus ( ) ;
} else if ( mutation . target . parentNode === SelectedCharacterTab ) {
setTimeout ( RA _CountCharTokens , 200 ) ;
2024-01-21 16:27:09 +01:00
} else if ( mutation . target . classList . contains ( 'mes_text' ) ) {
if ( mutation . target instanceof HTMLElement ) {
for ( const element of mutation . target . getElementsByTagName ( 'math' ) ) {
element . childNodes . forEach ( function ( child ) {
if ( child . nodeType === Node . TEXT _NODE ) {
child . textContent = '' ;
}
} ) ;
}
}
2023-07-20 19:32:15 +02:00
}
} ) ;
} ) ;
observer . observe ( document . documentElement , observerConfig ) ;
/ * *
* Converts generation time from milliseconds to a human - readable format .
*
* The function takes total generation time as an input , then converts it to a format
* of "_ Days, _ Hours, _ Minutes, _ Seconds" . If the generation time does not exceed a
* particular measure ( like days or hours ) , that measure will not be included in the output .
*
* @ param { number } total _gen _time - The total generation time in milliseconds .
* @ returns { string } - A human - readable string that represents the time spent generating characters .
* /
export function humanizeGenTime ( total _gen _time ) {
//convert time_spent to humanized format of "_ Hours, _ Minutes, _ Seconds" from milliseconds
let time _spent = total _gen _time || 0 ;
time _spent = Math . floor ( time _spent / 1000 ) ;
let seconds = time _spent % 60 ;
time _spent = Math . floor ( time _spent / 60 ) ;
let minutes = time _spent % 60 ;
time _spent = Math . floor ( time _spent / 60 ) ;
let hours = time _spent % 24 ;
time _spent = Math . floor ( time _spent / 24 ) ;
let days = time _spent ;
2023-12-02 19:04:51 +01:00
time _spent = '' ;
2023-07-20 19:32:15 +02:00
if ( days > 0 ) { time _spent += ` ${ days } Days, ` ; }
if ( hours > 0 ) { time _spent += ` ${ hours } Hours, ` ; }
if ( minutes > 0 ) { time _spent += ` ${ minutes } Minutes, ` ; }
time _spent += ` ${ seconds } Seconds ` ;
return time _spent ;
}
2023-12-15 00:37:17 +01:00
let parsedUA = null ;
try {
parsedUA = Bowser . parse ( navigator . userAgent ) ;
} catch {
// In case the user agent is an empty string or Bowser can't parse it for some other reason
}
2023-08-24 22:52:03 +02:00
/ * *
* Checks if the device is a mobile device .
* @ returns { boolean } - True if the device is a mobile device , false otherwise .
* /
export function isMobile ( ) {
2023-12-15 00:37:17 +01:00
const mobileTypes = [ 'mobile' , 'tablet' ] ;
2023-07-20 19:32:15 +02:00
2023-12-15 00:37:17 +01:00
return mobileTypes . includes ( parsedUA ? . platform ? . type ) ;
2023-07-20 19:32:15 +02:00
}
2024-03-21 00:11:47 +01:00
export function shouldSendOnEnter ( ) {
2023-07-20 19:32:15 +02:00
if ( ! power _user ) {
return false ;
}
switch ( power _user . send _on _enter ) {
case send _on _enter _options . DISABLED :
return false ;
case send _on _enter _options . AUTO :
return ! isMobile ( ) ;
case send _on _enter _options . ENABLED :
return true ;
}
}
//RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
//Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
//Does not break old characters/chats, as the code just uses whatever timestamp exists in the chat.
//New chats made with characters will use this new formatting.
export function humanizedDateTime ( ) {
let baseDate = new Date ( Date . now ( ) ) ;
let humanYear = baseDate . getFullYear ( ) ;
let humanMonth = baseDate . getMonth ( ) + 1 ;
let humanDate = baseDate . getDate ( ) ;
2023-12-02 19:04:51 +01:00
let humanHour = ( baseDate . getHours ( ) < 10 ? '0' : '' ) + baseDate . getHours ( ) ;
2023-07-20 19:32:15 +02:00
let humanMinute =
2023-12-02 19:04:51 +01:00
( baseDate . getMinutes ( ) < 10 ? '0' : '' ) + baseDate . getMinutes ( ) ;
2023-07-20 19:32:15 +02:00
let humanSecond =
2023-12-02 19:04:51 +01:00
( baseDate . getSeconds ( ) < 10 ? '0' : '' ) + baseDate . getSeconds ( ) ;
2023-07-20 19:32:15 +02:00
let HumanizedDateTime =
2023-12-02 19:04:51 +01:00
humanYear + '-' + humanMonth + '-' + humanDate + '@' + humanHour + 'h' + humanMinute + 'm' + humanSecond + 's' ;
2023-07-20 19:32:15 +02:00
return HumanizedDateTime ;
}
//this is a common format version to display a timestamp on each chat message
//returns something like: June 19, 2023 2:20pm
export function getMessageTimeStamp ( ) {
const date = Date . now ( ) ;
const months = [ 'January' , 'February' , 'March' , 'April' , 'May' , 'June' , 'July' , 'August' , 'September' , 'October' , 'November' , 'December' ] ;
const d = new Date ( date ) ;
const month = months [ d . getMonth ( ) ] ;
const day = d . getDate ( ) ;
const year = d . getFullYear ( ) ;
let hours = d . getHours ( ) ;
const minutes = ( '0' + d . getMinutes ( ) ) . slice ( - 2 ) ;
let meridiem = 'am' ;
if ( hours >= 12 ) {
meridiem = 'pm' ;
hours -= 12 ;
}
if ( hours === 0 ) {
hours = 12 ;
}
const formattedDate = month + ' ' + day + ', ' + year + ' ' + hours + ':' + minutes + meridiem ;
return formattedDate ;
}
// triggers:
2023-12-02 19:04:51 +01:00
$ ( '#rm_button_create' ) . on ( 'click' , function ( ) { //when "+New Character" is clicked
$ ( SelectedCharacterTab ) . children ( 'h2' ) . html ( '' ) ; // empty nav's 3rd panel tab
2023-07-20 19:32:15 +02:00
} ) ;
//when any input is made to the create/edit character form textareas
2023-12-02 19:04:51 +01:00
$ ( '#rm_ch_create_block' ) . on ( 'input' , function ( ) { countTokensDebounced ( ) ; } ) ;
2023-07-20 19:32:15 +02:00
//when any input is made to the advanced editing popup textareas
2023-12-02 19:04:51 +01:00
$ ( '#character_popup' ) . on ( 'input' , function ( ) { countTokensDebounced ( ) ; } ) ;
2023-07-20 19:32:15 +02:00
//function:
export function RA _CountCharTokens ( ) {
2023-08-21 14:32:27 +02:00
let total _tokens = 0 ;
2023-09-09 23:58:37 +02:00
let permanent _tokens = 0 ;
2023-08-21 14:32:27 +02:00
$ ( '[data-token-counter]' ) . each ( function ( ) {
const counter = $ ( this ) ;
const input = $ ( document . getElementById ( counter . data ( 'token-counter' ) ) ) ;
2023-09-09 23:58:37 +02:00
const isPermanent = counter . data ( 'token-permanent' ) === true ;
2023-08-22 17:32:18 +02:00
const value = String ( input . val ( ) ) ;
2023-08-21 14:32:27 +02:00
if ( input . length === 0 ) {
counter . text ( 'Invalid input reference' ) ;
return ;
2023-07-20 19:32:15 +02:00
}
2023-08-21 14:32:27 +02:00
if ( ! value ) {
counter . text ( 0 ) ;
return ;
2023-07-20 19:32:15 +02:00
}
2023-08-21 14:32:27 +02:00
const valueHash = getStringHash ( value ) ;
2023-07-20 19:32:15 +02:00
2023-08-21 14:32:27 +02:00
if ( input . data ( 'last-value-hash' ) === valueHash ) {
total _tokens += Number ( counter . text ( ) ) ;
2023-09-09 23:58:37 +02:00
permanent _tokens += isPermanent ? Number ( counter . text ( ) ) : 0 ;
2023-08-21 14:32:27 +02:00
} else {
2023-10-21 13:39:01 +02:00
// We substitute macro for existing characters, but not for the character being created
const valueToCount = menu _type === 'create' ? value : substituteParams ( value ) ;
const tokens = getTokenCount ( valueToCount ) ;
2023-08-21 14:32:27 +02:00
counter . text ( tokens ) ;
total _tokens += tokens ;
2023-09-09 23:58:37 +02:00
permanent _tokens += isPermanent ? tokens : 0 ;
2023-08-21 14:32:27 +02:00
input . data ( 'last-value-hash' , valueHash ) ;
}
2023-07-20 19:32:15 +02:00
} ) ;
2023-08-21 14:32:27 +02:00
// Warn if total tokens exceeds the limit of half the max context
2023-07-20 19:32:15 +02:00
const tokenLimit = Math . max ( ( ( main _api !== 'openai' ? max _context : oai _settings . openai _max _context ) / 2 ) , 1024 ) ;
2023-08-21 14:32:27 +02:00
const showWarning = ( total _tokens > tokenLimit ) ;
$ ( '#result_info_total_tokens' ) . text ( total _tokens ) ;
2023-09-09 23:58:37 +02:00
$ ( '#result_info_permanent_tokens' ) . text ( permanent _tokens ) ;
2023-08-21 14:32:27 +02:00
$ ( '#result_info_text' ) . toggleClass ( 'neutral_warning' , showWarning ) ;
$ ( '#chartokenwarning' ) . toggle ( showWarning ) ;
2023-07-20 19:32:15 +02:00
}
2023-07-30 01:48:08 +02:00
/ * *
* Auto load chat with the last active character or group .
* Fires when active _character is defined and auto _load _chat is true .
* The function first tries to find a character with a specific ID from the global settings .
* If it doesn ' t exist , it tries to find a group with a specific grid from the global settings .
* If the character list hadn ' t been loaded yet , it calls itself again after 100 ms delay .
* The character or group is selected ( clicked ) if it is found .
* /
2023-07-20 19:32:15 +02:00
async function RA _autoloadchat ( ) {
2023-08-19 15:14:25 +02:00
if ( document . querySelector ( '#rm_print_characters_block .character_select' ) !== null ) {
2023-07-30 01:48:08 +02:00
// active character is the name, we should look it up in the character list and get the id
2024-03-09 20:58:13 +01:00
if ( active _character !== null && active _character !== undefined ) {
const active _character _id = characters . findIndex ( x => getTagKeyForEntity ( x ) === active _character ) ;
if ( active _character _id !== null ) {
await selectCharacterById ( String ( active _character _id ) ) ;
2024-03-27 04:28:24 +01:00
// Do a little tomfoolery to spoof the tag selector
const selectedCharElement = $ ( ` #rm_print_characters_block .character_select[chid=" ${ active _character _id } "] ` )
applyTagsOnCharacterSelect . call ( selectedCharElement ) ;
2024-03-09 20:58:13 +01:00
}
2023-07-20 19:32:15 +02:00
}
2023-08-18 22:13:15 +02:00
2024-03-09 20:58:13 +01:00
if ( active _group !== null && active _group !== undefined ) {
2023-11-04 22:25:22 +01:00
await openGroupById ( String ( active _group ) ) ;
2023-07-20 19:32:15 +02:00
}
2023-07-30 01:48:08 +02:00
// if the character list hadn't been loaded yet, try again.
2023-07-20 19:32:15 +02:00
} else { setTimeout ( RA _autoloadchat , 100 ) ; }
}
export async function favsToHotswap ( ) {
2023-08-18 22:13:15 +02:00
const entities = getEntitiesList ( { doFilter : false } ) ;
2023-07-20 19:32:15 +02:00
const container = $ ( '#right-nav-panel .hotswap' ) ;
2023-08-18 22:13:15 +02:00
2024-03-06 04:59:39 +01:00
const favs = entities . filter ( x => x . item . fav || x . item . fav == 'true' ) ;
2023-09-14 14:56:01 +02:00
2023-09-15 10:55:16 +02:00
//helpful instruction message if no characters are favorited
2024-03-06 04:59:39 +01:00
if ( favs . length == 0 ) {
2024-03-16 21:32:01 +01:00
container . html ( '<small><span><i class="fa-solid fa-star"></i> <span data-i18n="Favorite characters to add them to HotSwaps">Favorite characters to add them to HotSwaps</span></span></small>' ) ;
2024-03-06 04:59:39 +01:00
return ;
2023-09-15 10:55:16 +02:00
}
2024-03-06 04:59:39 +01:00
2024-03-09 20:58:13 +01:00
buildAvatarList ( container , favs , { selectable : true , highlightFavs : false } ) ;
2023-07-20 19:32:15 +02:00
}
//changes input bar and send button display depending on connection status
function RA _checkOnlineStatus ( ) {
2023-12-02 19:04:51 +01:00
if ( online _status == 'no_connection' ) {
$ ( '#send_textarea' ) . attr ( 'placeholder' , 'Not connected to API!' ) ; //Input bar placeholder tells users they are not connected
$ ( '#send_form' ) . addClass ( 'no-connection' ) ; //entire input form area is red when not connected
$ ( '#send_but' ) . addClass ( 'displayNone' ) ; //send button is hidden when not connected;
$ ( '#mes_continue' ) . addClass ( 'displayNone' ) ; //continue button is hidden when not connected;
$ ( '#API-status-top' ) . removeClass ( 'fa-plug' ) ;
$ ( '#API-status-top' ) . addClass ( 'fa-plug-circle-exclamation redOverlayGlow' ) ;
2023-07-20 19:32:15 +02:00
connection _made = false ;
} else {
2023-12-02 19:04:51 +01:00
if ( online _status !== undefined && online _status !== 'no_connection' ) {
$ ( '#send_textarea' ) . attr ( 'placeholder' , 'Type a message, or /? for help' ) ; //on connect, placeholder tells user to type message
$ ( '#send_form' ) . removeClass ( 'no-connection' ) ;
$ ( '#API-status-top' ) . removeClass ( 'fa-plug-circle-exclamation redOverlayGlow' ) ;
$ ( '#API-status-top' ) . addClass ( 'fa-plug' ) ;
2023-07-20 19:32:15 +02:00
connection _made = true ;
retry _delay = 100 ;
if ( ! is _send _press && ! ( selected _group && is _group _generating ) ) {
2023-12-02 19:04:51 +01:00
$ ( '#send_but' ) . removeClass ( 'displayNone' ) ; //on connect, send button shows
$ ( '#mes_continue' ) . removeClass ( 'displayNone' ) ; //continue button is shown when connected
2023-07-20 19:32:15 +02:00
}
}
}
}
//Auto-connect to API (when set to kobold, API URL exists, and auto_connect is true)
function RA _autoconnect ( PrevApi ) {
// secrets.js or script.js not loaded
if ( SECRET _KEYS === undefined || online _status === undefined ) {
setTimeout ( RA _autoconnect , 100 ) ;
return ;
}
2024-01-05 19:27:19 +01:00
if ( online _status === 'no_connection' && power _user . auto _connect ) {
2023-07-20 19:32:15 +02:00
switch ( main _api ) {
case 'kobold' :
2023-10-15 23:50:29 +02:00
if ( api _server && isValidUrl ( api _server ) ) {
2023-12-02 19:04:51 +01:00
$ ( '#api_button' ) . trigger ( 'click' ) ;
2023-07-20 19:32:15 +02:00
}
break ;
case 'novel' :
if ( secret _state [ SECRET _KEYS . NOVEL ] ) {
2023-12-02 19:04:51 +01:00
$ ( '#api_button_novel' ) . trigger ( 'click' ) ;
2023-07-20 19:32:15 +02:00
}
break ;
case 'textgenerationwebui' :
2024-02-16 16:07:06 +01:00
if ( ( textgen _settings . type === textgen _types . MANCER && secret _state [ SECRET _KEYS . MANCER ] )
|| ( textgen _settings . type === textgen _types . TOGETHERAI && secret _state [ SECRET _KEYS . TOGETHERAI ] )
2024-03-07 10:55:08 +01:00
|| ( textgen _settings . type === textgen _types . INFERMATICAI && secret _state [ SECRET _KEYS . INFERMATICAI ] )
|| ( textgen _settings . type === textgen _types . DREAMGEN && secret _state [ SECRET _KEYS . DREAMGEN ] )
|| ( textgen _settings . type === textgen _types . OPENROUTER && secret _state [ SECRET _KEYS . OPENROUTER ] )
2023-12-17 22:38:03 +01:00
) {
2023-12-02 19:04:51 +01:00
$ ( '#api_button_textgenerationwebui' ) . trigger ( 'click' ) ;
2023-11-08 01:52:03 +01:00
}
2023-12-19 15:38:11 +01:00
else if ( isValidUrl ( getTextGenServer ( ) ) ) {
2023-12-02 19:04:51 +01:00
$ ( '#api_button_textgenerationwebui' ) . trigger ( 'click' ) ;
2023-07-20 19:32:15 +02:00
}
break ;
case 'openai' :
2023-07-28 20:33:29 +02:00
if ( ( ( secret _state [ SECRET _KEYS . OPENAI ] || oai _settings . reverse _proxy ) && oai _settings . chat _completion _source == chat _completion _sources . OPENAI )
|| ( ( secret _state [ SECRET _KEYS . CLAUDE ] || oai _settings . reverse _proxy ) && oai _settings . chat _completion _source == chat _completion _sources . CLAUDE )
2023-08-20 12:55:37 +02:00
|| ( ( secret _state [ SECRET _KEYS . SCALE ] || secret _state [ SECRET _KEYS . SCALE _COOKIE ] ) && oai _settings . chat _completion _source == chat _completion _sources . SCALE )
2023-07-20 19:32:15 +02:00
|| ( oai _settings . chat _completion _source == chat _completion _sources . WINDOWAI )
|| ( secret _state [ SECRET _KEYS . OPENROUTER ] && oai _settings . chat _completion _source == chat _completion _sources . OPENROUTER )
2023-08-19 17:20:42 +02:00
|| ( secret _state [ SECRET _KEYS . AI21 ] && oai _settings . chat _completion _source == chat _completion _sources . AI21 )
2023-12-14 02:53:26 +01:00
|| ( secret _state [ SECRET _KEYS . MAKERSUITE ] && oai _settings . chat _completion _source == chat _completion _sources . MAKERSUITE )
2023-12-15 22:15:57 +01:00
|| ( secret _state [ SECRET _KEYS . MISTRALAI ] && oai _settings . chat _completion _source == chat _completion _sources . MISTRALAI )
2024-04-01 23:20:17 +02:00
|| ( secret _state [ SECRET _KEYS . COHERE ] && oai _settings . chat _completion _source == chat _completion _sources . COHERE )
2023-12-20 20:05:20 +01:00
|| ( isValidUrl ( oai _settings . custom _url ) && oai _settings . chat _completion _source == chat _completion _sources . CUSTOM )
2023-07-20 19:32:15 +02:00
) {
2023-12-02 19:04:51 +01:00
$ ( '#api_button_openai' ) . trigger ( 'click' ) ;
2023-07-20 19:32:15 +02:00
}
break ;
}
if ( ! connection _made ) {
retry _delay = Math . min ( retry _delay * 2 , 30000 ) ; // double retry delay up to to 30 secs
2023-11-08 01:52:03 +01:00
// console.log('connection attempts: ' + RA_AC_retries + ' delay: ' + (retry_delay / 1000) + 's');
// setTimeout(RA_autoconnect, retry_delay);
2023-07-20 19:32:15 +02:00
}
}
}
function OpenNavPanels ( ) {
2023-12-15 00:03:10 +01:00
if ( ! isMobile ( ) ) {
2023-07-20 19:32:15 +02:00
//auto-open R nav if locked and previously open
2023-12-02 19:04:51 +01:00
if ( LoadLocalBool ( 'NavLockOn' ) == true && LoadLocalBool ( 'NavOpened' ) == true ) {
2023-07-20 19:32:15 +02:00
//console.log("RA -- clicking right nav to open");
2023-12-02 19:04:51 +01:00
$ ( '#rightNavDrawerIcon' ) . click ( ) ;
2023-07-20 19:32:15 +02:00
}
//auto-open L nav if locked and previously open
2023-12-02 19:04:51 +01:00
if ( LoadLocalBool ( 'LNavLockOn' ) == true && LoadLocalBool ( 'LNavOpened' ) == true ) {
console . debug ( 'RA -- clicking left nav to open' ) ;
$ ( '#leftNavDrawerIcon' ) . click ( ) ;
2023-07-20 19:32:15 +02:00
}
//auto-open WI if locked and previously open
2023-12-02 19:04:51 +01:00
if ( LoadLocalBool ( 'WINavLockOn' ) == true && LoadLocalBool ( 'WINavOpened' ) == true ) {
console . debug ( 'RA -- clicking WI to open' ) ;
$ ( '#WIDrawerIcon' ) . click ( ) ;
2023-07-20 19:32:15 +02:00
}
}
}
2023-11-27 23:29:34 +01:00
function restoreUserInput ( ) {
if ( ! power _user . restore _user _input ) {
console . debug ( 'restoreUserInput disabled' ) ;
return ;
}
2023-12-02 19:04:51 +01:00
const userInput = LoadLocal ( 'userInput' ) ;
2023-11-27 23:29:34 +01:00
if ( userInput ) {
2023-12-02 19:04:51 +01:00
$ ( '#send_textarea' ) . val ( userInput ) . trigger ( 'input' ) ;
2023-11-27 23:29:34 +01:00
}
}
function saveUserInput ( ) {
2023-12-02 19:04:51 +01:00
const userInput = String ( $ ( '#send_textarea' ) . val ( ) ) ;
SaveLocal ( 'userInput' , userInput ) ;
2023-11-27 23:29:34 +01:00
}
2024-03-26 17:11:15 +01:00
const saveUserInputDebounced = debounce ( saveUserInput ) ;
2023-07-20 19:32:15 +02:00
// Make the DIV element draggable:
// THIRD UPDATE, prevent resize window breaks and smartly handle saving
export function dragElement ( elmnt ) {
var hasBeenDraggedByUser = false ;
var isMouseDown = false ;
var pos1 = 0 , pos2 = 0 , pos3 = 0 , pos4 = 0 ;
var height , width , top , left , right , bottom ,
maxX , maxY , winHeight , winWidth ,
2023-12-02 17:25:30 +01:00
topbar , topBarFirstX , topBarLastY ;
2023-07-20 19:32:15 +02:00
var elmntName = elmnt . attr ( 'id' ) ;
2023-09-05 17:23:24 +02:00
console . debug ( ` dragElement called for ${ elmntName } ` ) ;
2023-07-20 19:32:15 +02:00
const elmntNameEscaped = $ . escapeSelector ( elmntName ) ;
2023-09-05 17:23:24 +02:00
console . debug ( ` dragElement escaped name: ${ elmntNameEscaped } ` ) ;
2023-07-20 19:32:15 +02:00
const elmntHeader = $ ( ` # ${ elmntNameEscaped } header ` ) ;
if ( elmntHeader . length ) {
elmntHeader . off ( 'mousedown' ) . on ( 'mousedown' , ( e ) => {
2023-12-02 20:11:06 +01:00
hasBeenDraggedByUser = true ;
2023-08-03 14:17:34 +02:00
observer . observe ( elmnt . get ( 0 ) , { attributes : true , attributeFilter : [ 'style' ] } ) ;
2023-07-20 19:32:15 +02:00
dragMouseDown ( e ) ;
} ) ;
2023-08-03 14:17:34 +02:00
$ ( elmnt ) . off ( 'mousedown' ) . on ( 'mousedown' , ( ) => {
2023-12-02 20:11:06 +01:00
isMouseDown = true ;
2023-08-03 14:17:34 +02:00
observer . observe ( elmnt . get ( 0 ) , { attributes : true , attributeFilter : [ 'style' ] } ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-07-20 19:32:15 +02:00
}
const observer = new MutationObserver ( ( mutations ) => {
const target = mutations [ 0 ] . target ;
if ( ! $ ( target ) . is ( ':visible' )
|| $ ( target ) . hasClass ( 'resizing' )
|| Number ( ( String ( target . height ) . replace ( 'px' , '' ) ) ) < 50
|| Number ( ( String ( target . width ) . replace ( 'px' , '' ) ) ) < 50
|| power _user . movingUI === false
2023-12-15 00:05:59 +01:00
|| isMobile ( )
2023-07-20 19:32:15 +02:00
) {
2023-12-02 20:11:06 +01:00
console . debug ( 'aborting mutator' ) ;
return ;
2023-07-20 19:32:15 +02:00
}
//console.debug(left + width, winWidth, hasBeenDraggedByUser, isMouseDown)
const style = getComputedStyle ( target ) ; //use computed values because not all CSS are set by default
height = target . offsetHeight ;
width = target . offsetWidth ;
top = parseInt ( style . top ) ;
left = parseInt ( style . left ) ;
right = parseInt ( style . right ) ;
bottom = parseInt ( style . bottom ) ;
maxX = parseInt ( width + left ) ;
maxY = parseInt ( height + top ) ;
winWidth = window . innerWidth ;
winHeight = window . innerHeight ;
2023-12-02 20:11:06 +01:00
topbar = document . getElementById ( 'top-bar' ) ;
const topbarstyle = getComputedStyle ( topbar ) ;
topBarFirstX = parseInt ( topbarstyle . marginInline ) ;
2023-08-27 18:40:40 +02:00
topBarLastY = parseInt ( topbarstyle . height ) ;
2023-07-20 19:32:15 +02:00
/ * c o n s o l e . l o g ( `
winWidth : $ { winWidth } , winHeight : $ { winHeight }
sheldWidth : $ { sheldWidth }
X : $ { $ ( elmnt ) . css ( 'left' ) }
Y : $ { $ ( elmnt ) . css ( 'top' ) }
MaxX : $ { maxX } , MaxY : $ { maxY }
height : $ { height }
width : $ { width }
Topbar 1 st X : $ { topBarFirstX }
TopBar lastX : $ { topBarLastX }
` );*/
//prepare an empty poweruser object for the item being altered if we don't have one already
if ( ! power _user . movingUIState [ elmntName ] ) {
2023-12-02 20:11:06 +01:00
console . debug ( ` adding config property for ${ elmntName } ` ) ;
2023-07-20 19:32:15 +02:00
power _user . movingUIState [ elmntName ] = { } ;
}
//only record position changes if caused by a user click-drag
if ( hasBeenDraggedByUser && isMouseDown ) {
power _user . movingUIState [ elmntName ] . top = top ;
power _user . movingUIState [ elmntName ] . left = left ;
power _user . movingUIState [ elmntName ] . right = right ;
power _user . movingUIState [ elmntName ] . bottom = bottom ;
power _user . movingUIState [ elmntName ] . margin = 'unset' ;
}
//handle resizing
if ( ! hasBeenDraggedByUser && isMouseDown ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'saw resize, NOT header drag' ) ;
2023-07-20 19:32:15 +02:00
//prevent resizing offscreen
if ( top + elmnt . height ( ) >= winHeight ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'resizing height to prevent offscreen' ) ;
2023-12-02 19:04:51 +01:00
elmnt . css ( 'height' , winHeight - top - 1 + 'px' ) ;
2023-07-20 19:32:15 +02:00
}
if ( left + elmnt . width ( ) >= winWidth ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'resizing width to prevent offscreen' ) ;
2023-12-02 19:04:51 +01:00
elmnt . css ( 'width' , winWidth - left - 1 + 'px' ) ;
2023-07-20 19:32:15 +02:00
}
//prevent resizing from top left into the top bar
2023-08-27 18:40:40 +02:00
if ( top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX
2023-07-20 19:32:15 +02:00
) {
2023-12-02 20:11:06 +01:00
console . debug ( 'prevent topbar underlap resize' ) ;
2023-12-02 19:04:51 +01:00
elmnt . css ( 'width' , width - 1 + 'px' ) ;
2023-07-20 19:32:15 +02:00
}
//set css to prevent weird resize behavior (does not save)
2023-12-02 20:11:06 +01:00
elmnt . css ( 'left' , left ) ;
elmnt . css ( 'top' , top ) ;
2023-07-20 19:32:15 +02:00
//set a listener for mouseup to save new width/height
elmnt . off ( 'mouseup' ) . on ( 'mouseup' , ( ) => {
2023-12-02 20:11:06 +01:00
console . debug ( ` Saving ${ elmntName } Height/Width ` ) ;
2023-09-02 21:40:15 +02:00
// check if the height or width actually changed
if ( power _user . movingUIState [ elmntName ] . width === width && power _user . movingUIState [ elmntName ] . height === height ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'no change detected, aborting save' ) ;
return ;
2023-09-02 21:40:15 +02:00
}
2023-07-20 19:32:15 +02:00
power _user . movingUIState [ elmntName ] . width = width ;
power _user . movingUIState [ elmntName ] . height = height ;
2023-08-28 06:49:20 +02:00
eventSource . emit ( 'resizeUI' , elmntName ) ;
2023-07-20 19:32:15 +02:00
saveSettingsDebounced ( ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-07-20 19:32:15 +02:00
}
//handle dragging hit detection
if ( hasBeenDraggedByUser && isMouseDown ) {
//prevent dragging offscreen
if ( top <= 0 ) {
elmnt . css ( 'top' , '0px' ) ;
} else if ( maxY >= winHeight ) {
2023-12-02 19:04:51 +01:00
elmnt . css ( 'top' , winHeight - maxY + top - 1 + 'px' ) ;
2023-07-20 19:32:15 +02:00
}
if ( left <= 0 ) {
elmnt . css ( 'left' , '0px' ) ;
} else if ( maxX >= winWidth ) {
2023-12-02 19:04:51 +01:00
elmnt . css ( 'left' , winWidth - maxX + left - 1 + 'px' ) ;
2023-07-20 19:32:15 +02:00
}
//prevent underlap with topbar div
2023-08-31 22:44:36 +02:00
/ *
2023-08-27 18:40:40 +02:00
if ( top < topBarLastY
2023-07-20 19:32:15 +02:00
&& ( maxX >= topBarFirstX && left <= topBarFirstX //elmnt is hitting topbar from left side
|| left <= topBarLastX && maxX >= topBarLastX //elmnt is hitting topbar from right side
|| left >= topBarFirstX && maxX <= topBarLastX ) //elmnt hitting topbar in the middle
) {
console . debug ( 'topbar hit' )
elmnt . css ( 'top' , top + 1 + "px" ) ;
}
2023-08-31 22:44:36 +02:00
* /
2023-07-20 19:32:15 +02:00
}
// Check if the element header exists and set the listener on the grabber
if ( elmntHeader . length ) {
elmntHeader . off ( 'mousedown' ) . on ( 'mousedown' , ( e ) => {
2023-12-02 20:11:06 +01:00
console . debug ( 'listener started from header' ) ;
2023-07-20 19:32:15 +02:00
dragMouseDown ( e ) ;
} ) ;
} else {
elmnt . off ( 'mousedown' ) . on ( 'mousedown' , dragMouseDown ) ;
}
} ) ;
function dragMouseDown ( e ) {
if ( e ) {
hasBeenDraggedByUser = true ;
e . preventDefault ( ) ;
pos3 = e . clientX ; //mouse X at click
pos4 = e . clientY ; //mouse Y at click
}
$ ( document ) . on ( 'mouseup' , closeDragElement ) ;
$ ( document ) . on ( 'mousemove' , elementDrag ) ;
}
function elementDrag ( e ) {
if ( ! power _user . movingUIState [ elmntName ] ) {
power _user . movingUIState [ elmntName ] = { } ;
}
e = e || window . event ;
e . preventDefault ( ) ;
pos1 = pos3 - e . clientX ; //X change amt (-1 or 1)
pos2 = pos4 - e . clientY ; //Y change amt (-1 or 1)
pos3 = e . clientX ; //new mouse X
pos4 = e . clientY ; //new mouse Y
elmnt . attr ( 'data-dragged' , 'true' ) ;
//first set css to computed values to avoid CSS NaN results from 'auto', etc
2023-12-02 19:04:51 +01:00
elmnt . css ( 'left' , ( elmnt . offset ( ) . left ) + 'px' ) ;
elmnt . css ( 'top' , ( elmnt . offset ( ) . top ) + 'px' ) ;
2023-07-20 19:32:15 +02:00
//then update element position styles to account for drag changes
elmnt . css ( 'margin' , 'unset' ) ;
2023-12-02 19:04:51 +01:00
elmnt . css ( 'left' , ( elmnt . offset ( ) . left - pos1 ) + 'px' ) ;
elmnt . css ( 'top' , ( elmnt . offset ( ) . top - pos2 ) + 'px' ) ;
elmnt . css ( 'right' , ( ( winWidth - maxX ) + 'px' ) ) ;
elmnt . css ( 'bottom' , ( ( winHeight - maxY ) + 'px' ) ) ;
2023-07-20 19:32:15 +02:00
// Height/Width here are for visuals only, and are not saved to settings
// required because some divs do hot have a set width/height..
// and will defaults to shrink to min value of 100px set in CSS file
2023-12-02 20:11:06 +01:00
elmnt . css ( 'height' , height ) ;
elmnt . css ( 'width' , width ) ;
2023-07-20 19:32:15 +02:00
/ *
console . log ( `
winWidth : $ { winWidth } , winHeight : $ { winHeight }
sheldWidth : $ { sheldWidth }
X : $ { $ ( elmnt ) . css ( 'left' ) }
Y : $ { $ ( elmnt ) . css ( 'top' ) }
MaxX : $ { maxX } , MaxY : $ { maxY }
height : $ { height }
width : $ { width }
Topbar 1 st X : $ { topBarFirstX }
TopBar lastX : $ { topBarLastX }
` );
* /
2023-12-02 20:11:06 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
function closeDragElement ( ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'drag finished' ) ;
2023-07-20 19:32:15 +02:00
hasBeenDraggedByUser = false ;
isMouseDown = false ;
$ ( document ) . off ( 'mouseup' , closeDragElement ) ;
$ ( document ) . off ( 'mousemove' , elementDrag ) ;
2023-12-02 19:04:51 +01:00
$ ( 'body' ) . css ( 'overflow' , '' ) ;
2023-07-20 19:32:15 +02:00
// Clear the "data-dragged" attribute
elmnt . attr ( 'data-dragged' , 'false' ) ;
2023-12-02 20:11:06 +01:00
observer . disconnect ( ) ;
console . debug ( ` Saving ${ elmntName } UI position ` ) ;
2023-07-20 19:32:15 +02:00
saveSettingsDebounced ( ) ;
}
}
export async function initMovingUI ( ) {
2023-12-15 00:05:59 +01:00
if ( ! isMobile ( ) && power _user . movingUI === true ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'START MOVING UI' ) ;
2023-12-02 19:04:51 +01:00
dragElement ( $ ( '#sheld' ) ) ;
dragElement ( $ ( '#left-nav-panel' ) ) ;
dragElement ( $ ( '#right-nav-panel' ) ) ;
dragElement ( $ ( '#WorldInfo' ) ) ;
2023-12-02 20:11:06 +01:00
dragElement ( $ ( '#floatingPrompt' ) ) ;
2024-03-19 00:40:02 +01:00
dragElement ( $ ( '#logprobsViewer' ) ) ;
dragElement ( $ ( '#cfgConfig' ) ) ;
2023-07-20 19:32:15 +02:00
}
}
2024-03-26 17:11:00 +01:00
/**@type {HTMLTextAreaElement} */
const sendTextArea = document . querySelector ( '#send_textarea' ) ;
2024-03-27 21:18:20 +01:00
const chatBlock = document . getElementById ( 'chat' ) ;
const isFirefox = navigator . userAgent . toLowerCase ( ) . indexOf ( 'firefox' ) > - 1 ;
2024-03-26 17:11:00 +01:00
/ * *
* this makes the chat input text area resize vertically to match the text size ( limited by CSS at 50 % window height )
* /
function autoFitSendTextArea ( ) {
const originalScrollBottom = chatBlock . scrollHeight - ( chatBlock . scrollTop + chatBlock . offsetHeight ) ;
if ( sendTextArea . scrollHeight == sendTextArea . offsetHeight ) {
2024-03-27 21:18:20 +01:00
// Needs to be pulled dynamically because it is affected by font size changes
const sendTextAreaMinHeight = window . getComputedStyle ( sendTextArea ) . getPropertyValue ( 'min-height' ) ;
sendTextArea . style . height = sendTextAreaMinHeight ;
2024-03-26 17:11:00 +01:00
}
sendTextArea . style . height = sendTextArea . scrollHeight + 0.3 + 'px' ;
if ( ! isFirefox ) {
const newScrollTop = Math . round ( chatBlock . scrollHeight - ( chatBlock . offsetHeight + originalScrollBottom ) ) ;
chatBlock . scrollTop = newScrollTop ;
}
}
export const autoFitSendTextAreaDebounced = debounce ( autoFitSendTextArea ) ;
2023-07-20 19:32:15 +02:00
// ---------------------------------------------------
2023-08-24 22:52:03 +02:00
export function initRossMods ( ) {
2023-07-20 19:32:15 +02:00
// initial status check
setTimeout ( ( ) => {
RA _checkOnlineStatus ( ) ;
} , 100 ) ;
2024-01-05 19:27:19 +01:00
if ( power _user . auto _load _chat ) {
RA _autoloadchat ( ) ;
}
2023-07-20 19:32:15 +02:00
2024-01-05 19:27:19 +01:00
if ( power _user . auto _connect ) {
RA _autoconnect ( ) ;
}
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$ ( '#main_api' ) . change ( function ( ) {
2023-07-20 19:32:15 +02:00
var PrevAPI = main _api ;
setTimeout ( ( ) => RA _autoconnect ( PrevAPI ) , 100 ) ;
} ) ;
2024-01-05 19:27:19 +01:00
2023-12-02 19:04:51 +01:00
$ ( '#api_button' ) . click ( function ( ) { setTimeout ( RA _checkOnlineStatus , 100 ) ; } ) ;
2023-07-20 19:32:15 +02:00
//toggle pin class when lock toggle clicked
2023-12-02 19:04:51 +01:00
$ ( RPanelPin ) . on ( 'click' , function ( ) {
SaveLocal ( 'NavLockOn' , $ ( RPanelPin ) . prop ( 'checked' ) ) ;
if ( $ ( RPanelPin ) . prop ( 'checked' ) == true ) {
2023-07-20 19:32:15 +02:00
//console.log('adding pin class to right nav');
$ ( RightNavPanel ) . addClass ( 'pinnedOpen' ) ;
} else {
//console.log('removing pin class from right nav');
$ ( RightNavPanel ) . removeClass ( 'pinnedOpen' ) ;
if ( $ ( RightNavPanel ) . hasClass ( 'openDrawer' ) && $ ( '.openDrawer' ) . length > 1 ) {
2023-12-02 19:04:51 +01:00
$ ( RightNavPanel ) . slideToggle ( 200 , 'swing' ) ;
2023-07-20 19:32:15 +02:00
//$(rightNavDrawerIcon).toggleClass('openIcon closedIcon');
$ ( RightNavPanel ) . toggleClass ( 'openDrawer closedDrawer' ) ;
}
}
} ) ;
2023-12-02 19:04:51 +01:00
$ ( LPanelPin ) . on ( 'click' , function ( ) {
SaveLocal ( 'LNavLockOn' , $ ( LPanelPin ) . prop ( 'checked' ) ) ;
if ( $ ( LPanelPin ) . prop ( 'checked' ) == true ) {
2023-07-20 19:32:15 +02:00
//console.log('adding pin class to Left nav');
$ ( LeftNavPanel ) . addClass ( 'pinnedOpen' ) ;
} else {
//console.log('removing pin class from Left nav');
$ ( LeftNavPanel ) . removeClass ( 'pinnedOpen' ) ;
if ( $ ( LeftNavPanel ) . hasClass ( 'openDrawer' ) && $ ( '.openDrawer' ) . length > 1 ) {
2023-12-02 19:04:51 +01:00
$ ( LeftNavPanel ) . slideToggle ( 200 , 'swing' ) ;
2023-07-20 19:32:15 +02:00
//$(leftNavDrawerIcon).toggleClass('openIcon closedIcon');
$ ( LeftNavPanel ) . toggleClass ( 'openDrawer closedDrawer' ) ;
}
}
} ) ;
2023-12-02 19:04:51 +01:00
$ ( WIPanelPin ) . on ( 'click' , function ( ) {
SaveLocal ( 'WINavLockOn' , $ ( WIPanelPin ) . prop ( 'checked' ) ) ;
if ( $ ( WIPanelPin ) . prop ( 'checked' ) == true ) {
2023-07-20 19:32:15 +02:00
console . debug ( 'adding pin class to WI' ) ;
$ ( WorldInfo ) . addClass ( 'pinnedOpen' ) ;
} else {
console . debug ( 'removing pin class from WI' ) ;
$ ( WorldInfo ) . removeClass ( 'pinnedOpen' ) ;
if ( $ ( WorldInfo ) . hasClass ( 'openDrawer' ) && $ ( '.openDrawer' ) . length > 1 ) {
console . debug ( 'closing WI after lock removal' ) ;
2023-12-02 19:04:51 +01:00
$ ( WorldInfo ) . slideToggle ( 200 , 'swing' ) ;
2023-07-20 19:32:15 +02:00
//$(WorldInfoDrawerIcon).toggleClass('openIcon closedIcon');
$ ( WorldInfo ) . toggleClass ( 'openDrawer closedDrawer' ) ;
}
}
} ) ;
// read the state of right Nav Lock and apply to rightnav classlist
2023-12-02 19:04:51 +01:00
$ ( RPanelPin ) . prop ( 'checked' , LoadLocalBool ( 'NavLockOn' ) ) ;
if ( LoadLocalBool ( 'NavLockOn' ) == true ) {
2023-07-20 19:32:15 +02:00
//console.log('setting pin class via local var');
$ ( RightNavPanel ) . addClass ( 'pinnedOpen' ) ;
}
2023-12-02 16:15:47 +01:00
if ( $ ( RPanelPin ) . prop ( 'checked' ) ) {
2023-07-20 19:32:15 +02:00
console . debug ( 'setting pin class via checkbox state' ) ;
$ ( RightNavPanel ) . addClass ( 'pinnedOpen' ) ;
}
// read the state of left Nav Lock and apply to leftnav classlist
2023-12-02 19:04:51 +01:00
$ ( LPanelPin ) . prop ( 'checked' , LoadLocalBool ( 'LNavLockOn' ) ) ;
if ( LoadLocalBool ( 'LNavLockOn' ) == true ) {
2023-07-20 19:32:15 +02:00
//console.log('setting pin class via local var');
$ ( LeftNavPanel ) . addClass ( 'pinnedOpen' ) ;
}
2023-12-02 16:15:47 +01:00
if ( $ ( LPanelPin ) . prop ( 'checked' ) ) {
2023-07-20 19:32:15 +02:00
console . debug ( 'setting pin class via checkbox state' ) ;
$ ( LeftNavPanel ) . addClass ( 'pinnedOpen' ) ;
}
// read the state of left Nav Lock and apply to leftnav classlist
2023-12-02 19:04:51 +01:00
$ ( WIPanelPin ) . prop ( 'checked' , LoadLocalBool ( 'WINavLockOn' ) ) ;
if ( LoadLocalBool ( 'WINavLockOn' ) == true ) {
2023-07-20 19:32:15 +02:00
//console.log('setting pin class via local var');
$ ( WorldInfo ) . addClass ( 'pinnedOpen' ) ;
}
2023-12-02 16:15:47 +01:00
if ( $ ( WIPanelPin ) . prop ( 'checked' ) ) {
2023-07-20 19:32:15 +02:00
console . debug ( 'setting pin class via checkbox state' ) ;
$ ( WorldInfo ) . addClass ( 'pinnedOpen' ) ;
}
//save state of Right nav being open or closed
2023-12-02 19:04:51 +01:00
$ ( '#rightNavDrawerIcon' ) . on ( 'click' , function ( ) {
if ( ! $ ( '#rightNavDrawerIcon' ) . hasClass ( 'openIcon' ) ) {
2023-07-20 19:32:15 +02:00
SaveLocal ( 'NavOpened' , 'true' ) ;
} else { SaveLocal ( 'NavOpened' , 'false' ) ; }
} ) ;
//save state of Left nav being open or closed
2023-12-02 19:04:51 +01:00
$ ( '#leftNavDrawerIcon' ) . on ( 'click' , function ( ) {
if ( ! $ ( '#leftNavDrawerIcon' ) . hasClass ( 'openIcon' ) ) {
2023-07-20 19:32:15 +02:00
SaveLocal ( 'LNavOpened' , 'true' ) ;
} else { SaveLocal ( 'LNavOpened' , 'false' ) ; }
} ) ;
//save state of Left nav being open or closed
2023-12-02 19:04:51 +01:00
$ ( '#WorldInfo' ) . on ( 'click' , function ( ) {
if ( ! $ ( '#WorldInfo' ) . hasClass ( 'openIcon' ) ) {
2023-07-20 19:32:15 +02:00
SaveLocal ( 'WINavOpened' , 'true' ) ;
} else { SaveLocal ( 'WINavOpened' , 'false' ) ; }
} ) ;
var chatbarInFocus = false ;
$ ( '#send_textarea' ) . focus ( function ( ) {
chatbarInFocus = true ;
} ) ;
$ ( '#send_textarea' ) . blur ( function ( ) {
chatbarInFocus = false ;
} ) ;
setTimeout ( ( ) => {
OpenNavPanels ( ) ;
} , 300 ) ;
$ ( SelectedCharacterTab ) . click ( function ( ) { SaveLocal ( 'SelectedNavTab' , 'rm_button_selected_ch' ) ; } ) ;
2023-12-02 19:04:51 +01:00
$ ( '#rm_button_characters' ) . click ( function ( ) { SaveLocal ( 'SelectedNavTab' , 'rm_button_characters' ) ; } ) ;
2023-07-20 19:32:15 +02:00
// when a char is selected from the list, save them as the auto-load character for next page load
2023-07-30 01:48:08 +02:00
// when a char is selected from the list, save their name as the auto-load character for next page load
2023-12-02 19:04:51 +01:00
$ ( document ) . on ( 'click' , '.character_select' , function ( ) {
2024-03-07 23:48:50 +01:00
const characterId = $ ( this ) . attr ( 'chid' ) || $ ( this ) . data ( 'id' ) ;
2023-08-19 21:22:24 +02:00
setActiveCharacter ( characterId ) ;
2023-07-30 01:48:08 +02:00
setActiveGroup ( null ) ;
2023-07-30 02:05:12 +02:00
saveSettingsDebounced ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-12-02 19:04:51 +01:00
$ ( document ) . on ( 'click' , '.group_select' , function ( ) {
2024-03-09 20:58:13 +01:00
const groupId = $ ( this ) . attr ( 'chid' ) || $ ( this ) . attr ( 'grid' ) || $ ( this ) . data ( 'id' ) ;
2023-07-30 01:48:08 +02:00
setActiveCharacter ( null ) ;
2023-08-19 21:22:24 +02:00
setActiveGroup ( groupId ) ;
2023-07-30 02:05:12 +02:00
saveSettingsDebounced ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2024-03-27 21:18:20 +01:00
$ ( sendTextArea ) . on ( 'input' , ( ) => {
if ( sendTextArea . scrollHeight > sendTextArea . offsetHeight || sendTextArea . value === '' ) {
2024-03-26 17:11:00 +01:00
autoFitSendTextArea ( ) ;
} else {
autoFitSendTextAreaDebounced ( ) ;
2023-12-04 10:00:22 +01:00
}
2024-03-26 17:11:15 +01:00
saveUserInputDebounced ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2023-11-27 23:29:34 +01:00
restoreUserInput ( ) ;
2023-07-20 19:32:15 +02:00
//Regenerate if user swipes on the last mesage in chat
document . addEventListener ( 'swiped-left' , function ( e ) {
2023-10-23 03:54:17 +02:00
if ( power _user . gestures === false ) {
2023-12-02 20:11:06 +01:00
return ;
2023-10-23 03:54:17 +02:00
}
2023-12-02 19:04:51 +01:00
if ( $ ( '.mes_edit_buttons, .drawer-content, #character_popup, #dialogue_popup, #WorldInfo, #right-nav-panel, #left-nav-panel, #select_chat_popup, #floatingPrompt' ) . is ( ':visible' ) ) {
2023-12-02 20:11:06 +01:00
return ;
2023-11-09 06:40:14 +01:00
}
2023-07-20 19:32:15 +02:00
var SwipeButR = $ ( '.swipe_right:last' ) ;
2023-08-22 12:07:24 +02:00
var SwipeTargetMesClassParent = $ ( e . target ) . closest ( '.last_mes' ) ;
2023-07-20 19:32:15 +02:00
if ( SwipeTargetMesClassParent !== null ) {
if ( SwipeButR . css ( 'display' ) === 'flex' ) {
SwipeButR . click ( ) ;
}
}
} ) ;
document . addEventListener ( 'swiped-right' , function ( e ) {
2023-10-23 03:54:17 +02:00
if ( power _user . gestures === false ) {
2023-12-02 20:11:06 +01:00
return ;
2023-10-23 03:54:17 +02:00
}
2023-12-02 19:04:51 +01:00
if ( $ ( '.mes_edit_buttons, .drawer-content, #character_popup, #dialogue_popup, #WorldInfo, #right-nav-panel, #left-nav-panel, #select_chat_popup, #floatingPrompt' ) . is ( ':visible' ) ) {
2023-12-02 20:11:06 +01:00
return ;
2023-11-09 06:40:14 +01:00
}
2023-07-20 19:32:15 +02:00
var SwipeButL = $ ( '.swipe_left:last' ) ;
2023-08-22 12:07:24 +02:00
var SwipeTargetMesClassParent = $ ( e . target ) . closest ( '.last_mes' ) ;
2023-07-20 19:32:15 +02:00
if ( SwipeTargetMesClassParent !== null ) {
if ( SwipeButL . css ( 'display' ) === 'flex' ) {
SwipeButL . click ( ) ;
}
}
} ) ;
function isInputElementInFocus ( ) {
//return $(document.activeElement).is(":input");
var focused = $ ( ':focus' ) ;
if ( focused . is ( 'input' ) || focused . is ( 'textarea' ) || focused . prop ( 'contenteditable' ) == 'true' ) {
if ( focused . attr ( 'id' ) === 'send_textarea' ) {
return false ;
}
return true ;
}
return false ;
}
$ ( document ) . on ( 'keydown' , function ( event ) {
2023-09-08 20:27:33 +02:00
processHotkeys ( event . originalEvent ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2024-03-26 17:09:26 +01:00
const hotkeyTargets = {
'send_textarea' : sendTextArea ,
'dialogue_popup_input' : document . querySelector ( '#dialogue_popup_input' ) ,
} ;
2023-07-20 19:32:15 +02:00
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
2023-09-08 20:27:33 +02:00
/ * *
* @ param { KeyboardEvent } event
* /
2023-07-20 19:32:15 +02:00
function processHotkeys ( event ) {
//Enter to send when send_textarea in focus
2024-03-26 17:09:26 +01:00
if ( document . activeElement == hotkeyTargets [ 'send_textarea' ] ) {
2023-07-20 19:32:15 +02:00
const sendOnEnter = shouldSendOnEnter ( ) ;
2023-12-25 09:29:14 +01:00
if ( ! event . shiftKey && ! event . ctrlKey && ! event . altKey && event . key == 'Enter' && sendOnEnter ) {
2023-07-20 19:32:15 +02:00
event . preventDefault ( ) ;
2023-12-25 09:29:14 +01:00
sendTextareaMessage ( ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
}
2024-03-26 17:09:26 +01:00
if ( document . activeElement == hotkeyTargets [ 'dialogue_popup_input' ] && ! isMobile ( ) ) {
2023-12-02 19:04:51 +01:00
if ( ! event . shiftKey && ! event . ctrlKey && event . key == 'Enter' ) {
2023-08-03 10:32:08 +02:00
event . preventDefault ( ) ;
$ ( '#dialogue_popup_ok' ) . trigger ( 'click' ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-08-03 10:32:08 +02:00
}
}
2023-07-20 19:32:15 +02:00
//ctrl+shift+up to scroll to context line
2023-12-02 19:04:51 +01:00
if ( event . shiftKey && event . ctrlKey && event . key == 'ArrowUp' ) {
2023-07-20 19:32:15 +02:00
event . preventDefault ( ) ;
let contextLine = $ ( '.lastInContext' ) ;
if ( contextLine . length !== 0 ) {
$ ( '#chat' ) . animate ( {
2023-12-02 21:06:57 +01:00
scrollTop : contextLine . offset ( ) . top - $ ( '#chat' ) . offset ( ) . top + $ ( '#chat' ) . scrollTop ( ) ,
2023-07-20 19:32:15 +02:00
} , 300 ) ;
} else { toastr . warning ( 'Context line not found, send a message first!' ) ; }
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
//ctrl+shift+down to scroll to bottom of chat
2023-12-02 19:04:51 +01:00
if ( event . shiftKey && event . ctrlKey && event . key == 'ArrowDown' ) {
2023-07-20 19:32:15 +02:00
event . preventDefault ( ) ;
$ ( '#chat' ) . animate ( {
2023-12-02 21:06:57 +01:00
scrollTop : $ ( '#chat' ) . prop ( 'scrollHeight' ) ,
2023-07-20 19:32:15 +02:00
} , 300 ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
2023-09-08 20:27:33 +02:00
// Alt+Enter or AltGr+Enter to Continue
2023-12-02 19:04:51 +01:00
if ( ( event . altKey || ( event . altKey && event . ctrlKey ) ) && event . key == 'Enter' ) {
2023-09-08 20:27:33 +02:00
if ( is _send _press == false ) {
2023-12-02 19:04:51 +01:00
console . debug ( 'Continuing with Alt+Enter' ) ;
2023-09-08 20:27:33 +02:00
$ ( '#option_continue' ) . trigger ( 'click' ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-09-08 20:27:33 +02:00
}
}
2023-07-20 19:32:15 +02:00
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
2023-12-02 19:04:51 +01:00
if ( event . ctrlKey && event . key == 'Enter' ) {
const editMesDone = $ ( '.mes_edit_done:visible' ) ;
2023-07-20 19:32:15 +02:00
if ( editMesDone . length > 0 ) {
2023-12-02 19:04:51 +01:00
console . debug ( 'Accepting edits with Ctrl+Enter' ) ;
2023-07-20 19:32:15 +02:00
editMesDone . trigger ( 'click' ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
} else if ( is _send _press == false ) {
2023-12-17 20:27:33 +01:00
const skipConfirmKey = 'RegenerateWithCtrlEnter' ;
const skipConfirm = LoadLocalBool ( skipConfirmKey ) ;
function doRegenerate ( ) {
console . debug ( 'Regenerating with Ctrl+Enter' ) ;
$ ( '#option_regenerate' ) . trigger ( 'click' ) ;
$ ( '#options' ) . hide ( ) ;
}
if ( skipConfirm ) {
doRegenerate ( ) ;
} else {
const popupText = `
< div class = "marginBot10" > Are you sure you want to regenerate the latest message ? < / d i v >
< label class = "checkbox_label justifyCenter" for = "regenerateWithCtrlEnter" >
< input type = "checkbox" id = "regenerateWithCtrlEnter" >
Don ' t ask again
< / l a b e l > ` ;
2024-01-21 16:27:09 +01:00
callPopup ( popupText , 'confirm' ) . then ( result => {
2023-12-17 20:27:33 +01:00
if ( ! result ) {
return ;
}
const regenerateWithCtrlEnter = $ ( '#regenerateWithCtrlEnter' ) . prop ( 'checked' ) ;
SaveLocal ( skipConfirmKey , regenerateWithCtrlEnter ) ;
doRegenerate ( ) ;
} ) ;
}
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
} else {
2023-12-02 19:04:51 +01:00
console . debug ( 'Ctrl+Enter ignored' ) ;
2023-07-20 19:32:15 +02:00
}
}
2023-08-31 13:54:35 +02:00
2023-08-27 05:32:22 +02:00
// Helper function to check if nanogallery2's lightbox is active
function isNanogallery2LightboxActive ( ) {
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior
2024-03-26 17:09:26 +01:00
return document . body . classList . contains ( 'nGY2_body_scrollbar' ) ;
2023-08-27 05:32:22 +02:00
}
2023-12-02 19:04:51 +01:00
if ( event . key == 'ArrowLeft' ) { //swipes left
2023-07-20 19:32:15 +02:00
if (
2023-08-27 05:32:22 +02:00
! isNanogallery2LightboxActive ( ) && // Check if lightbox is NOT active
2023-12-02 19:04:51 +01:00
$ ( '.swipe_left:last' ) . css ( 'display' ) === 'flex' &&
$ ( '#send_textarea' ) . val ( ) === '' &&
$ ( '#character_popup' ) . css ( 'display' ) === 'none' &&
$ ( '#shadow_select_chat_popup' ) . css ( 'display' ) === 'none' &&
2023-07-20 19:32:15 +02:00
! isInputElementInFocus ( )
) {
$ ( '.swipe_left:last' ) . click ( ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
}
2023-12-02 19:04:51 +01:00
if ( event . key == 'ArrowRight' ) { //swipes right
2023-07-20 19:32:15 +02:00
if (
2023-08-27 05:32:22 +02:00
! isNanogallery2LightboxActive ( ) && // Check if lightbox is NOT active
2023-12-02 19:04:51 +01:00
$ ( '.swipe_right:last' ) . css ( 'display' ) === 'flex' &&
$ ( '#send_textarea' ) . val ( ) === '' &&
$ ( '#character_popup' ) . css ( 'display' ) === 'none' &&
$ ( '#shadow_select_chat_popup' ) . css ( 'display' ) === 'none' &&
2023-07-20 19:32:15 +02:00
! isInputElementInFocus ( )
) {
$ ( '.swipe_right:last' ) . click ( ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
}
2023-08-27 05:32:22 +02:00
2023-12-02 19:04:51 +01:00
if ( event . ctrlKey && event . key == 'ArrowUp' ) { //edits last USER message if chatbar is empty and focused
2023-07-20 19:32:15 +02:00
if (
2024-03-26 17:09:26 +01:00
hotkeyTargets [ 'send_textarea' ] . value === '' &&
2023-07-20 19:32:15 +02:00
chatbarInFocus === true &&
2023-12-02 19:04:51 +01:00
( $ ( '.swipe_right:last' ) . css ( 'display' ) === 'flex' || $ ( '.last_mes' ) . attr ( 'is_system' ) === 'true' ) &&
$ ( '#character_popup' ) . css ( 'display' ) === 'none' &&
$ ( '#shadow_select_chat_popup' ) . css ( 'display' ) === 'none'
2023-07-20 19:32:15 +02:00
) {
const isUserMesList = document . querySelectorAll ( 'div[is_user="true"]' ) ;
const lastIsUserMes = isUserMesList [ isUserMesList . length - 1 ] ;
const editMes = lastIsUserMes . querySelector ( '.mes_block .mes_edit' ) ;
if ( editMes !== null ) {
2023-07-30 20:37:49 +02:00
$ ( editMes ) . trigger ( 'click' ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
}
}
2023-12-02 19:04:51 +01:00
if ( event . key == 'ArrowUp' ) { //edits last message if chatbar is empty and focused
2023-12-17 12:18:51 +01:00
console . log ( 'got uparrow input' ) ;
2023-07-20 19:32:15 +02:00
if (
2024-03-26 17:09:26 +01:00
hotkeyTargets [ 'send_textarea' ] . value === '' &&
2023-07-20 19:32:15 +02:00
chatbarInFocus === true &&
2023-12-17 12:18:51 +01:00
//$('.swipe_right:last').css('display') === 'flex' &&
2023-12-17 15:19:20 +01:00
$ ( '.last_mes .mes_buttons' ) . is ( ':visible' ) &&
2023-12-02 19:04:51 +01:00
$ ( '#character_popup' ) . css ( 'display' ) === 'none' &&
$ ( '#shadow_select_chat_popup' ) . css ( 'display' ) === 'none'
2023-07-20 19:32:15 +02:00
) {
const lastMes = document . querySelector ( '.last_mes' ) ;
const editMes = lastMes . querySelector ( '.mes_block .mes_edit' ) ;
if ( editMes !== null ) {
$ ( editMes ) . click ( ) ;
2024-03-26 17:09:26 +01:00
return ;
2023-07-20 19:32:15 +02:00
}
}
}
2023-07-30 19:41:15 +02:00
2023-12-02 19:04:51 +01:00
if ( event . key == 'Escape' ) { //closes various panels
2023-08-30 21:46:33 +02:00
2023-08-13 22:33:46 +02:00
//dont override Escape hotkey functions from script.js
//"close edit box" and "cancel stream generation".
2023-12-02 19:04:51 +01:00
if ( $ ( '#curEditTextarea' ) . is ( ':visible' ) || $ ( '#mes_stop' ) . is ( ':visible' ) ) {
2023-12-02 20:11:06 +01:00
console . debug ( 'escape key, but deferring to script.js routines' ) ;
return ;
2023-07-30 19:56:39 +02:00
}
2023-12-02 19:04:51 +01:00
if ( $ ( '#dialogue_popup' ) . is ( ':visible' ) ) {
if ( $ ( '#dialogue_popup_cancel' ) . is ( ':visible' ) ) {
$ ( '#dialogue_popup_cancel' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
} else {
2023-12-02 20:11:06 +01:00
$ ( '#dialogue_popup_ok' ) . trigger ( 'click' ) ;
return ;
2023-07-30 19:41:15 +02:00
}
}
2023-08-13 22:33:46 +02:00
2023-12-02 19:04:51 +01:00
if ( $ ( '#select_chat_popup' ) . is ( ':visible' ) ) {
$ ( '#select_chat_cross' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
}
2023-08-13 22:33:46 +02:00
2023-12-02 19:04:51 +01:00
if ( $ ( '#character_popup' ) . is ( ':visible' ) ) {
$ ( '#character_cross' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
}
2023-12-02 19:04:51 +01:00
if ( $ ( '.drawer-content' )
2023-07-30 19:41:15 +02:00
. not ( '#WorldInfo' )
. not ( '#left-nav-panel' )
. not ( '#right-nav-panel' )
2023-08-13 22:33:46 +02:00
. not ( '#floatingPrompt' )
2023-12-11 14:39:58 +01:00
. not ( '#cfgConfig' )
2024-02-07 18:28:34 +01:00
. not ( '#logprobsViewer' )
2024-03-17 18:23:01 +01:00
. not ( '#movingDivs > div' )
2023-12-02 19:04:51 +01:00
. is ( ':visible' ) ) {
let visibleDrawerContent = $ ( '.drawer-content:visible' )
2023-07-30 19:41:15 +02:00
. not ( '#WorldInfo' )
. not ( '#left-nav-panel' )
. not ( '#right-nav-panel' )
2023-12-11 14:39:58 +01:00
. not ( '#floatingPrompt' )
2024-01-23 06:00:31 +01:00
. not ( '#cfgConfig' )
2024-03-17 18:23:01 +01:00
. not ( '#logprobsViewer' )
. not ( '#movingDivs > div' ) ;
2023-07-30 19:41:15 +02:00
$ ( visibleDrawerContent ) . parent ( ) . find ( '.drawer-icon' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
}
2023-12-02 19:04:51 +01:00
if ( $ ( '#floatingPrompt' ) . is ( ':visible' ) ) {
$ ( '#ANClose' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
}
2023-08-13 22:33:46 +02:00
2023-12-02 19:04:51 +01:00
if ( $ ( '#WorldInfo' ) . is ( ':visible' ) ) {
$ ( '#WIDrawerIcon' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
}
2023-08-13 22:33:46 +02:00
2023-12-11 14:39:58 +01:00
if ( $ ( '#cfgConfig' ) . is ( ':visible' ) ) {
$ ( '#CFGClose' ) . trigger ( 'click' ) ;
return ;
}
2024-01-23 06:00:31 +01:00
if ( $ ( '#logprobsViewer' ) . is ( ':visible' ) ) {
$ ( '#logprobsViewerClose' ) . trigger ( 'click' ) ;
return ;
}
2024-03-17 18:23:01 +01:00
$ ( '#movingDivs > div' ) . each ( function ( ) {
if ( $ ( this ) . is ( ':visible' ) ) {
$ ( '#movingDivs > div .floating_panel_close' ) . trigger ( 'click' ) ;
return ;
}
} ) ;
2023-12-02 19:04:51 +01:00
if ( $ ( '#left-nav-panel' ) . is ( ':visible' ) &&
2023-08-13 22:33:46 +02:00
$ ( LPanelPin ) . prop ( 'checked' ) === false ) {
2023-12-02 19:04:51 +01:00
$ ( '#leftNavDrawerIcon' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
}
2023-08-13 22:33:46 +02:00
2023-12-02 19:04:51 +01:00
if ( $ ( '#right-nav-panel' ) . is ( ':visible' ) &&
2023-08-13 22:33:46 +02:00
$ ( RPanelPin ) . prop ( 'checked' ) === false ) {
2023-12-02 19:04:51 +01:00
$ ( '#rightNavDrawerIcon' ) . trigger ( 'click' ) ;
2023-12-02 20:11:06 +01:00
return ;
2023-07-30 19:41:15 +02:00
}
2023-12-02 19:04:51 +01:00
if ( $ ( '.draggable' ) . is ( ':visible' ) ) {
2023-09-17 12:41:17 +02:00
// Remove the first matched element
$ ( '.draggable:first' ) . remove ( ) ;
return ;
}
2023-07-30 19:41:15 +02:00
}
2023-08-03 07:44:23 +02:00
2023-09-17 12:41:17 +02:00
2023-08-30 21:58:14 +02:00
2023-08-03 07:44:23 +02:00
if ( event . ctrlKey && /^[1-9]$/ . test ( event . key ) ) {
2023-08-30 21:46:33 +02:00
// This will eventually be to trigger quick replies
2023-08-03 07:44:23 +02:00
event . preventDefault ( ) ;
2023-12-02 19:04:51 +01:00
console . log ( 'Ctrl +' + event . key + ' pressed!' ) ;
2023-08-03 07:44:23 +02:00
}
2023-07-20 19:32:15 +02:00
}
2023-08-24 22:52:03 +02:00
}