Merge pull request #4096 from SillyTavern/TopbarFadeMUIAndGetWorldEntryRefactors UI performance optimizations and refactoring

f33db29e76f474e4f938720d2ca06ba556af1d12

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

Signed
27 files changed, +2246 -2127Ignore whitespace
public/css/animations.css+10 -0
@@ -55,6 +55,7 @@
5555
5656/* Flashing for highlighting animation */
5757@keyframes flash {
58+
5859 0%,
5960 50%,
6061 100% {
@@ -122,3 +123,12 @@
122123 border-top-color: var(--progFlashColor);
123124 }
124125}
126+
127+/* Scroll delay animations */
128+@keyframes hide-scroll {
129+
130+ from,
131+ to {
132+ overflow-y: hidden;
133+ }
134+}
public/css/character-group-overlay.css+1 -1
@@ -1,5 +1,5 @@
11#rm_print_characters_block.group_overlay_mode_select .character_select {
22 transition: background-color 0.4svar(--animation-duration-3x) ease;
33 background-color: rgba(170, 170, 170, 0.15);
44}
55
public/css/loader.css+1 -1
@@ -18,7 +18,7 @@
1818
1919#load-spinner {
2020 --spinner-size: 2em;
2121 transition: all 300msvar(--animation-duration-2x) ease-out;
2222 opacity: 1;
2323 top: calc(50% - var(--spinner-size) / 2);
2424 left: calc(50% - var(--spinner-size) / 2);
public/css/login.css+1 -1
@@ -23,7 +23,7 @@ body.login .userSelect {
2323 width: 30%;
2424 cursor: pointer;
2525 margin: 5px 0;
2626 transition: background-color 0.15svar(--animation-duration) ease-in-out;
2727 display: flex;
2828 align-items: center;
2929 justify-content: center;
public/css/logprobs.css+1 -1
@@ -43,7 +43,7 @@
4343 height: 25px;
4444 margin-left: 5px;
4545 opacity: 0.5;
4646 transition: all 250msvar(--animation-duration-2x);
4747 position: unset !important;
4848}
4949
public/css/mobile-styles.css+9 -0
@@ -237,6 +237,15 @@
237237 top: var(--topBarBlockSize) !important;
238238 left: 0 !important;
239239 backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
240+
241+ @starting-style {
242+ height: 0;
243+ }
244+ }
245+
246+ #left-nav-panel:not(.openDrawer),
247+ #right-nav-panel:not(.openDrawer) {
248+ height: 0;
240249 }
241250
242251 /*
public/css/promptmanager.css+2 -2
@@ -79,7 +79,7 @@
7979 justify-content: center;
8080 margin-left: 0.25em;
8181 cursor: pointer;
8282 transition: 0.3svar(--animation-duration-2x) ease-in-out;
8383 height: 20px;
8484 width: 20px;
8585 filter: drop-shadow(0px 0px 2px black);
@@ -239,7 +239,7 @@
239239 flex-direction: column;
240240 justify-content: center;
241241 margin-left: 0.25em;
242242 transition: 0.3svar(--animation-duration-2x) ease-in-out;
243243 filter: drop-shadow(0px 0px 2px black);
244244}
245245
public/css/rm-groups.css+1 -1
@@ -172,7 +172,7 @@
172172#rm_group_members .group_member:not(.disabled) .right_menu_button[data-action="disable"] {
173173 opacity: 0.4;
174174 filter: brightness(0.5);
175175 transition: all 0.2svar(--animation-duration-2x) ease-in-out;
176176}
177177
178178/* #rm_group_members .right_menu_button[data-action="speak"]:hover, */
public/css/select2-overrides.css+3 -3
@@ -125,7 +125,7 @@
125125 background-color: unset;
126126 color: var(--SmartThemeBodyColor);
127127 opacity: 0.5;
128128 transition: opacity 200msvar(--animation-duration-2x) ease-in-out;
129129 position: relative;
130130}
131131
@@ -203,7 +203,7 @@
203203/* Styling for choice remove icon */
204204span.select2.select2-container .select2-selection__choice__remove {
205205 cursor: pointer;
206206 transition: background-color 0.3svar(--animation-duration-2x);
207207 color: var(--SmartThemeBodyColor);
208208 background-color: var(--black50a);
209209}
@@ -220,7 +220,7 @@ span.select2.select2-container .select2-selection__choice__remove:hover {
220220
221221.select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display {
222222 cursor: pointer;
223223 transition: background-color 0.3svar(--animation-duration-2x);
224224 color: var(--SmartThemeBodyColor);
225225 background-color: var(--black50a);
226226 white-space: break-spaces;
public/css/st-tailwind.css+5 -1
@@ -332,6 +332,10 @@
332332 overflow: hidden;
333333}
334334
335+.overflowYScroll {
336+ overflow-y: scroll;
337+}
338+
335339.padding0 {
336340 padding: 0;
337341}
@@ -480,7 +484,7 @@
480484}
481485
482486.hoverglow {
483487 transition: opacity 200msvar(--animation-duration-2x);
484488}
485489
486490.hoverglow:hover {
public/css/tags.css+1 -1
@@ -167,7 +167,7 @@
167167}
168168
169169.rm_tag_filter .tag.actionable {
170170 transition: opacity 200msvar(--animation-duration-2x);
171171}
172172
173173.rm_tag_filter .tag:hover {
public/css/toggle-dependent.css+7 -0
@@ -19,6 +19,7 @@ body:not(.tts) #ttsExtensionNarrateAll {
1919}
2020
2121body.no-hotswap .hotswap,
22+body.no-hotswap .hotswap~hr,
2223body.no-timer .mes_timer,
2324body.no-timestamps .timestamp,
2425body.no-tokenCount .tokenCounterDisplay,
@@ -28,6 +29,12 @@ body.hideChatAvatars .mesAvatarWrapper .avatar {
2829 display: none !important;
2930}
3031
32+body.no-hotswap #CharListButtonAndHotSwaps .flex-container {
33+ flex-wrap: nowrap;
34+ align-items: flex-end;
35+ flex-flow: row;
36+}
37+
3138body.hideChatAvatars .last_mes:not(.smallSysMes) {
3239 padding-bottom: 20px !important;
3340}
public/css/world-info.css+6 -2
@@ -198,7 +198,11 @@
198198}
199199
200200.world_entry {
201201 transition: opacity 500msvar(--animation-duration-3x);
202+}
203+
204+.world_entry_edit {
205+ display: contents;
202206}
203207
204208.disabledWIEntry {
@@ -267,7 +271,7 @@ select.keyselect+span.select2-container .select2-selection--multiple {
267271
268272 opacity: 0.5;
269273 color: var(--SmartThemeBodyColor);
270274 transition: opacity 0.3svar(--animation-duration-2x);
271275}
272276
273277.switch_input_type_icon:hover {
public/index.html+614 -606
@@ -140,7 +140,7 @@
140140 <div data-preset-manager-import="novel" class="margin0 menu_button_icon menu_button" title="Import preset" data-i18n="[title]Import preset">
141141 <i class="fa-fw fa-solid fa-file-import"></i>
142142 </div>
143143 <div data-preset-manager-export="novel" class="margin0 menu_button_icon menu_button" title="Export preset" data-i18n="[title]Export preset">
144144 <i class="fa-fw fa-solid fa-file-export"></i>
145145 </div>
146146 <div data-preset-manager-delete="novel" class="margin0 menu_button_icon menu_button" title="Delete the preset" data-i18n="[title]Delete the preset">
@@ -663,7 +663,7 @@
663663 <div data-source="openrouter" class="range-block">
664664 <div class="range-block-title" title="Allow compressing requests by removing messages from the middle of the prompt.">
665665 <span data-i18n="Middle-out Transform">Middle-out Transform</span>
666666 <a href="https://openrouter.ai/docs/transforms" target="_blank" rel="noreferrer noopener" class="note-link-span fa-solid fa-circle-question"></a>
667667 </div>
668668 <div class="wide100p">
669669 <select id="openrouter_middleout" class="text_pole">
@@ -3301,16 +3301,16 @@
33013301 <span id="vertexai_service_account_info"></span>
33023302 </div>
33033303 <textarea id="vertexai_service_account_json" class="text_pole textarea_compact" rows="4" placeholder='Paste your Service Account JSON content here, e.g.:
33043304 {
33053305 "type": "service_account",
33063306 "project_id": "your-project-id",
33073307 "private_key_id": "...",
33083308 "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
33093309 "client_email": "...",
33103310 "client_id": "...",
33113311 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
33123312 "token_uri": "https://oauth2.googleapis.com/token"
33133313 }'></textarea>
33143314 <div data-for="vertexai_service_account_json" class="neutral_warning" data-i18n="For privacy reasons, your Service Account JSON content will be hidden after you reload the page.">
33153315 For privacy reasons, your Service Account JSON content will be hidden after you reload the page.
33163316 </div>
@@ -3827,7 +3827,7 @@
38273827 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
38283828 <small><i class="fa-solid fa-link menu_button margin0"></i></small>
38293829 </label>
38303830 <label id="instruct_enabled_label" for="instruct_enabled" class="checkbox_label flex1" title="Enable Instruct Mode" data-i18n="[title]instruct_enabled">
38313831 <input id="instruct_enabled" type="checkbox" style="display:none;" />
38323832 <small><i class="fa-solid fa-power-off menu_button togglable margin0"></i></small>
38333833 </label>
@@ -5286,7 +5286,7 @@
52865286 <div class="flex-container wide100p alignitemscenter spaceBetween flexNoGap">
52875287 <div class="flex-container alignItemsBaseline wide100p">
52885288 <div class="flex1 flex-container alignItemsBaseline">
52895289 <h3 class="margin0" >
52905290 <span data-i18n="Persona Management">Persona Management</span>
52915291 <a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank">
52925292 <span class="fa-solid fa-circle-question note-link-span"></span>
@@ -5457,10 +5457,12 @@
54575457 <div class="right_menu_button fa-solid fa-list-ul" id="rm_button_characters" title="Select/Create Characters" data-i18n="[title]Select/Create Characters"></div>
54585458 </div>
54595459 <div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p">
54605460 <div class="hotswap avatars_inline scroll-reset-container expander" data-i18n="[no_favs]Favorite characters to add them to HotSwaps" no_favs="Favorite characters to add them to HotSwaps"></div>
5461+ </div>
5462+ <hr class="wide100p">
54615463 </div>
54625464 </div>
5463- <hr>
5465+
54645466 <!-- this div structure must be preserved until group peeking can adjust -->
54655467 <div id="rm_PinAndTabs">
54665468 <div id="right-nav-panel-tabs" class="">
@@ -5485,340 +5487,343 @@
54855487 </div>
54865488 </div>
54875489 <!-- end group peeking cope structure-->
5488- <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;">
5490+
5489- <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data">
5491+ <div class="scrollableInner">
5490- <div id="avatar-and-name-block">
5492+ <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;">
5491- <div id="name_div">
5493+ <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data">
5492- <input id="character_name_pole" name="ch_name" class="text_pole" data-i18n="[placeholder]Name this character" placeholder="Name this character" value="" autocomplete="off">
5494+ <div id="avatar-and-name-block">
54935495 <div classid="extension_token_countername_div">
5494- <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="character_name_pole" data-token-permanent="true">counting...</span>
5496+ <input id="character_name_pole" name="ch_name" class="text_pole" data-i18n="[placeholder]Name this character" placeholder="Name this character" value="" autocomplete="off">
5495- </div>
5497+ <div class="extension_token_counter">
5496- </div>
5498+ <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="character_name_pole" data-token-permanent="true">counting...</span>
5497- <div class="flex-container flexFlowColumn expander flexNoGap">
5499+ </div>
5498- <div id="avatar_div" class="avatar_div buttons_block alignitemsflexstart justifySpaceBetween flexnowrap">
5500+ </div>
5499- <label id="avatar_div_div" class="add_avatar avatar" for="add_avatar_button" title="Click to select a new avatar for this character" data-i18n="[title]Click to select a new avatar for this character">
5501+ <div class="flex-container flexFlowColumn expander flexNoGap">
5500- <img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
5502+ <div id="avatar_div" class="avatar_div buttons_block alignitemsflexstart justifySpaceBetween flexnowrap">
5501- <input hidden type="file" id="add_avatar_button" name="avatar" accept="image/*">
5503+ <label id="avatar_div_div" class="add_avatar avatar" for="add_avatar_button" title="Click to select a new avatar for this character" data-i18n="[title]Click to select a new avatar for this character">
5502- </label>
5504+ <img id="avatar_load_preview" src="img/ai4.png" alt="avatar">
5503- <div class="flex-container" id="avatar_controls">
5505+ <input hidden type="file" id="add_avatar_button" name="avatar" accept="image/*">
5504- <div class="form_create_bottom_buttons_block buttons_block">
5506+ </label>
5505- <div id="rm_button_back" class="menu_button fa-solid fa-left-long "></div>
5507+ <div class="flex-container" id="avatar_controls">
5506- <!-- <div id="renameCharButton" class="menu_button fa-solid fa-user-pen" title="Rename Character"></div> -->
5508+ <div class="form_create_bottom_buttons_block buttons_block">
55075509 <div id="favorite_buttonrm_button_back" class="menu_button fa-solid fa-star" title="Add to Favorites" dataleft-i18n="[title]Add tolong Favorites"></div>
5508- <input type="hidden" id="fav_checkbox" name="fav" />
5510+ <!-- <div id="renameCharButton" class="menu_button fa-solid fa-user-pen" title="Rename Character"></div> -->
55095511 <div id="advanced_divfavorite_button" class="menu_button fa-solid fa-book star" title="AdvancedAdd Definitionsto Favorites" data-i18n="[title]AdvancedAdd Definitionto Favorites"></div>
5510- <div id="world_button" class="menu_button fa-solid fa-globe" title="Character Lore&#10;&#10;Click to load&#10;Shift-click to open 'Link to World Info' popup" data-i18n="[title]world_button_title"></div>
5512+ <input type="hidden" id="fav_checkbox" name="fav" />
55115513 <div classid="chat_lorebook_buttonadvanced_div" class="menu_button fa-solid fa-passportbook " title="Chat Lore&#10;Alt+Click to open theAdvanced lorebookDefinitions" data-i18n="[title]Chat Lore Alt+Click to open theAdvanced lorebookDefinition"></div>
55125514 <div id="char_connections_buttonworld_button" class="menu_button fa-solid fa-face-smileglobe" title="ConnectedCharacter PersonasLore&#10;&#10;Click to load&#10;Shift-click to open 'Link to World Info' popup" data-i18n="[title]Connected Personasworld_button_title"></div>
55135515 <div id="export_button" class="chat_lorebook_button menu_button fa-solid fa-file-export passport" title="ExportChat andLore&#10;Alt+Click Downloadto open the lorebook" data-i18n="[title]ExportChat andLore DownloadAlt+Click to open the lorebook"></div>
55145516 <!-- <div id="set_chat_scenariochar_connections_button" class="menu_button fa-solid fa-scrollface-smile" title="Set aConnected chatPersonas" scenariodata-i18n="[title]Connected overridePersonas"></div> -->
55155517 <!-- <div id="set_character_worldexport_button" class="menu_button fa-solid fa-globefile-export " title="Set aExport characterand WorldDownload" Infodata-i18n="[title]Export /and LorebookDownload"></div> -->
55165518 <!-- <div id="dupe_buttonset_chat_scenario" class="menu_button fa-solid fa-clone scroll" title="DuplicateSet Character"a data-i18n="[title]Duplicatechat Characterscenario override"></div> -->
55175519 <label!-- for="create_button"<div id="create_button_labelset_character_world" class="menu_button fa-solid fa-user-checkglobe" title="CreateSet Character"a data-i18n="[title]Createcharacter CharacterWorld Info / Lorebook"></div> -->
5518- <input type="submit" id="create_button" name="create_button">
5520+ <div id="dupe_button" class="menu_button fa-solid fa-clone " title="Duplicate Character" data-i18n="[title]Duplicate Character"></div>
5519- </label>
5521+ <label for="create_button" id="create_button_label" class="menu_button fa-solid fa-user-check" title="Create Character" data-i18n="[title]Create Character">
5520- <div id="delete_button" class="menu_button fa-solid fa-skull red_button" title="Delete Character" data-i18n="[title]Delete Character"></div>
5522+ <input type="submit" id="create_button" name="create_button">
55215523 </divlabel>
5522- <label class="flex1 height100p" for="char-management-dropdown">
5524+ <div id="delete_button" class="menu_button fa-solid fa-skull red_button" title="Delete Character" data-i18n="[title]Delete Character"></div>
5523- <select id="char-management-dropdown" class="text_pole">
5525+ </div>
5524- <option value="default" disabled selected data-i18n="More...">More...</option>
5526+ <label class="flex1 height100p" for="char-management-dropdown">
55255527 <optionselect id="set_character_worldchar-management-dropdown" data-i18nclass="Link to World Infotext_pole">
5526- Link to World Info
5528+ <option value="default" disabled selected data-i18n="More...">More...</option>
5527- </option>
5529+ <option id="set_character_world" data-i18n="Link to World Info">
5528- <option id="import_character_info" data-i18n="Import Card Lore">
5530+ Link to World Info
5529- Import Card Lore
5530- </option>
5531- <option id="set_chat_scenario" data-i18n="Scenario Override">
5532- Scenario Override
5533- </option>
5534- <option id="convert_to_persona" data-i18n="Convert to Persona">
5535- Convert to Persona
5536- </option>
5537- <option id="renameCharButton" data-i18n="Rename">
5538- Rename
5539- </option>
5540- <option id="character_source" data-i18n="Link to Source">
5541- Link to Source
5542- </option>
5543- <option id="replace_update" data-i18n="Replace / Update">
5544- Replace / Update
5545- </option>
5546- <option id="import_tags" data-i18n="Import Tags">
5547- Import Tags
5548- </option>
5549- <option id="set_as_assistant" data-i18n="Set / Unset as Welcome Page Assistant">
5550- Set / Unset as Welcome Page Assistant
5551- </option>
5552- <!--<option id="dupe_button">
5553- Duplicate
55545531 </option>
55555532 <option id="export_buttonimport_character_info" data-i18n="Import Card Lore">
5556- Export
5533+ Import Card Lore
55575534 </option>
55585535 <option id="delete_buttonset_chat_scenario" data-i18n="Scenario Override">
5559- Delete
5536+ Scenario Override
55605537 </option>-->
5561- </select>
5538+ <option id="convert_to_persona" data-i18n="Convert to Persona">
5562- </label>
5539+ Convert to Persona
5540+ </option>
5541+ <option id="renameCharButton" data-i18n="Rename">
5542+ Rename
5543+ </option>
5544+ <option id="character_source" data-i18n="Link to Source">
5545+ Link to Source
5546+ </option>
5547+ <option id="replace_update" data-i18n="Replace / Update">
5548+ Replace / Update
5549+ </option>
5550+ <option id="import_tags" data-i18n="Import Tags">
5551+ Import Tags
5552+ </option>
5553+ <option id="set_as_assistant" data-i18n="Set / Unset as Welcome Page Assistant">
5554+ Set / Unset as Welcome Page Assistant
5555+ </option>
5556+ <!--<option id="dupe_button">
5557+ Duplicate
5558+ </option>
5559+ <option id="export_button">
5560+ Export
5561+ </option>
5562+ <option id="delete_button">
5563+ Delete
5564+ </option>-->
5565+ </select>
5566+ </label>
5567+ </div>
55635568 </div>
5564- </div>
5569+ <div id="tags_div">
55655570 <div idclass="tags_divtag_controls">
5566- <div class="tag_controls">
5571+ <input id="tagInput" class="text_pole textarea_compact tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5567- <input id="tagInput" class="text_pole textarea_compact tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5572+ <div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div>
5568- <div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div>
5573+ </div>
5574+ <div id="tagList" class="tags"></div>
55695575 </div>
5570- <div id="tagList" class="tags"></div>
55715576 </div>
55725577 </div>
5573- </div>
5574- <hr>
5575- <div id="spoiler_free_desc" class="flex-container flexFlowColumn flex1 flexNoGap">
5576- <div id="creators_notes_div" class="title_restorable flexGap5">
5577- <span class="flex1" data-i18n="Creator's Notes">Creator's Notes</span>
5578- <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small>
5579- <div id="creators_note_styles_button" class="margin0 menu_button fa-solid fa-palette fa-fw" title="Allow / Forbid the use of global styles for this character." data-i18n="[title]Allow / Forbid the use of global styles for this character."></div>
5580- <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div>
5581- </div>
5582- <div id="creator_notes_spoiler" class="flex1"></div>
5583- <!-- A button to show / hide description_div and description_textarea and first_message_div and firstmessage_textarea-->
5584- </div>
5585- <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
55865578 <hr>
55875579 <div id="description_divspoiler_free_desc" class="title_restorableflex-container flexFlowColumn flex1 flexNoGap">
55885580 <div id="creators_notes_div" class="flex-containertitle_restorable alignitemscenterflexGap5">
55895581 <span data-i18nclass="Character Descriptionflex1" classdata-i18n="mdhotkey_locationCreator's Notes">DescriptionCreator's Notes</span>
5590- <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
5582+ <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small>
5591- <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank">
5583+ <div id="creators_note_styles_button" class="margin0 menu_button fa-solid fa-palette fa-fw" title="Allow / Forbid the use of global styles for this character." data-i18n="[title]Allow / Forbid the use of global styles for this character."></div>
5592- <span class="fa-solid fa-circle-question note-link-span"></span>
5584+ <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div>
5593- </a>
55945585 </div>
5595- <div id="character_open_media_overrides" class="menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this character." data-i18n="[title]Click to allow/forbid the use of external media for this character.">
5586+ <div id="creator_notes_spoiler" class="flex1"></div>
5596- <i id="character_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5587+ <!-- A button to show / hide description_div and description_textarea and first_message_div and firstmessage_textarea-->
5597- <i id="character_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5598- <span data-i18n="Ext. Media">
5599- Ext. Media
5600- </span>
5601- </div>
5602- </div>
5603- <textarea id="description_textarea" class="mdHotkeys" data-i18n="[placeholder]Describe your character's physical and mental traits here." placeholder="Describe your character's physical and mental traits here." name="description" placeholder=""></textarea>
5604- <div class="extension_token_counter">
5605- <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span>
56065588 </div>
5607- </div>
5589+ <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
5608- <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1">
5590+ <hr>
56095591 <div id="first_message_divdescription_div" class="title_restorable">
56105592 <div class="flex-container alignitemscenter flex1">
56115593 <span data-i18n="FirstCharacter messageDescription" class="mdhotkey_location">First messageDescription</span>
56125594 <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textareadescription_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
56135595 <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#firstcharacter-messagedescription" class="notes-link" target="_blank">
56145596 <span class="fa-solid fa-circle-question note-link-span"></span>
56155597 </a>
5598+ </div>
5599+ <div id="character_open_media_overrides" class="menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this character." data-i18n="[title]Click to allow/forbid the use of external media for this character.">
5600+ <i id="character_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5601+ <i id="character_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5602+ <span data-i18n="Ext. Media">
5603+ Ext. Media
5604+ </span>
5605+ </div>
56165606 </div>
5617- <div class="menu_button menu_button_icon open_alternate_greetings margin0" title="Click to set additional greeting messages" data-i18n="[title]Click to set additional greeting messages">
5607+ <textarea id="description_textarea" class="mdHotkeys" data-i18n="[placeholder]Describe your character's physical and mental traits here." placeholder="Describe your character's physical and mental traits here." name="description" placeholder=""></textarea>
5618- <span data-i18n="Alt. Greetings">
5608+ <div class="extension_token_counter">
5619- Alt. Greetings
5609+ <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span>
5620- </span>
56215610 </div>
56225611 </div>
5623- <textarea class="mdHotkeys" id="firstmessage_textarea" data-i18n="[placeholder]This will be the first message from the character that starts every chat." placeholder="This will be the first message from the character that starts every chat." name="first_mes" placeholder=""></textarea>
5612+ <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1">
56245613 <div id="first_message_div" class="extension_token_countertitle_restorable">
5625- <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="firstmessage_textarea">counting...</span>
5614+ <div class="flex-container alignitemscenter flex1">
5626- </div>
5615+ <span data-i18n="First message" class="mdhotkey_location">First message</span>
5627- </div>
5616+ <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
5628- <!-- these divs are invisible and used for server communication purposes -->
5617+ <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#first-message" class="notes-link" target="_blank">
5629- <div id="hidden-divs">
5618+ <span class="fa-solid fa-circle-question note-link-span"></span>
5630- <input id="character_json_data" name="json_data" type="hidden">
5619+ </a>
5631- <input id="avatar_url_pole" name="avatar_url" type="hidden">
5620+ </div>
5632- <input id="selected_chat_pole" name="chat" type="hidden">
5621+ <div class="menu_button menu_button_icon open_alternate_greetings margin0" title="Click to set additional greeting messages" data-i18n="[title]Click to set additional greeting messages">
5633- <input id="create_date_pole" name="create_date" type="hidden">
5622+ <span data-i18n="Alt. Greetings">
5634- <input id="last_mes_pole" name="last_mes" type="hidden">
5623+ Alt. Greetings
5635- <input id="character_world" name="world" type="hidden">
5624+ </span>
5636- </div>
5637- <!-- now back to normal divs for display purposes-->
5638- </form>
5639- </div>
5640- <div name="Group Chat Edit Panel" id="rm_group_chats_block" class="right_menu flex-container flexNoGap">
5641- <div class="inline-drawer wide100p flexFlowColumn">
5642- <div id="groupControlsToggle" class="inline-drawer-toggle inline-drawer-header">
5643- <span>
5644- <span data-i18n="Group Controls">Group Controls</span>
5645- <a href="https://docs.sillytavern.app/usage/core-concepts/groupchats/" class="notes-link" target="_blank">
5646- <span class="fa-solid fa-circle-question note-link-span"></span>
5647- </a>
5648- </span>
5649- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5650- </div>
5651- <div class="inline-drawer-content">
5652- <div id="group-metadata-controls" class="marginTopBot5">
5653- <div class="flex-container wide100p">
5654- <input id="rm_group_chat_name" class="text_pole flex1" type="text" name="chat_name" data-i18n="[placeholder]Chat Name (Optional)" placeholder="Chat Name (Optional)" />
5655- <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div>
5656- </div>
5657- <div id="group_tags_div" class="wide100p">
5658- <div class="tag_controls">
5659- <input id="groupTagInput" class="text_pole textarea_compact tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5660- <div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div>
56615625 </div>
5662- <div id="groupTagList" class="tags paddingTopBot5"></div>
56635626 </div>
5664- <div id="rm_group_top_bar" class="flex-container alignitemscenter spaceBetween width100p fontsize80p">
5627+ <textarea class="mdHotkeys" id="firstmessage_textarea" data-i18n="[placeholder]This will be the first message from the character that starts every chat." placeholder="This will be the first message from the character that starts every chat." name="first_mes" placeholder=""></textarea>
5665- <div>
5628+ <div class="extension_token_counter">
5666- <label class="add_avatar avatar flex-container justifyCenter" for="group_avatar_button" title="Click to select a new avatar for this group" data-i18n="[title]Click to select a new avatar for this group">
5629+ <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="firstmessage_textarea">counting...</span>
5667- <div id="group_avatar_preview">
5630+ </div>
5668- <div class="avatar">
5631+ </div>
5669- <img src="img/ai4.png" alt="avatar">
5632+ <!-- these divs are invisible and used for server communication purposes -->
5670- </div>
5633+ <div id="hidden-divs">
5671- </div>
5634+ <input id="character_json_data" name="json_data" type="hidden">
5672- <input hidden type="file" id="group_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
5635+ <input id="avatar_url_pole" name="avatar_url" type="hidden">
5673- </label>
5636+ <input id="selected_chat_pole" name="chat" type="hidden">
5637+ <input id="create_date_pole" name="create_date" type="hidden">
5638+ <input id="last_mes_pole" name="last_mes" type="hidden">
5639+ <input id="character_world" name="world" type="hidden">
5640+ </div>
5641+ <!-- now back to normal divs for display purposes-->
5642+ </form>
5643+ </div>
5644+ <div name="Group Chat Edit Panel" id="rm_group_chats_block" class="right_menu flex-container flexNoGap">
5645+ <div class="inline-drawer wide100p flexFlowColumn">
5646+ <div id="groupControlsToggle" class="inline-drawer-toggle inline-drawer-header">
5647+ <span>
5648+ <span data-i18n="Group Controls">Group Controls</span>
5649+ <a href="https://docs.sillytavern.app/usage/core-concepts/groupchats/" class="notes-link" target="_blank">
5650+ <span class="fa-solid fa-circle-question note-link-span"></span>
5651+ </a>
5652+ </span>
5653+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5654+ </div>
5655+ <div class="inline-drawer-content">
5656+ <div id="group-metadata-controls" class="marginTopBot5">
5657+ <div class="flex-container wide100p">
5658+ <input id="rm_group_chat_name" class="text_pole flex1" type="text" name="chat_name" data-i18n="[placeholder]Chat Name (Optional)" placeholder="Chat Name (Optional)" />
5659+ <div class="chat_lorebook_button menu_button fa-solid fa-passport" title="Chat Lore" data-i18n="[title]Chat Lore"></div>
56745660 </div>
56755661 <div name="GroupStragegyAndOrder" id="rm_group_buttonsgroup_tags_div" class="flex-container paddingLeftRight5 flex2wide100p">
56765662 <div class="flex1 flexGap5tag_controls">
5677- <label for="rm_group_activation_strategy" class="flexnowrap width100p whitespacenowrap">
5663+ <input id="groupTagInput" class="text_pole textarea_compact tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" />
5678- <span data-i18n="Group reply strategy">Group reply strategy</span>
5664+ <div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div>
5679- </label>
5680- <select id="rm_group_activation_strategy">
5681- <option value="2" data-i18n="Manual">Manual</option>
5682- <option value="0" data-i18n="Natural order">Natural order</option>
5683- <option value="1" data-i18n="List order">List order</option>
5684- <option value="3" data-i18n="Pooled order">Pooled order</option>
5685- </select>
5686- </div>
5687- <div class="flex1 flexGap5">
5688- <label for="rm_group_generation_mode" class="flexnowrap width100p whitespacenowrap">
5689- <span data-i18n="Group generation handling mode">Group generation handling mode</span>
5690- </label>
5691- <select id="rm_group_generation_mode">
5692- <option value="0" data-i18n="Swap character cards">Swap character cards</option>
5693- <option value="1" data-i18n="Join character cards (exclude muted)">Join character cards (exclude muted)</option>
5694- <option value="2" data-i18n="Join character cards (include muted)">Join character cards (include muted)</option>
5695- </select>
5696- </div>
5697- <div class="flex1 flexGap5" title="Inserted before each part of the joined fields." data-i18n="[title]Inserted before each part of the joined fields.">
5698- <label for="rm_group_generation_mode_join_prefix" class="flexnowrap width100p whitespacenowrap">
5699- <span data-i18n="Join Prefix">Join Prefix</span>
5700- <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
5701- </div>
5702- </label>
5703- <textarea id="rm_group_generation_mode_join_prefix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
57045665 </div>
5705- <div class="flex1 flexGap5" title="Inserted after each part of the joined fields." data-i18n="[title]Inserted after each part of the joined fields.">
5666+ <div id="groupTagList" class="tags paddingTopBot5"></div>
5706- <label for="rm_group_generation_mode_join_suffix" class="flexnowrap width100p whitespacenowrap">
5667+ </div>
5707- <span data-i18n="Join Suffix">Join Suffix</span>
5668+ <div id="rm_group_top_bar" class="flex-container alignitemscenter spaceBetween width100p fontsize80p">
5708- <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
5669+ <div>
5670+ <label class="add_avatar avatar flex-container justifyCenter" for="group_avatar_button" title="Click to select a new avatar for this group" data-i18n="[title]Click to select a new avatar for this group">
5671+ <div id="group_avatar_preview">
5672+ <div class="avatar">
5673+ <img src="img/ai4.png" alt="avatar">
5674+ </div>
57095675 </div>
5676+ <input hidden type="file" id="group_avatar_button" name="avatar" accept="image/png, image/jpeg, image/jpg, image/gif, image/bmp">
57105677 </label>
5711- <textarea id="rm_group_generation_mode_join_suffix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
57125678 </div>
5713- </div>
5679+ <div name="GroupStragegyAndOrder" id="rm_group_buttons" class="flex-container paddingLeftRight5 flex2">
57145680 <div id="GroupFavDelOkBack" class="flex-containerflex1 flexGap5 spaceEvenly flex1">
5715- <div id="rm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-left-long"></div>
5681+ <label for="rm_group_activation_strategy" class="flexnowrap width100p whitespacenowrap">
5716- <div id="rm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-scroll" title="Set a group chat scenario" data-i18n="[title]Set a group chat scenario"></div>
5682+ <span data-i18n="Group reply strategy">Group reply strategy</span>
5717- <div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
5683+ </label>
57185684 <inputselect id="rm_group_fav" type="hiddenrm_group_activation_strategy" />
5719- <div id="group_open_media_overrides" class="heightFitContent margin0 menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this group." data-i18n="[title]Click to allow/forbid the use of external media for this group.">
5685+ <option value="2" data-i18n="Manual">Manual</option>
5720- <i id="group_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5686+ <option value="0" data-i18n="Natural order">Natural order</option>
5721- <i id="group_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5687+ <option value="1" data-i18n="List order">List order</option>
5688+ <option value="3" data-i18n="Pooled order">Pooled order</option>
5689+ </select>
5690+ </div>
5691+ <div class="flex1 flexGap5">
5692+ <label for="rm_group_generation_mode" class="flexnowrap width100p whitespacenowrap">
5693+ <span data-i18n="Group generation handling mode">Group generation handling mode</span>
5694+ </label>
5695+ <select id="rm_group_generation_mode">
5696+ <option value="0" data-i18n="Swap character cards">Swap character cards</option>
5697+ <option value="1" data-i18n="Join character cards (exclude muted)">Join character cards (exclude muted)</option>
5698+ <option value="2" data-i18n="Join character cards (include muted)">Join character cards (include muted)</option>
5699+ </select>
5700+ </div>
5701+ <div class="flex1 flexGap5" title="Inserted before each part of the joined fields." data-i18n="[title]Inserted before each part of the joined fields.">
5702+ <label for="rm_group_generation_mode_join_prefix" class="flexnowrap width100p whitespacenowrap">
5703+ <span data-i18n="Join Prefix">Join Prefix</span>
5704+ <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
5705+ </div>
5706+ </label>
5707+ <textarea id="rm_group_generation_mode_join_prefix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
5708+ </div>
5709+ <div class="flex1 flexGap5" title="Inserted after each part of the joined fields." data-i18n="[title]Inserted after each part of the joined fields.">
5710+ <label for="rm_group_generation_mode_join_suffix" class="flexnowrap width100p whitespacenowrap">
5711+ <span data-i18n="Join Suffix">Join Suffix</span>
5712+ <div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]When 'Join character cards' is selected, all respective fields of the characters are being joined together.This means that in the story string for example all character descriptions will be joined to one big text.If you want those fields to be separated, you can define a prefix or suffix here.This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)" title="When 'Join character cards' is selected, all respective fields of the characters are being joined together.&#13;This means that in the story string for example all character descriptions will be joined to one big text.&#13;If you want those fields to be separated, you can define a prefix or suffix here.&#13;&#13;This value supports normal macros and will also replace {{char}} with the relevant char's name and &lt;FIELDNAME&gt; with the name of the part (e.g.: description, personality, scenario, etc.)">
5713+ </div>
5714+ </label>
5715+ <textarea id="rm_group_generation_mode_join_suffix" class="text_pole wide100p textarea_compact autoSetHeight" placeholder="&mdash;" rows="1"></textarea>
5716+ </div>
57225717 </div>
5723- <div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-check" title="Create" data-i18n="[title]Create"></div>
5718+ <div id="GroupFavDelOkBack" class="flex-container flexGap5 spaceEvenly flex1">
57245719 <div id="rm_group_restore_avatarrm_button_back_from_group" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-images" title="Restore collage avatar" dataleft-i18n="[title]Restore collage avatarlong"></div>
57255720 <div id="rm_group_deleterm_group_scenario" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-trash-canscroll" title="DeleteSet a group chat scenario" data-i18n="[title]DeleteSet a group chat scenario"></div>
5726- <div class="flex1">
5721+ <div id="group_favorite_button" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-star" title="Add to Favorites" data-i18n="[title]Add to Favorites"></div>
57275722 <labelinput classid="checkbox_labelrm_group_fav" whitespacenowraptype="hidden" />
5728- <input id="rm_group_allow_self_responses" type="checkbox" />
5723+ <div id="group_open_media_overrides" class="heightFitContent margin0 menu_button menu_button_icon open_media_overrides" title="Click to allow/forbid the use of external media for this group." data-i18n="[title]Click to allow/forbid the use of external media for this group.">
5729- <span data-i18n="Allow self responses">Allow self responses</span>
5724+ <i id="group_media_allowed_icon" class="fa-solid fa-fw fa-link"></i>
5730- </label>
5725+ <i id="group_media_forbidden_icon" class="fa-solid fa-fw fa-link-slash"></i>
5731- <label id="rm_group_automode_label" class="checkbox_label whitespacenowrap">
5726+ </div>
5732- <input id="rm_group_automode" type="checkbox" />
5727+ <div id="rm_group_submit" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-check" title="Create" data-i18n="[title]Create"></div>
5733- <span data-i18n="Auto Mode">Auto Mode</span>
5728+ <div id="rm_group_restore_avatar" class="heightFitContent margin0 menu_button fa-solid fa-fw fa-images" title="Restore collage avatar" data-i18n="[title]Restore collage avatar"></div>
57345729 <inputdiv id="rm_group_automode_delayrm_group_delete" class="text_pole textarea_compact widthUnset"heightFitContent type="number"margin0 min="1"menu_button max="999"fa-solid step="1"fa-fw value="5fa-trash-can" title="Auto Mode delayDelete" data-i18n="[title]Auto Mode delayDelete" ></div>
5735- </label>
5730+ <div class="flex1">
57365731 <label id="rm_group_hidemutedsprites_label" class="checkbox_label whitespacenowrap">
57375732 <input id="rm_group_hidemutedspritesrm_group_allow_self_responses" type="checkbox" />
57385733 <span data-i18n="Hide MutedAllow Memberself Spritesresponses">Hide MutedAllow Memberself Spritesresponses</span>
57395734 </label>
5735+ <label id="rm_group_automode_label" class="checkbox_label whitespacenowrap">
5736+ <input id="rm_group_automode" type="checkbox" />
5737+ <span data-i18n="Auto Mode">Auto Mode</span>
5738+ <input id="rm_group_automode_delay" class="text_pole textarea_compact widthUnset" type="number" min="1" max="999" step="1" value="5" title="Auto Mode delay" data-i18n="[title]Auto Mode delay" />
5739+ </label>
5740+ <label id="rm_group_hidemutedsprites_label" class="checkbox_label whitespacenowrap">
5741+ <input id="rm_group_hidemutedsprites" type="checkbox" />
5742+ <span data-i18n="Hide Muted Member Sprites">Hide Muted Member Sprites</span>
5743+ </label>
5744+ </div>
57405745 </div>
57415746 </div>
57425747 </div>
57435748 </div>
57445749 </div>
5745- </div>
5750+ <div class="inline-drawer wide100p flexFlowColumn">
57465751 <div id="groupCurrentMemberListToggle" class="inline-drawer wide100p-toggle flexFlowColumninline-drawer-header">
5747- <div id="groupCurrentMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
5752+ <span data-i18n="Current Members">Current Members </span><i id="groupCurrentMemberPopoutButton" class="fa-solid fa-window-restore menu_button"></i>
5748- <span data-i18n="Current Members">Current Members </span><i id="groupCurrentMemberPopoutButton" class="fa-solid fa-window-restore menu_button"></i>
5753+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5749- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5750- </div>
5751- <div class="inline-drawer-content">
5752- <div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
5753- <div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div>
5754- <div id="rm_group_members" class="rm_group_members overflowYAuto flex-container" group_empty_text="Group is empty." data-i18n="[group_empty_text]Group is empty."></div>
57555754 </div>
5756- </div>
5755+ <div class="inline-drawer-content">
5757- </div>
5756+ <div id="currentGroupMembers" name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
5758- <div class="inline-drawer wide100p flexFlowColumn">
5757+ <div id="rm_group_members_pagination" class="rm_group_members_pagination group_pagination"></div>
5759- <div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
5758+ <div id="rm_group_members" class="rm_group_members overflowYAuto flex-container" group_empty_text="Group is empty." data-i18n="[group_empty_text]Group is empty."></div>
5760- <span data-i18n="Add Members">Add Members</span>
5761- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5762- </div>
5763- <div class="inline-drawer-content">
5764- <div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
5765- <div id="rm_group_add_members_header">
5766- <input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
57675759 </div>
5768- <div class="rm_tag_controls">
5760+ </div>
5769- <div class="tags rm_tag_filter"></div>
5761+ </div>
5762+ <div class="inline-drawer wide100p flexFlowColumn">
5763+ <div id="groupAddMemberListToggle" class="inline-drawer-toggle inline-drawer-header">
5764+ <span data-i18n="Add Members">Add Members</span>
5765+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
5766+ </div>
5767+ <div class="inline-drawer-content">
5768+ <div name="Unadded Char List" class="flex-container flexFlowColumn overflowYAuto flex1">
5769+ <div id="rm_group_add_members_header">
5770+ <input id="rm_group_filter" class="text_pole margin0" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
5771+ </div>
5772+ <div class="rm_tag_controls">
5773+ <div class="tags rm_tag_filter"></div>
5774+ </div>
5775+ <div id="rm_group_add_members_pagination" class="group_pagination"></div>
5776+ <div id="rm_group_add_members" class="overflowYAuto flex-container" no_characters_text="No characters available" data-i18n="[no_characters_text]No characters available"></div>
57705777 </div>
5771- <div id="rm_group_add_members_pagination" class="group_pagination"></div>
5772- <div id="rm_group_add_members" class="overflowYAuto flex-container" no_characters_text="No characters available" data-i18n="[no_characters_text]No characters available"></div>
57735778 </div>
57745779 </div>
57755780 </div>
5776- </div>
5781+ <div id="rm_character_import" class="right_menu" style="display: none;">
57775782 <divform id="rm_character_importform_import" classaction="right_menujavascript:void(null);" stylemethod="display:post" none;enctype="multipart/form-data">
5778- <form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data">
5783+ <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" name="avatar">
5779- <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" name="avatar">
5784+ <input id="character_import_file_type" name="file_type" class="text_pole" value="" autocomplete="off">
5780- <input id="character_import_file_type" name="file_type" class="text_pole" value="" autocomplete="off">
5781- </form>
5782- <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx" name="replace_avatar" hidden>
5783- </div>
5784- <div name="Character List Panel" id="rm_characters_block" class="right_menu">
5785- <div id="charListFixedTop">
5786- <form id="form_character_search_form" action="javascript:void(null);">
5787- <div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus "></div>
5788- <div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import faSmallFontSquareFix"></div>
5789- <div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
5790- <div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
5791- <input id="character_search_bar" class="text_pole width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
5792- <select id="character_sort_order" title="Characters sorting order" data-i18n="[title]Characters sorting order">
5793- <option data-field="search" data-order="desc" data-i18n="Search" hidden>Search</option>
5794- <option data-field="name" data-order="asc" data-i18n="A-Z">A-Z</option>
5795- <option data-field="name" data-order="desc" data-i18n="Z-A">Z-A</option>
5796- <option data-field="create_date" data-order="desc" data-i18n="Newest">Newest</option>
5797- <option data-field="create_date" data-order="asc" data-i18n="Oldest">Oldest</option>
5798- <option data-field="fav" data-order="desc" data-rule="boolean" data-i18n="Favorites">Favorites</option>
5799- <option data-field="date_last_chat" data-order="desc" data-i18n="Recent">Recent</option>
5800- <option data-field="chat_size" data-order="desc" data-i18n="Most chats">Most chats</option>
5801- <option data-field="chat_size" data-order="asc" data-i18n="Least chats">Least chats</option>
5802- <option data-field="data_size" data-order="desc" data-i18n="Most tokens">Most tokens</option>
5803- <option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option>
5804- <option data-field="name" data-order="random" data-i18n="Random">Random</option>
5805- </select>
58065785 </form>
5807- <div class="rm_tag_controls">
5786+ <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx" name="replace_avatar" hidden>
5808- <div class="tags rm_tag_filter"></div>
5809- <div class="tags rm_tag_bogus_drilldown"></div>
5810- </div>
5811- <hr>
58125787 </div>
5788+ <div name="Character List Panel" id="rm_characters_block" class="right_menu">
5789+ <div id="charListFixedTop">
5790+ <form id="form_character_search_form" action="javascript:void(null);">
5791+ <div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus "></div>
5792+ <div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import faSmallFontSquareFix"></div>
5793+ <div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
5794+ <div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div>
5795+ <input id="character_search_bar" class="text_pole width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." />
5796+ <select id="character_sort_order" title="Characters sorting order" data-i18n="[title]Characters sorting order">
5797+ <option data-field="search" data-order="desc" data-i18n="Search" hidden>Search</option>
5798+ <option data-field="name" data-order="asc" data-i18n="A-Z">A-Z</option>
5799+ <option data-field="name" data-order="desc" data-i18n="Z-A">Z-A</option>
5800+ <option data-field="create_date" data-order="desc" data-i18n="Newest">Newest</option>
5801+ <option data-field="create_date" data-order="asc" data-i18n="Oldest">Oldest</option>
5802+ <option data-field="fav" data-order="desc" data-rule="boolean" data-i18n="Favorites">Favorites</option>
5803+ <option data-field="date_last_chat" data-order="desc" data-i18n="Recent">Recent</option>
5804+ <option data-field="chat_size" data-order="desc" data-i18n="Most chats">Most chats</option>
5805+ <option data-field="chat_size" data-order="asc" data-i18n="Least chats">Least chats</option>
5806+ <option data-field="data_size" data-order="desc" data-i18n="Most tokens">Most tokens</option>
5807+ <option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option>
5808+ <option data-field="name" data-order="random" data-i18n="Random">Random</option>
5809+ </select>
5810+ </form>
5811+ <div class="rm_tag_controls">
5812+ <div class="tags rm_tag_filter"></div>
5813+ <div class="tags rm_tag_bogus_drilldown"></div>
5814+ </div>
5815+ <hr>
5816+ </div>
58135817
58145818 <div id="rm_print_characters_pagination">
58155819 <i id="charListGridToggle" class="fa-solid fa-table-cells-large menu_button" title="Toggle character grid view" data-i18n="[title]Toggle character grid view"></i>
58165820 <i id="bulkEditButton" class="fa-solid fa-edit menu_button bulkEditButton" title="Bulk edit characters&#13;&#13;Click to toggle characters&#13;Shift + Click to select/deselect a range of characters&#13;Right-click for actions" data-i18n="[title]Bulk_edit_characters"></i>
58175821 <div id="bulkSelectedCount" class="bulkEditOptionElement paginationjs-nav"></div>
58185822 <i id="bulkSelectAllButton" class="fa-solid fa-check-double menu_button bulkEditOptionElement bulkSelectAllButton" title="Bulk select all characters" data-i18n="[title]Bulk select all characters" style="display: none;"></i>
58195823 <i id="bulkDeleteButton" class="fa-solid fa-trash menu_button bulkEditOptionElement bulkDeleteButton" title="Bulk delete characters" data-i18n="[title]Bulk delete characters" style="display: none;"></i>
5824+ </div>
5825+ <div id="rm_print_characters_block" class="flexFlowColumn"></div>
58205826 </div>
5821- <div id="rm_print_characters_block" class="flexFlowColumn"></div>
58225827 </div>
58235828 </nav>
58245829 </div>
@@ -6195,6 +6200,298 @@
61956200 </div>
61966201 </div>
61976202 <div id="entry_edit_template" class="template_element">
6203+ <div class="world_entry_edit">
6204+ <div class="flex-container wide100p alignitemscenter">
6205+ <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter">
6206+ <div class="world_entry_form_control keyprimary flex1">
6207+ <small class="displayNone">
6208+ <span data-i18n="Comma separated (required)">
6209+ Comma separated (required)
6210+ </span>
6211+ </small>
6212+ <small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small>
6213+ <select class="keyprimaryselect keyselect select2_multi_sameline" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select>
6214+ <textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" style="display: none;"></textarea>
6215+ <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6216+ ⌨️
6217+ </button>
6218+ </div>
6219+ <div class="world_entry_form_control">
6220+ <small class="textAlignCenter" data-i18n="Logic">Logic</small>
6221+ <select name="entryLogicType" class="text_pole widthFitContent margin0">
6222+ <option value="0" data-i18n="AND ANY">AND ANY</option>
6223+ <option value="3" data-i18n="AND ALL">AND ALL</option>
6224+ <option value="1" data-i18n="NOT ALL">NOT ALL</option>
6225+ <option value="2" data-i18n="NOT ANY">NOT ANY</option>
6226+ </select>
6227+ </div>
6228+ <div class="world_entry_form_control keysecondary flex1">
6229+ <small class="displayNone">
6230+ <span data-i18n="(ignored if empty)">
6231+ (ignored if empty)
6232+ </span>
6233+ </small>
6234+ <small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small>
6235+ <select class="keysecondaryselect keyselect select2_multi_sameline" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select>
6236+ <textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" style="display: none;"></textarea>
6237+ <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6238+ ⌨️
6239+ </button>
6240+ </div>
6241+ </div>
6242+ <div name="perEntryOverridesBlock" class="flex-container wide100p alignitemscenter">
6243+ <div class="world_entry_form_control flex1">
6244+ <small class="textAlignCenter" data-i18n="Scan Depth">Scan Depth</small>
6245+ <input class="text_pole margin0" name="scanDepth" type="number" placeholder="Use global setting" data-i18n="[placeholder]Use global setting" max="1000">
6246+ </div>
6247+ <div class="world_entry_form_control flex1">
6248+ <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
6249+ <select name="caseSensitive" class="text_pole widthNatural margin0">
6250+ <option value="null" data-i18n="Use global">Use global</option>
6251+ <option value="true" data-i18n="Yes">Yes</option>
6252+ <option value="false" data-i18n="No">No</option>
6253+ </select>
6254+ </div>
6255+ <div class="world_entry_form_control flex1">
6256+ <small class="textAlignCenter" data-i18n="Whole Words">Whole Words</small>
6257+ <select name="matchWholeWords" class="text_pole widthNatural margin0">
6258+ <option value="null" data-i18n="Use global">Use global</option>
6259+ <option value="true" data-i18n="Yes">Yes</option>
6260+ <option value="false" data-i18n="No">No</option>
6261+ </select>
6262+ </div>
6263+ <div class="world_entry_form_control flex1">
6264+ <small class="textAlignCenter" data-i18n="Group Scoring">Group Scoring</small>
6265+ <select name="useGroupScoring" class="text_pole widthNatural margin0">
6266+ <option value="null" data-i18n="Use global">Use global</option>
6267+ <option value="true" data-i18n="Yes">Yes</option>
6268+ <option value="false" data-i18n="No">No</option>
6269+ </select>
6270+ </div>
6271+ <div class="world_entry_form_control flex1" title="Can be used to automatically activate Quick Replies" data-i18n="[title]Can be used to automatically activate Quick Replies">
6272+ <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
6273+ <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
6274+ </div>
6275+ <div class="world_entry_form_control flex1" title="Defines delay levels for recursive scans.&#13;&#13;Initially, only the first level (smallest number) will match.&#13;Once no matches are found, the next level becomes eligible for matching.&#13;This repeats until all levels are checked.&#13;&#13;Tied to the &quot;Delay until recursion&quot; setting." data-i18n="[title]delay_until_recursion_level">
6276+ <small class="textAlignCenter">
6277+ <span data-i18n="Recursion Level">Recursion Level</span>
6278+ <div class="fa-solid fa-circle-info opacity50p"></div>
6279+ </small>
6280+ <input class="text_pole margin0" name="delayUntilRecursionLevel" type="text" placeholder="1">
6281+ </div>
6282+ </div>
6283+ <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
6284+ <div class="world_entry_form_control flex1">
6285+ <label for="content ">
6286+ <small>
6287+ <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">
6288+ <span class="alignitemscenter flex-container">
6289+ <span data-i18n="Content" class="mdhotkey_location">
6290+ Content
6291+ </span>
6292+ <i class="editor_maximize fa-solid fa-maximize right_menu_button" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
6293+ </span>
6294+ <span>
6295+ (<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;
6296+ <span class="world_entry_form_uid_value" data-first-run="true"></span>
6297+ </span>
6298+ <div>
6299+ <label class="checkbox flex-container alignitemscenter flexNoGap">
6300+ <input type="checkbox" name="excludeRecursion" />
6301+ <span data-i18n="Non-recursable (will not be activated by another)">
6302+ Non-recursable (will not be activated by another)
6303+ </span>
6304+ </label>
6305+ <label class="checkbox flex-container alignitemscenter flexNoGap">
6306+ <input type="checkbox" name="preventRecursion" />
6307+ <span data-i18n="Prevent further recursion (this entry will not activate others)">
6308+ Prevent further recursion (will not activate others)
6309+ </span>
6310+ </label>
6311+ <label class="checkbox flex-container alignitemscenter flexNoGap">
6312+ <input type="checkbox" name="delay_until_recursion" />
6313+ <span data-i18n="Delay until recursion (can only be activated on recursive checking)">
6314+ Delay until recursion (can only be activated on recursive checking)
6315+ </span>
6316+ </label>
6317+ </div>
6318+ </span>
6319+ </small>
6320+ <small class="displayNone">
6321+ <span data-i18n="What this keyword should mean to the AI, sent verbatim">
6322+ What this keyword should mean to the AI, sent verbatim
6323+ </span>
6324+ </small>
6325+ </label>
6326+ <textarea class="text_pole mdHotkeys" name="content" rows="8" data-i18n="[placeholder]What this keyword should mean to the AI, sent verbatim" placeholder="What this keyword should mean to the AI, sent verbatim"></textarea>
6327+ </div>
6328+ </div>
6329+ <div class="world_entry_thin_controls commentContainer">
6330+ </div>
6331+ </div>
6332+ <div class="flex-container wide100p flexGap10">
6333+ <div class="flex4 flex-container flexFlowColumn flexNoGap">
6334+ <div class="flex-container justifySpaceBetween">
6335+ <small for="group">
6336+ <span data-i18n="Inclusion Group">Inclusion Group</span>
6337+ <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">
6338+ <span class="fa-solid fa-circle-question note-link-span"></span>
6339+ </a>
6340+ </small>
6341+ <label class="checkbox_label flexNoGap margin-r5" for="groupOverride">
6342+ <input type="checkbox" name="groupOverride" />
6343+ <span>
6344+ <small title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.">
6345+ <span data-i18n="Prioritize">Prioritize</span>
6346+ <div class="fa-solid fa-circle-info opacity50p"></div>
6347+ </small>
6348+ </span>
6349+ </label>
6350+ </div>
6351+ <div class="range-block-range">
6352+ <input type="text" class="text_pole margin0" name="group" rows="1" data-i18n="[placeholder]Only one entry with the same label will be activated" placeholder="Only one entry with the same label will be activated">
6353+ </div>
6354+ </div>
6355+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]A relative likelihood of entry activation within the group" title="A relative likelihood of entry activation within the group">
6356+ <div class="flex-container justifySpaceBetween marginBot5">
6357+ <small for="groupWeight" data-i18n="Group Weight">
6358+ Group Weight
6359+ </small>
6360+ </div>
6361+ <div class="range-block-range">
6362+ <input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="1" max="999999">
6363+ </div>
6364+ </div>
6365+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Sticky entries will stay active for N messages after being triggered." title="Sticky entries will stay active for N messages after being triggered.">
6366+ <div class="flex-container justifySpaceBetween marginBot5">
6367+ <small class="flex-container alignItemsBaseline" for="sticky">
6368+ <span data-i18n="Sticky">
6369+ Sticky
6370+ </span>
6371+ <i class="fa-solid fa-comments fa-xs"></i>
6372+ </small>
6373+ </div>
6374+ <div class="range-block-range">
6375+ <input class="text_pole margin0" name="sticky" type="number" data-i18n="[placeholder]Non-sticky" placeholder="Non-sticky" min="0" max="999999">
6376+ </div>
6377+ </div>
6378+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a cooldown can't be activated N messages after being triggered." title="Entries with a cooldown can't be activated N messages after being triggered.">
6379+ <div class="flex-container justifySpaceBetween marginBot5">
6380+ <small class="flex-container alignItemsBaseline" for="cooldown">
6381+ <span data-i18n="Cooldown">
6382+ Cooldown
6383+ </span>
6384+ <i class="fa-solid fa-comments fa-xs"></i>
6385+ </small>
6386+ </div>
6387+ <div class="range-block-range">
6388+ <input class="text_pole margin0" name="cooldown" type="number" data-i18n="[placeholder]No cooldown" placeholder="No cooldown" min="0" max="999999">
6389+ </div>
6390+ </div>
6391+ <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a delay can't be activated until there are N messages present in the chat." title="Entries with a delay can't be activated until there are N messages present in the chat.">
6392+ <div class="flex-container justifySpaceBetween marginBot5">
6393+ <small class="flex-container alignItemsBaseline" for="delay">
6394+ <span data-i18n="Delay">
6395+ Delay
6396+ </span>
6397+ <i class="fa-solid fa-comments fa-xs"></i>
6398+ </small>
6399+ </div>
6400+ <div class="range-block-range">
6401+ <input class="text_pole margin0" name="delay" type="number" data-i18n="[placeholder]No delay" placeholder="No delay" min="0" max="999999">
6402+ </div>
6403+ </div>
6404+ </div>
6405+ <div class="flex-container wide100p flexGap10">
6406+ <div class="flex4 flex-container flexFlowColumn flexNoGap">
6407+ <div class="flex-container justifySpaceBetween">
6408+ <small for="characterFilter" data-i18n="Filter to Characters or Tags">
6409+ Filter to Characters or Tags
6410+ </small>
6411+ <label class="checkbox_label flexNoGap margin-r5" for="character_exclusion">
6412+ <input type="checkbox" name="character_exclusion" />
6413+ <span>
6414+ <small title="Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry" data-i18n="[title]Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry">
6415+ <span data-i18n="Exclude">Exclude</span>
6416+ <div class="fa-solid fa-circle-info opacity50p"></div>
6417+ </small>
6418+ </span>
6419+ </label>
6420+ </div>
6421+ <div class="range-block-range">
6422+ <select name="characterFilter" class="select2_multi_sameline" multiple>
6423+ <option value="" data-i18n="-- Characters not found --">
6424+ -- Characters not found --
6425+ </option>
6426+ </select>
6427+ </div>
6428+ </div>
6429+ </div>
6430+ <div name="WIEntryBottomControls" class="flex-container flex1 justifySpaceBetween world_entry_form_horizontal">
6431+ <div class="flex-container flexFlowColumn flexNoGap wi-enter-footer-text">
6432+ <label class="checkbox flex-container">
6433+ <input type="checkbox" name="selective" />
6434+ <span data-i18n="Selective">Selective</span>
6435+ </label>
6436+ <label class="checkbox flex-container">
6437+ <input type="checkbox" name="useProbability" />
6438+ <span data-i18n="Use Probability">Use Probability</span>
6439+ </label>
6440+ <label class="checkbox flex-container">
6441+ <input type="checkbox" name="addMemo">
6442+ <span data-i18n="Add Memo">Add Memo</span>
6443+ </label>
6444+ </div>
6445+ </div>
6446+ <div class="inline-drawer wide100p flexFlowColumn">
6447+ <div class="inline-drawer-toggle inline-drawer-header inline-drawer-header-pointer userSettingsInnerExpandable">
6448+ <strong data-i18n="Additional Matching Sources">Additional Matching Sources</strong>
6449+ <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6450+ </div>
6451+ <div class="inline-drawer-content flex-container flexFlowRow flexGap10 paddingBottom5px">
6452+ <small class="flex-container flex1 flexFlowColumn">
6453+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6454+ <input type="checkbox" name="matchCharacterDescription" />
6455+ <span data-i18n="Character Description">
6456+ Character Description
6457+ </span>
6458+ </label>
6459+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6460+ <input type="checkbox" name="matchCharacterPersonality" />
6461+ <span data-i18n="Character Personality">
6462+ Character Personality
6463+ </span>
6464+ </label>
6465+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6466+ <input type="checkbox" name="matchScenario" />
6467+ <span data-i18n="Scenario">
6468+ Scenario
6469+ </span>
6470+ </label>
6471+ </small>
6472+ <small class="flex-container flex1 flexFlowColumn">
6473+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6474+ <input type="checkbox" name="matchPersonaDescription" />
6475+ <span data-i18n="Persona Description">
6476+ Persona Description
6477+ </span>
6478+ </label>
6479+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6480+ <input type="checkbox" name="matchCharacterDepthPrompt" />
6481+ <span data-i18n="Character's Note">
6482+ Character's Note
6483+ </span>
6484+ </label>
6485+ <label class="checkbox flex-container alignItemsCenter flexNoGap">
6486+ <input type="checkbox" name="matchCreatorNotes" />
6487+ <span data-i18n="Creator's Notes">
6488+ Creator's Notes
6489+ </span>
6490+ </label>
6491+ </small>
6492+ </div>
6493+ </div>
6494+ </div>
61986495 <div class="world_entry">
61996496 <form class="world_entry_form wi-card-entry">
62006497 <div class="inline-drawer wide100p">
@@ -6267,297 +6564,8 @@
62676564 <i class="menu_button duplicate_entry_button fa-solid fa-paste" title="Duplicate world info entry" data-i18n="[title]Duplicate world info entry" type="submit" value=""></i>
62686565 <i class="menu_button delete_entry_button fa-solid fa-trash-can" title="Delete world info entry" data-i18n="[title]Delete world info entry" type="submit" value=""></i>
62696566 </div>
62706567 <div class="inline-drawer-content inline-drawer-outlet flex-container paddingBottom5px wide100p ">
6271- <div class="flex-container wide100p alignitemscenter">
6568+ <!-- Outlet for world_entry_edit -->
6272- <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter">
6273- <div class="world_entry_form_control keyprimary flex1">
6274- <small class="displayNone">
6275- <span data-i18n="Comma separated (required)">
6276- Comma separated (required)
6277- </span>
6278- </small>
6279- <small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small>
6280- <select class="keyprimaryselect keyselect select2_multi_sameline" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select>
6281- <textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" style="display: none;"></textarea>
6282- <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6283- ⌨️
6284- </button>
6285- </div>
6286- <div class="world_entry_form_control">
6287- <small class="textAlignCenter" data-i18n="Logic">Logic</small>
6288- <select name="entryLogicType" class="text_pole widthFitContent margin0">
6289- <option value="0" data-i18n="AND ANY">AND ANY</option>
6290- <option value="3" data-i18n="AND ALL">AND ALL</option>
6291- <option value="1" data-i18n="NOT ALL">NOT ALL</option>
6292- <option value="2" data-i18n="NOT ANY">NOT ANY</option>
6293- </select>
6294- </div>
6295- <div class="world_entry_form_control keysecondary flex1">
6296- <small class="displayNone">
6297- <span data-i18n="(ignored if empty)">
6298- (ignored if empty)
6299- </span>
6300- </small>
6301- <small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small>
6302- <select class="keysecondaryselect keyselect select2_multi_sameline" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select>
6303- <textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" style="display: none;"></textarea>
6304- <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
6305- ⌨️
6306- </button>
6307- </div>
6308- </div>
6309- <div name="perEntryOverridesBlock" class="flex-container wide100p alignitemscenter">
6310- <div class="world_entry_form_control flex1">
6311- <small class="textAlignCenter" data-i18n="Scan Depth">Scan Depth</small>
6312- <input class="text_pole margin0" name="scanDepth" type="number" placeholder="Use global setting" data-i18n="[placeholder]Use global setting" max="1000">
6313- </div>
6314- <div class="world_entry_form_control flex1">
6315- <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
6316- <select name="caseSensitive" class="text_pole widthNatural margin0">
6317- <option value="null" data-i18n="Use global">Use global</option>
6318- <option value="true" data-i18n="Yes">Yes</option>
6319- <option value="false" data-i18n="No">No</option>
6320- </select>
6321- </div>
6322- <div class="world_entry_form_control flex1">
6323- <small class="textAlignCenter" data-i18n="Whole Words">Whole Words</small>
6324- <select name="matchWholeWords" class="text_pole widthNatural margin0">
6325- <option value="null" data-i18n="Use global">Use global</option>
6326- <option value="true" data-i18n="Yes">Yes</option>
6327- <option value="false" data-i18n="No">No</option>
6328- </select>
6329- </div>
6330- <div class="world_entry_form_control flex1">
6331- <small class="textAlignCenter" data-i18n="Group Scoring">Group Scoring</small>
6332- <select name="useGroupScoring" class="text_pole widthNatural margin0">
6333- <option value="null" data-i18n="Use global">Use global</option>
6334- <option value="true" data-i18n="Yes">Yes</option>
6335- <option value="false" data-i18n="No">No</option>
6336- </select>
6337- </div>
6338- <div class="world_entry_form_control flex1" title="Can be used to automatically activate Quick Replies" data-i18n="[title]Can be used to automatically activate Quick Replies">
6339- <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
6340- <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
6341- </div>
6342- <div class="world_entry_form_control flex1" title="Defines delay levels for recursive scans.&#13;&#13;Initially, only the first level (smallest number) will match.&#13;Once no matches are found, the next level becomes eligible for matching.&#13;This repeats until all levels are checked.&#13;&#13;Tied to the &quot;Delay until recursion&quot; setting." data-i18n="[title]delay_until_recursion_level">
6343- <small class="textAlignCenter">
6344- <span data-i18n="Recursion Level">Recursion Level</span>
6345- <div class="fa-solid fa-circle-info opacity50p"></div>
6346- </small>
6347- <input class="text_pole margin0" name="delayUntilRecursionLevel" type="text" placeholder="1">
6348- </div>
6349- </div>
6350- <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
6351- <div class="world_entry_form_control flex1">
6352- <label for="content ">
6353- <small>
6354- <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween">
6355- <span class="alignitemscenter flex-container">
6356- <span data-i18n="Content" class="mdhotkey_location">
6357- Content
6358- </span>
6359- <i class="editor_maximize fa-solid fa-maximize right_menu_button" title="Expand the editor" data-i18n="[title]Expand the editor"></i>
6360- </span>
6361- <span>
6362- (<span data-i18n="extension_token_counter">Tokens:</span>&nbsp; <span class="world_entry_form_token_counter" data-first-run="true">counting...</span>)&nbsp;
6363- <span class="world_entry_form_uid_value" data-first-run="true"></span>
6364- </span>
6365- <div>
6366- <label class="checkbox flex-container alignitemscenter flexNoGap">
6367- <input type="checkbox" name="exclude_recursion" />
6368- <span data-i18n="Non-recursable (will not be activated by another)">
6369- Non-recursable (will not be activated by another)
6370- </span>
6371- </label>
6372- <label class="checkbox flex-container alignitemscenter flexNoGap">
6373- <input type="checkbox" name="prevent_recursion" />
6374- <span data-i18n="Prevent further recursion (this entry will not activate others)">
6375- Prevent further recursion (will not activate others)
6376- </span>
6377- </label>
6378- <label class="checkbox flex-container alignitemscenter flexNoGap">
6379- <input type="checkbox" name="delay_until_recursion" />
6380- <span data-i18n="Delay until recursion (can only be activated on recursive checking)">
6381- Delay until recursion (can only be activated on recursive checking)
6382- </span>
6383- </label>
6384- </div>
6385- </span>
6386- </small>
6387- <small class="displayNone">
6388- <span data-i18n="What this keyword should mean to the AI, sent verbatim">
6389- What this keyword should mean to the AI, sent verbatim
6390- </span>
6391- </small>
6392- </label>
6393- <textarea class="text_pole mdHotkeys" name="content" rows="8" data-i18n="[placeholder]What this keyword should mean to the AI, sent verbatim" placeholder="What this keyword should mean to the AI, sent verbatim"></textarea>
6394- </div>
6395- </div>
6396- <div class="world_entry_thin_controls commentContainer">
6397- </div>
6398- </div>
6399- <div class="flex-container wide100p flexGap10">
6400- <div class="flex4 flex-container flexFlowColumn flexNoGap">
6401- <div class="flex-container justifySpaceBetween">
6402- <small for="group">
6403- <span data-i18n="Inclusion Group">Inclusion Group</span>
6404- <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">
6405- <span class="fa-solid fa-circle-question note-link-span"></span>
6406- </a>
6407- </small>
6408- <label class="checkbox_label flexNoGap margin-r5" for="groupOverride">
6409- <input type="checkbox" name="groupOverride" />
6410- <span>
6411- <small title="Prioritize this entry: When checked, this entry is prioritized out of all selections.&#13;If multiple are prioritized, the one with the highest 'Order' is chosen.&#13;" data-i18n="[title]Prioritize this entry: When checked, this entry is prioritized out of all selections.If multiple are prioritized, the one with the highest 'Order' is chosen.">
6412- <span data-i18n="Prioritize">Prioritize</span>
6413- <div class="fa-solid fa-circle-info opacity50p"></div>
6414- </small>
6415- </span>
6416- </label>
6417- </div>
6418- <div class="range-block-range">
6419- <input type="text" class="text_pole margin0" name="group" rows="1" data-i18n="[placeholder]Only one entry with the same label will be activated" placeholder="Only one entry with the same label will be activated">
6420- </div>
6421- </div>
6422- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]A relative likelihood of entry activation within the group" title="A relative likelihood of entry activation within the group">
6423- <div class="flex-container justifySpaceBetween marginBot5">
6424- <small for="groupWeight" data-i18n="Group Weight">
6425- Group Weight
6426- </small>
6427- </div>
6428- <div class="range-block-range">
6429- <input type="number" class="text_pole margin0" name="groupWeight" rows="1" placeholder="100" min="1" max="999999">
6430- </div>
6431- </div>
6432- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Sticky entries will stay active for N messages after being triggered." title="Sticky entries will stay active for N messages after being triggered.">
6433- <div class="flex-container justifySpaceBetween marginBot5">
6434- <small class="flex-container alignItemsBaseline" for="sticky">
6435- <span data-i18n="Sticky">
6436- Sticky
6437- </span>
6438- <i class="fa-solid fa-comments fa-xs"></i>
6439- </small>
6440- </div>
6441- <div class="range-block-range">
6442- <input class="text_pole margin0" name="sticky" type="number" data-i18n="[placeholder]Non-sticky" placeholder="Non-sticky" min="0" max="999999">
6443- </div>
6444- </div>
6445- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a cooldown can't be activated N messages after being triggered." title="Entries with a cooldown can't be activated N messages after being triggered.">
6446- <div class="flex-container justifySpaceBetween marginBot5">
6447- <small class="flex-container alignItemsBaseline" for="cooldown">
6448- <span data-i18n="Cooldown">
6449- Cooldown
6450- </span>
6451- <i class="fa-solid fa-comments fa-xs"></i>
6452- </small>
6453- </div>
6454- <div class="range-block-range">
6455- <input class="text_pole margin0" name="cooldown" type="number" data-i18n="[placeholder]No cooldown" placeholder="No cooldown" min="0" max="999999">
6456- </div>
6457- </div>
6458- <div class="flex2 flex-container flexFlowColumn flexNoGap" data-i18n="[title]Entries with a delay can't be activated until there are N messages present in the chat." title="Entries with a delay can't be activated until there are N messages present in the chat.">
6459- <div class="flex-container justifySpaceBetween marginBot5">
6460- <small class="flex-container alignItemsBaseline" for="delay">
6461- <span data-i18n="Delay">
6462- Delay
6463- </span>
6464- <i class="fa-solid fa-comments fa-xs"></i>
6465- </small>
6466- </div>
6467- <div class="range-block-range">
6468- <input class="text_pole margin0" name="delay" type="number" data-i18n="[placeholder]No delay" placeholder="No delay" min="0" max="999999">
6469- </div>
6470- </div>
6471- </div>
6472- <div class="flex-container wide100p flexGap10">
6473- <div class="flex4 flex-container flexFlowColumn flexNoGap">
6474- <div class="flex-container justifySpaceBetween">
6475- <small for="characterFilter" data-i18n="Filter to Characters or Tags">
6476- Filter to Characters or Tags
6477- </small>
6478- <label class="checkbox_label flexNoGap margin-r5" for="character_exclusion">
6479- <input type="checkbox" name="character_exclusion" />
6480- <span>
6481- <small title="Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry" data-i18n="[title]Switch the Character/Tags filter around to exclude the listed characters and tags from matching for this entry">
6482- <span data-i18n="Exclude">Exclude</span>
6483- <div class="fa-solid fa-circle-info opacity50p"></div>
6484- </small>
6485- </span>
6486- </label>
6487- </div>
6488- <div class="range-block-range">
6489- <select name="characterFilter" class="select2_multi_sameline" multiple>
6490- <option value="" data-i18n="-- Characters not found --">
6491- -- Characters not found --
6492- </option>
6493- </select>
6494- </div>
6495- </div>
6496- </div>
6497- <div name="WIEntryBottomControls" class="flex-container flex1 justifySpaceBetween world_entry_form_horizontal">
6498- <div class="flex-container flexFlowColumn flexNoGap wi-enter-footer-text ">
6499- <label class="checkbox flex-container">
6500- <input type="checkbox" name="selective" />
6501- <span data-i18n="Selective">Selective</span>
6502- </label>
6503- <label class="checkbox flex-container">
6504- <input type="checkbox" name="useProbability" />
6505- <span data-i18n="Use Probability">Use Probability</span>
6506- </label>
6507- <label class="checkbox flex-container">
6508- <input type="checkbox" name="addMemo">
6509- <span data-i18n="Add Memo">Add Memo</span>
6510- </label>
6511- </div>
6512- </div>
6513- <div class="inline-drawer wide100p flexFlowColumn">
6514- <div class="inline-drawer-toggle inline-drawer-header inline-drawer-header-pointer userSettingsInnerExpandable">
6515- <strong data-i18n="Additional Matching Sources">Additional Matching Sources</strong>
6516- <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6517- </div>
6518- <div class="inline-drawer-content flex-container flexFlowRow flexGap10 paddingBottom5px">
6519- <small class="flex-container flex1 flexFlowColumn">
6520- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6521- <input type="checkbox" name="matchCharacterDescription" />
6522- <span data-i18n="Character Description">
6523- Character Description
6524- </span>
6525- </label>
6526- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6527- <input type="checkbox" name="matchCharacterPersonality" />
6528- <span data-i18n="Character Personality">
6529- Character Personality
6530- </span>
6531- </label>
6532- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6533- <input type="checkbox" name="matchScenario" />
6534- <span data-i18n="Scenario">
6535- Scenario
6536- </span>
6537- </label>
6538- </small>
6539- <small class="flex-container flex1 flexFlowColumn">
6540- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6541- <input type="checkbox" name="matchPersonaDescription" />
6542- <span data-i18n="Persona Description">
6543- Persona Description
6544- </span>
6545- </label>
6546- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6547- <input type="checkbox" name="matchCharacterDepthPrompt" />
6548- <span data-i18n="Character's Note">
6549- Character's Note
6550- </span>
6551- </label>
6552- <label class="checkbox flex-container alignItemsCenter flexNoGap">
6553- <input type="checkbox" name="matchCreatorNotes" />
6554- <span data-i18n="Creator's Notes">
6555- Creator's Notes
6556- </span>
6557- </label>
6558- </small>
6559- </div>
6560- </div>
65616569 </div>
65626570 </div>
65636571 </form>
public/script.js+35 -85
@@ -10,7 +10,6 @@ import {
1010 SVGInject,
1111 Popper,
1212 initLibraryShims,
13- slideToggle,
1413 default as libs,
1514} from './lib.js';
1615
@@ -10320,86 +10319,43 @@ function doDrawerOpenClick() {
1032010319/**
1032110320 * Event handler to open or close a navbar drawer when a navbar icon is clicked.
1032210321 * Handles click events on .drawer-toggle elements.
1032310322 * @returns {Promise<void>}
1032410323 */
1032510324export async function doNavbarIconClick() {
1032610325 const icon = $(this).find('.drawer-icon');
1032710326 const drawer = $(this).parent().find('.drawer-content');
10328- if (drawer.hasClass('resizing')) { return; }
1032910327 const drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
1033010328 const targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
10331- const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
10329+
10332-
10330+ if (!drawerWasOpenAlready) {
10333- if (!drawerWasOpenAlready) { //to open the drawer
10331+ const $openDrawers = $('.openDrawer:not(.pinnedOpen)');
10334- $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
10332+ const $openIcons = $('.openIcon:not(.drawerPinnedOpen)');
10335- slideToggle(el, {
10333+ for (const iconEl of $openIcons) {
10336- ...getSlideToggleOptions(),
10334+ $(iconEl).toggleClass('closedIcon openIcon');
10337- onAnimationEnd: function (el) {
10335+ }
10338- el.closest('.drawer-content').classList.remove('resizing');
10336+ for (const el of $openDrawers) {
10339- },
10337+ $(el).toggleClass('closedDrawer openDrawer');
10340- });
10338+ }
10341- });
10339+ if ($openDrawers.length) {
10342- $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
10340+ await delay(animation_duration);
10343- $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
10341+ }
1034410342 icon.toggleClass('openIcon closedIcon');
1034510343 drawer.toggleClass('openDrawer closedDrawer');
1034610344
10347- //console.log(targetDrawerID);
1034810345 if (targetDrawerID === 'right-nav-panel') {
10349- $(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {
10346+ favsToHotswap();
10350- slideToggle(el, {
10347+ $('#rm_print_characters_block').trigger('scroll');
10351- ...getSlideToggleOptions(),
10352- elementDisplayStyle: 'flex',
10353- onAnimationEnd: function (el) {
10354- el.closest('.drawer-content').classList.remove('resizing');
10355- favsToHotswap();
10356- $('#rm_print_characters_block').trigger('scroll');
10357- },
10358- });
10359- });
10360- } else {
10361- $(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {
10362- slideToggle(el, {
10363- ...getSlideToggleOptions(),
10364- onAnimationEnd: function (el) {
10365- el.closest('.drawer-content').classList.remove('resizing');
10366- },
10367- });
10368- });
1036910348 }
1037010349
1037110350 // Set the height of "autoSetHeight" textareas within the drawer to their scroll height
1037210351 if (!CSS.supports('field-sizing', 'content')) {
1037310352 const textareas = $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {;
10374- await resetScrollHeight($(this));
10353+ for (const textarea of textareas) {
10375- return;
10354+ await resetScrollHeight($(textarea));
1037610355 });
1037710356 }
10378-
10357+ } else if (drawerWasOpenAlready) {
10379- } else if (drawerWasOpenAlready) { //to close manually
1038010358 icon.toggleClass('closedIcon openIcon');
10381-
10382- if (pinnedDrawerClicked) {
10383- $(drawer).addClass('resizing').each((_, el) => {
10384- slideToggle(el, {
10385- ...getSlideToggleOptions(),
10386- onAnimationEnd: function (el) {
10387- el.classList.remove('resizing');
10388- },
10389- });
10390- });
10391- }
10392- else {
10393- $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
10394- slideToggle(el, {
10395- ...getSlideToggleOptions(),
10396- onAnimationEnd: function (el) {
10397- el.closest('.drawer-content').classList.remove('resizing');
10398- },
10399- });
10400- });
10401- }
10402-
1040310359 drawer.toggleClass('closedDrawer openDrawer');
1040410360 }
1040510361}
@@ -12028,8 +11984,8 @@ jQuery(async function () {
1202811984
1202911985 $('.drawer-toggle').on('click', doNavbarIconClick);
1203011986
1203111987 $('html').on('touchstart mousedown', async function (e) {
1203211988 varconst clickTarget = $(e.target);
1203311989
1203411990 if (isExportPopupOpen
1203511991 && clickTarget.closest('#export_button').length == 0
@@ -12050,28 +12006,21 @@ jQuery(async function () {
1205012006 '#toast-container',
1205112007 '.select2-results',
1205212008 ];
12009+
1205312010 for (const id of forbiddenTargets) {
1205412011 if (clickTarget.closest(id).length > 0) {
1205512012 return;
1205612013 }
1205712014 }
1205812015
12059- var targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;
12016+ // This autocloses open drawers that are not pinned if a click happens inside the app which does not target them.
12060- if (clickTarget.hasClass('drawer-icon') == false && !clickTarget.hasClass('openDrawer')) {
12017+ const targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;
12061- if ($('.openDrawer').length !== 0) {
12018+ if (!clickTarget.hasClass('drawer-icon') && !clickTarget.hasClass('openDrawer')) {
12062- if (targetParentHasOpenDrawer === 0) {
12019+ const $openDrawers = $('.openDrawer').not('.pinnedOpen');
12063- //console.log($('.openDrawer').not('.pinnedOpen').length);
12020+ if ($openDrawers.length && targetParentHasOpenDrawer === 0) {
12064- $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
12021+ // Toggle icon and drawer classes
12065- slideToggle(el, {
12022+ $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
12066- ...getSlideToggleOptions(),
12023+ $openDrawers.toggleClass('closedDrawer openDrawer');
12067- onAnimationEnd: (el) => {
12068- el.closest('.drawer-content').classList.remove('resizing');
12069- },
12070- });
12071- });
12072- $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
12073- $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
12074- }
1207512024 }
1207612025 }
1207712026 });
@@ -12085,6 +12034,7 @@ jQuery(async function () {
1208512034 const drawerContent = drawer.find('>.inline-drawer-content');
1208612035 icon.toggleClass('down up');
1208712036 icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
12037+ drawer.trigger('inline-drawer-toggle');
1208812038 drawerContent.stop().slideToggle({
1208912039 complete: () => {
1209012040 $(this).css('height', '');
@@ -12181,7 +12131,7 @@ jQuery(async function () {
1218112131 if (!(e.target instanceof HTMLElement)) return;
1218212132 if (e.target.matches('#OpenAllWIEntries')) {
1218312133 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
1218412134 delay(0).then(() => toggleDrawer(drawer, true));
1218512135 });
1218612136 } else if (e.target.matches('#CloseAllWIEntries')) {
1218712137 document.querySelectorAll('#world_popup_entries_list .inline-drawer').forEach((/** @type {HTMLElement} */ drawer) => {
public/scripts/RossAscends-mods.js+137 -185
@@ -1,4 +1,4 @@
11import { DOMPurify, Bowser, slideToggle } from '../lib.js';
22
33import {
44 characters,
@@ -19,7 +19,7 @@ import {
1919 menu_type,
2020 substituteParams,
2121 sendTextareaMessage,
2222 getSlideToggleOptionsdoNavbarIconClick,
2323} from '../script.js';
2424
2525import {
@@ -473,54 +473,72 @@ const saveUserInputDebounced = debounce(saveUserInput);
473473
474474// Make the DIV element draggable:
475475
476-export function dragElement(elmnt) {
476+/**
477- var isHeaderBeingDragged = false;
477+ * Make the given element draggable. This is used for Moving UI.
478- var isMouseDown = false;
478+ * @param {JQuery} $elmnt - The element to make draggable.
479+ */
480+export function dragElement($elmnt) {
481+ let actionType = null; // "drag" or "resize"
482+ let isMouseDown = false;
479483
480484 varlet pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
481485 varlet height, width, top, left, right, bottom,
482486 maxX, maxY, winHeight, winWidth,
483487 topbar, topBarFirstX, topBarLastY;
484488
485489 varconst elmntName = $elmnt.attr('id');
486- console.debug(`dragElement called for ${elmntName}`);
487490 const elmntNameEscaped = $.escapeSelector(elmntName);
488491 const $elmntHeader = $(`#${elmntNameEscaped}header`);
489492
490- if (elmntHeader.length) {
493+ // Helper: Save position/size to state and emit events
491- elmntHeader.off('mousedown').on('mousedown', (e) => { //listener for drag handle repositioning
494+ function savePositionAndSize() {
492- isHeaderBeingDragged = true;
495+ if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
493- isMouseDown = true;
496+ power_user.movingUIState[elmntName].top = top;
494- observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
497+ power_user.movingUIState[elmntName].left = left;
495- dragMouseDown(e);
498+ power_user.movingUIState[elmntName].right = right;
496- });
499+ power_user.movingUIState[elmntName].bottom = bottom;
497- $(elmnt).off('mousedown').on('mousedown', () => { //listener for resize
500+ power_user.movingUIState[elmntName].margin = 'unset';
498- isMouseDown = true;
501+ if (actionType === 'resize') {
499- observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
502+ power_user.movingUIState[elmntName].width = width;
500- });
503+ power_user.movingUIState[elmntName].height = height;
504+ eventSource.emit('resizeUI', elmntName);
505+ }
506+ saveSettingsDebounced();
501507 }
502508
509+ // Helper: Clamp element within viewport
510+ function clampToViewport() {
511+ if (top <= 0) $elmnt.css('top', '0px');
512+ else if (maxY >= winHeight) $elmnt.css('top', winHeight - maxY + top - 1 + 'px');
513+ if (left <= 0) $elmnt.css('left', '0px');
514+ else if (maxX >= winWidth) $elmnt.css('left', winWidth - maxX + left - 1 + 'px');
515+ }
516+
517+ // Observer for style changes (position/size)
503518 const observer = new MutationObserver((mutations) => {
504519 const $target = $(mutations[0].target);
505- if (!$(target).is(':visible') //abort if element is invisible
520+ if (
506- || $(target).hasClass('resizing') //being auto-resized by other JS code
521+ !$target.is(':visible') ||
507- || Number((String(target.height).replace('px', ''))) < 50 //too short
522+ $target.hasClass('resizing') ||
508- || Number((String(target.width).replace('px', ''))) < 50 //too narrow
523+ $target.height() < 50 ||
509- || power_user.movingUI === false // if MUI is not turned on
524+ $target.width() < 50 ||
510- || isMobile() // if it's a mobile screen
525+ power_user.movingUI === false ||
526+ isMobile() ||
527+ !isMouseDown
511528 ) {
529+ observer.disconnect();
512530 return;
513531 }
514532
515533 const style = getComputedStyle($target[0]);
516534 height = parseInt(style.height);
517535 width = parseInt(style.width);
518536 top = parseInt(style.top);
519537 left = parseInt(style.left);
520538 right = parseInt(style.right);
521539 bottom = parseInt(style.bottom);
522540 maxX = parseInt(width + left);
523541 maxY = parseInt(height + top);
524542 winWidth = window.innerWidth;
525543 winHeight = window.innerHeight;
526544
@@ -529,182 +547,119 @@ export function dragElement(elmnt) {
529547 topBarFirstX = parseInt(topbarstyle.marginInline);
530548 topBarLastY = parseInt(topbarstyle.height);
531549
532- //prepare an empty poweruser object for the item being altered if we don't have one already
550+ // Prepare state object if missing
533551 if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
534- console.debug(`adding config property for ${elmntName}`);
535- power_user.movingUIState[elmntName] = {};
536- }
537552
538- //handle resizing
553+ if (actionType === 'resize') {
539- if (!isHeaderBeingDragged && isMouseDown) { //if user is dragging the resize handle (not in header)
540- let imgHeight, imgWidth, imageAspectRatio;
541554 let containerAspectRatio = height / width;
542-
555+ if ($elmnt.attr('id').startsWith('zoomFor_')) {
543- //force aspect ratio for zoomed avatars
556+ const zoomedAvatarImage = $elmnt.find('.zoomed_avatar_img');
544- if ($(elmnt).attr('id').startsWith('zoomFor_')) {
557+ const imgHeight = zoomedAvatarImage.height();
545558 letconst zoomedAvatarImageimgWidth = $(elmnt)zoomedAvatarImage.findwidth('.zoomed_avatar_img');
546- imgHeight = zoomedAvatarImage.height();
559+ const imageAspectRatio = imgHeight / imgWidth;
547- imgWidth = zoomedAvatarImage.width();
548- imageAspectRatio = imgHeight / imgWidth;
549-
550- // Maintain aspect ratio
551560 if (containerAspectRatio !== imageAspectRatio) {
552561 $elmnt.css('width', $elmnt.width());
553562 $elmnt.css('height', $elmnt.width() * imageAspectRatio);
554563 }
555-
564+ if (top + $elmnt.height() >= winHeight) {
556- // Prevent resizing offscreen
565+ $elmnt.css('height', winHeight - top - 1 + 'px');
557- if (top + elmnt.height() >= winHeight) {
566+ $elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
558- elmnt.css('height', winHeight - top - 1 + 'px');
559- elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
560567 }
561-
568+ if (left + $elmnt.width() >= winWidth) {
562- if (left + elmnt.width() >= winWidth) {
569+ $elmnt.css('width', winWidth - left - 1 + 'px');
563570 $elmnt.css('widthheight', (winWidth - left - 1) * imageAspectRatio + 'px');
564- elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
565- }
566- } else { //prevent divs that are not zoomedAvatars from resizing offscreen
567-
568- if (top + elmnt.height() >= winHeight) {
569- elmnt.css('height', winHeight - top - 1 + 'px');
570- }
571-
572- if (left + elmnt.width() >= winWidth) {
573- elmnt.css('width', winWidth - left - 1 + 'px');
574571 }
572+ } else {
573+ if (top + $elmnt.height() >= winHeight) $elmnt.css('height', winHeight - top - 1 + 'px');
574+ if (left + $elmnt.width() >= winWidth) $elmnt.css('width', winWidth - left - 1 + 'px');
575575 }
576-
577- //prevent resizing from top left into the top bar
578576 if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) {
579577 $elmnt.css('width', width - 1 + 'px');
580578 }
581-
579+ $elmnt.css({ left, top });
582- //set css to prevent weird resize behavior (does not save)
580+ $elmnt.off('mouseup').on('mouseup', () => {
583- elmnt.css('left', left);
581+ if (
584- elmnt.css('top', top);
582+ power_user.movingUIState[elmntName].width === $elmnt.width() &&
585-
583+ power_user.movingUIState[elmntName].height === $elmnt.height()
586- //set a listener for mouseup to save new width/height
584+ ) return;
587- $(window).off('mouseup').on('mouseup', () => {
585+ savePositionAndSize();
588- console.log(`Saving ${elmntName} Height/Width`);
586+ observer.disconnect();
589- // check if the height or width actually changed
590- if (power_user.movingUIState[elmntName].width === elmnt.width() && power_user.movingUIState[elmntName].height === elmnt.height()) {
591- console.log('no change detected, aborting save');
592- return;
593- }
594-
595- power_user.movingUIState[elmntName].width = width;
596- power_user.movingUIState[elmntName].height = height;
597- eventSource.emit('resizeUI', elmntName);
598- saveSettingsDebounced();
599- imgHeight = null;
600- imgWidth = null;
601- height = null;
602- width = null;
603-
604- containerAspectRatio = null;
605- imageAspectRatio = null;
606- $(window).off('mouseup');
607587 });
588+ } else if (actionType === 'drag') {
589+ clampToViewport();
608590 }
609591
610- //only record position changes if header is being dragged
592+ // Always update position in state
611- power_user.movingUIState[elmntName].top = top;
593+ savePositionAndSize();
612- power_user.movingUIState[elmntName].left = left;
613- power_user.movingUIState[elmntName].right = right;
614- power_user.movingUIState[elmntName].bottom = bottom;
615- power_user.movingUIState[elmntName].margin = 'unset';
616-
617- //handle dragging hit detection to prevent dragging offscreen
618- if (isHeaderBeingDragged && isMouseDown) {
619-
620- if (top <= 0) {
621- elmnt.css('top', '0px');
622- } else if (maxY >= winHeight) {
623- elmnt.css('top', winHeight - maxY + top - 1 + 'px');
624- }
625-
626- if (left <= 0) {
627- elmnt.css('left', '0px');
628- } else if (maxX >= winWidth) {
629- elmnt.css('left', winWidth - maxX + left - 1 + 'px');
630- }
631- }
632-
633- // Check if the element header exists and set the reposition listener on the grabber in the header
634- if (elmntHeader.length) {
635- elmntHeader.off('mousedown').on('mousedown', (e) => {
636- dragMouseDown(e);
637- });
638- } else { //if no header, put the listener on the elmnt itself.
639- elmnt.off('mousedown').on('mousedown', dragMouseDown);
640- }
641594 });
642595
596+ // Mouse event handlers
643597 function dragMouseDown(e) {
644-
645598 if (e) {
646599 isHeaderBeingDraggedactionType = true'drag';
600+ isMouseDown = true;
647601 e.preventDefault();
648602 pos3 = e.clientX; //mouse X at click
649603 pos4 = e.clientY; //mouse Y at click
650604 }
651605 $(document).on('mouseup', closeDragElement);
652606 $(document).on('mousemove', elementDrag);
653607 }
654608
655609 function elementDrag(e) {
656610 if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
657- power_user.movingUIState[elmntName] = {};
658- }
659-
660- e = e || window.event;
661611 e.preventDefault();
662-
612+ pos1 = pos3 - e.clientX;
663- pos1 = pos3 - e.clientX; //X change amt (-1 or 1)
613+ pos2 = pos4 - e.clientY;
664- pos2 = pos4 - e.clientY; //Y change amt (-1 or 1)
614+ pos3 = e.clientX;
665615 pos3pos4 = e.clientXclientY; //new mouse X
666- pos4 = e.clientY; //new mouse Y
616+ $elmnt.attr('data-dragged', 'true');
667-
617+ $elmnt.css('left', ($elmnt.offset().left - pos1) + 'px');
668- elmnt.attr('data-dragged', 'true');
618+ $elmnt.css('top', ($elmnt.offset().top - pos2) + 'px');
669-
619+ $elmnt.css('margin', 'unset');
670- //first set css to computed values to avoid CSS NaN results from 'auto', etc
620+ $elmnt.css('height', height);
671621 $elmnt.css('leftwidth', (elmnt.offset().left) + 'px'width);
672- elmnt.css('top', (elmnt.offset().top) + 'px');
673-
674- //then update element position styles to account for drag changes
675- elmnt.css('margin', 'unset');
676- elmnt.css('left', (elmnt.offset().left - pos1) + 'px');
677- elmnt.css('top', (elmnt.offset().top - pos2) + 'px');
678- /* elmnt.css('right', ((winWidth - maxX) + 'px'));
679- elmnt.css('bottom', ((winHeight - maxY) + 'px')); */
680-
681- // Height/Width here are for visuals only, and are not saved to settings.
682- // This is required because some divs do hot have a set width/height
683- // and will default to shrink to min value of 100px set in CSS file
684- elmnt.css('height', height);
685- elmnt.css('width', width);
686- return;
687622 }
688623
689624 function closeDragElement() {
690- console.debug('drag finished');
691- isHeaderBeingDragged = false;
692625 isMouseDown = false;
626+ actionType = null;
693627 $(document).off('mouseup', closeDragElement);
694628 $(document).off('mousemove', elementDrag);
695629 $('body')elmnt.cssattr('overflowdata-dragged', 'false');
696- // Clear the "data-dragged" attribute
697- elmnt.attr('data-dragged', 'false');
698630 observer.disconnect();
699- console.debug(`Saving ${elmntName} UI position`);
631+ savePositionAndSize();
700- saveSettingsDebounced();
632+ }
701- top = null;
633+
702- left = null;
634+ // Setup event listeners
703- right = null;
635+ if ($elmntHeader.length) {
704- bottom = null;
636+ $elmntHeader.off('mousedown').on('mousedown', (e) => {
705- maxX = null;
637+ if ($(e.target).hasClass('drag-grabber')) {
706638 maxY actionType = null'drag';
639+ isMouseDown = true;
640+ observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
641+ dragMouseDown(e);
642+ }
643+ });
707644 }
645+
646+ $elmnt.off('mousedown').on('mousedown', (e) => {
647+ const rect = $elmnt[0].getBoundingClientRect();
648+ const resizeMargin = 16;
649+ const isNearRight = e.clientX > rect.right - resizeMargin;
650+ const isNearBottom = e.clientY > rect.bottom - resizeMargin;
651+ if (isNearRight && isNearBottom) {
652+ actionType = 'resize';
653+ isMouseDown = true;
654+ observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
655+ }
656+ });
657+
658+ $elmnt.off('mouseup').on('mouseup', () => {
659+ isMouseDown = false;
660+ actionType = null;
661+ observer.disconnect();
662+ });
708663}
709664
710665export async function initMovingUI() {
@@ -775,9 +730,8 @@ export function initRossMods() {
775730 $(RightNavDrawerIcon).removeClass('drawerPinnedOpen');
776731
777732 if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
778- slideToggle(RightNavPanel, getSlideToggleOptions());
733+ const toggle = $('#unimportantYes');
779- $(RightNavDrawerIcon).toggleClass('closedIcon openIcon');
734+ doNavbarIconClick.call(toggle);
780- $(RightNavPanel).toggleClass('openDrawer closedDrawer');
781735 }
782736 }
783737 });
@@ -793,14 +747,13 @@ export function initRossMods() {
793747 $(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');
794748
795749 if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
796- slideToggle(LeftNavPanel, getSlideToggleOptions());
750+ const toggle = $('#ai-config-button>.drawer-toggle');
797- $(LeftNavDrawerIcon).toggleClass('closedIcon openIcon');
751+ doNavbarIconClick.call(toggle);
798- $(LeftNavPanel).toggleClass('openDrawer closedDrawer');
799752 }
800753 }
801754 });
802755
803756 $(WIPanelPin).on('click', async function () {
804757 accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));
805758 if ($(WIPanelPin).prop('checked') == true) {
806759 console.debug('adding pin class to WI');
@@ -813,9 +766,8 @@ export function initRossMods() {
813766
814767 if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
815768 console.debug('closing WI after lock removal');
816- slideToggle(WorldInfo, getSlideToggleOptions());
769+ const toggle = $('#WI-SP-button>.drawer-toggle');
817- $(WIDrawerIcon).toggleClass('closedIcon openIcon');
770+ doNavbarIconClick.call(toggle);
818- $(WorldInfo).toggleClass('openDrawer closedDrawer');
819771 }
820772 }
821773 });
public/scripts/authors-note.js+17 -14
@@ -1,9 +1,11 @@
11import {
2+ MAX_INJECTION_DEPTH,
23 animation_duration,
34 chat_metadata,
45 eventSource,
56 event_types,
67 extension_prompt_roles,
8+ extension_prompt_types,
79 saveSettingsDebounced,
810 this_chid,
911} from '../script.js';
@@ -347,7 +349,7 @@ export function setFloatingPrompt() {
347349 }
348350
349351 if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
350352 context.setExtensionPrompt(MODULE_NAME, '', extension_prompt_types.NONE, MAX_INJECTION_DEPTH);
351353 $('#extension_floating_counter').text('(disabled)');
352354 shouldWIAddPrompt = false;
353355 return;
@@ -380,7 +382,7 @@ export function setFloatingPrompt() {
380382 }
381383 context.setExtensionPrompt(
382384 MODULE_NAME,
383385 String(prompt),
384386 chat_metadata[metadata_keys.position],
385387 chat_metadata[metadata_keys.depth],
386388 extension_settings.note.allowWIScan,
@@ -396,37 +398,38 @@ function onANMenuItemClick() {
396398 }
397399
398400 //show AN if it's hidden
399- if ($('#floatingPrompt').css('display') !== 'flex') {
401+ const $ANcontainer = $('#floatingPrompt');
400- $('#floatingPrompt').addClass('resizing');
402+ if ($ANcontainer.css('display') !== 'flex') {
401403 $('#floatingPrompt')ANcontainer.cssaddClass('display', 'flexresizing');
402404 $('#floatingPrompt')ANcontainer.css('opacitydisplay', 0.0'flex');
403- $('#floatingPrompt').transition({
405+ $ANcontainer.css('opacity', 0.0);
406+ $ANcontainer.transition({
404407 opacity: 1.0,
405408 duration: animation_duration,
406409 }, async function () {
407410 await delay(50);
408411 $('#floatingPrompt')ANcontainer.removeClass('resizing');
409412 });
410413
411414 //auto-open the main AN inline drawer
412415 if ($('#ANBlockToggle')
413416 .siblings('.inline-drawer-content')
414417 .css('display') !== 'block') {
415418 $('#floatingPrompt')ANcontainer.addClass('resizing');
416419 $('#ANBlockToggle').clicktrigger('click');
417420 }
418421 } else {
419422 //hide AN if it's already displayed
420423 $('#floatingPrompt')ANcontainer.addClass('resizing');
421424 $('#floatingPrompt')ANcontainer.transition({
422425 opacity: 0.0,
423426 duration: animation_duration,
424427 }, async function () {
425428 await delay(50);
426429 $('#floatingPrompt')ANcontainer.removeClass('resizing');
427430 });
428431 setTimeout(function () {
429432 $('#floatingPrompt')ANcontainer.hide();
430433 }, animation_duration);
431434 }
432435
public/scripts/extensions/assets/style.css+1 -1
@@ -65,7 +65,7 @@
6565.asset-download-button-text {
6666 font: bold 20px "Quicksand", san-serif;
6767 color: #ffffff;
6868 transition: all 0.2svar(--animation-duration-2x);
6969}
7070
7171.asset-download-button-loading .asset-download-button-text {
public/scripts/extensions/gallery/index.js+17 -2
@@ -4,6 +4,8 @@ import {
44 characters,
55 getRequestHeaders,
66 event_types,
7+ animation_duration,
8+ animation_easing,
79} from '../../../script.js';
810import { groups, selected_group } from '../../group-chats.js';
911import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename } from '../../utils.js';
@@ -32,7 +34,15 @@ let galleryMaxRows = 3;
3234$('#movingDivs').on('click', '.dragClose', function () {
3335 const relatedId = $(this).data('related-id');
3436 if (!relatedId) return;
3537 const relatedElement = $(`#movingDivs > .draggable[id="${relatedId}"]`).remove();
38+ relatedElement.transition({
39+ opacity: 0,
40+ duration: animation_duration,
41+ easing: animation_easing,
42+ complete: () => {
43+ relatedElement.remove();
44+ },
45+ });
3646});
3747
3848const CUSTOM_GALLERY_REMOVED_EVENT = 'galleryRemoved';
@@ -373,7 +383,7 @@ async function makeMovable(url) {
373383 const id = 'gallery';
374384 const template = $('#generic_draggable_template').html();
375385 const newElement = $(template);
376386 newElement.css({ 'background-color',: 'var(--SmartThemeBlurTintColor)', 'opacity': 0 });
377387 newElement.attr('forChar', id);
378388 newElement.attr('id', id);
379389 newElement.find('.drag-grabber').attr('id', `${id}header`);
@@ -505,6 +515,11 @@ async function makeMovable(url) {
505515 loadMovingUIState();
506516 $(`.draggable[forChar="${id}"]`).css('display', 'block');
507517 dragElement(newElement);
518+ newElement.transition({
519+ opacity: 1,
520+ duration: animation_duration,
521+ easing: animation_easing,
522+ });
508523
509524 $(`.draggable[forChar="${id}"] img`).on('dragstart', (e) => {
510525 console.log('saw drag on avatar!');
public/scripts/extensions/memory/index.js+3 -1
@@ -16,6 +16,7 @@ import {
1616 getMaxContextSize,
1717 setExtensionPrompt,
1818 streamingProcessor,
19+ animation_easing,
1920} from '../../../script.js';
2021import { is_group_generating, selected_group } from '../../group-chats.js';
2122import { loadMovingUIState } from '../../power-user.js';
@@ -972,6 +973,7 @@ function doPopout(e) {
972973 </div>`;
973974 const newElement = $(template);
974975 newElement.attr('id', 'summaryExtensionPopout')
976+ .css('opacity', 0)
975977 .removeClass('zoomed_avatar')
976978 .addClass('draggable')
977979 .empty();
@@ -980,13 +982,13 @@ function doPopout(e) {
980982 originalElement.html('<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>');
981983 newElement.append(controlBarHtml).append(originalHTMLClone);
982984 $('body').append(newElement);
985+ newElement.transition({ opacity: 1, duration: animation_duration, easing: animation_easing });
983986 $('#summaryExtensionDrawerContents').addClass('scrollableInnerFull');
984987 setMemoryContext(prevSummaryBoxContents, false); //paste prev summary box contents into popout box
985988 setupListeners();
986989 loadSettings();
987990 loadMovingUIState();
988991
989- $('#summaryExtensionPopout').fadeIn(animation_duration);
990992 dragElement(newElement);
991993
992994 //setup listener for close button to restore extensions menu
public/scripts/extensions/quick-reply/style.css+13 -16
@@ -18,7 +18,7 @@
1818#qr--bar {
1919 outline: none;
2020 margin: 0;
21- transition: 0.3s;
21+ transition: var(--animation-duration-2x);
2222 opacity: 0.7;
2323 display: flex;
2424 align-items: center;
@@ -34,14 +34,12 @@
3434 right: 0.25em;
3535 top: 0;
3636}
37-
3837/*hide QR popout for mobile*/
3938@media screen and (max-width: 1000px) {
4039 #qr--bar > #qr--popoutTrigger {
4140 display: none;
4241 }
4342}
44-
4543#qr--bar.popoutVisible {
4644 padding-right: 2.5em;
4745}
@@ -61,9 +59,8 @@
6159 aspect-ratio: 1 / 1;
6260 font-size: 20px;
6361 opacity: 0.5;
6462 transition: all 250msvar(--animation-duration-2x);
6563}
66-
6764#qr--popout > .qr--body {
6865 overflow-y: auto;
6966}
@@ -124,7 +121,7 @@
124121 padding: 3px 5px;
125122 margin: 3px 0;
126123 cursor: pointer;
127- transition: 0.3s;
124+ transition: var(--animation-duration-2x);
128125 display: flex;
129126 align-items: center;
130127 justify-content: center;
@@ -175,7 +172,7 @@
175172 overflow: visible;
176173}
177174.ctx-menu .ctx-item .qr--hidden {
178175 display: none;
179176}
180177.list-group .list-group-item.ctx-header {
181178 font-weight: bold;
@@ -248,7 +245,7 @@
248245 display: flex;
249246 align-items: center;
250247 opacity: 0;
251248 transition: 100msvar(--animation-duration);
252249 margin: -2px 0 -11px 0;
253250 position: relative;
254251}
@@ -461,7 +458,7 @@
461458 cursor: not-allowed;
462459 opacity: 0.5;
463460 pointer-events: none;
464- transition: 200ms;
461+ transition: var(--animation-duration-2x);
465462 border-color: transparent;
466463}
467464.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting.qr--isPaused #qr--modal-debugButtons .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
@@ -492,7 +489,7 @@
492489 width: 6px;
493490 background-color: var(--SmartThemeBorderColor);
494491 border: 2px solid var(--SmartThemeBlurTintColor);
495492 transition: border-color 200msvar(--animation-duration-2x), background-color 200msvar(--animation-duration-2x);
496493 cursor: w-resize;
497494}
498495.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor.qr--isExecuting #qr--resizeHandle:hover {
@@ -574,7 +571,7 @@
574571 gap: 1em;
575572 text-align: left;
576573 opacity: 0.75;
577- transition: 200ms;
574+ transition: var(--animation-duration-2x);
578575 cursor: pointer;
579576}
580577.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--labels > label .qr--modal-switcherList .qr--modal-switcherItem:hover,
@@ -736,7 +733,7 @@
736733 display: flex;
737734}
738735.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute {
739- transition: 200ms;
736+ transition: var(--animation-duration-2x);
740737 filter: grayscale(0);
741738}
742739.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeButtons #qr--modal-execute.qr--busy {
@@ -825,7 +822,7 @@
825822 position: absolute;
826823 inset: 0;
827824 right: calc(100% - var(--prog) * 1%);
828- transition: 200ms;
825+ transition: var(--animation-duration-2x);
829826}
830827.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor #qr--modal-executeProgress.qr--paused:after {
831828 animation-name: qr--progressPulse;
@@ -1053,7 +1050,7 @@
10531050 white-space: pre;
10541051 font-weight: normal;
10551052 box-shadow: 0 0 0;
1056- transition: 200ms;
1053+ transition: var(--animation-duration-2x);
10571054}
10581055.popup:has(.qr--transferModal) .popup-button-ok:after {
10591056 content: 'Transfer';
@@ -1068,7 +1065,7 @@
10681065 white-space: pre;
10691066 font-weight: normal;
10701067 box-shadow: 0 0 0;
1071- transition: 200ms;
1068+ transition: var(--animation-duration-2x);
10721069}
10731070.popup:has(.qr--transferModal) .qr--copy:after {
10741071 content: 'Copy';
public/scripts/extensions/quick-reply/style.less+534 -370
@@ -1,11 +1,16 @@
11@keyframes qr--success {
2- 0%, 100% {
2+
3+ 0%,
4+ 100% {
35 color: var(--SmartThemeBodyColor);
46 }
5- 25%, 75% {
7+
8+ 25%,
9+ 75% {
610 color: rgb(81, 163, 81);
711 }
812}
13+
914&.qr--success {
1015 animation-name: qr--success;
1116 animation-duration: 3s;
@@ -13,10 +18,11 @@
1318 animation-delay: 0s;
1419 animation-iteration-count: 1;
1520}
21+
1622#qr--bar {
1723 outline: none;
1824 margin: 0;
19- transition: 0.3s;
25+ transition: var(--animation-duration-2x);
2026 opacity: 0.7;
2127 display: flex;
2228 align-items: center;
@@ -33,6 +39,14 @@
3339 top: 0;
3440 }
3541}
42+
43+/*hide QR popout for mobile*/
44+@media screen and (max-width: 1000px) {
45+ #qr--bar>#qr--popoutTrigger {
46+ display: none;
47+ }
48+}
49+
3650#qr--bar.popoutVisible {
3751 padding-right: 2.5em;
3852}
@@ -54,7 +68,7 @@
5468 aspect-ratio: 1 / 1;
5569 font-size: 20px;
5670 opacity: 0.5;
5771 transition: all 250msvar(--animation-duration-2x);
5872 }
5973 }
6074 }
@@ -68,17 +82,21 @@
6882#qr--popout>.qr--body {
6983 >.qr--buttons {
7084 --qr--color: transparent;
85+
7186 &.qr--color {
7287 background-color: var(--qr--color);
7388 }
89+
7490 &.qr--borderColor {
7591 background-color: transparent;
7692 border-left: 5px solid var(--qr--color);
7793 border-right: 5px solid var(--qr--color);
7894 }
95+
7996 &:has(.qr--buttons.qr--color) {
8097 margin: 5px;
8198 }
99+
82100 margin: 0;
83101 padding: 0;
84102 display: flex;
@@ -89,6 +107,7 @@
89107
90108 >.qr--buttons {
91109 display: contents;
110+
92111 &.qr--color {
93112 .qr--button:before {
94113 content: '';
@@ -97,11 +116,14 @@
97116 inset: -5px;
98117 z-index: -1;
99118 }
119+
100120 &.qr--borderColor {
101121 .qr--button:before {
102122 display: none;
103123 }
104- &:before, &:after {
124+
125+ &:before,
126+ &:after {
105127 content: '';
106128 width: 5px;
107129 background-color: var(--qr--color);
@@ -118,7 +140,7 @@
118140 padding: 3px 5px;
119141 margin: 3px 0;
120142 cursor: pointer;
121- transition: 0.3s;
143+ transition: var(--animation-duration-2x);
122144 display: flex;
123145 align-items: center;
124146 justify-content: center;
@@ -132,6 +154,7 @@
132154 .qr--hidden {
133155 display: none;
134156 }
157+
135158 .qr--button-icon {
136159 margin: 0 0.5em;
137160 }
@@ -274,18 +297,22 @@
274297 display: flex;
275298 align-items: center;
276299 opacity: 0;
277300 transition: 100msvar(--animation-duration);
278301 margin: -2px 0 -11px 0;
279302 position: relative;
303+
280304 .qr--actions {
281305 display: flex;
282306 gap: 0.25em;
283307 flex: 0 0 auto;
308+
284309 .qr--action {
285310 margin: 0;
286311 }
287312 }
288- &:before, &:after {
313+
314+ &:before,
315+ &:after {
289316 content: "";
290317 display: block;
291318 flex: 1 1 auto;
@@ -293,10 +320,13 @@
293320 margin: 0 1em;
294321 height: 0;
295322 }
296- &:hover, &:focus-within {
323+
324+ &:hover,
325+ &:focus-within {
297326 opacity: 1;
298327 }
299328 }
329+
300330 >.qr--set-item .qr--content {
301331 display: flex;
302332 flex-direction: row;
@@ -336,9 +366,11 @@
336366 display: flex;
337367 align-items: center;
338368 gap: 0.5em;
369+
339370 .qr--set-itemIcon:not(.fa-solid) {
340371 display: none;
341372 }
373+
342374 .qr--set-itemLabel {
343375 min-width: 24px;
344376 }
@@ -384,15 +416,16 @@
384416 align-items: baseline;
385417 }
386418 }
387- >#qr--autoExec {
388- .checkbox_label {
389- text-wrap: nowrap;
390419
391- .fa-fw {
420+ >#qr--autoExec {
392- margin-right: 2px;
421+ .checkbox_label {
393- }
422+ text-wrap: nowrap;
423+
424+ .fa-fw {
425+ margin-right: 2px;
394426 }
395427 }
428+ }
396429}
397430
398431
@@ -433,65 +466,75 @@
433466 bottom: unset;
434467 margin: unset;
435468 padding: 0;
469+
436470 &::backdrop {
437471 backdrop-filter: unset;
438472 background-color: transparent;
439473 }
474+
440475 .popup-body {
441476 flex: 0 0 auto;
442477 height: min-content;
443478 width: min-content;
444479 }
480+
445481 .popup-content {
446482 flex: 0 0 auto;
447483 margin-top: 0;
448484
449485 > #qr--modalEditor {
450486 max-height: 50vh;
451- > #qr--main,
487+
452488 > #qr--resizeHandlemain,
453489 > #qr--qrOptions > h3resizeHandle,
454490 > #qr--qrOptions > #qr--modal-executeButtonsh3,
455491 > #qr--qrOptions > #qr--modal-executeProgressexecuteButtons,
456- {
492+ >#qr--qrOptions>#qr--modal-executeProgress {
457493 display: none;
458494 }
495+
459496 #qr--qrOptions {
460497 width: auto;
461498 }
499+
462500 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-maximize {
463501 display: flex;
464502 }
503+
465504 #qr--modal-debugButtons .qr--modal-debugButton#qr--modal-minimize {
466505 display: none;
467506 }
507+
468508 #qr--modal-debugState {
469509 padding-top: 0;
470510 }
471511 }
472512 }
473513 }
474- &:has(.qr--isExecuting) {
514+
475- .popup-controls {
515+ &:has(.qr--isExecuting) {
476- display: none;
516+ .popup-controls {
477- }
517+ display: none;
478-
518+ }
479- .qr--highlight {
519+
480- position: absolute;
520+ .qr--highlight {
481- z-index: 50000;
521+ position: absolute;
482522 pointer z-eventsindex: none50000;
483- background-color: rgb(47 150 180 / 0.5);
523+ pointer-events: none;
484- &.qr--unresolved {
524+ background-color: rgb(47 150 180 / 0.5);
485- background-color: rgb(255 255 0 / 0.5);
525+
486- }
526+ &.qr--unresolved {
487- }
527+ background-color: rgb(255 255 0 / 0.5);
488- .qr--highlight-secondary {
528+ }
489- position: absolute;
529+ }
490- z-index: 50000;
530+
491- pointer-events: none;
531+ .qr--highlight-secondary {
492- border: 3px solid red;
532+ position: absolute;
493- }
533+ z-index: 50000;
494- }
534+ pointer-events: none;
535+ border: 3px solid red;
536+ }
537+ }
495538
496539 .popup-content {
497540 display: flex;
@@ -504,36 +547,41 @@
504547 gap: 1em;
505548 overflow: hidden;
506549
507550 &.qr--isExecuting {
508- #qr--main > h3:first-child,
551+
509552 #qr--main > .qr-h3:first-labelschild,
510553 #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettingslabels,
511- #qr--qrOptions > h3:first-child,
554+ #qr--main>.qr--modal-messageContainer>.qr--modal-editorSettings,
512555 #qr--qrOptions > #qr-h3:first-ctxEditorchild,
513556 #qr--qrOptions > .#qr--ctxEditorActionsctxEditor,
514557 #qr--qrOptions > .qr--ctxEditorActions + h3,
515558 #qr--qrOptions > .qr--ctxEditorActions + h3 + div,
516- {
559+ #qr--qrOptions>.qr--ctxEditorActions+h3+div {
517560 display: none;
518561 }
519- #qr--main > .qr--modal-messageContainer > #qr--modal-messageHolder > #qr--modal-message {
562+
520- visibility: hidden;
563+ #qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder>#qr--modal-message {
521- }
564+ visibility: hidden;
522- #qr--modal-debugButtons {
565+ }
523- display: flex;
566+
567+ #qr--modal-debugButtons {
568+ display: flex;
569+
524570 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
525571 cursor: not-allowed;
526572 opacity: 0.5;
527573 pointer-events: none;
528- transition: 200ms;
574+ transition: var(--animation-duration-2x);
529575 border-color: transparent;
530576 }
531577 }
578+
532579 &.qr--isPaused #qr--modal-debugButtons {
533580 .menu_button:not(#qr--modal-minimize, #qr--modal-maximize) {
534581 cursor: pointer;
535582 opacity: 1;
536583 pointer-events: all;
584+
537585 &#qr--modal-resume {
538586 animation-name: qr--debugPulse;
539587 animation-duration: 1500ms;
@@ -541,73 +589,90 @@
541589 animation-delay: 0s;
542590 animation-iteration-count: infinite;
543591 }
592+
544593 &#qr--modal-resume {
545594 border-color: rgb(81, 163, 81);
546595 }
596+
547597 &#qr--modal-step {
548598 border-color: var(--SmartThemeQuoteColor);
549599 }
600+
550601 &#qr--modal-stepInto {
551602 border-color: var(--SmartThemeQuoteColor);
552603 }
604+
553605 &#qr--modal-stepOut {
554606 border-color: var(--SmartThemeQuoteColor);
555607 }
556608 }
557609 }
558- #qr--resizeHandle {
610+
559- width: 6px;
611+ #qr--resizeHandle {
560- background-color: var(--SmartThemeBorderColor);
612+ width: 6px;
561613 border background-color: 2px solid var(--SmartThemeBlurTintColorSmartThemeBorderColor);
562- transition: border-color 200ms, background-color 200ms;
614+ border: 2px solid var(--SmartThemeBlurTintColor);
563- cursor: w-resize;
615+ transition: border-color var(--animation-duration-2x), background-color var(--animation-duration-2x);
564- &:hover {
616+ cursor: w-resize;
565- background-color: var(--SmartThemeQuoteColor);
617+
566- border-color: var(--SmartThemeQuoteColor);
618+ &:hover {
567- }
619+ background-color: var(--SmartThemeQuoteColor);
568- }
620+ border-color: var(--SmartThemeQuoteColor);
569- #qr--qrOptions {
621+ }
570- width: var(--width, auto);
622+ }
571- }
623+
572- }
624+ #qr--qrOptions {
573-
625+ width: var(--width, auto);
574- > #qr--main {
626+ }
575- flex: 1 1 auto;
627+ }
576- display: flex;
628+
577- flex-direction: column;
629+ >#qr--main {
578- overflow: hidden;
630+ flex: 1 1 auto;
579- > .qr--labels {
631+ display: flex;
580- flex: 0 0 auto;
632+ flex-direction: column;
581633 display overflow: flexhidden;
582- flex-direction: row;
634+
583- gap: 0.5em;
635+ >.qr--labels {
584- padding: 1px;
636+ flex: 0 0 auto;
585- > label, > .label {
637+ display: flex;
586- flex: 1 1 1px;
638+ flex-direction: row;
587- display: flex;
639+ gap: 0.5em;
588- flex-direction: column;
640+ padding: 1px;
641+
642+ >label,
643+ >.label {
644+ flex: 1 1 1px;
645+ display: flex;
646+ flex-direction: column;
589647 position: relative;
648+
590649 &.qr--fit {
591650 flex: 0 0 auto;
592651 justify-content: center;
593652 }
653+
594654 .qr--inputGroup {
595655 display: flex;
596656 align-items: baseline;
597657 gap: 0.5em;
658+
598659 input {
599660 flex: 1 1 auto;
600661 }
601662 }
602- .qr--labelText {
663+
603- flex: 1 1 auto;
664+ .qr--labelText {
604- }
665+ flex: 1 1 auto;
605- .qr--labelHint {
666+ }
606- flex: 1 1 auto;
667+
607- }
668+ .qr--labelHint {
608- input {
669+ flex: 1 1 auto;
609- flex: 0 0 auto;
670+ }
610- }
671+
672+ input {
673+ flex: 0 0 auto;
674+ }
675+
611676 .qr--modal-switcherList {
612677 background-color: var(--stcdx--bgColor);
613678 border: 1px solid var(--SmartThemeBorderColor);
@@ -625,25 +690,32 @@
625690 list-style: none;
626691 z-index: 40000;
627692 max-width: 100%;
693+
628694 .qr--modal-switcherItem {
629695 display: flex;
630696 gap: 1em;
631697 text-align: left;
632698 opacity: 0.75;
633- transition: 200ms;
699+ transition: var(--animation-duration-2x);
634700 cursor: pointer;
701+
635702 &:hover {
636703 opacity: 1;
637704 }
705+
638706 &.qr--current {
639707 opacity: 1;
640- .qr--label, .qr--id {
708+
709+ .qr--label,
710+ .qr--id {
641711 font-weight: bold;
642712 }
643713 }
644714 }
715+
645716 .qr--label {
646717 white-space: nowrap;
718+
647719 .menu_button {
648720 display: inline-block;
649721 height: min-content;
@@ -651,11 +723,19 @@
651723 margin: 0 0.5em 0 0;
652724 }
653725 }
726+
654727 .qr--id {
655728 opacity: 0.5;
656- &:before { content: "["; }
729+
657- &:after { content: "]"; }
730+ &:before {
731+ content: "[";
732+ }
733+
734+ &:after {
735+ content: "]";
736+ }
658737 }
738+
659739 .qr--message {
660740 height: 1lh;
661741 overflow: hidden;
@@ -664,102 +744,124 @@
664744 opacity: 0.5;
665745 }
666746 }
667747 }
668748 }
669- > .qr--modal-messageContainer {
749+
670- flex: 1 1 auto;
750+ >.qr--modal-messageContainer {
671- display: flex;
751+ flex: 1 1 auto;
672- flex-direction: column;
752+ display: flex;
673- overflow: hidden;
753+ flex-direction: column;
674- > .qr--modal-editorSettings {
754+ overflow: hidden;
675- display: flex;
755+
756+ >.qr--modal-editorSettings {
757+ display: flex;
676758 flex-wrap: wrap;
677759 flex-direction: row;
678760 column-gap: 1em;
679761 color: var(--grey70);
680762 font-size: smaller;
681763 align-items: baseline;
682- > .checkbox_label {
764+
683- white-space: nowrap;
765+ >.checkbox_label {
684- > input {
766+ white-space: nowrap;
685- font-size: inherit;
767+
686- }
768+ >input {
687- }
769+ font-size: inherit;
688770 }
689- > #qr--modal-messageHolder {
771+ }
690- flex: 1 1 auto;
772+ }
691- display: grid;
773+
692- text-align: left;
774+ >#qr--modal-messageHolder {
693- overflow: hidden;
775+ flex: 1 1 auto;
694- &.qr--noSyntax {
776+ display: grid;
695- > #qr--modal-messageSyntax {
777+ text-align: left;
696778 display overflow: nonehidden;
697- }
779+
698- > #qr--modal-message {
780+ &.qr--noSyntax {
699- background-color: var(--ac-style-color-background);
781+ >#qr--modal-messageSyntax {
700- color: var(--ac-style-color-text);
782+ display: none;
701- &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
783+ }
784+
785+ >#qr--modal-message {
786+ background-color: var(--ac-style-color-background);
787+ color: var(--ac-style-color-text);
788+
789+ &::-webkit-scrollbar,
790+ &::-webkit-scrollbar-thumb {
702791 visibility: visible;
703792 cursor: unset;
704793 }
705- &::selection {
794+
706- color: unset;
795+ &::selection {
707- background-color: rgba(108 171 251 / 0.25);
796+ color: unset;
708797 @supports ( background-color: rgbrgba(from white r108 g171 b251 / 0.25)) {;
709- background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
798+
710- }
799+ @supports (color: rgb(from white r g b / 0.25)) {
711- }
800+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
712801 }
713802 }
714- > #qr--modal-messageSyntax {
803+ }
715- grid-column: 1;
804+ }
716- grid-row: 1;
805+
717- padding: 0;
806+ >#qr--modal-messageSyntax {
718- margin: 0;
807+ grid-column: 1;
719- border: none;
808+ grid-row: 1;
720809 overflow padding: hidden0;
721- min-width: 100%;
810+ margin: 0;
722811 width border: 0none;
723- > #qr--modal-messageSyntaxInner {
812+ overflow: hidden;
724813 height min-width: 100%;
725- }
814+ width: 0;
726- }
815+
727816 > #qr--modal-messagemessageSyntaxInner {
728- background-color: transparent;
817+ height: 100%;
729- color: transparent;
818+ }
730- grid-column: 1;
819+ }
731- grid-row: 1;
820+
732- caret-color: var(--ac-style-color-text);
821+ >#qr--modal-message {
733- overflow: auto;
822+ background-color: transparent;
734- &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
823+ color: transparent;
735- visibility: hidden;
824+ grid-column: 1;
736- cursor: default;
825+ grid-row: 1;
737- }
826+ caret-color: var(--ac-style-color-text);
738- &::selection {
827+ overflow: auto;
739- color: transparent;
828+
740- background-color: rgba(108 171 251 / 0.25);
829+ &::-webkit-scrollbar,
741- @supports (color: rgb(from white r g b / 0.25)) {
830+ &::-webkit-scrollbar-thumb {
742- background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
831+ visibility: hidden;
743- }
832+ cursor: default;
744833 }
745- }
834+
746- #qr--modal-message, #qr--modal-messageSyntaxInner {
835+ &::selection {
747- font-family: var(--monoFontFamily);
836+ color: transparent;
748- padding: 0.75em;
837+ background-color: rgba(108 171 251 / 0.25);
749- margin: 0;
838+
750- resize: none;
839+ @supports (color: rgb(from white r g b / 0.25)) {
751- line-height: 1.2;
840+ background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
752- border: 1px solid var(--SmartThemeBorderColor);
841+ }
753- border-radius: 5px;
842+ }
754- position: relative;
843+ }
755- }
844+
756- }
845+ #qr--modal-message,
757- }
846+ #qr--modal-messageSyntaxInner {
758- }
847+ font-family: var(--monoFontFamily);
848+ padding: 0.75em;
849+ margin: 0;
850+ resize: none;
851+ line-height: 1.2;
852+ border: 1px solid var(--SmartThemeBorderColor);
853+ border-radius: 5px;
854+ position: relative;
855+ }
856+ }
857+ }
858+ }
859+
759860 #qr--modal-icon {
760861 height: 100%;
761862 aspect-ratio: 1 / 1;
762863 }
864+
763865 #qr--modal-executeButtons {
764866 display: flex;
765867 gap: 1em;
@@ -778,7 +880,7 @@
778880 }
779881
780882 #qr--modal-execute {
781- transition: 200ms;
883+ transition: var(--animation-duration-2x);
782884 filter: grayscale(0);
783885
784886 &.qr--busy {
@@ -819,16 +921,20 @@
819921 border-color: rgb(215, 136, 114);
820922 }
821923 }
822- #qr--modal-debugButtons {
924+
823- display: none;
925+ #qr--modal-debugButtons {
824926 gap display: 1emnone;
825- .qr--modal-debugButton {
927+ gap: 1em;
826- aspect-ratio: 1.25 / 1;
928+
827- width: 2.25em;
929+ .qr--modal-debugButton {
930+ aspect-ratio: 1.25 / 1;
931+ width: 2.25em;
828932 position: relative;
933+
829934 &:not(.fa-solid) {
830935 border-width: 1px;
831936 border-style: solid;
937+
832938 &:after {
833939 content: '';
834940 position: absolute;
@@ -839,23 +945,28 @@
839945 mask-repeat: no-repeat;
840946 }
841947 }
948+
842949 &#qr--modal-resume:after {
843950 mask-image: url('/img/step-resume.svg');
844951 }
952+
845953 &#qr--modal-step:after {
846954 mask-image: url('/img/step-over.svg');
847955 }
956+
848957 &#qr--modal-stepInto:after {
849958 mask-image: url('/img/step-into.svg');
850959 }
960+
851961 &#qr--modal-stepOut:after {
852962 mask-image: url('/img/step-out.svg');
853963 }
964+
854965 &#qr--modal-maximize {
855966 display: none;
856967 }
857968 }
858969 }
859970
860971 #qr--modal-send_textarea {
861972 flex: 0 0 auto;
@@ -879,7 +990,7 @@
879990 position: absolute;
880991 inset: 0;
881992 right: calc(100% - var(--prog) * 1%);
882- transition: 200ms;
993+ transition: var(--animation-duration-2x);
883994 }
884995
885996 &.qr--paused:after {
@@ -939,135 +1050,180 @@
9391050 overflow: auto;
9401051 min-width: 100%;
9411052 width: 0;
9421053 white-space: pre-wrap;
9431054 }
944- #qr--modal-debugState {
1055+
945- display: none;
1056+ #qr--modal-debugState {
946- &.qr--active {
1057+ display: none;
947- display: block;
1058+
948- }
1059+ &.qr--active {
949- text-align: left;
1060+ display: block;
950- font-size: smaller;
1061+ }
951- font-family: var(--monoFontFamily);
1062+
952- // background-color: rgb(146, 190, 252);
1063+ text-align: left;
953- color: white;
1064+ font-size: smaller;
954- padding: 0.5em 0;
1065+ font-family: var(--monoFontFamily);
955- overflow: auto;
1066+ // background-color: rgb(146, 190, 252);
956- min-width: 100%;
1067+ color: white;
9571068 width padding: 0.5em 0;
958- white-space: pre-wrap;
1069+ overflow: auto;
959-
1070+ min-width: 100%;
960- .qr--scope {
1071+ width: 0;
961- display: grid;
1072+ white-space: pre-wrap;
962- grid-template-columns: 0fr 1fr 1fr;
1073+
963- column-gap: 0em;
1074+ .qr--scope {
964- .qr--title {
1075+ display: grid;
9651076 grid-columntemplate-columns: 10fr /1fr 41fr;
9661077 font column-weightgap: bold0em;
967- font-family: var(--mainFontFamily);
1078+
968- background-color: var(--black50a);
1079+ .qr--title {
969- padding: 0.25em;
1080+ grid-column: 1 / 4;
970- margin-top: 0.5em;
1081+ font-weight: bold;
971- }
1082+ font-family: var(--mainFontFamily);
972- .qr--var, .qr--macro, .qr--pipe {
1083+ background-color: var(--black50a);
973- display: contents;
1084+ padding: 0.25em;
974- &:nth-child(2n + 1) {
1085+ margin-top: 0.5em;
975- .qr--key, .qr--val {
1086+ }
976- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
1087+
977- }
1088+ .qr--var,
9781089 .qr--val {macro,
979- &:nth-child(2n) {
1090+ .qr--pipe {
980- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
1091+ display: contents;
981- }
1092+
982- &:hover {
1093+ &:nth-child(2n + 1) {
983- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
1094+
984- }
1095+ .qr--key,
985- }
1096+ .qr--val {
986- }
1097+ background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
987- &:nth-child(2n) {
1098+ }
988- .qr--val {
1099+
989- &:nth-child(2n) {
1100+ .qr--val {
990- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
1101+ &:nth-child(2n) {
991- }
1102+ background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
992- &:hover {
1103+ }
993- background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
1104+
994- }
1105+ &:hover {
995- }
1106+ background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
9961107 }
997- &.qr--isHidden {
1108+ }
998- .qr--key, .qr--val {
1109+ }
999- opacity: 0.5;
1110+
1000- }
1111+ &:nth-child(2n) {
1001- }
1112+ .qr--val {
1002- .qr--val {
1113+ &:nth-child(2n) {
1003- grid-column: 2 / 4;
1114+ background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
1004- &.qr--singleCol {
1115+ }
1005- grid-column: unset;
1116+
1006- }
1117+ &:hover {
1007- &.qr--simple {
1118+ background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
1008- &:before, &:after {
1119+ }
1009- content: '"';
1120+ }
1010- color: var(--SmartThemeQuoteColor);
1121+ }
1011- }
1122+
1012- }
1123+ &.qr--isHidden {
1013- &.qr--unresolved {
1124+
1014- &:after {
1125+ .qr--key,
1015- content: '-UNRESOLVED-';
1126+ .qr--val {
1016- font-style: italic;
1127+ opacity: 0.5;
1017- color: var(--SmartThemeQuoteColor);
1128+ }
10181129 }
1019- }
1130+
1020- }
1131+ .qr--val {
1021- }
1132+ grid-column: 2 / 4;
1022- .qr--key {
1133+
1023- margin-left: 0.5em;
1134+ &.qr--singleCol {
10241135 padding grid-rightcolumn: 1emunset;
1025- &:after { content: ": "; }
1136+ }
1026- }
1137+
10271138 .qr--pipe, &.qr--macrosimple {
1028- > .qr--key {
1139+
1029- &:before { content: "{{"; }
1140+ &:before,
1030- &:after { content: "}}: "; }
1141+ &:after {
1031- }
1142+ content: '"';
1032- }
1143+ color: var(--SmartThemeQuoteColor);
1033- .qr--scope {
1144+ }
1034- display: contents;
1145+ }
1035- .qr--pipe {
1146+
10361147 .qr--key, &.qr--valunresolved {
1037- opacity: 0.5;
1148+ &:after {
1038- }
1149+ content: '-UNRESOLVED-';
1039- }
1150+ font-style: italic;
1040- }
1151+ color: var(--SmartThemeQuoteColor);
10411152 }
1042-
1153+ }
1043- .qr--stack {
1154+ }
1155+ }
1156+
1157+ .qr--key {
1158+ margin-left: 0.5em;
1159+ padding-right: 1em;
1160+
1161+ &:after {
1162+ content: ": ";
1163+ }
1164+ }
1165+
1166+ .qr--pipe,
1167+ .qr--macro {
1168+ >.qr--key {
1169+ &:before {
1170+ content: "{{";
1171+ }
1172+
1173+ &:after {
1174+ content: "}}: ";
1175+ }
1176+ }
1177+ }
1178+
1179+ .qr--scope {
1180+ display: contents;
1181+
1182+ .qr--pipe {
1183+
1184+ .qr--key,
1185+ .qr--val {
1186+ opacity: 0.5;
1187+ }
1188+ }
1189+ }
1190+ }
1191+
1192+ .qr--stack {
10441193 display: grid;
10451194 grid-template-columns: 1fr 0fr;
1046- .qr--title {
1195+
1047- grid-column: 1 / 3;
1196+ .qr--title {
1048- font-weight: bold;
1197+ grid-column: 1 / 3;
1049- font-family: var(--mainFontFamily);
1198+ font-weight: bold;
10501199 background font-colorfamily: var(--black50amainFontFamily);
1051- padding: 0.25em;
1200+ background-color: var(--black50a);
1052- margin-top: 1em;
1201+ padding: 0.25em;
1053- }
1202+ margin-top: 1em;
1054- .qr--item {
1203+ }
1204+
1205+ .qr--item {
10551206 display: contents;
1056- &:nth-child(2n + 1) {
1207+
1057- .qr--name, .qr--source {
1208+ &:nth-child(2n + 1) {
1209+
1210+ .qr--name,
1211+ .qr--source {
10581212 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
10591213 }
10601214 }
1215+
10611216 .qr--name {
10621217 margin-left: 0.5em;
10631218 }
1219+
10641220 .qr--source {
10651221 opacity: 0.5;
10661222 text-align: right;
10671223 white-space: nowrap;
10681224 }
10691225 }
10701226 }
10711227 }
10721228 }
10731229 }
@@ -1086,6 +1242,7 @@
10861242}
10871243
10881244@keyframes qr--debugPulse {
1245+
10891246 0%,
10901247 100% {
10911248 border-color: rgb(81, 163, 81);
@@ -1097,10 +1254,11 @@
10971254}
10981255
10991256.popup.qr--hide {
11001257 opacity: 0 !important;
1101- &::backdrop {
1258+
1102- opacity: 0 !important;
1259+ &::backdrop {
1103- }
1260+ opacity: 0 !important;
1261+ }
11041262}
11051263
11061264.popup.qr--hide::backdrop {
@@ -1110,50 +1268,56 @@
11101268
11111269
11121270.popup:has(.qr--transferModal) {
11131271 .popup-button-ok {
11141272 &:after {
11151273 content: 'Transfer';
11161274 height: 0;
11171275 overflow: hidden;
11181276 font-weight: bold;
11191277 }
1120- display: flex;
1278+
1121- align-items: center;
1279+ display: flex;
11221280 flex align-directionitems: columncenter;
11231281 white flex-spacedirection: precolumn;
11241282 font white-weightspace: normalpre;
1125- box-shadow: 0 0 0;
1283+ font-weight: normal;
1126- transition: 200ms;
1284+ box-shadow: 0 0 0;
1127- }
1285+ transition: var(--animation-duration-2x);
1128- .qr--copy {
1286+ }
1129- &:after {
1287+
1130- content: 'Copy';
1288+ .qr--copy {
1131- height: 0;
1289+ &:after {
1132- overflow: hidden;
1290+ content: 'Copy';
1133- font-weight: bold;
1291+ height: 0;
1134- }
1292+ overflow: hidden;
1135- display: flex;
1293+ font-weight: bold;
1136- align-items: center;
1294+ }
1137- flex-direction: column;
1295+
1138- white-space: pre;
1296+ display: flex;
11391297 font align-weightitems: normalcenter;
1140- box-shadow: 0 0 0;
1298+ flex-direction: column;
1141- transition: 200ms;
1299+ white-space: pre;
1142- }
1300+ font-weight: normal;
1143- &:has(.qr--transferSelect:focus) {
1301+ box-shadow: 0 0 0;
1144- .popup-button-ok {
1302+ transition: var(--animation-duration-2x);
1145- font-weight: bold;
1303+ }
1146- box-shadow: 0 0 10px;
1304+
1147- }
1305+ &:has(.qr--transferSelect:focus) {
11481306 & .qrpopup-button-isCopyok {
1149- .popup-button-ok {
1307+ font-weight: bold;
1150- font-weight: normal;
1308+ box-shadow: 0 0 10px;
1151- box-shadow: 0 0 0;
1309+ }
1152- }
1310+
11531311 &.qr--copyisCopy {
1154- font-weight: bold;
1312+ .popup-button-ok {
1155- box-shadow: 0 0 10px;
1313+ font-weight: normal;
1156- }
1314+ box-shadow: 0 0 0;
11571315 }
1158- }
1316+
1317+ .qr--copy {
1318+ font-weight: bold;
1319+ box-shadow: 0 0 10px;
1320+ }
1321+ }
1322+ }
11591323}
public/scripts/extensions/regex/style.css+2 -2
@@ -24,7 +24,7 @@
2424
2525#scoped_scripts_block {
2626 opacity: 1;
2727 transition: opacity 0.2svar(--animation-duration-2x) ease-in-out;
2828}
2929
3030#scoped_scripts_block .move_to_scoped {
@@ -78,7 +78,7 @@ input.enable_scoped {
7878 cursor: pointer;
7979 opacity: 0.5;
8080 filter: grayscale(0.5);
8181 transition: opacity 0.2svar(--animation-duration-2x) ease-in-out;
8282}
8383
8484.regex-toggle-off:hover {
public/scripts/extensions/tts/style.css+1 -1
@@ -25,7 +25,7 @@
2525 outline: none;
2626 border: none;
2727 cursor: pointer;
28- transition: 0.3s;
28+ transition: var(--animation-duration-2x);
2929 opacity: 0.7;
3030 align-items: center;
3131 justify-content: center;
public/scripts/utils.js+8 -6
@@ -2153,20 +2153,22 @@ export function getFreeName(name, list, numberFormatter = (n) => ` #${n}`) {
21532153 */
21542154export function toggleDrawer(drawer, expand = true) {
21552155 /** @type {HTMLElement} */
21562156 const icon = drawer.querySelector(':scope > .inline-drawer-header .inline-drawer-icon');
21572157 /** @type {HTMLElement} */
21582158 const content = drawer.querySelector(':scope > .inline-drawer-content');
21592159
21602160 if (expand) {
2161- icon.classList.remove('up', 'fa-circle-chevron-up');
2162- icon.classList.add('down', 'fa-circle-chevron-down');
2163- content.style.display = 'block';
2164- } else {
21652161 icon.classList.remove('down', 'fa-circle-chevron-down');
21662162 icon.classList.add('up', 'fa-circle-chevron-up');
2163+ content.style.display = 'block';
2164+ } else {
2165+ icon.classList.remove('up', 'fa-circle-chevron-up');
2166+ icon.classList.add('down', 'fa-circle-chevron-down');
21672167 content.style.display = 'none';
21682168 }
21692169
2170+ drawer.dispatchEvent(new CustomEvent('inline-drawer-toggle', { bubbles: true }));
2171+
21702172 // Set the height of "autoSetHeight" textareas within the inline-drawer to their scroll height
21712173 if (!CSS.supports('field-sizing', 'content')) {
21722174 content.querySelectorAll('textarea.autoSetHeight').forEach(resetScrollHeight);
public/scripts/world-info.js+683 -752
@@ -58,7 +58,8 @@ export const scan_state = {
5858 MIN_ACTIVATIONS: 3,
5959};
6060
6161const WI_ENTRY_EDIT_TEMPLATEWI_ENTRY_HEADER_TEMPLATE = $('#entry_edit_template .world_entry');
62+const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry_edit');
6263
6364export let world_info = {};
6465export let selected_world_info = [];
@@ -85,6 +86,7 @@ const saveSettingsDebounced = debounce(() => {
8586}, debounce_timeout.relaxed);
8687const sortFn = (a, b) => b.order - a.order;
8788let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
89+let isSaveWorldInfoDisabled = false;
8890
8991// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
9092export const worldInfoFilter = new FilterHelper(() => updateEditor());
@@ -965,6 +967,7 @@ export function reloadEditor(file, loadIfNotSelected = false) {
965967 }
966968}
967969
970+//MARK: regWISlashCommands
968971function registerWorldInfoSlashCommands() {
969972 /**
970973 * Gets a *rough* approximation of the current chat context.
@@ -1754,12 +1757,12 @@ function registerWorldInfoSlashCommands() {
17541757 */
17551758export async function showWorldEditor(name) {
17561759 if (!name) {
17571760 await hideWorldEditor();
17581761 return;
17591762 }
17601763
17611764 const wiData = await loadWorldInfo(name);
17621765 await displayWorldEntries(name, wiData);
17631766}
17641767
17651768/**
@@ -1815,8 +1818,8 @@ export async function updateWorldInfoList() {
18151818 }
18161819}
18171820
18181821async function hideWorldEditor() {
18191822 await displayWorldEntries(null, null);
18201823}
18211824
18221825function getWIElement(name) {
@@ -1937,15 +1940,72 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f
19371940 worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
19381941}
19391942
1940-function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
1943+function clearEntryList($list) {
1941- updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
1944+ console.time('clearEntryList');
19421945
1943- const worldEntriesList = $('#world_popup_entries_list');
1946+ if (!$list.children().length) {
1947+ console.debug('List already empty, skipping cleanup.');
1948+ console.timeEnd('clearEntryList');
1949+ return;
1950+ }
1951+
1952+ // Step 1: Clean all <option> elements within <select>
1953+ $list.find('option').each(function () {
1954+ const $option = $(this);
1955+ $option.off();
1956+ $.cleanData([$option[0]]);
1957+ $option.remove();
1958+ });
19441959
1945- // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
1960+ // Step 2: Clean all <select> elements
1946- // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
1961+ $list.find('select').each(function () {
1947- worldEntriesList.find('*').off();
1962+ const $select = $(this);
1948- worldEntriesList.empty().show();
1963+ // Remove Select2-related data and container if present
1964+ if ($select.data('select2')) {
1965+ try {
1966+ $select.select2('destroy');
1967+ } catch (e) {
1968+ console.debug('Select2 destroy failed:', e);
1969+ }
1970+ }
1971+ const $container = $select.parent();
1972+ if ($container.length) {
1973+ $container.find('*').off();
1974+ $.cleanData($container.find('*').get());
1975+ $container.remove();
1976+ }
1977+
1978+ $select.off();
1979+ $.cleanData([$select[0]]);
1980+ });
1981+
1982+ // Step 3: Clean <div>, <span>, <input>
1983+ $list.find('div, span, input').each(function () {
1984+ const $elem = $(this);
1985+ $elem.off();
1986+ $.cleanData([$elem[0]]);
1987+ $elem.remove();
1988+ });
1989+
1990+ const totalElementsOfAnyKindLeftInList = $list.children().length;
1991+
1992+ // Final cleanup
1993+ if (totalElementsOfAnyKindLeftInList) {
1994+ console.time('empty');
1995+ $list.empty();
1996+ console.timeEnd('empty');
1997+ }
1998+
1999+ console.timeEnd('clearEntryList');
2000+}
2001+
2002+//MARK: displayWorldEntries
2003+async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
2004+ updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);
2005+
2006+ const worldEntriesList = $('#world_popup_entries_list');
2007+ clearEntryList(worldEntriesList);
2008+ worldEntriesList.show();
19492009
19502010 if (!data || !('entries' in data)) {
19512011 $('#world_popup_new').off('click').on('click', nullWorldInfo);
@@ -2038,22 +2098,39 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
20382098 formatNavigator: PAGINATION_TEMPLATE,
20392099 showNavigator: true,
20402100 callback: async function (/** @type {object[]} */ page) {
2041- // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
2101+ try {
2042- // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
2102+ // Prevent saveWorldInfo from firing timeouts while rendering the list
2043- worldEntriesList.find('*').off();
2103+ isSaveWorldInfoDisabled = true;
2044- worldEntriesList.empty();
2104+ clearEntryList(worldEntriesList);
20452105
20462106 const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
2047- const blocksPromises = page.map(async (entry) => await getWorldEntry(name, data, entry)).filter(x => x);
2107+ const blocks = [];
2048- const blocks = await Promise.all(blocksPromises);
2108+
2049- const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
2109+ for (const entry of page) {
2050- if (!isCustomOrder) {
2110+ try {
2051- blocks.forEach(block => {
2111+ const block = await getWorldEntry(name, data, entry);
2052- block.find('.drag-handle').remove();
2112+ if (block) {
2053- });
2113+ blocks.push(block);
2114+ }
2115+ } catch (error) {
2116+ console.error(`Error while processing entry ${entry.uid}:`, error);
2117+ }
2118+ }
2119+
2120+ const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
2121+ if (!isCustomOrder) {
2122+ blocks.forEach(block => {
2123+ block.find('.drag-handle').remove();
2124+ });
2125+ }
2126+
2127+ worldEntriesList.append(keywordHeaders);
2128+ worldEntriesList.append(blocks);
2129+ } catch (error) {
2130+ console.error('Error while rendering WI entries:', error);
2131+ } finally {
2132+ isSaveWorldInfoDisabled = false;
20542133 }
2055- worldEntriesList.append(keywordHeaders);
2056- worldEntriesList.append(blocks);
20572134 },
20582135 afterSizeSelectorChange: function (e) {
20592136 accountStorage.setItem(storageKey, e.target.value);
@@ -2173,7 +2250,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
21732250 if (selectedIndex !== -1) {
21742251 $('#world_editor_select').val(selectedIndex).trigger('change');
21752252 } else {
21762253 await hideWorldEditor();
21772254 }
21782255 }
21792256 });
@@ -2211,6 +2288,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
22112288 await saveWorldInfo(name, data);
22122289 },
22132290 });
2291+
22142292 //$("#world_popup_entries_list").disableSelection();
22152293}
22162294
@@ -2453,208 +2531,137 @@ export function parseRegexFromString(input) {
24532531 }
24542532}
24552533
2456-export async function getWorldEntry(name, data, entry) {
2534+//MARK: getWorldEntry
2457- if (!data.entries[entry.uid]) {
2535+function enableKeysInputHelper({ template, entry, entryPropName, originalDataValueName, name, data }) {
2458- return;
2536+ const isFancyInput = !isMobile() && !power_user.wi_key_input_plaintext;
2459- }
2537+ const input = isFancyInput ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`);
2460-
2538+ input.data('uid', entry.uid);
2461- const template = WI_ENTRY_EDIT_TEMPLATE.clone();
2539+ input.on('click', function (event) {
2462- template.data('uid', entry.uid);
2540+ event.stopPropagation();
2463- template.attr('uid', entry.uid);
2541+ });
2464-
2465- // Init default state of WI Key toggle (=> true)
2466- if (typeof power_user.wi_key_input_plaintext === 'undefined') power_user.wi_key_input_plaintext = true;
24672542
2468- /** Function to build the keys input controls @param {string} entryPropName @param {string} originalDataValueName */
2543+ function templateStyling(item, { searchStyle = false } = {}) {
2469- function enableKeysInput(entryPropName, originalDataValueName) {
2544+ const content = $('<span>').addClass('item').text(item.text).attr('title', `${item.text}\n\nClick to edit`);
24702545 const isFancyInputisRegex = !isMobileisValidRegex() && !power_useritem.wi_key_input_plaintexttext);
2471- const input = isFancyInput ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`);
2546+ if (isRegex) {
2472- input.data('uid', entry.uid);
2547+ content.html(highlightRegex(item.text));
2473- input.on('click', function (event) {
2548+ content.addClass('regex_item').prepend($('<span>').addClass('regex_icon').text('•*').attr('title', 'Regex'));
2474- // Prevent closing the drawer on clicking the input
2549+ }
2475- event.stopPropagation();
2550+ if (searchStyle && item.count) {
2551+ const wrapper = $('<span>').addClass('result_block').append(content);
2552+ wrapper.append($('<span>').addClass('item_count').text(item.count).attr('title', `Used as a key ${item.count} ${item.count != 1 ? 'times' : 'time'} in this lorebook`));
2553+ return wrapper;
2554+ }
2555+ return content;
2556+ }
2557+
2558+ if (isFancyInput) {
2559+ select2ModifyOptions(input, entry[entryPropName], { select: true, changeEventArgs: { skipReset: true, noSave: true } });
2560+ input.select2({
2561+ ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),
2562+ tags: true,
2563+ tokenSeparators: [','],
2564+ tokenizer: customTokenizer,
2565+ placeholder: input.attr('placeholder'),
2566+ templateResult: item => templateStyling(item, { searchStyle: true }),
2567+ templateSelection: item => templateStyling(item),
24762568 });
24772569
2478- function templateStyling(/** @type {Select2Option} */ item, { searchStyle = false } = {}) {
2570+ // TypeScript-safe event handler
2479- const content = $('<span>').addClass('item').text(item.text).attr('title', `${item.text}\n\nClick to edit`);
2571+ /**
2480- const isRegex = isValidRegex(item.text);
2572+ * @param {Event} _event
2481- if (isRegex) {
2573+ * @param {{ skipReset?: boolean, noSave?: boolean }} [arg]
2482- content.html(highlightRegex(item.text));
2574+ */
2483- content.addClass('regex_item').prepend($('<span>').addClass('regex_icon').text('•*').attr('title', 'Regex'));
2575+ input.on('change', async function (_event, arg) {
2484- }
2576+ const uid = $(this).data('uid');
2485-
2577+ const keys = ($(this).select2('data')).map(x => x.text);
2486- if (searchStyle && item.count) {
2578+ const skipReset = arg?.skipReset ?? false;
2487- // Build a wrapping element
2579+ const noSave = arg?.noSave ?? false;
2488- const wrapper = $('<span>').addClass('result_block')
2580+ if (!skipReset) await resetScrollHeight(this);
2489- .append(content);
2581+ if (!noSave) {
2490- wrapper.append($('<span>').addClass('item_count').text(item.count).attr('title', `Used as a key ${item.count} ${item.count != 1 ? 'times' : 'time'} in this lorebook`));
2582+ data.entries[uid][entryPropName] = keys;
2491- return wrapper;
2583+ setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2584+ await saveWorldInfo(name, data);
24922585 }
2586+ $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);
2587+ });
24932588
2494- return content;
2589+ input.toggleClass('empty', !entry[entryPropName].length);
2495- }
2590+ input.on('select2:select', event => updateWorldEntryKeyOptionsCache([event.params.data]));
2496-
2591+ input.on('select2:unselect', event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true }));
2497- if (isFancyInput) {
2592+
2498- // First initialize existing values as options, before initializing select2, to speed up performance
2593+ select2ChoiceClickSubscribe(input, target => {
2499- select2ModifyOptions(input, entry[entryPropName], { select: true, changeEventArgs: { skipReset: true, noSave: true } });
2594+ const key = $(target.closest('.regex-highlight, .item')).text();
2500-
2595+ const selected = input.val();
2501- input.select2({
2596+ if (!Array.isArray(selected)) return;
2502- ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),
2597+ var index = selected.indexOf(getSelect2OptionId(key));
2503- tags: true,
2598+ if (index > -1) selected.splice(index, 1);
2504- tokenSeparators: [','],
2599+ input.val(selected).trigger('change');
2505- tokenizer: customTokenizer,
2600+ updateWorldEntryKeyOptionsCache([key], { remove: true });
2506- placeholder: input.attr('placeholder'),
2601+ input.next('span.select2-container').find('textarea').val(key).trigger('input');
25072602 templateResult: item => templateStyling(item }, { searchStyleopenDrawer: true }),;
2508- templateSelection: item => templateStyling(item),
2603+ } else {
2509- });
2604+ template.find(`select[name="${entryPropName}"]`).hide();
2510- input.on('change', async function (_, { skipReset, noSave } = {}) {
2605+ input.show();
2511- const uid = $(this).data('uid');
2606+ /**
25122607 /* * @typeparam {string[]Event} */_event
2513- const keys = ($(this).select2('data')).map(x => x.text);
2608+ * @param {{ skipReset?: boolean, noSave?: boolean }} [arg]
2514-
2609+ */
2515- !skipReset && await resetScrollHeight(this);
2610+ input.on('change', async function (_event, arg) {
2516- if (!noSave) {
2611+ const uid = $(this).data('uid');
2517- data.entries[uid][entryPropName] = keys;
2612+ const value = String($(this).val());
2518- setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2613+ const skipReset = arg?.skipReset ?? false;
2519- await saveWorldInfo(name, data);
2614+ const noSave = arg?.noSave ?? false;
2520- }
2615+ if (!skipReset) await resetScrollHeight(this);
2616+ if (!noSave) {
2617+ data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
2618+ setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2619+ await saveWorldInfo(name, data);
25212620 $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);
25222621 });
2523- input.toggleClass('empty', !entry[entryPropName].length);
2622+ });
2524- input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data]));
2623+ input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
2525- input.on('select2:unselect', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true }));
2526-
2527- select2ChoiceClickSubscribe(input, target => {
2528- const key = $(target.closest('.regex-highlight, .item')).text();
2529- console.debug('Editing WI key', key);
2530-
2531- // Remove the current key from the actual selection
2532- const selected = input.val();
2533- if (!Array.isArray(selected)) return;
2534- var index = selected.indexOf(getSelect2OptionId(key));
2535- if (index > -1) selected.splice(index, 1);
2536- input.val(selected).trigger('change');
2537- // Manually update the cache, that change event is not gonna trigger it
2538- updateWorldEntryKeyOptionsCache([key], { remove: true });
2539-
2540- // We need to "hack" the actual text input into the currently open textarea
2541- input.next('span.select2-container').find('textarea')
2542- .val(key).trigger('input');
2543- }, { openDrawer: true });
2544- }
2545- else {
2546- // Compatibility with mobile devices. On mobile we need a text input field, not a select option control, so we need its own event handlers
2547- template.find(`select[name="${entryPropName}"]`).hide();
2548- input.show();
2549-
2550- input.on('input', async function (_, { skipReset, noSave } = {}) {
2551- const uid = $(this).data('uid');
2552- const value = String($(this).val());
2553- !skipReset && await resetScrollHeight(this);
2554- if (!noSave) {
2555- data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
2556- setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2557- await saveWorldInfo(name, data);
2558- $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);
2559- }
2560- });
2561- input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
2562- }
2563- return { isFancy: isFancyInput, control: input };
25642624 }
2625+ return { isFancy: isFancyInput, control: input };
2626+}
25652627
2566- // key
2628+/**
2567- const keyInput = enableKeysInput('key', 'keys');
2629+ * Helper to handle match checkboxes for WI entries.
2568-
2630+ */
2569- // keysecondary
2631+function handleMatchCheckboxHelper({ template, entry, fieldName, data, name }) {
2570- const keySecondaryInput = enableKeysInput('keysecondary', 'secondary_keys');
2632+ const key = originalWIDataKeyMap[fieldName];
2571-
2633+ const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`);
2572- // draw key input switch button
2634+ checkBoxElem.data('uid', entry.uid);
25732635 template.find('.switch_input_type_icon')checkBoxElem.on('clickinput', async function () {
2574- power_user.wi_key_input_plaintext = !power_user.wi_key_input_plaintext;
2575- saveSettingsDebounced();
2576-
2577- // Just redraw the panel
2578- const uid = ($(this).parents('.world_entry')).data('uid');
2579- updateEditor(uid, false);
2580-
2581- $(`.world_entry[uid="${uid}"] .inline-drawer-icon`).trigger('click');
2582- // setTimeout(() => {
2583- // }, debounce_timeout.standard);
2584- }).each((_, icon) => {
2585- $(icon).attr('title', $(icon).data(power_user.wi_key_input_plaintext ? 'tooltip-on' : 'tooltip-off'));
2586- $(icon).text($(icon).data(power_user.wi_key_input_plaintext ? 'icon-on' : 'icon-off'));
2587- });
2588-
2589- // logic AND/NOT
2590- const selectiveLogicDropdown = template.find('select[name="entryLogicType"]');
2591- selectiveLogicDropdown.data('uid', entry.uid);
2592-
2593- selectiveLogicDropdown.on('click', function (event) {
2594- event.stopPropagation();
2595- });
2596-
2597- selectiveLogicDropdown.on('input', async function () {
2598- const uid = $(this).data('uid');
2599- const value = Number($(this).val());
2600- data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
2601- setWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
2602- await saveWorldInfo(name, data);
2603- });
2604-
2605- template
2606- .find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`)
2607- .prop('selected', true)
2608- .trigger('input');
2609-
2610- // Character filter
2611- const characterFilterLabel = template.find('label[for="characterFilter"] > small');
2612- characterFilterLabel.text(entry.characterFilter?.isExclude ? 'Exclude Character(s)' : 'Filter to Character(s)');
2613-
2614- // exclude characters checkbox
2615- const characterExclusionInput = template.find('input[name="character_exclusion"]');
2616- characterExclusionInput.data('uid', entry.uid);
2617- characterExclusionInput.on('input', async function () {
26182636 const uid = $(this).data('uid');
26192637 const value = $(this).prop('checked');
2620- characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)');
2638+ data.entries[uid][fieldName] = value;
2621- if (data.entries[uid].characterFilter) {
2639+ setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]);
2622- if (!value && data.entries[uid].characterFilter.names.length === 0 && data.entries[uid].characterFilter.tags.length === 0) {
2623- delete data.entries[uid].characterFilter;
2624- } else {
2625- data.entries[uid].characterFilter.isExclude = value;
2626- }
2627- } else if (value) {
2628- Object.assign(
2629- data.entries[uid],
2630- {
2631- characterFilter: {
2632- isExclude: true,
2633- names: [],
2634- tags: [],
2635- },
2636- },
2637- );
2638- }
2639-
2640- // Verify names to exist in the system
2641- if (data.entries[uid]?.characterFilter?.names?.length > 0) {
2642- for (const name of [...data.entries[uid].characterFilter.names]) {
2643- if (!getContext().characters.find(x => x.avatar.replace(/\.[^/.]+$/, '') === name)) {
2644- console.warn(`World Info: Character ${name} not found. Removing from the entry filter.`, entry);
2645- data.entries[uid].characterFilter.names = data.entries[uid].characterFilter.names.filter(x => x !== name);
2646- }
2647- }
2648- }
2649-
2650- setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
26512640 await saveWorldInfo(name, data);
26522641 });
26532642 characterExclusionInputcheckBoxElem.prop('checked', !!entry.characterFilter?.isExclude ?? false[fieldName]).trigger('input');
2643+}
26542644
2655- const characterFilter = template.find('select[name="characterFilter"]');
2645+/**
2656- characterFilter.data('uid', entry.uid);
2646+ * Helper to update position/order display.
2647+ */
2648+function updatePosOrdDisplayHelper({ template, data, uid }) {
2649+ let entry = data.entries[uid];
2650+ let posText = entry.position;
2651+ switch (entry.position) {
2652+ case 0: posText = '↑CD'; break;
2653+ case 1: posText = 'CD↓'; break;
2654+ case 2: posText = '↑AN'; break;
2655+ case 3: posText = 'AN↓'; break;
2656+ case 4: posText = `@D${entry.depth}`; break;
2657+ }
2658+ template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`);
2659+}
26572660
2661+/**
2662+ * Helper to initialize character filter select2.
2663+ */
2664+function initCharacterFilterSelect2Helper(characterFilter, t) {
26582665 if (!isMobile()) {
26592666 $(characterFilter).select2({
26602667 width: '100%',
@@ -2663,7 +2670,12 @@ export async function getWorldEntry(name, data, entry) {
26632670 closeOnSelect: false,
26642671 });
26652672 }
2673+}
26662674
2675+/**
2676+ * Helper to fill character and tag options for character filter.
2677+ */
2678+function fillCharacterAndTagOptionsHelper({ characterFilter, entry, getContext }) {
26672679 const characters = getContext().characters;
26682680 characters.forEach((character) => {
26692681 const option = document.createElement('option');
@@ -2673,7 +2685,6 @@ export async function getWorldEntry(name, data, entry) {
26732685 option.setAttribute('data-type', 'character');
26742686 characterFilter.append(option);
26752687 });
2676-
26772688 const tags = getContext().tags;
26782689 tags.forEach((tag) => {
26792690 const option = document.createElement('option');
@@ -2683,14 +2694,17 @@ export async function getWorldEntry(name, data, entry) {
26832694 option.setAttribute('data-type', 'tag');
26842695 characterFilter.append(option);
26852696 });
2697+}
26862698
2699+/**
2700+ * Helper to handle character filter changes.
2701+ */
2702+function handleCharacterFilterChangeHelper({ characterFilter, data, entry, name, world_names, getContext, setWIOriginalDataValue, saveWorldInfo, t }) {
26872703 characterFilter.on('mousedown change', async function (e) {
2688- // If there's no world names, don't do anything
26892704 if (world_names.length === 0) {
26902705 e.preventDefault();
26912706 return;
26922707 }
2693-
26942708 const uid = $(this).data('uid');
26952709 const selected = $(this).find(':selected');
26962710 if ((!selected || selected?.length === 0) && !data.entries[uid].characterFilter?.isExclude) {
@@ -2712,361 +2726,99 @@ export async function getWorldEntry(name, data, entry) {
27122726 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
27132727 await saveWorldInfo(name, data);
27142728 });
2729+}
27152730
2716- // comment
2731+/**
2717- const commentInput = template.find('textarea[name="comment"]');
2732+ * Helper to handle probability input.
2718- const commentToggle = template.find('input[name="addMemo"]');
2733+ */
2719- commentInput.data('uid', entry.uid);
2734+function handleProbabilityInputHelper({ probabilityInput, data, entry, name, setWIOriginalDataValue, saveWorldInfo }) {
2720- commentInput.on('input', async function (_, { skipReset } = {}) {
2721- const uid = $(this).data('uid');
2722- const value = $(this).val();
2723- !skipReset && await resetScrollHeight(this);
2724- data.entries[uid].comment = value;
2725-
2726- setWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
2727- await saveWorldInfo(name, data);
2728- });
2729- commentToggle.data('uid', entry.uid);
2730- commentToggle.on('input', async function () {
2731- const uid = $(this).data('uid');
2732- const value = $(this).prop('checked');
2733- //console.log(value)
2734- const commentContainer = $(this)
2735- .closest('.world_entry')
2736- .find('.commentContainer');
2737- data.entries[uid].addMemo = value;
2738- await saveWorldInfo(name, data);
2739- value ? commentContainer.show() : commentContainer.hide();
2740- });
2741-
2742- commentInput.val(entry.comment).trigger('input', { skipReset: true });
2743- //initScrollHeight(commentInput);
2744- commentToggle.prop('checked', true /* entry.addMemo */).trigger('input');
2745- commentToggle.parent().hide();
2746-
2747- // content
2748- const counter = template.find('.world_entry_form_token_counter');
2749- const countTokensDebounced = debounce(async function (counter, value) {
2750- const numberOfTokens = await getTokenCountAsync(value);
2751- $(counter).text(numberOfTokens);
2752- }, debounce_timeout.relaxed);
2753-
2754- const contentInputId = `world_entry_content_${entry.uid}`;
2755- const contentInput = template.find('textarea[name="content"]');
2756- contentInput.data('uid', entry.uid);
2757- contentInput.attr('id', contentInputId);
2758- contentInput.on('input', async function (_, { skipCount } = {}) {
2759- const uid = $(this).data('uid');
2760- const value = $(this).val();
2761- data.entries[uid].content = value;
2762-
2763- setWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);
2764- await saveWorldInfo(name, data);
2765-
2766- if (skipCount) {
2767- return;
2768- }
2769-
2770- // count tokens
2771- countTokensDebounced(counter, value);
2772- });
2773- contentInput.val(entry.content).trigger('input', { skipCount: true });
2774-
2775- const contentExpandButton = template.find('.editor_maximize');
2776- contentExpandButton.attr('data-for', contentInputId);
2777-
2778- template.find('.inline-drawer-toggle').on('click', function () {
2779- if (counter.data('first-run')) {
2780- counter.data('first-run', false);
2781- countTokensDebounced(counter, contentInput.val());
2782- if (!keyInput.isFancy) initScrollHeight(keyInput.control);
2783- if (!keySecondaryInput.isFancy) initScrollHeight(keySecondaryInput.control);
2784- }
2785- });
2786-
2787- // selective
2788- const selectiveInput = template.find('input[name="selective"]');
2789- selectiveInput.data('uid', entry.uid);
2790- selectiveInput.on('input', async function () {
2791- const uid = $(this).data('uid');
2792- const value = $(this).prop('checked');
2793- data.entries[uid].selective = value;
2794-
2795- setWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
2796- await saveWorldInfo(name, data);
2797-
2798- const keysecondary = $(this)
2799- .closest('.world_entry')
2800- .find('.keysecondary');
2801-
2802- const keysecondarytextpole = $(this)
2803- .closest('.world_entry')
2804- .find('.keysecondarytextpole');
2805-
2806- const keyprimaryselect = $(this)
2807- .closest('.world_entry')
2808- .find('.keyprimaryselect');
2809-
2810- const keyprimaryHeight = keyprimaryselect.outerHeight();
2811- keysecondarytextpole.css('height', keyprimaryHeight + 'px');
2812-
2813- value ? keysecondary.show() : keysecondary.hide();
2814-
2815- });
2816- //forced on, ignored if empty
2817- selectiveInput.prop('checked', true /* entry.selective */).trigger('input');
2818- selectiveInput.parent().hide();
2819-
2820-
2821- // constant
2822- /*
2823- const constantInput = template.find('input[name="constant"]');
2824- constantInput.data("uid", entry.uid);
2825- constantInput.on("input", async function () {
2826- const uid = $(this).data("uid");
2827- const value = $(this).prop("checked");
2828- data.entries[uid].constant = value;
2829- setOriginalDataValue(data, uid, "constant", data.entries[uid].constant);
2830- await saveWorldInfo(name, data);
2831- });
2832- constantInput.prop("checked", entry.constant).trigger("input");
2833- */
2834-
2835- // order
2836- const orderInput = template.find('input[name="order"]');
2837- orderInput.data('uid', entry.uid);
2838- orderInput.on('input', async function () {
2839- const uid = $(this).data('uid');
2840- const value = Number($(this).val());
2841-
2842- data.entries[uid].order = !isNaN(value) ? value : 0;
2843- updatePosOrdDisplay(uid);
2844- setWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
2845- await saveWorldInfo(name, data);
2846- });
2847- orderInput.val(entry.order).trigger('input');
2848- orderInput.css('width', 'calc(3em + 15px)');
2849-
2850- // group
2851- const groupInput = template.find('input[name="group"]');
2852- groupInput.data('uid', entry.uid);
2853- groupInput.on('input', async function () {
2854- const uid = $(this).data('uid');
2855- const value = String($(this).val()).trim();
2856-
2857- data.entries[uid].group = value;
2858- setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
2859- await saveWorldInfo(name, data);
2860- });
2861- groupInput.val(entry.group ?? '').trigger('input');
2862- setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
2863-
2864- // inclusion priority
2865- const groupOverrideInput = template.find('input[name="groupOverride"]');
2866- groupOverrideInput.data('uid', entry.uid);
2867- groupOverrideInput.on('input', async function () {
2868- const uid = $(this).data('uid');
2869- const value = $(this).prop('checked');
2870- data.entries[uid].groupOverride = value;
2871- setWIOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
2872- await saveWorldInfo(name, data);
2873- });
2874- groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
2875-
2876- // group weight
2877- const groupWeightInput = template.find('input[name="groupWeight"]');
2878- groupWeightInput.data('uid', entry.uid);
2879- groupWeightInput.on('input', async function () {
2880- const uid = $(this).data('uid');
2881- let value = Number($(this).val());
2882- const min = Number($(this).attr('min'));
2883- const max = Number($(this).attr('max'));
2884-
2885- // Clamp the value
2886- if (value < min) {
2887- value = min;
2888- $(this).val(min);
2889- } else if (value > max) {
2890- value = max;
2891- $(this).val(max);
2892- }
2893-
2894- data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
2895- setWIOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
2896- await saveWorldInfo(name, data);
2897- });
2898- groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
2899-
2900- // sticky
2901- const sticky = template.find('input[name="sticky"]');
2902- sticky.data('uid', entry.uid);
2903- sticky.on('input', async function () {
2904- const uid = $(this).data('uid');
2905- const value = Number($(this).val());
2906- data.entries[uid].sticky = !isNaN(value) ? value : null;
2907-
2908- setWIOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
2909- await saveWorldInfo(name, data);
2910- });
2911- sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
2912-
2913- // cooldown
2914- const cooldown = template.find('input[name="cooldown"]');
2915- cooldown.data('uid', entry.uid);
2916- cooldown.on('input', async function () {
2917- const uid = $(this).data('uid');
2918- const value = Number($(this).val());
2919- data.entries[uid].cooldown = !isNaN(value) ? value : null;
2920-
2921- setWIOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
2922- await saveWorldInfo(name, data);
2923- });
2924- cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
2925-
2926- // delay
2927- const delay = template.find('input[name="delay"]');
2928- delay.data('uid', entry.uid);
2929- delay.on('input', async function () {
2930- const uid = $(this).data('uid');
2931- const value = Number($(this).val());
2932- data.entries[uid].delay = !isNaN(value) ? value : null;
2933-
2934- setWIOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
2935- await saveWorldInfo(name, data);
2936- });
2937- delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
2938-
2939- // probability
2940- if (entry.probability === undefined) {
2941- entry.probability = null;
2942- }
2943-
2944- // depth
2945- const depthInput = template.find('input[name="depth"]');
2946- depthInput.data('uid', entry.uid);
2947-
2948- depthInput.on('input', async function () {
2949- const uid = $(this).data('uid');
2950- const value = Number($(this).val());
2951-
2952- data.entries[uid].depth = !isNaN(value) ? value : 0;
2953- updatePosOrdDisplay(uid);
2954- setWIOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
2955- await saveWorldInfo(name, data);
2956- });
2957- depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
2958- depthInput.css('width', 'calc(3em + 15px)');
2959-
2960- // Hide by default unless depth is specified
2961- if (entry.position === world_info_position.atDepth) {
2962- //depthInput.parent().hide();
2963- }
2964-
2965- const probabilityInput = template.find('input[name="probability"]');
29662735 probabilityInput.data('uid', entry.uid);
29672736 probabilityInput.on('input', async function () {
29682737 const uid = $(this).data('uid');
29692738 const value = Number($(this).val());
2970-
29712739 data.entries[uid].probability = !isNaN(value) ? value : null;
2972-
2973- // Clamp probability to 0-100
29742740 if (data.entries[uid].probability !== null) {
29752741 data.entries[uid].probability = Math.min(100, Math.max(0, data.entries[uid].probability));
2976-
29772742 if (data.entries[uid].probability !== value) {
29782743 $(this).val(data.entries[uid].probability);
29792744 }
29802745 }
2981-
29822746 setWIOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
29832747 await saveWorldInfo(name, data);
29842748 });
29852749 probabilityInput.val(entry.probability).trigger('input');
29862750 probabilityInput.css('width', 'calc(3em + 15px)');
2751+}
29872752
2988- // probability toggle
2753+/**
2989- if (entry.useProbability === undefined) {
2754+ * Helper to handle probability toggle.
2990- entry.useProbability = false;
2755+ */
2991- }
2756+function handleProbabilityToggleHelper({ probabilityToggle, data, entry, name, probabilityInput, setWIOriginalDataValue, saveWorldInfo }) {
2992-
2993- const probabilityToggle = template.find('input[name="useProbability"]');
29942757 probabilityToggle.data('uid', entry.uid);
29952758 probabilityToggle.on('input', async function () {
29962759 const uid = $(this).data('uid');
29972760 const value = $(this).prop('checked');
29982761 data.entries[uid].useProbability = value;
29992762 const probabilityContainer = $(this).closest('.world_entry').find('.probabilityContainer');
3000- .closest('.world_entry')
3001- .find('.probabilityContainer');
30022763 await saveWorldInfo(name, data);
30032764 value ? probabilityContainer.show() : probabilityContainer.hide();
3004-
30052765 if (value && data.entries[uid].probability === null) {
30062766 data.entries[uid].probability = 100;
30072767 }
3008-
30092768 if (!value) {
30102769 data.entries[uid].probability = null;
30112770 }
3012-
30132771 probabilityInput.val(data.entries[uid].probability).trigger('input');
30142772 });
3015- //forced on, 100% by default
2773+ probabilityToggle.prop('checked', true).trigger('input');
3016- probabilityToggle.prop('checked', true /* entry.useProbability */).trigger('input');
30172774 probabilityToggle.parent().hide();
2775+}
30182776
3019- // position
2777+/**
3020- if (entry.position === undefined) {
2778+ * Helper to handle select2 dropdowns for boolean selects.
3021- entry.position = 0;
2779+ */
3022- }
2780+function handleBooleanSelectHelper({ selectElem, entry, entryKey, data, name, setWIOriginalDataValue, saveWorldInfo }) {
3023-
2781+ selectElem.data('uid', entry.uid);
3024- const positionInput = template.find('select[name="position"]');
2782+ selectElem.on('input', async function () {
3025- //initScrollHeight(positionInput);
2783+ const uid = $(this).data('uid');
3026- positionInput.data('uid', entry.uid);
2784+ const value = $(this).val();
3027- positionInput.on('click', function (event) {
2785+ data.entries[uid][entryKey] = value === 'null' ? null : value === 'true';
3028- // Prevent closing the drawer on clicking the input
2786+ setWIOriginalDataValue(data, uid, `extensions.${entryKey.replace(/[A-Z]/g, m => `_${m.toLowerCase()}`)}`, data.entries[uid][entryKey]);
3029- event.stopPropagation();
2787+ await saveWorldInfo(name, data);
30302788 });
3031- positionInput.on('input', async function () {
2789+ selectElem.val((entry[entryKey] === null || entry[entryKey] === undefined) ? 'null' : entry[entryKey] ? 'true' : 'false').trigger('input');
2790+}
2791+
2792+/**
2793+ * Helper to handle input fields for numbers.
2794+ */
2795+function handleNumberInputHelper({ inputElem, entry, entryKey, data, name, setWIOriginalDataValue, saveWorldInfo, min, max, clamp = false }) {
2796+ inputElem.data('uid', entry.uid);
2797+ inputElem.on('input', async function () {
30322798 const uid = $(this).data('uid');
30332799 constlet value = Number($(this).val());
3034- data.entries[uid].position = !isNaN(value) ? value : 0;
2800+ if (clamp) {
30352801 if (value ===< world_info_position.atDepthmin) {
3036- depthInput.prop('disabled', false);
2802+ value = min;
3037- depthInput.css('visibility', 'visible');
2803+ $(this).val(min);
3038- //depthInput.parent().show();
2804+ } else if (value > max) {
3039- const role = Number($(this).find(':selected').data('role'));
2805+ value = max;
3040- data.entries[uid].role = role;
2806+ $(this).val(max);
3041- } else {
2807+ }
3042- depthInput.prop('disabled', true);
3043- depthInput.css('visibility', 'hidden');
3044- data.entries[uid].role = null;
3045- //depthInput.parent().hide();
30462808 }
3047- updatePosOrdDisplay(uid);
2809+ data.entries[uid][entryKey] = !isNaN(value) ? value : null;
3048- // Spec v2 only supports before_char and after_char
2810+ setWIOriginalDataValue(data, uid, `extensions.${entryKey.replace(/[A-Z]/g, m => `_${m.toLowerCase()}`)}`, data.entries[uid][entryKey]);
3049- setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
3050- // Write the original value as extensions field
3051- setWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
3052- setWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
30532811 await saveWorldInfo(name, data);
30542812 });
2813+ inputElem.val(entry[entryKey] ?? (clamp ? min : '')).trigger('input');
2814+}
30552815
3056- const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
2816+/**
3057- template
2817+ * Helper to handle tri-state selector for constant/normal/vectorized.
3058- .find(`select[name="position"] option[value="${entry.position}"][data-role="${roleValue}"]`)
2818+ */
3059- .prop('selected', true)
2819+function handleEntryStateSelectorHelper({ entryStateSelector, entry, data, name, setWIOriginalDataValue, saveWorldInfo }) {
3060- .trigger('input');
3061-
3062- //add UID above content box (less important doesn't need to be always visible)
3063- template.find('.world_entry_form_uid_value').text(`(UID: ${entry.uid})`);
3064-
3065- //new tri-state selector for constant/normal/vectorized
3066- const entryStateSelector = template.find('select[name="entryStateSelector"]');
30672820 entryStateSelector.data('uid', entry.uid);
30682821 entryStateSelector.on('click', function (event) {
3069- // Prevent closing the drawer on clicking the input
30702822 event.stopPropagation();
30712823 });
30722824 entryStateSelector.on('input', async function () {
@@ -3093,12 +2845,17 @@ export async function getWorldEntry(name, data, entry) {
30932845 break;
30942846 }
30952847 await saveWorldInfo(name, data);
3096-
30972848 });
2849+ const entryState = () => entry.constant === true ? 'constant' : entry.vectorized === true ? 'vectorized' : 'normal';
2850+ entryStateSelector.find(`option[value=${entryState()}]`).prop('selected', true).trigger('input');
2851+}
30982852
3099- const entryKillSwitch = template.find('div[name="entryKillSwitch"]');
2853+/**
2854+ * Helper to handle kill switch toggle.
2855+ */
2856+function handleEntryKillSwitchHelper({ entryKillSwitch, entry, data, name, setWIOriginalDataValue, saveWorldInfo, template }) {
31002857 entryKillSwitch.data('uid', entry.uid);
31012858 entryKillSwitch.on('click', async function (event) {
31022859 const uid = entry.uid;
31032860 data.entries[uid].disable = !data.entries[uid].disable;
31042861 const isActive = !data.entries[uid].disable;
@@ -3107,104 +2864,114 @@ export async function getWorldEntry(name, data, entry) {
31072864 entryKillSwitch.toggleClass('fa-toggle-off', !isActive);
31082865 entryKillSwitch.toggleClass('fa-toggle-on', isActive);
31092866 await saveWorldInfo(name, data);
3110-
31112867 });
3112-
3113- const entryState = function () {
3114- if (entry.constant === true) {
3115- return 'constant';
3116- } else if (entry.vectorized === true) {
3117- return 'vectorized';
3118- } else {
3119- return 'normal';
3120- }
3121- };
3122-
31232868 const isActive = !entry.disable;
31242869 template.toggleClass('disabledWIEntry', !isActive);
31252870 entryKillSwitch.toggleClass('fa-toggle-off', !isActive);
31262871 entryKillSwitch.toggleClass('fa-toggle-on', isActive);
2872+}
2873+
2874+/**
2875+ * Main function to build the WI entry editor template.
2876+ */
2877+export async function getWorldEntry(name, data, entry) {
2878+ if (!data.entries[entry.uid]) return;
31272879
3128- template
2880+ const headerTemplate = WI_ENTRY_HEADER_TEMPLATE.clone();
3129- .find(`select[name="entryStateSelector"] option[value=${entryState()}]`)
2881+ headerTemplate.data('uid', entry.uid);
31302882 headerTemplate.propattr('selecteduid', trueentry.uid);
3131- .trigger('input');
31322883
3133- // exclude recursion
2884+ if (typeof power_user.wi_key_input_plaintext === 'undefined') power_user.wi_key_input_plaintext = true;
3134- const excludeRecursionInput = template.find('input[name="exclude_recursion"]');
2885+
3135- excludeRecursionInput.data('uid', entry.uid);
2886+ // Comment
3136- excludeRecursionInput.on('input', async function () {
2887+ const commentInput = headerTemplate.find('textarea[name="comment"]');
2888+ commentInput.data('uid', entry.uid);
2889+ commentInput.on('input', async function (_, { skipReset } = {}) {
31372890 const uid = $(this).data('uid');
31382891 const value = $(this).propval('checked');
3139- data.entries[uid].excludeRecursion = value;
2892+ !skipReset && await resetScrollHeight(this);
3140- setWIOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
2893+ data.entries[uid].comment = value;
2894+ setWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
31412895 await saveWorldInfo(name, data);
31422896 });
31432897 excludeRecursionInputcommentInput.propval('checked', entry.excludeRecursioncomment).trigger('input', { skipReset: true });
31442898
31452899 // prevent recursionOrder
31462900 const preventRecursionInputorderInput = templateheaderTemplate.find('input[name="prevent_recursionorder"]');
31472901 preventRecursionInputorderInput.data('uid', entry.uid);
31482902 preventRecursionInputorderInput.on('input', async function () {
31492903 const uid = $(this).data('uid');
31502904 const value = Number($(this).propval('checked'));
31512905 data.entries[uid].preventRecursionorder = !isNaN(value) ? value : 0;
3152- setWIOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
2906+ updatePosOrdDisplayHelper({ template: headerTemplate, data, uid });
2907+ setWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
31532908 await saveWorldInfo(name, data);
31542909 });
31552910 preventRecursionInputorderInput.propval('checked', entry.preventRecursionorder).trigger('input');
3156-
2911+ orderInput.css('width', 'calc(3em + 15px)');
3157- // delay until recursion
3158- // delay until recursion level
3159- const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
3160- delayUntilRecursionInput.data('uid', entry.uid);
3161- const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');
3162- delayUntilRecursionLevelInput.data('uid', entry.uid);
3163- delayUntilRecursionInput.on('input', async function () {
3164- const uid = $(this).data('uid');
3165- const toggled = $(this).prop('checked');
3166-
3167- // If the value contains a number, we'll take that one (set by the level input), otherwise we can use true/false switch
3168- const value = toggled ? data.entries[uid].delayUntilRecursion || true : false;
31692912
3170- if (!toggled) delayUntilRecursionLevelInput.val('');
2913+ // Probability
2914+ handleProbabilityInputHelper({ probabilityInput: headerTemplate.find('input[name="probability"]'), data, entry, name, setWIOriginalDataValue, saveWorldInfo });
31712915
3172- data.entries[uid].delayUntilRecursion = value;
2916+ // Depth
3173- setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2917+ handleNumberInputHelper({
3174- await saveWorldInfo(name, data);
2918+ inputElem: headerTemplate.find('input[name="depth"]'),
2919+ entry, entryKey: 'depth', data, name, setWIOriginalDataValue, saveWorldInfo,
2920+ min: 0, max: MAX_SCAN_DEPTH, clamp: false,
31752921 });
3176- delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
2922+ headerTemplate.find('input[name="depth"]').css('width', 'calc(3em + 15px)');
3177- delayUntilRecursionLevelInput.on('input', async function () {
2923+
2924+ // Position
2925+ if (entry.position === undefined) entry.position = 0;
2926+ const positionInput = headerTemplate.find('select[name="position"]');
2927+ positionInput.data('uid', entry.uid);
2928+ positionInput.on('click', e => e.stopPropagation());
2929+ positionInput.on('input', async function () {
31782930 const uid = $(this).data('uid');
31792931 const contentvalue = Number($(this).val());
3180- const value = content === '' ? (typeof data.entries[uid].delayUntilRecursion === 'boolean' ? data.entries[uid].delayUntilRecursion : true)
2932+ data.entries[uid].position = !isNaN(value) ? value : 0;
3181- : content === 1 ? true
2933+ const depthInput = headerTemplate.find('input[name="depth"]');
3182- : !isNaN(Number(content)) ? Number(content)
2934+ if (value === world_info_position.atDepth) {
3183- : false;
2935+ depthInput.prop('disabled', false);
3184-
2936+ depthInput.css('visibility', 'visible');
3185- data.entries[uid].delayUntilRecursion = value;
2937+ const role = Number($(this).find(':selected').data('role'));
3186- setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2938+ data.entries[uid].role = role;
2939+ } else {
2940+ depthInput.prop('disabled', true);
2941+ depthInput.css('visibility', 'hidden');
2942+ data.entries[uid].role = null;
2943+ }
2944+ updatePosOrdDisplayHelper({ template: headerTemplate, data, uid });
2945+ setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
2946+ setWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
2947+ setWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
31872948 await saveWorldInfo(name, data);
31882949 });
3189- // No need to retrigger inpout event, we'll just set the curret current value. It was edited/saved above already
2950+ const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
3190- delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input');
2951+ headerTemplate.find(`select[name="position"] option[value="${entry.position}"][data-role="${roleValue}"]`).prop('selected', true).trigger('input');
2952+
2953+ // Tri-state selector
2954+ handleEntryStateSelectorHelper({
2955+ entryStateSelector: headerTemplate.find('select[name="entryStateSelector"]'),
2956+ entry, data, name, setWIOriginalDataValue, saveWorldInfo,
2957+ });
2958+
2959+ // Kill switch
2960+ handleEntryKillSwitchHelper({
2961+ entryKillSwitch: headerTemplate.find('div[name="entryKillSwitch"]'),
2962+ entry, data, name, setWIOriginalDataValue, saveWorldInfo, template: headerTemplate,
2963+ });
31912964
31922965 // duplicateDuplicate/delete/move buttonbuttons
3193- const duplicateButton = template.find('.duplicate_entry_button');
2966+ headerTemplate.find('.duplicate_entry_button').data('uid', entry.uid).on('click', async function () {
3194- duplicateButton.data('uid', entry.uid);
3195- duplicateButton.on('click', async function () {
31962967 const uid = $(this).data('uid');
31972968 const entryentryDup = duplicateWorldInfoEntry(data, uid);
31982969 if (entryentryDup) {
31992970 await saveWorldInfo(name, data);
32002971 updateEditor(entryentryDup.uid);
32012972 }
32022973 });
3203-
2974+ headerTemplate.find('.delete_entry_button').data('uid', entry.uid).on('click', async function (e) {
3204- // delete button
3205- const deleteButton = template.find('.delete_entry_button');
3206- deleteButton.data('uid', entry.uid);
3207- deleteButton.on('click', async function (e) {
32082975 e.stopPropagation();
32092976 const uid = $(this).data('uid');
32102977 const deleted = await deleteWorldInfoEntry(data, uid);
@@ -3213,36 +2980,24 @@ export async function getWorldEntry(name, data, entry) {
32132980 await saveWorldInfo(name, data);
32142981 updateEditor(navigation_option.previous);
32152982 });
3216-
2983+ headerTemplate.find('.move_entry_button').attr('data-uid', entry.uid).attr('data-current-world', name).on('click', async function (e) {
3217- // move button
3218- const moveButton = template.find('.move_entry_button');
3219- moveButton.attr('data-uid', entry.uid);
3220- moveButton.attr('data-current-world', name);
3221- moveButton.on('click', async function (e) {
32222984 e.stopPropagation();
32232985 const sourceUid = $(this).attr('data-uid');
32242986 const sourceWorld = $(this).attr('data-current-world');
32252987 const sourceWorldInfo = await loadWorldInfo(sourceWorld);
32262988 if (!sourceWorldInfo) {return;
3227- return;
3228- }
32292989 const sourceName = sourceWorldInfo.entries[sourceUid]?.comment;
32302990 if (sourceName === undefined) {return;
3231- return;
3232- }
3233-
32342991 const select = document.createElement('select');
32352992 select.id = 'move_entry_target_select';
32362993 select.classList.add('text_pole', 'wide100p', 'marginTop10');
3237-
32382994 const defaultOption = document.createElement('option');
32392995 defaultOption.value = '';
32402996 defaultOption.textContent = `-- ${t`Select Target Lorebook`} --`;
32412997 select.appendChild(defaultOption);
3242-
32432998 let selectableWorldCount = 0;
32442999 world_names.forEach(worldName => {
32453000 if (worldName !== sourceWorld) { // Exclude current world
32463001 const option = document.createElement('option');
32473002 option.value = world_names.indexOf(worldName).toString();
32483003 option.textContent = worldName;
@@ -3250,177 +3005,343 @@ export async function getWorldEntry(name, data, entry) {
32503005 selectableWorldCount++;
32513006 }
32523007 });
3253-
32543008 if (selectableWorldCount === 0) {
32553009 toastr.warning(t`There are no other lorebooks to move to.`);
32563010 return;
32573011 }
3258-
3259- // Create wrapper div
32603012 const wrapper = document.createElement('div');
32613013 wrapper.textContent = t`Move '${sourceName}' to:`;
3262-
3263- // Create container and append elements
32643014 const container = document.createElement('div');
32653015 container.appendChild(wrapper);
32663016 container.appendChild(select);
3267-
32683017 let selectedWorldIndex = -1;
32693018 select.addEventListener('change', function () {
32703019 selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
32713020 });
3272-
32733021 const popupConfirm = await callGenericPopup(container, POPUP_TYPE.CONFIRM, '', {
32743022 okButton: t`Move`,
32753023 cancelButton: t`Cancel`,
32763024 });
32773025 if (!popupConfirm) {return;
3278- return;
3026+ if (selectedWorldIndex === -1) return;
3279- }
3280-
3281- if (selectedWorldIndex === -1) {
3282- return;
3283- }
3284-
32853027 const selectedValue = world_names[selectedWorldIndex];
3286-
32873028 if (!selectedValue) {
32883029 toastr.warning(t`Please select a target lorebook.`);
32893030 return;
32903031 }
3291-
32923032 await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid);
32933033 });
32943034
3295- // scan depth
3035+ let drawerInitialized = false;
3296- const scanDepthInput = template.find('input[name="scanDepth"]');
3036+ let drawerDestroyTimeout = null;
3297- scanDepthInput.data('uid', entry.uid);
3037+ headerTemplate.find('.inline-drawer').on('inline-drawer-toggle', function () {
3298- scanDepthInput.on('input', async function () {
3038+ if (drawerDestroyTimeout) {
3299- const uid = $(this).data('uid');
3039+ clearTimeout(drawerDestroyTimeout);
3300- const isEmpty = $(this).val() === '';
3040+ drawerDestroyTimeout = null;
3301- const value = Number($(this).val());
3041+ }
3302-
3042+ if (drawerInitialized) {
3303- // Clamp if necessary
3043+ drawerDestroyTimeout = setTimeout(() => {
3304- if (value < 0) {
3044+ // Drawer was reopened, so we don't destroy it
3305- $(this).val(0).trigger('input');
3045+ if (editOutlet.is(':visible')) {
3306- toastr.warning('Scan depth cannot be negative');
3046+ return;
3307- return;
3047+ }
3308- }
3048+ drawerInitialized = false;
3309-
3049+ clearEntryList(editOutlet);
3310- if (value > MAX_SCAN_DEPTH) {
3050+ drawerDestroyTimeout = null;
3311- $(this).val(MAX_SCAN_DEPTH).trigger('input');
3051+ }, debounce_timeout.relaxed);
3312- toastr.warning(`Scan depth cannot exceed ${MAX_SCAN_DEPTH}`);
3052+ } else {
3313- return;
3053+ drawerInitialized = true;
3054+ addEditorDrawerContent();
33143055 }
3315-
3316- data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
3317- setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
3318- await saveWorldInfo(name, data);
33193056 });
3320- scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
33213057
3322- // case sensitive select
3058+ const editOutlet = headerTemplate.find('.inline-drawer-outlet');
3323- const caseSensitiveSelect = template.find('select[name="caseSensitive"]');
3324- caseSensitiveSelect.data('uid', entry.uid);
3325- caseSensitiveSelect.on('input', async function () {
3326- const uid = $(this).data('uid');
3327- const value = $(this).val();
33283059
3329- data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
3060+ function addEditorDrawerContent() {
3330- setWIOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
3061+ const editTemplate = WI_ENTRY_EDIT_TEMPLATE.clone();
3331- await saveWorldInfo(name, data);
3332- });
3333- caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
33343062
3335- // match whole words select
3063+ // UID display
3336- const matchWholeWordsSelect = template.find('select[name="matchWholeWords"]');
3064+ editTemplate.find('.world_entry_form_uid_value').text(`(UID: ${entry.uid})`);
3337- matchWholeWordsSelect.data('uid', entry.uid);
3338- matchWholeWordsSelect.on('input', async function () {
3339- const uid = $(this).data('uid');
3340- const value = $(this).val();
33413065
3342- data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
3066+ // Key inputs
3343- setWIOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
3067+ const keyInput = enableKeysInputHelper({ template: editTemplate, entry, entryPropName: 'key', originalDataValueName: 'keys', name, data });
3344- await saveWorldInfo(name, data);
3068+ const keySecondaryInput = enableKeysInputHelper({ template: editTemplate, entry, entryPropName: 'keysecondary', originalDataValueName: 'secondary_keys', name, data });
3345- });
3069+ if (!keyInput.isFancy) initScrollHeight(keyInput.control);
3346- matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
3070+ if (!keySecondaryInput.isFancy) initScrollHeight(keySecondaryInput.control);
33473071
33483072 // use groupKey scoringinput selectswitch
3349- const useGroupScoringSelect = template.find('select[name="useGroupScoring"]');
3073+ editTemplate.find('.switch_input_type_icon').on('click', function () {
3350- useGroupScoringSelect.data('uid', entry.uid);
3074+ power_user.wi_key_input_plaintext = !power_user.wi_key_input_plaintext;
3351- useGroupScoringSelect.on('input', async function () {
3075+ saveSettingsDebounced();
33523076 const uid = ($(this).parents('.world_entry')).data('uid');
3353- const value = $(this).val();
3077+ updateEditor(uid, false);
3078+ $(`.world_entry[uid="${uid}"] .inline-drawer-icon`).trigger('click');
3079+ }).each((_, icon) => {
3080+ $(icon).attr('title', $(icon).data(power_user.wi_key_input_plaintext ? 'tooltip-on' : 'tooltip-off'));
3081+ $(icon).text($(icon).data(power_user.wi_key_input_plaintext ? 'icon-on' : 'icon-off'));
3082+ });
33543083
3355- data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
3084+ // Probability toggle
3356- setWIOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
3085+ handleProbabilityToggleHelper({
3357- await saveWorldInfo(name, data);
3086+ probabilityToggle: editTemplate.find('input[name="useProbability"]'),
3358- });
3087+ data, entry, name,
3359- useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
3088+ probabilityInput: headerTemplate.find('input[name="probability"]'),
3089+ setWIOriginalDataValue, saveWorldInfo,
3090+ });
33603091
3361- function handleMatchCheckbox(fieldName) {
3092+ // Comment toggle
33623093 const keycommentToggle = originalWIDataKeyMapeditTemplate.find('input[fieldNamename="addMemo"]');
3363- const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`);
3094+ commentToggle.data('uid', entry.uid);
3364- checkBoxElem.data('uid', entry.uid);
3095+ commentToggle.on('input', async function () {
3365- checkBoxElem.on('input', async function () {
33663096 const uid = $(this).data('uid');
33673097 const value = $(this).prop('checked');
3098+ const commentContainer = $(this).closest('.world_entry').find('.commentContainer');
3099+ data.entries[uid].addMemo = value;
3100+ await saveWorldInfo(name, data);
3101+ value ? commentContainer.show() : commentContainer.hide();
3102+ });
3103+ commentToggle.prop('checked', true).trigger('input');
3104+ commentToggle.parent().hide();
3105+
3106+ // Logic AND/NOT
3107+ const selectiveLogicDropdown = editTemplate.find('select[name="entryLogicType"]');
3108+ selectiveLogicDropdown.data('uid', entry.uid);
3109+ selectiveLogicDropdown.on('click', e => e.stopPropagation());
3110+ selectiveLogicDropdown.on('input', async function () {
3111+ const uid = $(this).data('uid');
3112+ const value = Number($(this).val());
3113+ data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
3114+ setWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
3115+ await saveWorldInfo(name, data);
3116+ });
3117+ editTemplate.find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`).prop('selected', true).trigger('input');
33683118
3369- data.entries[uid][fieldName] = value;
3119+ // Selective
3370- setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]);
3120+ const selectiveInput = editTemplate.find('input[name="selective"]');
3121+ selectiveInput.data('uid', entry.uid);
3122+ selectiveInput.on('input', async function () {
3123+ const uid = $(this).data('uid');
3124+ const value = $(this).prop('checked');
3125+ data.entries[uid].selective = value;
3126+ setWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
33713127 await saveWorldInfo(name, data);
3128+ const keysecondary = $(this).closest('.world_entry').find('.keysecondary');
3129+ const keysecondarytextpole = $(this).closest('.world_entry').find('.keysecondarytextpole');
3130+ const keyprimaryselect = $(this).closest('.world_entry').find('.keyprimaryselect');
3131+ const keyprimaryHeight = keyprimaryselect.outerHeight();
3132+ keysecondarytextpole.css('height', keyprimaryHeight + 'px');
3133+ value ? keysecondary.show() : keysecondary.hide();
3134+ });
3135+ selectiveInput.prop('checked', true).trigger('input');
3136+ selectiveInput.parent().hide();
3137+
3138+ // Character filter
3139+ const characterFilterLabel = editTemplate.find('label[for="characterFilter"] > small');
3140+ characterFilterLabel.text(entry.characterFilter?.isExclude ? 'Exclude Character(s)' : 'Filter to Character(s)');
3141+ const characterExclusionInput = editTemplate.find('input[name="character_exclusion"]');
3142+ characterExclusionInput.data('uid', entry.uid);
3143+ characterExclusionInput.on('input', async function () {
3144+ const uid = $(this).data('uid');
3145+ const value = $(this).prop('checked');
3146+ characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)');
3147+ if (data.entries[uid].characterFilter) {
3148+ if (!value && data.entries[uid].characterFilter.names.length === 0 && data.entries[uid].characterFilter.tags.length === 0) {
3149+ delete data.entries[uid].characterFilter;
3150+ } else {
3151+ data.entries[uid].characterFilter.isExclude = value;
3152+ }
3153+ } else if (value) {
3154+ Object.assign(data.entries[uid], { characterFilter: { isExclude: true, names: [], tags: [] } });
3155+ }
3156+ if (data.entries[uid]?.characterFilter?.names?.length > 0) {
3157+ for (const name of [...data.entries[uid].characterFilter.names]) {
3158+ if (!getContext().characters.find(x => x.avatar.replace(/\.[^/.]+$/, '') === name)) {
3159+ data.entries[uid].characterFilter.names = data.entries[uid].characterFilter.names.filter(x => x !== name);
3160+ }
3161+ }
3162+ }
3163+ setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
3164+ await saveWorldInfo(name, data);
3165+ });
3166+ characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
3167+
3168+ const characterFilter = editTemplate.find('select[name="characterFilter"]');
3169+ characterFilter.data('uid', entry.uid);
3170+ initCharacterFilterSelect2Helper(characterFilter, t);
3171+ fillCharacterAndTagOptionsHelper({ characterFilter, entry, getContext });
3172+ handleCharacterFilterChangeHelper({
3173+ characterFilter, data, entry, name, world_names, getContext, setWIOriginalDataValue, saveWorldInfo, t,
33723174 });
3373- checkBoxElem.prop('checked', !!entry[fieldName]).trigger('input');
3374- }
33753175
3376- handleMatchCheckbox('matchPersonaDescription');
3176+ // Content
3377- handleMatchCheckbox('matchCharacterDescription');
3177+ const counter = editTemplate.find('.world_entry_form_token_counter');
3378- handleMatchCheckbox('matchCharacterPersonality');
3178+ const countTokensDebounced = debounce(async function (counter, value) {
3379- handleMatchCheckbox('matchCharacterDepthPrompt');
3179+ const numberOfTokens = await getTokenCountAsync(value);
3380- handleMatchCheckbox('matchScenario');
3180+ $(counter).text(numberOfTokens);
3381- handleMatchCheckbox('matchCreatorNotes');
3181+ }, debounce_timeout.relaxed);
3182+ const contentInputId = `world_entry_content_${entry.uid}`;
3183+ const contentInput = editTemplate.find('textarea[name="content"]');
3184+ contentInput.data('uid', entry.uid);
3185+ contentInput.attr('id', contentInputId);
3186+ contentInput.on('input', async function (_, { skipCount } = {}) {
3187+ const uid = $(this).data('uid');
3188+ const value = $(this).val();
3189+ data.entries[uid].content = value;
3190+ setWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);
3191+ await saveWorldInfo(name, data);
3192+ if (!skipCount) countTokensDebounced(counter, value);
3193+ });
3194+ contentInput.val(entry.content).trigger('input', { skipCount: true });
3195+ editTemplate.find('.editor_maximize').attr('data-for', contentInputId);
33823196
33833197 // automationScan iddepth
33843198 const automationIdInputscanDepthInput = templateeditTemplate.find('input[name="automationIdscanDepth"]');
33853199 automationIdInput scanDepthInput.data('uid', entry.uid);
33863200 automationIdInput scanDepthInput.on('input', async function () {
33873201 const uid = $(this).data('uid');
33883202 const valueisEmpty = $(this).val() === '';
3203+ const value = Number($(this).val());
3204+ if (value < 0) {
3205+ $(this).val(0).trigger('input');
3206+ toastr.warning('Scan depth cannot be negative');
3207+ return;
3208+ }
3209+ if (value > MAX_SCAN_DEPTH) {
3210+ $(this).val(MAX_SCAN_DEPTH).trigger('input');
3211+ toastr.warning(`Scan depth cannot exceed ${MAX_SCAN_DEPTH}`);
3212+ return;
3213+ }
3214+ data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
3215+ setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
3216+ await saveWorldInfo(name, data);
3217+ });
3218+ scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
33893219
3390- data.entries[uid].automationId = value;
3220+ // Group
3391- setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
3221+ const groupInput = editTemplate.find('input[name="group"]');
3392- await saveWorldInfo(name, data);
3222+ groupInput.data('uid', entry.uid);
3393- });
3223+ groupInput.on('input', async function () {
3394- automationIdInput.val(entry.automationId ?? '').trigger('input');
3224+ const uid = $(this).data('uid');
3395- setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);
3225+ const value = String($(this).val()).trim();
3226+ data.entries[uid].group = value;
3227+ setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
3228+ await saveWorldInfo(name, data);
3229+ });
3230+ groupInput.val(entry.group ?? '').trigger('input');
3231+ setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
33963232
3397- template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed
3233+ // Inclusion priority
3234+ const groupOverrideInput = editTemplate.find('input[name="groupOverride"]');
3235+ groupOverrideInput.data('uid', entry.uid);
3236+ groupOverrideInput.on('input', async function () {
3237+ const uid = $(this).data('uid');
3238+ const value = $(this).prop('checked');
3239+ data.entries[uid].groupOverride = value;
3240+ setWIOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
3241+ await saveWorldInfo(name, data);
3242+ });
3243+ groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
33983244
3399- function updatePosOrdDisplay(uid) {
3245+ // Group weight
3400- // display position/order info left of keyword box
3246+ handleNumberInputHelper({
3401- let entry = data.entries[uid];
3247+ inputElem: editTemplate.find('input[name="groupWeight"]'),
3402- let posText = entry.position;
3248+ entry, entryKey: 'groupWeight', data, name, setWIOriginalDataValue, saveWorldInfo,
3403- switch (entry.position) {
3249+ min: 1, max: 10000, clamp: true,
3404- case 0:
3250+ });
3405- posText = '↑CD';
3251+
3406- break;
3252+ // Sticky, cooldown, delay
3407- case 1:
3253+ handleNumberInputHelper({
3408- posText = 'CD↓';
3254+ inputElem: editTemplate.find('input[name="sticky"]'),
3409- break;
3255+ entry, entryKey: 'sticky', data, name, setWIOriginalDataValue, saveWorldInfo,
3410- case 2:
3256+ min: 1, max: 10000, clamp: false,
3411- posText = '↑AN';
3257+ });
3412- break;
3258+ handleNumberInputHelper({
3413- case 3:
3259+ inputElem: editTemplate.find('input[name="cooldown"]'),
3414- posText = 'AN↓';
3260+ entry, entryKey: 'cooldown', data, name, setWIOriginalDataValue, saveWorldInfo,
3415- break;
3261+ min: 1, max: 10000, clamp: false,
3416- case 4:
3262+ });
3417- posText = `@D${entry.depth}`;
3263+ handleNumberInputHelper({
3418- break;
3264+ inputElem: editTemplate.find('input[name="delay"]'),
3419- }
3265+ entry, entryKey: 'delay', data, name, setWIOriginalDataValue, saveWorldInfo,
3420- template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`);
3266+ min: 1, max: 10000, clamp: false,
3267+ });
3268+
3269+ // Exclude/prevent recursion
3270+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'excludeRecursion', data, name });
3271+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'preventRecursion', data, name });
3272+
3273+ // Delay until recursion
3274+ const delayUntilRecursionInput = editTemplate.find('input[name="delay_until_recursion"]');
3275+ delayUntilRecursionInput.data('uid', entry.uid);
3276+ const delayUntilRecursionLevelInput = editTemplate.find('input[name="delayUntilRecursionLevel"]');
3277+ delayUntilRecursionLevelInput.data('uid', entry.uid);
3278+ delayUntilRecursionInput.on('input', async function () {
3279+ const uid = $(this).data('uid');
3280+ const toggled = $(this).prop('checked');
3281+ const value = toggled ? data.entries[uid].delayUntilRecursion || true : false;
3282+ if (!toggled) delayUntilRecursionLevelInput.val('');
3283+ data.entries[uid].delayUntilRecursion = value;
3284+ setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
3285+ await saveWorldInfo(name, data);
3286+ });
3287+ delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
3288+ delayUntilRecursionLevelInput.on('input', async function () {
3289+ const uid = $(this).data('uid');
3290+ const content = $(this).val();
3291+ const value = content === '' ? (typeof data.entries[uid].delayUntilRecursion === 'boolean' ? data.entries[uid].delayUntilRecursion : true)
3292+ : content === 1 ? true
3293+ : !isNaN(Number(content)) ? Number(content)
3294+ : false;
3295+ data.entries[uid].delayUntilRecursion = value;
3296+ setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
3297+ await saveWorldInfo(name, data);
3298+ });
3299+ delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input');
3300+
3301+ // Boolean selects
3302+ handleBooleanSelectHelper({
3303+ selectElem: editTemplate.find('select[name="caseSensitive"]'),
3304+ entry, entryKey: 'caseSensitive', data, name, setWIOriginalDataValue, saveWorldInfo,
3305+ });
3306+ handleBooleanSelectHelper({
3307+ selectElem: editTemplate.find('select[name="matchWholeWords"]'),
3308+ entry, entryKey: 'matchWholeWords', data, name, setWIOriginalDataValue, saveWorldInfo,
3309+ });
3310+ handleBooleanSelectHelper({
3311+ selectElem: editTemplate.find('select[name="useGroupScoring"]'),
3312+ entry, entryKey: 'useGroupScoring', data, name, setWIOriginalDataValue, saveWorldInfo,
3313+ });
3314+
3315+ // Match checkboxes
3316+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchPersonaDescription', data, name });
3317+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCharacterDescription', data, name });
3318+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCharacterPersonality', data, name });
3319+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCharacterDepthPrompt', data, name });
3320+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchScenario', data, name });
3321+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCreatorNotes', data, name });
3322+
3323+ // Automation ID
3324+ const automationIdInput = editTemplate.find('input[name="automationId"]');
3325+ automationIdInput.data('uid', entry.uid);
3326+ automationIdInput.on('input', async function () {
3327+ const uid = $(this).data('uid');
3328+ const value = $(this).val();
3329+ data.entries[uid].automationId = value;
3330+ setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
3331+ await saveWorldInfo(name, data);
3332+ });
3333+ automationIdInput.val(entry.automationId ?? '').trigger('input');
3334+ setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);
3335+
3336+ countTokensDebounced(counter, contentInput.val());
3337+
3338+ editTemplate.find('.inline-drawer-content').css('display', 'none');
3339+ editOutlet.append(editTemplate);
34213340 }
34223341
3423- return template;
3342+ headerTemplate.find('.inline-drawer-content').css('display', 'none');
3343+
3344+ return headerTemplate;
34243345}
34253346
34263347/**
@@ -3661,6 +3582,11 @@ async function _save(name, data) {
36613582 * @return {Promise<void>} A promise that resolves when the world info is saved
36623583 */
36633584export async function saveWorldInfo(name, data, immediately = false) {
3585+ // Saving is temporarily disabled
3586+ if (isSaveWorldInfoDisabled) {
3587+ return;
3588+ }
3589+
36643590 if (!name || !data) {
36653591 return;
36663592 }
@@ -3819,7 +3745,7 @@ export async function createNewWorldInfo(worldName, { interactive = false } = {}
38193745 if (selectedIndex !== -1) {
38203746 $('#world_editor_select').val(selectedIndex).trigger('change');
38213747 } else {
38223748 await hideWorldEditor();
38233749 }
38243750
38253751 return true;
@@ -4067,6 +3993,8 @@ function parseDecorators(content) {
40673993 * @property {Set<any>} allActivatedEntries All entries.
40683994 * @returns {Promise<WIActivated>} The world info activated.
40693995 */
3996+
3997+//MARK: checkWorldInfo
40703998export async function checkWorldInfo(chat, maxContext, isDryRun, globalScanData) {
40713999 const context = getContext();
40724000 const buffer = new WorldInfoBuffer(chat, globalScanData);
@@ -5385,7 +5313,7 @@ export function initWorldInfo() {
53855313 const selectedIndex = String($('#world_editor_select').find(':selected').val());
53865314
53875315 if (selectedIndex === '') {
53885316 await hideWorldEditor();
53895317 } else {
53905318 const worldName = world_names[selectedIndex];
53915319 showWorldEditor(worldName);
@@ -5537,9 +5465,12 @@ export function initWorldInfo() {
55375465 select2ChoiceClickSubscribe($('#world_info'), target => {
55385466 const name = $(target).text();
55395467 const selectedIndex = world_names.indexOf(name);
5540- if (selectedIndex !== -1) {
5468+ const alreadySelectedInEditor = $('#world_editor_select option:selected').text() === name;
5469+ if (selectedIndex !== -1 && !alreadySelectedInEditor) {
55415470 $('#world_editor_select').val(selectedIndex).trigger('change');
55425471 console.log('Quick selection of world', name);
5472+ } else {
5473+ console.warn('lets not reload an already loaded list yes?');
55435474 }
55445475 }, { buttonStyle: true, closeDrawer: true });
55455476 }
public/style.css+133 -72
@@ -14,6 +14,7 @@
1414@import url(css/data-maid.css);
1515
1616:root {
17+ interpolate-size: allow-keywords;
1718 --doc-height: 100%;
1819 --transparent: rgba(0, 0, 0, 0);
1920
@@ -122,7 +123,9 @@
122123 --inline-avatar-small-factor: 0.6;
123124
124125 --animation-duration: 125ms;
125126 --animation-duration-slow2x: calc(var(--animation-duration) * 32);
127+ --animation-duration-3x: calc(var(--animation-duration) * 3);
128+ --animation-duration-slow: var(--animation-duration-3x);
126129}
127130
128131* {
@@ -719,7 +722,7 @@ hr {
719722 position: absolute;
720723 width: 100%;
721724 height: 100%;
722725 transition: background-image 0.5svar(--animation-duration-3x) ease-in-out;
723726}
724727
725728/* Background fitting options */
@@ -835,7 +838,7 @@ body.reduced-motion #bg_custom {
835838 cursor: -webkit-grab;
836839 display: none;
837840 filter: drop-shadow(0px 0px 0px black);
838841 transition: all 250msvar(--animation-duration-2x);
839842 font-size: calc(var(--mainFontSize)*1.3);
840843}
841844
@@ -851,7 +854,7 @@ body .panelControlBar {
851854 min-width: 55px;
852855 justify-content: flex-end;
853856 gap: 0px;
854857 transition: all 250msvar(--animation-duration-2x);
855858}
856859
857860.zoomed_avatar .panelControlBar {
@@ -973,7 +976,7 @@ body .panelControlBar {
973976 display: flex;
974977 align-items: center;
975978 justify-content: center;
976979 transition: opacity 300msvar(--animation-duration-2x);
977980}
978981
979982#rightSendForm>div:hover,
@@ -1000,7 +1003,7 @@ body .panelControlBar {
10001003 padding-right: 2px;
10011004 place-self: center;
10021005 cursor: pointer;
1003- transition: 0.3s;
1006+ transition: var(--animation-duration-2x);
10041007 opacity: 0.7;
10051008}
10061009
@@ -1047,7 +1050,7 @@ body .panelControlBar {
10471050 padding-right: 2px;
10481051 place-self: center;
10491052 cursor: pointer;
1050- transition: 0.3s;
1053+ transition: var(--animation-duration-2x);
10511054 opacity: 1;
10521055 display: none;
10531056
@@ -1094,7 +1097,7 @@ body .panelControlBar {
10941097 z-index: 2001;
10951098 margin-left: 10px;
10961099 padding: 0;
1097- transition: 0.3s;
1100+ transition: var(--animation-duration-2x);
10981101 display: flex;
10991102 align-items: center;
11001103 order: 2;
@@ -1337,7 +1340,7 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
13371340 border: var(--avatar-base-border-radius) solid var(--SmartThemeBodyColor);
13381341 margin: var(--avatar-base-border-radius);
13391342 cursor: pointer;
13401343 transition: filter 0.2svar(--animation-duration) ease-in-out;
13411344}
13421345
13431346.add_avatar:hover {
@@ -1350,7 +1353,7 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
13501353 border-radius: var(--avatar-base-border-radius-round);
13511354 border-style: none;
13521355 flex: 1;
1353- transition: 250ms;
1356+ transition: var(--animation-duration-2x);
13541357}
13551358
13561359.last_mes .mesAvatarWrapper {
@@ -1542,7 +1545,7 @@ button {
15421545 100% var(--progWidthClip),
15431546 100% 100%,
15441547 0% 100%);
15451548 transition: clip-path 200msvar(--animation-duration-2x);
15461549}
15471550
15481551#send_textarea:placeholder-shown {
@@ -2493,8 +2496,8 @@ body[data-stscript-style] .hljs.language-stscript {
24932496 margin: 3px;
24942497 opacity: 0.75;
24952498 filter: grayscale(1);
24962499 -webkit-transition: all 250msvar(--animation-duration-2x) ease-in-out;
24972500 transition: all 250msvar(--animation-duration-2x) ease-in-out;
24982501}
24992502
25002503#character_popup .editor_maximize:hover {
@@ -2648,8 +2651,8 @@ input[type="file"] {
26482651 margin-top: 0;
26492652 padding: 1px;
26502653 filter: grayscale(1) brightness(75%);
26512654 -webkit-transition: all 0.5svar(--animation-duration-3x) ease-in-out;
26522655 transition: all 0.5svar(--animation-duration-3x) ease-in-out;
26532656}
26542657
26552658.right_menu_button:hover {
@@ -2669,7 +2672,7 @@ input[type="file"] {
26692672#lm_button_panel_pin_div,
26702673#WI_button_panel_pin_div {
26712674 opacity: 0.5;
2672- transition: 0.3s;
2675+ transition: var(--animation-duration-2x);
26732676}
26742677
26752678#rm_button_panel_pin_div:hover,
@@ -2805,7 +2808,6 @@ input[type="file"] {
28052808}
28062809
28072810#rm_extensions_block {
2808- display: none;
28092811 overflow-y: auto;
28102812}
28112813
@@ -2830,6 +2832,7 @@ input[type="file"] {
28302832 margin: 0;
28312833 right: unset;
28322834 width: calc(((100dvw - var(--sheldWidth)) / 2) - 1px);
2835+ height: unset;
28332836
28342837}
28352838
@@ -2871,7 +2874,7 @@ input[type="file"] {
28712874 aspect-ratio: 1 / 1;
28722875 font-size: 20px;
28732876 opacity: 0.5;
28742877 transition: all 250msvar(--animation-duration-2x);
28752878}
28762879
28772880.floating_panel_maximize:hover,
@@ -2885,7 +2888,7 @@ input[type="file"] {
28852888 aspect-ratio: 1 / 1;
28862889 font-size: calc(var(--mainFontSize)*1.3);
28872890 opacity: 0.5;
28882891 transition: all 250msvar(--animation-duration-2x);
28892892 filter: drop-shadow(0px 0px 2px black);
28902893 text-shadow: none;
28912894}
@@ -2934,7 +2937,6 @@ select option:not(:checked) {
29342937/*#######################################################################*/
29352938
29362939#rm_api_block {
2937- display: none;
29382940 overflow-y: auto;
29392941}
29402942
@@ -3021,7 +3023,7 @@ select option:not(:checked) {
30213023
30223024#rm_characters_block {
30233025 display: flex;
30243026 overflow-y: autohidden;
30253027 flex-direction: column;
30263028 height: 100%;
30273029}
@@ -3321,7 +3323,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
33213323 text-shadow: none;
33223324 margin: 0;
33233325 filter: drop-shadow(0px 0px 3px white);
33243326 transition: opacity 0.2svar(--animation-duration-2x) ease-in-out;
33253327 display: none;
33263328}
33273329
@@ -3474,7 +3476,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
34743476 border: 1px solid var(--SmartThemeBorderColor);
34753477 border-radius: 10px;
34763478 cursor: pointer;
3477- transition: 0.2s;
3479+ transition: var(--animation-duration-2x);
34783480}
34793481
34803482.avatar-container {
@@ -3769,7 +3771,7 @@ grammarly-extension {
37693771 width: min-content;
37703772 cursor: pointer;
37713773 margin: 5px 0;
3772- transition: 0.3s;
3774+ transition: var(--animation-duration-2x);
37733775 display: flex;
37743776 align-items: center;
37753777 justify-content: center;
@@ -3835,7 +3837,7 @@ grammarly-extension {
38353837 background-color: var(--black30a);
38363838 border: 1px solid var(--SmartThemeBorderColor);
38373839 cursor: grab;
38383840 transition: background-color 200msvar(--animation-duration-2x) ease-in-out;
38393841 position: relative;
38403842 user-select: none;
38413843 display: flex;
@@ -4009,7 +4011,7 @@ input[type="checkbox"]::before {
40094011 width: 0.65em;
40104012 height: 0.65em;
40114013 transform: scale(0);
40124014 transition: 120msvar(--animation-duration) transform ease-in-out;
40134015 box-shadow: inset 1em 1em var(--SmartThemeCheckboxTickColor);
40144016 transform-origin: bottom left;
40154017 clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
@@ -4181,6 +4183,13 @@ input[type='checkbox'].del_checkbox {
41814183 background-repeat: no-repeat !important;
41824184 box-shadow: inset 0 0 2px var(--black50a) !important;
41834185 cursor: ew-resize !important;
4186+}
4187+
4188+/*
4189+ * Setting a z-index will allow scrollbar to display properly in Firefox,
4190+ * while constraining to open drawers fixes a single frame linger in Chrome.
4191+ */
4192+.drawer-content.openDrawer .neo-range-slider {
41844193 z-index: 1;
41854194}
41864195
@@ -4345,7 +4354,7 @@ input[type="range"]::-webkit-slider-thumb {
43454354 border-radius: 100%;
43464355 box-shadow: 0 0 3px black;
43474356 */
43484357 transition: all 250msvar(--animation-duration-2x);
43494358}
43504359
43514360.topRightInset {
@@ -4365,7 +4374,7 @@ input[type="range"]::-webkit-slider-thumb {
43654374 font-weight: 400;
43664375 text-decoration: none;
43674376 border-bottom: 2px dashed red;
4368- transition: 0.3s;
4377+ transition: var(--animation-duration-2x);
43694378}
43704379
43714380.whitespacenowrap {
@@ -4398,7 +4407,7 @@ input[type="range"]::-webkit-slider-thumb {
43984407 gap: 4px;
43994408 flex-wrap: nowrap;
44004409 justify-content: flex-end;
44014410 transition: all 200msvar(--animation-duration-2x);
44024411 overflow-x: hidden;
44034412 padding: 1px;
44044413}
@@ -4410,7 +4419,7 @@ input[type="range"]::-webkit-slider-thumb {
44104419.mes_button,
44114420.extraMesButtons>div {
44124421 cursor: pointer;
44134422 transition: opacity 0.2svar(--animation-duration-2x) ease-in-out;
44144423 filter: drop-shadow(0px 0px 2px black);
44154424 opacity: 0.3;
44164425 padding: 1px 3px;
@@ -4441,7 +4450,7 @@ input[type="range"]::-webkit-slider-thumb {
44414450 cursor: pointer;
44424451 padding-bottom: 5px;
44434452 filter: drop-shadow(0px 0px 2px black);
44444453 transition: 0.3svar(--animation-duration-2x) ease-in-out;
44454454}
44464455
44474456.mes_reasoning_actions {
@@ -4776,7 +4785,7 @@ body .ui-slider-handle {
47764785 text-align: center;
47774786 margin-left: 0;
47784787 opacity: 1 !important;
47794788 transition: filter 200msvar(--animation-duration-2x);
47804789 filter: brightness(1.2);
47814790}
47824791
@@ -4834,7 +4843,7 @@ body .ui-widget-content li {
48344843 align-items: center;
48354844 cursor: pointer;
48364845 opacity: 0.5;
48374846 transition: opacity 200msvar(--animation-duration-2x);
48384847}
48394848
48404849body .ui-widget-content li:hover {
@@ -4996,34 +5005,6 @@ a:hover {
49965005/* Right nav panel and nav-toggle */
49975006/* ############################################################# */
49985007
4999-#right-nav-panel {
5000- width: calc((100vw - var(--sheldWidth) - 2px) /2);
5001- width: calc((100dvw - var(--sheldWidth) - 2px) /2);
5002- max-height: calc(100vh - var(--topBarBlockSize));
5003- max-height: calc(100dvh - var(--topBarBlockSize));
5004- height: calc(100vh - var(--topBarBlockSize));
5005- height: calc(100dvh - var(--topBarBlockSize));
5006- position: fixed;
5007- top: 0;
5008- margin: 0;
5009- right: 0;
5010- left: calc(100% - var(--sheldWidth) /2 + var(--sheldWidth) +1px);
5011- padding: 5px;
5012- margin-bottom: 5px;
5013- backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
5014- background-color: var(--SmartThemeBlurTintColor);
5015- -webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
5016- z-index: 3000;
5017-
5018- border: 1px solid var(--SmartThemeBorderColor);
5019-
5020- box-shadow: none;
5021- border-radius: 10px;
5022- overflow: hidden;
5023- flex-flow: column;
5024- min-width: 100px;
5025-}
5026-
50275008#nav-toggle {
50285009 position: fixed;
50295010 right: 13px;
@@ -5047,7 +5028,7 @@ a:hover {
50475028 display: none;
50485029 position: relative;
50495030 width: fit-content;
50505031 transition: all 0.1svar(--animation-duration);
50515032 padding: 0.5rem;
50525033}
50535034
@@ -5239,7 +5220,7 @@ body:not(.sd) .mes_img_swipes {
52395220 border-radius: 10px;
52405221 padding: 2px 5px;
52415222 border: 1px solid var(--SmartThemeBorderColor);
52425223 transition: all 250msvar(--animation-duration-2x);
52435224}
52445225
52455226.standoutHeader.inline-drawer-header {
@@ -5322,12 +5303,12 @@ body:has(#character_popup.open) #top-settings-holder:has(.drawer-content.openDra
53225303}
53235304
53245305.drawer-icon.openIcon {
53255306 transition: all 0.275svar(--animation-duration-2x);
53265307}
53275308
53285309.drawer-icon.closedIcon {
53295310 opacity: 0.3;
53305311 transition: all 0.275svar(--animation-duration-2x);
53315312}
53325313
53335314.drawer-icon.closedIcon:hover {
@@ -5390,7 +5371,7 @@ body:has(#character_popup.open) #top-settings-holder:has(.drawer-content.openDra
53905371 border: 1px solid var(--SmartThemeBorderColor);
53915372 min-width: 450px;
53925373 width: var(--sheldWidth);
53935374 overflow-y: autohidden;
53945375 max-height: calc(100vh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
53955376 max-height: calc(100dvh - calc(var(--topBarBlockSize) + var(--bottomFormBlockSize)));
53965377 display: none;
@@ -5399,9 +5380,30 @@ body:has(#character_popup.open) #top-settings-holder:has(.drawer-content.openDra
53995380 left: 0;
54005381 right: 0;
54015382 margin: 0 auto;
5383+ height: 0;
54025384 backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
54035385 -webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
54045386 /* z-index: 1000 !important; */
5387+ transition-property: height, display;
5388+ transition-duration: var(--animation-duration-2x);
5389+ transition-timing-function: ease-in-out;
5390+ transition-behavior: allow-discrete;
5391+}
5392+
5393+#movingDivs>.drawer-content {
5394+ height: unset;
5395+}
5396+
5397+.drawer-content.openDrawer {
5398+ display: block;
5399+ height: auto;
5400+ height: calc-size(auto, size);
5401+ overflow-y: auto;
5402+ animation: hide-scroll var(--animation-duration-2x) backwards;
5403+
5404+ @starting-style {
5405+ height: 0;
5406+ }
54055407}
54065408
54075409body.movingUI .inline-drawer-maximize {
@@ -5428,8 +5430,7 @@ body:not(.movingUI) .drawer-content.maximized {
54285430.fillLeft {
54295431 width: calc((100vw - var(--sheldWidth) - 2px) /2);
54305432 width: calc((100dvw - var(--sheldWidth) - 2px) /2);
5431- height: calc(100vh - var(--topBarBlockSize));
5433+ height: 0;
5432- height: calc(100dvh - var(--topBarBlockSize));
54335434 max-height: calc(100vh - var(--topBarBlockSize));
54345435 max-height: calc(100dvh - var(--topBarBlockSize));
54355436 position: fixed;
@@ -5443,6 +5444,66 @@ body:not(.movingUI) .drawer-content.maximized {
54435444 border: 1px solid var(--SmartThemeBorderColor);
54445445}
54455446
5447+.fillLeft.openDrawer {
5448+ overflow-y: hidden;
5449+ height: calc(100vh - var(--topBarBlockSize));
5450+ height: calc(100dvh - var(--topBarBlockSize));
5451+ height: calc-size((100dvh - var(--topBarBlockSize)), size);
5452+
5453+ @starting-style {
5454+ height: 0;
5455+ }
5456+}
5457+
5458+.fillRight {
5459+ width: calc((100vw - var(--sheldWidth) - 2px) /2);
5460+ width: calc((100dvw - var(--sheldWidth) - 2px) /2);
5461+ max-height: calc(100vh - var(--topBarBlockSize));
5462+ max-height: calc(100dvh - var(--topBarBlockSize));
5463+ height: 0;
5464+ position: fixed;
5465+ top: 0;
5466+ margin: 0;
5467+ right: 0;
5468+ left: calc(100% - var(--sheldWidth) /2 + var(--sheldWidth) +1px);
5469+ padding: 5px;
5470+ margin-bottom: 5px;
5471+ backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
5472+ background-color: var(--SmartThemeBlurTintColor);
5473+ -webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength)));
5474+ z-index: 3000;
5475+
5476+ border: 1px solid var(--SmartThemeBorderColor);
5477+
5478+ box-shadow: none;
5479+ border-radius: 10px;
5480+ overflow: hidden;
5481+ flex-flow: column;
5482+ min-width: 100px;
5483+}
5484+
5485+.fillRight.openDrawer {
5486+ display: flex;
5487+ height: calc(100vh - var(--topBarBlockSize));
5488+ height: calc(100dvh - var(--topBarBlockSize));
5489+ height: calc-size((100dvh - var(--topBarBlockSize)), size);
5490+
5491+ @starting-style {
5492+ height: 0;
5493+ }
5494+}
5495+
5496+/*
5497+.fillRight #rm_print_characters_block {
5498+ overflow-y: hidden;
5499+}
5500+
5501+.fillRight.openDrawer #rm_print_characters_block {
5502+ overflow-y: auto;
5503+ animation: hide-scroll var(--animation-duration-2x) backwards;
5504+}
5505+*/
5506+
54465507.scrollableInner {
54475508 overflow-x: hidden;
54485509 height: calc(100% - 30px);
@@ -5709,7 +5770,7 @@ body:not(.movingUI) .drawer-content.maximized {
57095770 aspect-ratio: 1 / 1;
57105771 font-size: 20px;
57115772 opacity: 0.5;
57125773 transition: all 250msvar(--animation-duration-2x);
57135774}
57145775
57155776/* #groupMemberListPopout,
@@ -5796,7 +5857,7 @@ body:not(.movingUI) .drawer-content.maximized {
57965857 color: var(--SmartThemeBodyColor);
57975858 border: 1px solid var(--SmartThemeBorderColor);
57985859 border-radius: 5px;
57995860 transition: opacity 0.2svar(--animation-duration-2x);
58005861 opacity: 0.8;
58015862 cursor: pointer;
58025863}
@@ -5988,7 +6049,7 @@ body:not(.movingUI) .drawer-content.maximized {
59886049 margin: 7px;
59896050 width: calc(100% - 7px);
59906051 box-sizing: border-box;
59916052 transition: transform 0.2svar(--animation-duration-2x) ease-in-out, background-color 0.2svar(--animation-duration-2x) ease-in-out, border 0.2svar(--animation-duration-2x) ease-in-out;
59926053}
59936054
59946055.model-card .details-container {
@@ -6000,7 +6061,7 @@ body:not(.movingUI) .drawer-content.maximized {
60006061 transform: scale(1.01);
60016062 background-color: #444;
60026063 /* Smooth transition */
60036064 transition: transform 0.2svar(--animation-duration-2x) ease-in-out, background-color 0.2svar(--animation-duration-2x) ease-in-out;
60046065}
60056066
60066067.model-card.selected {