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

f33db29e76f474e4f938720d2ca06ba556af1d12

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

Signed
27 files changed, +1270 -1151Showing whitespace changes
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+85 -77
@@ -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>
54615461 </div>
5462+ <hr class="wide100p">
54625463 </div>
54635464 <hr/div>
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,6 +5487,8 @@
54855487 </div>
54865488 </div>
54875489 <!-- end group peeking cope structure-->
5490+
5491+ <div class="scrollableInner">
54885492 <div name="Solo Char Create/Edit Panel" id="rm_ch_create_block" class="right_menu flex-container flexFlowColumn" style="display: none;">
54895493 <form id="form_create" action="javascript:void(null);" method="post" enctype="multipart/form-data">
54905494 <div id="avatar-and-name-block">
@@ -5820,6 +5824,7 @@
58205824 </div>
58215825 <div id="rm_print_characters_block" class="flexFlowColumn"></div>
58225826 </div>
5827+ </div>
58235828 </nav>
58245829 </div>
58255830 </div>
@@ -6195,79 +6200,7 @@
61956200 </div>
61966201 </div>
61976202 <div id="entry_edit_template" class="template_element">
61986203 <div class="world_entryworld_entry_edit">
6199- <form class="world_entry_form wi-card-entry">
6200- <div class="inline-drawer wide100p">
6201- <div class="inline-drawer-header gap5px padding0">
6202- <span class="drag-handle">&#9776;</span>
6203- <div class="gap5px world_entry_thin_controls wide100p alignitemscenter">
6204- <div class="inline-drawer-toggle fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6205- <div class="fa-solid fa-toggle-on killSwitch" name="entryKillSwitch" data-i18n="[title]Toggle entry's active state." title="Toggle entry's active state."></div>
6206- <div class="flex-container alignitemscenter wide100p">
6207- <div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter">
6208- <div class="flex-container flex1">
6209- <textarea class="text_pole" rows="1" name="comment" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
6210- </div>
6211- <!-- <span class="world_entry_form_position_value"></span> -->
6212- <select data-i18n="[title]WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized" title="WI Entry Status:&#13;🔵 Constant&#13;🟢 Normal&#13;🔗 Vectorized" name="entryStateSelector" class="text_pole widthNatural margin0">
6213- <option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option>
6214- <option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option>
6215- <option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option>
6216- </select>
6217- </div>
6218- <div class="WIEnteryHeaderControls flex-container">
6219- <div name="PositionBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text">
6220- <label for="position" class="WIEntryHeaderTitleMobile" data-i18n="Position:">Position:</label>
6221- <select name="position" class="text_pole widthNatural margin0" data-i18n="[title]T_Position" title="↑Char: Before Character Definitions&#13;↓Char: After Character Definitions&#13;↑EM: Before Example Messages&#13;↓EM: After Example Messages&#13;↑AN: Before Author's Note&#13;↓AN: After Author's Note&#13;@D ⚙️: at Depth (System)&#13;@D 👤: at Depth (User)&#13;@D 🤖: at Depth (Assistant)">
6222- <option value="0" data-role="" data-i18n="Before Char Defs">
6223- ↑Char
6224- </option>
6225- <option value="1" data-role="" data-i18n="After Char Defs">
6226- ↓Char
6227- </option>
6228- <option value="5" data-role="" data-i18n="Before EM">
6229- ↑EM
6230- </option>
6231- <option value="6" data-role="" data-i18n="After EM">
6232- ↓EM
6233- </option>
6234- <option value="2" data-role="" data-i18n="Before AN">
6235- ↑AN
6236- </option>
6237- <option value="3" data-role="" data-i18n="After AN">
6238- ↓AN
6239- </option>
6240- <option value="4" data-role="0" data-i18n="at Depth System">
6241- @D ⚙️
6242- </option>
6243- <option value="4" data-role="1" data-i18n="at Depth User">
6244- @D 👤
6245- </option>
6246- <option value="4" data-role="2" data-i18n="at Depth AI">
6247- @D 🤖
6248- </option>
6249- </select>
6250- </div>
6251- <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6252- <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>
6253- <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999" />
6254- </div>
6255- <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6256- <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>
6257- <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999" />
6258- </div>
6259- <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">
6260- <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>
6261- <input title="Probability" data-i18n="[title]Probability" class="text_pole wideMax100px margin0" type="number" name="probability" placeholder="" min="0" max="100" />
6262- </div>
6263- </div>
6264- </div>
6265- </div>
6266- <i class="menu_button move_entry_button fa-solid fa-right-left" title="Move Entry to Another Lorebook" data-i18n="[title]Move Entry to Another Lorebook"></i>
6267- <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>
6268- <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>
6269- </div>
6270- <div class="inline-drawer-content flex-container paddingBottom5px wide100p">
62716204 <div class="flex-container wide100p alignitemscenter">
62726205 <div name="keywordsAndLogicBlock" class="flex-container wide100p alignitemscenter">
62736206 <div class="world_entry_form_control keyprimary flex1">
@@ -6364,13 +6297,13 @@
63646297 </span>
63656298 <div>
63666299 <label class="checkbox flex-container alignitemscenter flexNoGap">
63676300 <input type="checkbox" name="exclude_recursionexcludeRecursion" />
63686301 <span data-i18n="Non-recursable (will not be activated by another)">
63696302 Non-recursable (will not be activated by another)
63706303 </span>
63716304 </label>
63726305 <label class="checkbox flex-container alignitemscenter flexNoGap">
63736306 <input type="checkbox" name="prevent_recursionpreventRecursion" />
63746307 <span data-i18n="Prevent further recursion (this entry will not activate others)">
63756308 Prevent further recursion (will not activate others)
63766309 </span>
@@ -6559,6 +6492,81 @@
65596492 </div>
65606493 </div>
65616494 </div>
6495+ <div class="world_entry">
6496+ <form class="world_entry_form wi-card-entry">
6497+ <div class="inline-drawer wide100p">
6498+ <div class="inline-drawer-header gap5px padding0">
6499+ <span class="drag-handle">&#9776;</span>
6500+ <div class="gap5px world_entry_thin_controls wide100p alignitemscenter">
6501+ <div class="inline-drawer-toggle fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6502+ <div class="fa-solid fa-toggle-on killSwitch" name="entryKillSwitch" data-i18n="[title]Toggle entry's active state." title="Toggle entry's active state."></div>
6503+ <div class="flex-container alignitemscenter wide100p">
6504+ <div class="WIEntryTitleAndStatus flex-container flex1 alignitemscenter">
6505+ <div class="flex-container flex1">
6506+ <textarea class="text_pole" rows="1" name="comment" data-i18n="[placeholder]Entry Title/Memo" placeholder="Entry Title/Memo"></textarea>
6507+ </div>
6508+ <!-- <span class="world_entry_form_position_value"></span> -->
6509+ <select data-i18n="[title]WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized" title="WI Entry Status:&#13;🔵 Constant&#13;🟢 Normal&#13;🔗 Vectorized" name="entryStateSelector" class="text_pole widthNatural margin0">
6510+ <option value="constant" title="Constant" data-i18n="[title]WI_Entry_Status_Constant">🔵</option>
6511+ <option value="normal" title="Normal" data-i18n="[title]WI_Entry_Status_Normal">🟢</option>
6512+ <option value="vectorized" title="Vectorized" data-i18n="[title]WI_Entry_Status_Vectorized">🔗</option>
6513+ </select>
6514+ </div>
6515+ <div class="WIEnteryHeaderControls flex-container">
6516+ <div name="PositionBlock" class="world_entry_form_control world_entry_form_radios wi-enter-footer-text">
6517+ <label for="position" class="WIEntryHeaderTitleMobile" data-i18n="Position:">Position:</label>
6518+ <select name="position" class="text_pole widthNatural margin0" data-i18n="[title]T_Position" title="↑Char: Before Character Definitions&#13;↓Char: After Character Definitions&#13;↑EM: Before Example Messages&#13;↓EM: After Example Messages&#13;↑AN: Before Author's Note&#13;↓AN: After Author's Note&#13;@D ⚙️: at Depth (System)&#13;@D 👤: at Depth (User)&#13;@D 🤖: at Depth (Assistant)">
6519+ <option value="0" data-role="" data-i18n="Before Char Defs">
6520+ ↑Char
6521+ </option>
6522+ <option value="1" data-role="" data-i18n="After Char Defs">
6523+ ↓Char
6524+ </option>
6525+ <option value="5" data-role="" data-i18n="Before EM">
6526+ ↑EM
6527+ </option>
6528+ <option value="6" data-role="" data-i18n="After EM">
6529+ ↓EM
6530+ </option>
6531+ <option value="2" data-role="" data-i18n="Before AN">
6532+ ↑AN
6533+ </option>
6534+ <option value="3" data-role="" data-i18n="After AN">
6535+ ↓AN
6536+ </option>
6537+ <option value="4" data-role="0" data-i18n="at Depth System">
6538+ @D ⚙️
6539+ </option>
6540+ <option value="4" data-role="1" data-i18n="at Depth User">
6541+ @D 👤
6542+ </option>
6543+ <option value="4" data-role="2" data-i18n="at Depth AI">
6544+ @D 🤖
6545+ </option>
6546+ </select>
6547+ </div>
6548+ <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6549+ <label for="depth" class="WIEntryHeaderTitleMobile" data-i18n="Depth:">Depth:</label>
6550+ <input title="Depth" class="text_pole wideMax100px margin0" type="number" name="depth" data-i18n="[title]Depth" placeholder="" min="0" max="9999" />
6551+ </div>
6552+ <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap">
6553+ <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Order:">Order:</label>
6554+ <input title="Order" data-i18n="[title]Order" class="text_pole wideMax100px margin0" type="number" name="order" placeholder="" min="0" max="9999" />
6555+ </div>
6556+ <div class="world_entry_form_control wi-enter-footer-text flex-container flexNoGap probabilityContainer">
6557+ <label for="order" class="WIEntryHeaderTitleMobile" data-i18n="Trigger %:">Trigger %:</label>
6558+ <input title="Probability" data-i18n="[title]Probability" class="text_pole wideMax100px margin0" type="number" name="probability" placeholder="" min="0" max="100" />
6559+ </div>
6560+ </div>
6561+ </div>
6562+ </div>
6563+ <i class="menu_button move_entry_button fa-solid fa-right-left" title="Move Entry to Another Lorebook" data-i18n="[title]Move Entry to Another Lorebook"></i>
6564+ <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>
6565+ <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>
6566+ </div>
6567+ <div class="inline-drawer-content inline-drawer-outlet flex-container paddingBottom5px wide100p ">
6568+ <!-- Outlet for world_entry_edit -->
6569+ </div>
65626570 </div>
65636571 </form>
65646572 </div>
public/script.js+32 -82
@@ -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) => {
10350- slideToggle(el, {
10351- ...getSlideToggleOptions(),
10352- elementDisplayStyle: 'flex',
10353- onAnimationEnd: function (el) {
10354- el.closest('.drawer-content').classList.remove('resizing');
1035510346 favsToHotswap();
1035610347 $('#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));
10376- });
1037710355 }
10378-
10379- } else if (drawerWasOpenAlready) { //to close manually
10380- 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- });
1039110356 }
10392- else {
10357+ } else if (drawerWasOpenAlready) {
10393- $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
10358+ icon.toggleClass('closedIcon openIcon');
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, {
12066- ...getSlideToggleOptions(),
12067- onAnimationEnd: (el) => {
12068- el.closest('.drawer-content').classList.remove('resizing');
12069- },
12070- });
12071- });
1207212022 $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
1207312023 $('.openDrawer').not('.pinnedOpen')openDrawers.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);
554- }
555-
556- // Prevent resizing offscreen
557- if (top + elmnt.height() >= winHeight) {
558- elmnt.css('height', winHeight - top - 1 + 'px');
559- elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
560563 }
561-
564+ if (top + $elmnt.height() >= winHeight) {
562- if (left + elmnt.width() >= winWidth) {
565+ $elmnt.css('height', winHeight - top - 1 + 'px');
563566 $elmnt.css('width', winWidth(winHeight - lefttop - 1) / imageAspectRatio + 'px');
564- elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
565567 }
566- } else { //prevent divs that are not zoomedAvatars from resizing offscreen
568+ if (left + $elmnt.width() >= winWidth) {
567-
569+ $elmnt.css('width', winWidth - left - 1 + 'px');
568- if (top + elmnt.height() >= winHeight) {
570+ $elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
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');
580- }
581-
582- //set css to prevent weird resize behavior (does not save)
583- elmnt.css('left', left);
584- elmnt.css('top', top);
585-
586- //set a listener for mouseup to save new width/height
587- $(window).off('mouseup').on('mouseup', () => {
588- console.log(`Saving ${elmntName} Height/Width`);
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;
593578 }
594-
579+ $elmnt.css({ left, top });
595- power_user.movingUIState[elmntName].width = width;
580+ $elmnt.off('mouseup').on('mouseup', () => {
596- power_user.movingUIState[elmntName].height = height;
581+ if (
597- eventSource.emit('resizeUI', elmntName);
582+ power_user.movingUIState[elmntName].width === $elmnt.width() &&
598- saveSettingsDebounced();
583+ power_user.movingUIState[elmntName].height === $elmnt.height()
599584 imgHeight =) nullreturn;
600- imgWidth = null;
585+ savePositionAndSize();
601- height = null;
586+ observer.disconnect();
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();
701- top = null;
702- left = null;
703- right = null;
704- bottom = null;
705- maxX = null;
706- maxY = null;
707632 }
633+
634+ // Setup event listeners
635+ if ($elmntHeader.length) {
636+ $elmntHeader.off('mousedown').on('mousedown', (e) => {
637+ if ($(e.target).hasClass('drag-grabber')) {
638+ actionType = 'drag';
639+ isMouseDown = true;
640+ observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
641+ dragMouseDown(e);
642+ }
643+ });
644+ }
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+11 -14
@@ -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;
@@ -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+201 -37
@@ -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,6 +416,7 @@
384416 align-items: baseline;
385417 }
386418 }
419+
387420 >#qr--autoExec {
388421 .checkbox_label {
389422 text-wrap: nowrap;
@@ -433,44 +466,52 @@
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;
487+
451488 >#qr--main,
452489 >#qr--resizeHandle,
453490 >#qr--qrOptions>h3,
454491 >#qr--qrOptions>#qr--modal-executeButtons,
455492 > #qr--qrOptions > #qr--modal-executeProgress {
456- {
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 }
514+
474515 &:has(.qr--isExecuting) {
475516 .popup-controls {
476517 display: none;
@@ -481,10 +522,12 @@
481522 z-index: 50000;
482523 pointer-events: none;
483524 background-color: rgb(47 150 180 / 0.5);
525+
484526 &.qr--unresolved {
485527 background-color: rgb(255 255 0 / 0.5);
486528 }
487529 }
530+
488531 .qr--highlight-secondary {
489532 position: absolute;
490533 z-index: 50000;
@@ -505,6 +548,7 @@
505548 overflow: hidden;
506549
507550 &.qr--isExecuting {
551+
508552 #qr--main>h3:first-child,
509553 #qr--main>.qr--labels,
510554 #qr--main>.qr--modal-messageContainer>.qr--modal-editorSettings,
@@ -512,28 +556,32 @@
512556 #qr--qrOptions>#qr--ctxEditor,
513557 #qr--qrOptions>.qr--ctxEditorActions,
514558 #qr--qrOptions>.qr--ctxEditorActions+h3,
515559 #qr--qrOptions > .qr--ctxEditorActions + h3 + div {
516- {
517560 display: none;
518561 }
562+
519563 #qr--main>.qr--modal-messageContainer>#qr--modal-messageHolder>#qr--modal-message {
520564 visibility: hidden;
521565 }
566+
522567 #qr--modal-debugButtons {
523568 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,31 +589,38 @@
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 }
610+
558611 #qr--resizeHandle {
559612 width: 6px;
560613 background-color: var(--SmartThemeBorderColor);
561614 border: 2px solid var(--SmartThemeBlurTintColor);
562615 transition: border-color 200msvar(--animation-duration-2x), background-color 200msvar(--animation-duration-2x);
563616 cursor: w-resize;
617+
564618 &:hover {
565619 background-color: var(--SmartThemeQuoteColor);
566620 border-color: var(--SmartThemeQuoteColor);
567621 }
568622 }
623+
569624 #qr--qrOptions {
570625 width: var(--width, auto);
571626 }
@@ -576,38 +631,48 @@
576631 display: flex;
577632 flex-direction: column;
578633 overflow: hidden;
634+
579635 >.qr--labels {
580636 flex: 0 0 auto;
581637 display: flex;
582638 flex-direction: row;
583639 gap: 0.5em;
584640 padding: 1px;
585- > label, > .label {
641+
642+ >label,
643+ >.label {
586644 flex: 1 1 1px;
587645 display: flex;
588646 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 }
663+
602664 .qr--labelText {
603665 flex: 1 1 auto;
604666 }
667+
605668 .qr--labelHint {
606669 flex: 1 1 auto;
607670 }
671+
608672 input {
609673 flex: 0 0 auto;
610674 }
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: "[";
658732 }
733+
734+ &:after {
735+ content: "]";
736+ }
737+ }
738+
659739 .qr--message {
660740 height: 1lh;
661741 overflow: hidden;
@@ -666,11 +746,13 @@
666746 }
667747 }
668748 }
749+
669750 >.qr--modal-messageContainer {
670751 flex: 1 1 auto;
671752 display: flex;
672753 flex-direction: column;
673754 overflow: hidden;
755+
674756 >.qr--modal-editorSettings {
675757 display: flex;
676758 flex-wrap: wrap;
@@ -679,38 +761,48 @@
679761 color: var(--grey70);
680762 font-size: smaller;
681763 align-items: baseline;
764+
682765 >.checkbox_label {
683766 white-space: nowrap;
767+
684768 >input {
685769 font-size: inherit;
686770 }
687771 }
688772 }
773+
689774 >#qr--modal-messageHolder {
690775 flex: 1 1 auto;
691776 display: grid;
692777 text-align: left;
693778 overflow: hidden;
779+
694780 &.qr--noSyntax {
695781 >#qr--modal-messageSyntax {
696782 display: none;
697783 }
784+
698785 >#qr--modal-message {
699786 background-color: var(--ac-style-color-background);
700787 color: var(--ac-style-color-text);
701- &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
788+
789+ &::-webkit-scrollbar,
790+ &::-webkit-scrollbar-thumb {
702791 visibility: visible;
703792 cursor: unset;
704793 }
794+
705795 &::selection {
706796 color: unset;
707797 background-color: rgba(108 171 251 / 0.25);
798+
708799 @supports (color: rgb(from white r g b / 0.25)) {
709800 background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
710801 }
711802 }
712803 }
713804 }
805+
714806 >#qr--modal-messageSyntax {
715807 grid-column: 1;
716808 grid-row: 1;
@@ -720,10 +812,12 @@
720812 overflow: hidden;
721813 min-width: 100%;
722814 width: 0;
815+
723816 >#qr--modal-messageSyntaxInner {
724817 height: 100%;
725818 }
726819 }
820+
727821 >#qr--modal-message {
728822 background-color: transparent;
729823 color: transparent;
@@ -731,19 +825,25 @@
731825 grid-row: 1;
732826 caret-color: var(--ac-style-color-text);
733827 overflow: auto;
734- &::-webkit-scrollbar, &::-webkit-scrollbar-thumb {
828+
829+ &::-webkit-scrollbar,
830+ &::-webkit-scrollbar-thumb {
735831 visibility: hidden;
736832 cursor: default;
737833 }
834+
738835 &::selection {
739836 color: transparent;
740837 background-color: rgba(108 171 251 / 0.25);
838+
741839 @supports (color: rgb(from white r g b / 0.25)) {
742840 background-color: rgb(from var(--ac-style-color-matchedText) r g b / 0.25);
743841 }
744842 }
745843 }
746- #qr--modal-message, #qr--modal-messageSyntaxInner {
844+
845+ #qr--modal-message,
846+ #qr--modal-messageSyntaxInner {
747847 font-family: var(--monoFontFamily);
748848 padding: 0.75em;
749849 margin: 0;
@@ -756,10 +856,12 @@
756856 }
757857 }
758858 }
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 }
924+
822925 #qr--modal-debugButtons {
823926 display: none;
824927 gap: 1em;
928+
825929 .qr--modal-debugButton {
826930 aspect-ratio: 1.25 / 1;
827931 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,18 +945,23 @@
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 }
@@ -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 {
@@ -941,11 +1052,14 @@
9411052 width: 0;
9421053 white-space: pre-wrap;
9431054 }
1055+
9441056 #qr--modal-debugState {
9451057 display: none;
1058+
9461059 &.qr--active {
9471060 display: block;
9481061 }
1062+
9491063 text-align: left;
9501064 font-size: smaller;
9511065 font-family: var(--monoFontFamily);
@@ -961,6 +1075,7 @@
9611075 display: grid;
9621076 grid-template-columns: 0fr 1fr 1fr;
9631077 column-gap: 0em;
1078+
9641079 .qr--title {
9651080 grid-column: 1 / 4;
9661081 font-weight: bold;
@@ -969,47 +1084,66 @@
9691084 padding: 0.25em;
9701085 margin-top: 0.5em;
9711086 }
972- .qr--var, .qr--macro, .qr--pipe {
1087+
1088+ .qr--var,
1089+ .qr--macro,
1090+ .qr--pipe {
9731091 display: contents;
1092+
9741093 &:nth-child(2n + 1) {
975- .qr--key, .qr--val {
1094+
1095+ .qr--key,
1096+ .qr--val {
9761097 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.25);
9771098 }
1099+
9781100 .qr--val {
9791101 &:nth-child(2n) {
9801102 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.125);
9811103 }
1104+
9821105 &:hover {
9831106 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
9841107 }
9851108 }
9861109 }
1110+
9871111 &:nth-child(2n) {
9881112 .qr--val {
9891113 &:nth-child(2n) {
9901114 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.0625);
9911115 }
1116+
9921117 &:hover {
9931118 background-color: rgb(from var(--SmartThemeEmColor) r g b / 0.5);
9941119 }
9951120 }
9961121 }
1122+
9971123 &.qr--isHidden {
998- .qr--key, .qr--val {
1124+
1125+ .qr--key,
1126+ .qr--val {
9991127 opacity: 0.5;
10001128 }
10011129 }
1130+
10021131 .qr--val {
10031132 grid-column: 2 / 4;
1133+
10041134 &.qr--singleCol {
10051135 grid-column: unset;
10061136 }
1137+
10071138 &.qr--simple {
1008- &:before, &:after {
1139+
1140+ &:before,
1141+ &:after {
10091142 content: '"';
10101143 color: var(--SmartThemeQuoteColor);
10111144 }
10121145 }
1146+
10131147 &.qr--unresolved {
10141148 &:after {
10151149 content: '-UNRESOLVED-';
@@ -1019,21 +1153,36 @@
10191153 }
10201154 }
10211155 }
1156+
10221157 .qr--key {
10231158 margin-left: 0.5em;
10241159 padding-right: 1em;
1025- &:after { content: ": "; }
1160+
1161+ &:after {
1162+ content: ": ";
10261163 }
1027- .qr--pipe, .qr--macro {
1164+ }
1165+
1166+ .qr--pipe,
1167+ .qr--macro {
10281168 >.qr--key {
1029- &:before { content: "{{"; }
1169+ &:before {
1030- &:after { content: "}}: "; }
1170+ content: "{{";
1171+ }
1172+
1173+ &:after {
1174+ content: "}}: ";
1175+ }
10311176 }
10321177 }
1178+
10331179 .qr--scope {
10341180 display: contents;
1181+
10351182 .qr--pipe {
1036- .qr--key, .qr--val {
1183+
1184+ .qr--key,
1185+ .qr--val {
10371186 opacity: 0.5;
10381187 }
10391188 }
@@ -1043,6 +1192,7 @@
10431192 .qr--stack {
10441193 display: grid;
10451194 grid-template-columns: 1fr 0fr;
1195+
10461196 .qr--title {
10471197 grid-column: 1 / 3;
10481198 font-weight: bold;
@@ -1051,16 +1201,22 @@
10511201 padding: 0.25em;
10521202 margin-top: 1em;
10531203 }
1204+
10541205 .qr--item {
10551206 display: contents;
1207+
10561208 &:nth-child(2n + 1) {
1057- .qr--name, .qr--source {
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;
@@ -1086,6 +1242,7 @@
10861242}
10871243
10881244@keyframes qr--debugPulse {
1245+
10891246 0%,
10901247 100% {
10911248 border-color: rgb(81, 163, 81);
@@ -1098,6 +1255,7 @@
10981255
10991256.popup.qr--hide {
11001257 opacity: 0 !important;
1258+
11011259 &::backdrop {
11021260 opacity: 0 !important;
11031261 }
@@ -1117,14 +1275,16 @@
11171275 overflow: hidden;
11181276 font-weight: bold;
11191277 }
1278+
11201279 display: flex;
11211280 align-items: center;
11221281 flex-direction: column;
11231282 white-space: pre;
11241283 font-weight: normal;
11251284 box-shadow: 0 0 0;
1126- transition: 200ms;
1285+ transition: var(--animation-duration-2x);
11271286 }
1287+
11281288 .qr--copy {
11291289 &:after {
11301290 content: 'Copy';
@@ -1132,24 +1292,28 @@
11321292 overflow: hidden;
11331293 font-weight: bold;
11341294 }
1295+
11351296 display: flex;
11361297 align-items: center;
11371298 flex-direction: column;
11381299 white-space: pre;
11391300 font-weight: normal;
11401301 box-shadow: 0 0 0;
1141- transition: 200ms;
1302+ transition: var(--animation-duration-2x);
11421303 }
1304+
11431305 &:has(.qr--transferSelect:focus) {
11441306 .popup-button-ok {
11451307 font-weight: bold;
11461308 box-shadow: 0 0 10px;
11471309 }
1310+
11481311 &.qr--isCopy {
11491312 .popup-button-ok {
11501313 font-weight: normal;
11511314 box-shadow: 0 0 0;
11521315 }
1316+
11531317 .qr--copy {
11541318 font-weight: bold;
11551319 box-shadow: 0 0 10px;
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+574 -643
@@ -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+ });
1959+
1960+ // Step 2: Clean all <select> elements
1961+ $list.find('select').each(function () {
1962+ const $select = $(this);
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+ }
19441998
1945- // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
1999+ console.timeEnd('clearEntryList');
1946- // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
2000+}
1947- worldEntriesList.find('*').off();
2001+
1948- worldEntriesList.empty().show();
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+
2109+ for (const entry of page) {
2110+ try {
2111+ const block = await getWorldEntry(name, data, entry);
2112+ if (block) {
2113+ blocks.push(block);
2114+ }
2115+ } catch (error) {
2116+ console.error(`Error while processing entry ${entry.uid}:`, error);
2117+ }
2118+ }
2119+
20492120 const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
20502121 if (!isCustomOrder) {
20512122 blocks.forEach(block => {
20522123 block.find('.drag-handle').remove();
20532124 });
20542125 }
2126+
20552127 worldEntriesList.append(keywordHeaders);
20562128 worldEntriesList.append(blocks);
2129+ } catch (error) {
2130+ console.error('Error while rendering WI entries:', error);
2131+ } finally {
2132+ isSaveWorldInfoDisabled = false;
2133+ }
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,51 +2531,32 @@ 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;
2459- }
2460-
2461- const template = WI_ENTRY_EDIT_TEMPLATE.clone();
2462- template.data('uid', entry.uid);
2463- template.attr('uid', entry.uid);
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;
2467-
2468- /** Function to build the keys input controls @param {string} entryPropName @param {string} originalDataValueName */
2469- function enableKeysInput(entryPropName, originalDataValueName) {
24702536 const isFancyInput = !isMobile() && !power_user.wi_key_input_plaintext;
24712537 const input = isFancyInput ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`);
24722538 input.data('uid', entry.uid);
24732539 input.on('click', function (event) {
2474- // Prevent closing the drawer on clicking the input
24752540 event.stopPropagation();
24762541 });
24772542
24782543 function templateStyling(/** @type {Select2Option} */ item, { searchStyle = false } = {}) {
24792544 const content = $('<span>').addClass('item').text(item.text).attr('title', `${item.text}\n\nClick to edit`);
24802545 const isRegex = isValidRegex(item.text);
24812546 if (isRegex) {
24822547 content.html(highlightRegex(item.text));
24832548 content.addClass('regex_item').prepend($('<span>').addClass('regex_icon').text('•*').attr('title', 'Regex'));
24842549 }
2485-
24862550 if (searchStyle && item.count) {
2487- // Build a wrapping element
2551+ const wrapper = $('<span>').addClass('result_block').append(content);
2488- const wrapper = $('<span>').addClass('result_block')
2489- .append(content);
24902552 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`));
24912553 return wrapper;
24922554 }
2493-
24942555 return content;
24952556 }
24962557
24972558 if (isFancyInput) {
2498- // First initialize existing values as options, before initializing select2, to speed up performance
24992559 select2ModifyOptions(input, entry[entryPropName], { select: true, changeEventArgs: { skipReset: true, noSave: true } });
2500-
25012560 input.select2({
25022561 ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),
25032562 tags: true,
@@ -2507,12 +2566,18 @@ export async function getWorldEntry(name, data, entry) {
25072566 templateResult: item => templateStyling(item, { searchStyle: true }),
25082567 templateSelection: item => templateStyling(item),
25092568 });
2510- input.on('change', async function (_, { skipReset, noSave } = {}) {
2569+
2570+ // TypeScript-safe event handler
2571+ /**
2572+ * @param {Event} _event
2573+ * @param {{ skipReset?: boolean, noSave?: boolean }} [arg]
2574+ */
2575+ input.on('change', async function (_event, arg) {
25112576 const uid = $(this).data('uid');
2512- /** @type {string[]} */
25132577 const keys = ($(this).select2('data')).map(x => x.text);
2514-
2578+ const skipReset = arg?.skipReset ?? false;
2515- !skipReset && await resetScrollHeight(this);
2579+ const noSave = arg?.noSave ?? false;
2580+ if (!skipReset) await resetScrollHeight(this);
25162581 if (!noSave) {
25172582 data.entries[uid][entryPropName] = keys;
25182583 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
@@ -2520,37 +2585,34 @@ export async function getWorldEntry(name, data, entry) {
25202585 }
25212586 $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);
25222587 });
2588+
25232589 input.toggleClass('empty', !entry[entryPropName].length);
25242590 input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data]));
25252591 input.on('select2:unselect', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true }));
25262592
25272593 select2ChoiceClickSubscribe(input, target => {
25282594 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
25322595 const selected = input.val();
25332596 if (!Array.isArray(selected)) return;
25342597 var index = selected.indexOf(getSelect2OptionId(key));
25352598 if (index > -1) selected.splice(index, 1);
25362599 input.val(selected).trigger('change');
2537- // Manually update the cache, that change event is not gonna trigger it
25382600 updateWorldEntryKeyOptionsCache([key], { remove: true });
2539-
2601+ input.next('span.select2-container').find('textarea').val(key).trigger('input');
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');
25432602 }, { openDrawer: true });
2544- }
2603+ } else {
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
25472604 template.find(`select[name="${entryPropName}"]`).hide();
25482605 input.show();
2549-
2606+ /**
2550- input.on('input', async function (_, { skipReset, noSave } = {}) {
2607+ * @param {Event} _event
2608+ * @param {{ skipReset?: boolean, noSave?: boolean }} [arg]
2609+ */
2610+ input.on('change', async function (_event, arg) {
25512611 const uid = $(this).data('uid');
25522612 const value = String($(this).val());
2553- !skipReset && await resetScrollHeight(this);
2613+ const skipReset = arg?.skipReset ?? false;
2614+ const noSave = arg?.noSave ?? false;
2615+ if (!skipReset) await resetScrollHeight(this);
25542616 if (!noSave) {
25552617 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
25562618 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
@@ -2563,98 +2625,43 @@ export async function getWorldEntry(name, data, entry) {
25632625 return { isFancy: isFancyInput, control: input };
25642626}
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 () {
25982636 const uid = $(this).data('uid');
25992637 const value = Number($(this).valprop()'checked');
26002638 data.entries[uid].selectiveLogic[fieldName] = !isNaN(value) ? value : world_info_logic.AND_ANY;
26012639 setWIOriginalDataValue(data, uid, 'selectiveLogic'key, data.entries[uid].selectiveLogic[fieldName]);
26022640 await saveWorldInfo(name, data);
26032641 });
2604-
2642+ checkBoxElem.prop('checked', !!entry[fieldName]).trigger('input');
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 () {
2618- const uid = $(this).data('uid');
2619- const value = $(this).prop('checked');
2620- characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)');
2621- if (data.entries[uid].characterFilter) {
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- );
26382643}
26392644
2640- // Verify names to exist in the system
2645+/**
2641- if (data.entries[uid]?.characterFilter?.names?.length > 0) {
2646+ * Helper to update position/order display.
2642- for (const name of [...data.entries[uid].characterFilter.names]) {
2647+ */
2643- if (!getContext().characters.find(x => x.avatar.replace(/\.[^/.]+$/, '') === name)) {
2648+function updatePosOrdDisplayHelper({ template, data, uid }) {
2644- console.warn(`World Info: Character ${name} not found. Removing from the entry filter.`, entry);
2649+ let entry = data.entries[uid];
2645- data.entries[uid].characterFilter.names = data.entries[uid].characterFilter.names.filter(x => x !== name);
2650+ let posText = entry.position;
2646- }
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;
26472657 }
2658+ template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`);
26482659}
26492660
2650- setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2661+/**
2651- await saveWorldInfo(name, data);
2662+ * Helper to initialize character filter select2.
2652- });
2663+ */
2653- characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
2664+function initCharacterFilterSelect2Helper(characterFilter, t) {
2654-
2655- const characterFilter = template.find('select[name="characterFilter"]');
2656- characterFilter.data('uid', entry.uid);
2657-
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 });
2715-
2716- // comment
2717- const commentInput = template.find('textarea[name="comment"]');
2718- const commentToggle = template.find('input[name="addMemo"]');
2719- commentInput.data('uid', entry.uid);
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);
27842729}
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');
28012730
2802- const keysecondarytextpole = $(this)
2731+/**
2803- .closest('.world_entry')
2732+ * Helper to handle probability input.
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");
28332733 */
2834-
2734+function handleProbabilityInputHelper({ probabilityInput, data, entry, name, setWIOriginalDataValue, saveWorldInfo }) {
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)');
2987-
2988- // probability toggle
2989- if (entry.useProbability === undefined) {
2990- entry.useProbability = false;
29912751}
29922752
2993- const probabilityToggle = template.find('input[name="useProbability"]');
2753+/**
2754+ * Helper to handle probability toggle.
2755+ */
2756+function handleProbabilityToggleHelper({ probabilityToggle, data, entry, name, probabilityInput, setWIOriginalDataValue, saveWorldInfo }) {
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();
3018-
3019- // position
3020- if (entry.position === undefined) {
3021- entry.position = 0;
30222775}
30232776
3024- const positionInput = template.find('select[name="position"]');
2777+/**
3025- //initScrollHeight(positionInput);
2778+ * Helper to handle select2 dropdowns for boolean selects.
3026- positionInput.data('uid', entry.uid);
2779+ */
3027- positionInput.on('click', function (event) {
2780+function handleBooleanSelectHelper({ selectElem, entry, entryKey, data, name, setWIOriginalDataValue, saveWorldInfo }) {
3028- // Prevent closing the drawer on clicking the input
2781+ selectElem.data('uid', entry.uid);
3029- event.stopPropagation();
2782+ selectElem.on('input', async function () {
2783+ const uid = $(this).data('uid');
2784+ const value = $(this).val();
2785+ data.entries[uid][entryKey] = value === 'null' ? null : value === 'true';
2786+ setWIOriginalDataValue(data, uid, `extensions.${entryKey.replace(/[A-Z]/g, m => `_${m.toLowerCase()}`)}`, data.entries[uid][entryKey]);
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 {
3042- depthInput.prop('disabled', true);
3043- depthInput.css('visibility', 'hidden');
3044- data.entries[uid].role = null;
3045- //depthInput.parent().hide();
30462807 }
3047- updatePosOrdDisplay(uid);
2808+ }
3048- // Spec v2 only supports before_char and after_char
2809+ data.entries[uid][entryKey] = !isNaN(value) ? value : null;
3049- setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
2810+ setWIOriginalDataValue(data, uid, `extensions.${entryKey.replace(/[A-Z]/g, m => `_${m.toLowerCase()}`)}`, data.entries[uid][entryKey]);
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');
31662912
3167- // If the value contains a number, we'll take that one (set by the level input), otherwise we can use true/false switch
2913+ // Probability
3168- const value = toggled ? data.entries[uid].delayUntilRecursion || true : false;
2914+ handleProbabilityInputHelper({ probabilityInput: headerTemplate.find('input[name="probability"]'), data, entry, name, setWIOriginalDataValue, saveWorldInfo });
31692915
3170- if (!toggled) delayUntilRecursionLevelInput.val('');
2916+ // Depth
2917+ handleNumberInputHelper({
2918+ inputElem: headerTemplate.find('input[name="depth"]'),
2919+ entry, entryKey: 'depth', data, name, setWIOriginalDataValue, saveWorldInfo,
2920+ min: 0, max: MAX_SCAN_DEPTH, clamp: false,
2921+ });
2922+ headerTemplate.find('input[name="depth"]').css('width', 'calc(3em + 15px)');
31712923
3172- data.entries[uid].delayUntilRecursion = value;
2924+ // Position
3173- setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
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 () {
2930+ const uid = $(this).data('uid');
2931+ const value = Number($(this).val());
2932+ data.entries[uid].position = !isNaN(value) ? value : 0;
2933+ const depthInput = headerTemplate.find('input[name="depth"]');
2934+ if (value === world_info_position.atDepth) {
2935+ depthInput.prop('disabled', false);
2936+ depthInput.css('visibility', 'visible');
2937+ const role = Number($(this).find(':selected').data('role'));
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);
31742948 await saveWorldInfo(name, data);
31752949 });
3176- delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
2950+ const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
3177- delayUntilRecursionLevelInput.on('input', async function () {
2951+ headerTemplate.find(`select[name="position"] option[value="${entry.position}"][data-role="${roleValue}"]`).prop('selected', true).trigger('input');
3178- const uid = $(this).data('uid');
3179- const content = $(this).val();
3180- const value = content === '' ? (typeof data.entries[uid].delayUntilRecursion === 'boolean' ? data.entries[uid].delayUntilRecursion : true)
3181- : content === 1 ? true
3182- : !isNaN(Number(content)) ? Number(content)
3183- : false;
31842952
3185- data.entries[uid].delayUntilRecursion = value;
2953+ // Tri-state selector
3186- setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2954+ handleEntryStateSelectorHelper({
3187- await saveWorldInfo(name, data);
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,
31882963 });
3189- // No need to retrigger inpout event, we'll just set the curret current value. It was edited/saved above already
3190- delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input');
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,143 +3005,327 @@ 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;
3026+ if (selectedWorldIndex === -1) return;
3027+ const selectedValue = world_names[selectedWorldIndex];
3028+ if (!selectedValue) {
3029+ toastr.warning(t`Please select a target lorebook.`);
32783030 return;
32793031 }
3032+ await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid);
3033+ });
32803034
3281- if (selectedWorldIndex === -1) {
3035+ let drawerInitialized = false;
3036+ let drawerDestroyTimeout = null;
3037+ headerTemplate.find('.inline-drawer').on('inline-drawer-toggle', function () {
3038+ if (drawerDestroyTimeout) {
3039+ clearTimeout(drawerDestroyTimeout);
3040+ drawerDestroyTimeout = null;
3041+ }
3042+ if (drawerInitialized) {
3043+ drawerDestroyTimeout = setTimeout(() => {
3044+ // Drawer was reopened, so we don't destroy it
3045+ if (editOutlet.is(':visible')) {
32823046 return;
32833047 }
3048+ drawerInitialized = false;
3049+ clearEntryList(editOutlet);
3050+ drawerDestroyTimeout = null;
3051+ }, debounce_timeout.relaxed);
3052+ } else {
3053+ drawerInitialized = true;
3054+ addEditorDrawerContent();
3055+ }
3056+ });
32843057
3285- const selectedValue = world_names[selectedWorldIndex];
3058+ const editOutlet = headerTemplate.find('.inline-drawer-outlet');
32863059
32873060 if function addEditorDrawerContent(!selectedValue) {
3288- toastr.warning(t`Please select a target lorebook.`);
3061+ const editTemplate = WI_ENTRY_EDIT_TEMPLATE.clone();
3289- return;
3062+
3063+ // UID display
3064+ editTemplate.find('.world_entry_form_uid_value').text(`(UID: ${entry.uid})`);
3065+
3066+ // Key inputs
3067+ const keyInput = enableKeysInputHelper({ template: editTemplate, entry, entryPropName: 'key', originalDataValueName: 'keys', name, data });
3068+ const keySecondaryInput = enableKeysInputHelper({ template: editTemplate, entry, entryPropName: 'keysecondary', originalDataValueName: 'secondary_keys', name, data });
3069+ if (!keyInput.isFancy) initScrollHeight(keyInput.control);
3070+ if (!keySecondaryInput.isFancy) initScrollHeight(keySecondaryInput.control);
3071+
3072+ // Key input switch
3073+ editTemplate.find('.switch_input_type_icon').on('click', function () {
3074+ power_user.wi_key_input_plaintext = !power_user.wi_key_input_plaintext;
3075+ saveSettingsDebounced();
3076+ const uid = ($(this).parents('.world_entry')).data('uid');
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+ });
3083+
3084+ // Probability toggle
3085+ handleProbabilityToggleHelper({
3086+ probabilityToggle: editTemplate.find('input[name="useProbability"]'),
3087+ data, entry, name,
3088+ probabilityInput: headerTemplate.find('input[name="probability"]'),
3089+ setWIOriginalDataValue, saveWorldInfo,
3090+ });
3091+
3092+ // Comment toggle
3093+ const commentToggle = editTemplate.find('input[name="addMemo"]');
3094+ commentToggle.data('uid', entry.uid);
3095+ commentToggle.on('input', async function () {
3096+ const uid = $(this).data('uid');
3097+ 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');
3118+
3119+ // Selective
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);
3127+ 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;
32903152 }
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');
32913167
3292- await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid);
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,
3174+ });
3175+
3176+ // Content
3177+ const counter = editTemplate.find('.world_entry_form_token_counter');
3178+ const countTokensDebounced = debounce(async function (counter, value) {
3179+ const numberOfTokens = await getTokenCountAsync(value);
3180+ $(counter).text(numberOfTokens);
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);
32933193 });
3194+ contentInput.val(entry.content).trigger('input', { skipCount: true });
3195+ editTemplate.find('.editor_maximize').attr('data-for', contentInputId);
32943196
32953197 // scanScan depth
32963198 const scanDepthInput = templateeditTemplate.find('input[name="scanDepth"]');
32973199 scanDepthInput.data('uid', entry.uid);
32983200 scanDepthInput.on('input', async function () {
32993201 const uid = $(this).data('uid');
33003202 const isEmpty = $(this).val() === '';
33013203 const value = Number($(this).val());
3302-
3303- // Clamp if necessary
33043204 if (value < 0) {
33053205 $(this).val(0).trigger('input');
33063206 toastr.warning('Scan depth cannot be negative');
33073207 return;
33083208 }
3309-
33103209 if (value > MAX_SCAN_DEPTH) {
33113210 $(this).val(MAX_SCAN_DEPTH).trigger('input');
33123211 toastr.warning(`Scan depth cannot exceed ${MAX_SCAN_DEPTH}`);
33133212 return;
33143213 }
3315-
33163214 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
33173215 setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
33183216 await saveWorldInfo(name, data);
33193217 });
33203218 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
33213219
3322- // case sensitive select
3220+ // Group
33233221 const caseSensitiveSelectgroupInput = templateeditTemplate.find('selectinput[name="caseSensitivegroup"]');
33243222 caseSensitiveSelect groupInput.data('uid', entry.uid);
33253223 caseSensitiveSelect groupInput.on('input', async function () {
33263224 const uid = $(this).data('uid');
33273225 const value = String($(this).val()).trim();
3328-
3226+ data.entries[uid].group = value;
3329- data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
3227+ setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
3330- setWIOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
33313228 await saveWorldInfo(name, data);
33323229 });
3333- caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
3230+ groupInput.val(entry.group ?? '').trigger('input');
3231+ setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
33343232
3335- // match whole words select
3233+ // Inclusion priority
33363234 const matchWholeWordsSelectgroupOverrideInput = templateeditTemplate.find('selectinput[name="matchWholeWordsgroupOverride"]');
33373235 matchWholeWordsSelect groupOverrideInput.data('uid', entry.uid);
33383236 matchWholeWordsSelect groupOverrideInput.on('input', async function () {
33393237 const uid = $(this).data('uid');
33403238 const value = $(this).valprop('checked');
3341-
3239+ data.entries[uid].groupOverride = value;
3342- data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
3240+ setWIOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
3343- setWIOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
33443241 await saveWorldInfo(name, data);
33453242 });
3346- matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
3243+ groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
33473244
3348- // use group scoring select
3245+ // Group weight
3349- const useGroupScoringSelect = template.find('select[name="useGroupScoring"]');
3246+ handleNumberInputHelper({
3350- useGroupScoringSelect.data('uid', entry.uid);
3247+ inputElem: editTemplate.find('input[name="groupWeight"]'),
3351- useGroupScoringSelect.on('input', async function () {
3248+ entry, entryKey: 'groupWeight', data, name, setWIOriginalDataValue, saveWorldInfo,
3352- const uid = $(this).data('uid');
3249+ min: 1, max: 10000, clamp: true,
3353- const value = $(this).val();
3250+ });
33543251
3355- data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
3252+ // Sticky, cooldown, delay
3356- setWIOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
3253+ handleNumberInputHelper({
3357- await saveWorldInfo(name, data);
3254+ inputElem: editTemplate.find('input[name="sticky"]'),
3255+ entry, entryKey: 'sticky', data, name, setWIOriginalDataValue, saveWorldInfo,
3256+ min: 1, max: 10000, clamp: false,
3257+ });
3258+ handleNumberInputHelper({
3259+ inputElem: editTemplate.find('input[name="cooldown"]'),
3260+ entry, entryKey: 'cooldown', data, name, setWIOriginalDataValue, saveWorldInfo,
3261+ min: 1, max: 10000, clamp: false,
3262+ });
3263+ handleNumberInputHelper({
3264+ inputElem: editTemplate.find('input[name="delay"]'),
3265+ entry, entryKey: 'delay', data, name, setWIOriginalDataValue, saveWorldInfo,
3266+ min: 1, max: 10000, clamp: false,
33583267 });
3359- useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
33603268
3361- function handleMatchCheckbox(fieldName) {
3269+ // Exclude/prevent recursion
3362- const key = originalWIDataKeyMap[fieldName];
3270+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'excludeRecursion', data, name });
3363- const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`);
3271+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'preventRecursion', data, name });
3364- checkBoxElem.data('uid', entry.uid);
3365- checkBoxElem.on('input', async function () {
3366- const uid = $(this).data('uid');
3367- const value = $(this).prop('checked');
33683272
3369- data.entries[uid][fieldName] = value;
3273+ // Delay until recursion
3370- setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]);
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);
33713285 await saveWorldInfo(name, data);
33723286 });
33733287 checkBoxElemdelayUntilRecursionInput.prop('checked', !!entry[fieldName].delayUntilRecursion).trigger('input');
3374- }
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+ });
33753314
3376- handleMatchCheckbox('matchPersonaDescription');
3315+ // Match checkboxes
3377- handleMatchCheckbox('matchCharacterDescription');
3316+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchPersonaDescription', data, name });
3378- handleMatchCheckbox('matchCharacterPersonality');
3317+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCharacterDescription', data, name });
3379- handleMatchCheckbox('matchCharacterDepthPrompt');
3318+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCharacterPersonality', data, name });
3380- handleMatchCheckbox('matchScenario');
3319+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCharacterDepthPrompt', data, name });
3381- handleMatchCheckbox('matchCreatorNotes');
3320+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchScenario', data, name });
3321+ handleMatchCheckboxHelper({ template: editTemplate, entry, fieldName: 'matchCreatorNotes', data, name });
33823322
33833323 // automationAutomation idID
33843324 const automationIdInput = templateeditTemplate.find('input[name="automationId"]');
33853325 automationIdInput.data('uid', entry.uid);
33863326 automationIdInput.on('input', async function () {
33873327 const uid = $(this).data('uid');
33883328 const value = $(this).val();
3389-
33903329 data.entries[uid].automationId = value;
33913330 setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
33923331 await saveWorldInfo(name, data);
@@ -3394,33 +3333,15 @@ export async function getWorldEntry(name, data, entry) {
33943333 automationIdInput.val(entry.automationId ?? '').trigger('input');
33953334 setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);
33963335
3397- template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed
3336+ countTokensDebounced(counter, contentInput.val());
33983337
3399- function updatePosOrdDisplay(uid) {
3338+ editTemplate.find('.inline-drawer-content').css('display', 'none');
3400- // display position/order info left of keyword box
3339+ editOutlet.append(editTemplate);
3401- let entry = data.entries[uid];
3402- let posText = entry.position;
3403- switch (entry.position) {
3404- case 0:
3405- posText = '↑CD';
3406- break;
3407- case 1:
3408- posText = 'CD↓';
3409- break;
3410- case 2:
3411- posText = '↑AN';
3412- break;
3413- case 3:
3414- posText = 'AN↓';
3415- break;
3416- case 4:
3417- posText = `@D${entry.depth}`;
3418- break;
3419- }
3420- template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`);
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 {