- replace top bar toggle animations with opacity fades - refactor MovingUI - refactor getWorldEntry

edbc257e8167c13e4642dbb3e74425fcdc9a4802

RossAscends <124905043+RossAscends@users.noreply.github.com>

3 files changed, +680 -877Ignore whitespace
public/script.js+96 -60
@@ -10317,12 +10317,50 @@ function doDrawerOpenClick() {
10317 doNavbarIconClick.call(drawerToggle);10317 doNavbarIconClick.call(drawerToggle);
10318}10318}
1031910319
10320
10321// Helper for animating open/close (opacity only)
10322async function animateDrawer($el, open, displayStyle = 'block', onEnd) {
10323 //const slideOptions = getSlideToggleOptions();
10324 const duration = 250;
10325 const easing = 'swing';
10326 $el.stop(true, true);
10327
10328 if (open) {
10329 $el.css({ display: displayStyle, opacity: 0 });
10330 $el.animate(
10331 { opacity: 1 },
10332 {
10333 duration,
10334 easing,
10335 complete: function () {
10336 $el.css({ opacity: '', display: displayStyle });
10337 if (typeof onEnd === 'function') onEnd(this);
10338 },
10339 },
10340 );
10341 } else {
10342 $el.animate(
10343 { opacity: 0 },
10344 {
10345 duration,
10346 easing,
10347 complete: function () {
10348 $el.css({ display: 'none', opacity: '0' });
10349 if (typeof onEnd === 'function') onEnd(this);
10350 },
10351 },
10352 );
10353 }
10354}
10355
10320/**10356/**
10321 * Event handler to open or close a navbar drawer when a navbar icon is clicked.10357 * Event handler to open or close a navbar drawer when a navbar icon is clicked.
10322 * Handles click events on .drawer-toggle elements.10358 * Handles click events on .drawer-toggle elements.
10323 * @returns {void}10359 * @returns {void}
10324 */10360 */
10325function doNavbarIconClick() {10361// ...existing code...
10362export async function doNavbarIconClick() {
10363 //console.warn('called for, ', $(this));
10326 const icon = $(this).find('.drawer-icon');10364 const icon = $(this).find('.drawer-icon');
10327 const drawer = $(this).parent().find('.drawer-content');10365 const drawer = $(this).parent().find('.drawer-content');
10328 if (drawer.hasClass('resizing')) { return; }10366 if (drawer.hasClass('resizing')) { return; }
@@ -10330,72 +10368,69 @@ function doNavbarIconClick() {
10330 const targetDrawerID = $(this).parent().find('.drawer-content').attr('id');10368 const targetDrawerID = $(this).parent().find('.drawer-content').attr('id');
10331 const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');10369 const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
1033210370
10333 if (!drawerWasOpenAlready) { //to open the drawer10371 if (!drawerWasOpenAlready) { // to open the drawer
10334 $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {10372 // Close all open drawers except pinned ones
10335 slideToggle(el, {10373 const $openDrawers = $('.openDrawer:not(.pinnedOpen)');
10336 ...getSlideToggleOptions(),10374 for (const el of $openDrawers) {
10337 onAnimationEnd: function (el) {10375 $(el).addClass('resizing');
10338 el.closest('.drawer-content').classList.remove('resizing');10376 await animateDrawer($(el), false, undefined, function (el) {
10339 },10377 $(el).closest('.drawer-content').removeClass('resizing');
10340 });10378 });
10341 });10379 }
10342 $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');10380
10343 $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');10381 // Toggle icon and drawer classes
10382 const $openIcons = $('.openIcon:not(.drawerPinnedOpen)');
10383 for (const iconEl of $openIcons) {
10384 $(iconEl).toggleClass('closedIcon openIcon');
10385 }
10386 for (const el of $openDrawers) {
10387 $(el).toggleClass('closedDrawer openDrawer');
10388 }
10344 icon.toggleClass('openIcon closedIcon');10389 icon.toggleClass('openIcon closedIcon');
10345 drawer.toggleClass('openDrawer closedDrawer');10390 drawer.toggleClass('openDrawer closedDrawer');
1034610391
10347 //console.log(targetDrawerID);10392 // Open the target drawer
10348 if (targetDrawerID === 'right-nav-panel') {10393 const $drawerContents = $(this).closest('.drawer').find('.drawer-content');
10349 $(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {10394 for (const el of $drawerContents) {
10350 slideToggle(el, {10395 $(el).addClass('resizing');
10351 ...getSlideToggleOptions(),10396 if (targetDrawerID === 'right-nav-panel') {
10352 elementDisplayStyle: 'flex',10397 await animateDrawer($(el), true, 'flex', function (el) {
10353 onAnimationEnd: function (el) {10398 $(el).closest('.drawer-content').removeClass('resizing');
10354 el.closest('.drawer-content').classList.remove('resizing');10399 favsToHotswap();
10355 favsToHotswap();10400 $('#rm_print_characters_block').trigger('scroll');
10356 $('#rm_print_characters_block').trigger('scroll');
10357 },
10358 });10401 });
10359 });10402 } else {
10360 } else {10403 await animateDrawer($(el), true, undefined, function (el) {
10361 $(this).closest('.drawer').find('.drawer-content').addClass('resizing').each((_, el) => {10404 $(el).closest('.drawer-content').removeClass('resizing');
10362 slideToggle(el, {
10363 ...getSlideToggleOptions(),
10364 onAnimationEnd: function (el) {
10365 el.closest('.drawer-content').classList.remove('resizing');
10366 },
10367 });10405 });
10368 });10406 }
10369 }10407 }
1037010408
10371 // Set the height of "autoSetHeight" textareas within the drawer to their scroll height10409 // Set the height of "autoSetHeight" textareas within the drawer to their scroll height
10372 if (!CSS.supports('field-sizing', 'content')) {10410 if (!CSS.supports('field-sizing', 'content')) {
10373 $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight').each(async function () {10411 const textareas = $(this).closest('.drawer').find('.drawer-content textarea.autoSetHeight');
10374 await resetScrollHeight($(this));10412 for (const textarea of textareas) {
10413 await resetScrollHeight($(textarea));
10375 return;10414 return;
10376 });10415 }
10377 }10416 }
1037810417
10379 } else if (drawerWasOpenAlready) { //to close manually10418 } else if (drawerWasOpenAlready) { // to close manually
10419 console.warn('saw drawer was already open');
10380 icon.toggleClass('closedIcon openIcon');10420 icon.toggleClass('closedIcon openIcon');
1038110421
10382 if (pinnedDrawerClicked) {10422 if (pinnedDrawerClicked) {
10383 $(drawer).addClass('resizing').each((_, el) => {10423 $(drawer).addClass('resizing').each((_, el) => {
10384 slideToggle(el, {10424 animateDrawer($(el), false, undefined, function (el) {
10385 ...getSlideToggleOptions(),10425 el.classList.remove('resizing');
10386 onAnimationEnd: function (el) {
10387 el.classList.remove('resizing');
10388 },
10389 });10426 });
10390 });10427 });
10391 }10428 }
10392 else {10429 else {
10430 console.warn('not pinned drawer');
10393 $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {10431 $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {
10394 slideToggle(el, {10432 animateDrawer($(el), false, undefined, function (el) {
10395 ...getSlideToggleOptions(),10433 el.closest('.drawer-content').classList.remove('resizing');
10396 onAnimationEnd: function (el) {
10397 el.closest('.drawer-content').classList.remove('resizing');
10398 },
10399 });10434 });
10400 });10435 });
10401 }10436 }
@@ -10403,7 +10438,7 @@ function doNavbarIconClick() {
10403 drawer.toggleClass('closedDrawer openDrawer');10438 drawer.toggleClass('closedDrawer openDrawer');
10404 }10439 }
10405}10440}
1040610441// ...existing code...
10407function addDebugFunctions() {10442function addDebugFunctions() {
10408 const doBackfill = async () => {10443 const doBackfill = async () => {
10409 for (const message of chat) {10444 for (const message of chat) {
@@ -12028,7 +12063,7 @@ jQuery(async function () {
1202812063
12029 $('.drawer-toggle').on('click', doNavbarIconClick);12064 $('.drawer-toggle').on('click', doNavbarIconClick);
1203012065
12031 $('html').on('touchstart mousedown', function (e) {12066 $('html').on('touchstart mousedown', async function (e) {
12032 var clickTarget = $(e.target);12067 var clickTarget = $(e.target);
1203312068
12034 if (isExportPopupOpen12069 if (isExportPopupOpen
@@ -12056,22 +12091,23 @@ jQuery(async function () {
12056 }12091 }
12057 }12092 }
1205812093
12094
12095 // This autocloses open drawers that are not pinned if a click happens inside the app which does not target them.
12059 var targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;12096 var targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;
12060 if (clickTarget.hasClass('drawer-icon') == false && !clickTarget.hasClass('openDrawer')) {12097 if (!clickTarget.hasClass('drawer-icon') && !clickTarget.hasClass('openDrawer')) {
12061 if ($('.openDrawer').length !== 0) {12098 const $openDrawers = $('.openDrawer').not('.pinnedOpen');
12062 if (targetParentHasOpenDrawer === 0) {12099 if ($openDrawers.length && targetParentHasOpenDrawer === 0) {
12063 //console.log($('.openDrawer').not('.pinnedOpen').length);12100 // Animate close for each open drawer
12064 $('.openDrawer').not('.pinnedOpen').addClass('resizing').each((_, el) => {12101 for (const el of $openDrawers) {
12065 slideToggle(el, {12102 $(el).addClass('resizing');
12066 ...getSlideToggleOptions(),12103 // Use the same animateDrawer helper as in doNavbarIconClick
12067 onAnimationEnd: (el) => {12104 await animateDrawer($(el), false, undefined, function (el) {
12068 el.closest('.drawer-content').classList.remove('resizing');12105 $(el).closest('.drawer-content').removeClass('resizing');
12069 },
12070 });
12071 });12106 });
12072 $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
12073 $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
12074 }12107 }
12108 // Toggle icon and drawer classes after animation
12109 $('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
12110 $openDrawers.toggleClass('closedDrawer openDrawer');
12075 }12111 }
12076 }12112 }
12077 });12113 });
public/scripts/RossAscends-mods.js+136 -184
@@ -19,7 +19,7 @@ import {
19 menu_type,19 menu_type,
20 substituteParams,20 substituteParams,
21 sendTextareaMessage,21 sendTextareaMessage,
22 getSlideToggleOptions,22 doNavbarIconClick,
23} from '../script.js';23} from '../script.js';
2424
25import {25import {
@@ -473,54 +473,72 @@ const saveUserInputDebounced = debounce(saveUserInput);
473473
474// Make the DIV element draggable:474// Make the DIV element draggable:
475475
476export 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 */
480export function dragElement($elmnt) {
481 let actionType = null; // "drag" or "resize"
482 let isMouseDown = false;
479483
480 var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;484 let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
481 var height, width, top, left, right, bottom,485 let height, width, top, left, right, bottom,
482 maxX, maxY, winHeight, winWidth,486 maxX, maxY, winHeight, winWidth,
483 topbar, topBarFirstX, topBarLastY;487 topbar, topBarFirstX, topBarLastY;
484488
485 var elmntName = elmnt.attr('id');489 const elmntName = $elmnt.attr('id');
486 console.debug(`dragElement called for ${elmntName}`);
487 const elmntNameEscaped = $.escapeSelector(elmntName);490 const elmntNameEscaped = $.escapeSelector(elmntName);
488 const elmntHeader = $(`#${elmntNameEscaped}header`);491 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 repositioning494 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 resize500 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();
501 }507 }
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)
503 const observer = new MutationObserver((mutations) => {518 const observer = new MutationObserver((mutations) => {
504 const target = mutations[0].target;519 const $target = $(mutations[0].target);
505 if (!$(target).is(':visible') //abort if element is invisible520 if (
506 || $(target).hasClass('resizing') //being auto-resized by other JS code521 !$target.is(':visible') ||
507 || Number((String(target.height).replace('px', ''))) < 50 //too short522 $target.hasClass('resizing') ||
508 || Number((String(target.width).replace('px', ''))) < 50 //too narrow523 $target.height() < 50 ||
509 || power_user.movingUI === false // if MUI is not turned on524 $target.width() < 50 ||
510 || isMobile() // if it's a mobile screen525 power_user.movingUI === false ||
526 isMobile() ||
527 !isMouseDown
511 ) {528 ) {
529 observer.disconnect();
512 return;530 return;
513 }531 }
514532
515 const style = getComputedStyle(target);533 const style = getComputedStyle($target[0]);
516 height = parseInt(style.height);534 height = parseInt(style.height);
517 width = parseInt(style.width);535 width = parseInt(style.width);
518 top = parseInt(style.top);536 top = parseInt(style.top);
519 left = parseInt(style.left);537 left = parseInt(style.left);
520 right = parseInt(style.right);538 right = parseInt(style.right);
521 bottom = parseInt(style.bottom);539 bottom = parseInt(style.bottom);
522 maxX = parseInt(width + left);540 maxX = width + left;
523 maxY = parseInt(height + top);541 maxY = height + top;
524 winWidth = window.innerWidth;542 winWidth = window.innerWidth;
525 winHeight = window.innerHeight;543 winHeight = window.innerHeight;
526544
@@ -529,182 +547,119 @@ export function dragElement(elmnt) {
529 topBarFirstX = parseInt(topbarstyle.marginInline);547 topBarFirstX = parseInt(topbarstyle.marginInline);
530 topBarLastY = parseInt(topbarstyle.height);548 topBarLastY = parseInt(topbarstyle.height);
531549
532 //prepare an empty poweruser object for the item being altered if we don't have one already550 // Prepare state object if missing
533 if (!power_user.movingUIState[elmntName]) {551 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 resizing553 if (actionType === 'resize') {
539 if (!isHeaderBeingDragged && isMouseDown) { //if user is dragging the resize handle (not in header)
540 let imgHeight, imgWidth, imageAspectRatio;
541 let containerAspectRatio = height / width;554 let containerAspectRatio = height / width;
542555 if ($elmnt.attr('id').startsWith('zoomFor_')) {
543 //force aspect ratio for zoomed avatars556 const zoomedAvatarImage = $elmnt.find('.zoomed_avatar_img');
544 if ($(elmnt).attr('id').startsWith('zoomFor_')) {557 const imgHeight = zoomedAvatarImage.height();
545 let zoomedAvatarImage = $(elmnt).find('.zoomed_avatar_img');558 const imgWidth = zoomedAvatarImage.width();
546 imgHeight = zoomedAvatarImage.height();559 const imageAspectRatio = imgHeight / imgWidth;
547 imgWidth = zoomedAvatarImage.width();
548 imageAspectRatio = imgHeight / imgWidth;
549
550 // Maintain aspect ratio
551 if (containerAspectRatio !== imageAspectRatio) {560 if (containerAspectRatio !== imageAspectRatio) {
552 elmnt.css('width', elmnt.width());561 $elmnt.css('width', $elmnt.width());
553 elmnt.css('height', elmnt.width() * imageAspectRatio);562 $elmnt.css('height', $elmnt.width() * imageAspectRatio);
554 }563 }
555564 if (top + $elmnt.height() >= winHeight) {
556 // Prevent resizing offscreen565 $elmnt.css('height', winHeight - top - 1 + 'px');
557 if (top + elmnt.height() >= winHeight) {566 $elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
558 elmnt.css('height', winHeight - top - 1 + 'px');
559 elmnt.css('width', (winHeight - top - 1) / imageAspectRatio + 'px');
560 }567 }
561568 if (left + $elmnt.width() >= winWidth) {
562 if (left + elmnt.width() >= winWidth) {569 $elmnt.css('width', winWidth - left - 1 + 'px');
563 elmnt.css('width', winWidth - left - 1 + 'px');570 $elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
564 elmnt.css('height', (winWidth - left - 1) * imageAspectRatio + 'px');
565 }
566 } else { //prevent divs that are not zoomedAvatars from resizing offscreen
567
568 if (top + elmnt.height() >= winHeight) {
569 elmnt.css('height', winHeight - top - 1 + 'px');
570 }
571
572 if (left + elmnt.width() >= winWidth) {
573 elmnt.css('width', winWidth - left - 1 + 'px');
574 }571 }
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');
575 }575 }
576
577 //prevent resizing from top left into the top bar
578 if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) {576 if (top < topBarLastY && maxX >= topBarFirstX && left <= topBarFirstX) {
579 elmnt.css('width', width - 1 + 'px');577 $elmnt.css('width', width - 1 + 'px');
580 }578 }
581579 $elmnt.css({ left, top });
582 //set css to prevent weird resize behavior (does not save)580 $elmnt.off('mouseup').on('mouseup', () => {
583 elmnt.css('left', left);581 if (
584 elmnt.css('top', top);582 power_user.movingUIState[elmntName].width === $elmnt.width() &&
585583 power_user.movingUIState[elmntName].height === $elmnt.height()
586 //set a listener for mouseup to save new width/height584 ) return;
587 $(window).off('mouseup').on('mouseup', () => {585 savePositionAndSize();
588 console.log(`Saving ${elmntName} Height/Width`);586 observer.disconnect();
589 // check if the height or width actually changed
590 if (power_user.movingUIState[elmntName].width === elmnt.width() && power_user.movingUIState[elmntName].height === elmnt.height()) {
591 console.log('no change detected, aborting save');
592 return;
593 }
594
595 power_user.movingUIState[elmntName].width = width;
596 power_user.movingUIState[elmntName].height = height;
597 eventSource.emit('resizeUI', elmntName);
598 saveSettingsDebounced();
599 imgHeight = null;
600 imgWidth = null;
601 height = null;
602 width = null;
603
604 containerAspectRatio = null;
605 imageAspectRatio = null;
606 $(window).off('mouseup');
607 });587 });
588 } else if (actionType === 'drag') {
589 clampToViewport();
608 }590 }
609591
610 //only record position changes if header is being dragged592 // 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 }
641 });594 });
642595
596 // Mouse event handlers
643 function dragMouseDown(e) {597 function dragMouseDown(e) {
644
645 if (e) {598 if (e) {
646 isHeaderBeingDragged = true;599 actionType = 'drag';
600 isMouseDown = true;
647 e.preventDefault();601 e.preventDefault();
648 pos3 = e.clientX; //mouse X at click602 pos3 = e.clientX;
649 pos4 = e.clientY; //mouse Y at click603 pos4 = e.clientY;
650 }604 }
651 $(document).on('mouseup', closeDragElement);605 $(document).on('mouseup', closeDragElement);
652 $(document).on('mousemove', elementDrag);606 $(document).on('mousemove', elementDrag);
653 }607 }
654608
655 function elementDrag(e) {609 function elementDrag(e) {
656 if (!power_user.movingUIState[elmntName]) {610 if (!power_user.movingUIState[elmntName]) power_user.movingUIState[elmntName] = {};
657 power_user.movingUIState[elmntName] = {};
658 }
659
660 e = e || window.event;
661 e.preventDefault();611 e.preventDefault();
662612 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;
665 pos3 = e.clientX; //new mouse X615 pos4 = e.clientY;
666 pos4 = e.clientY; //new mouse Y616 $elmnt.attr('data-dragged', 'true');
667617 $elmnt.css('left', ($elmnt.offset().left - pos1) + 'px');
668 elmnt.attr('data-dragged', 'true');618 $elmnt.css('top', ($elmnt.offset().top - pos2) + 'px');
669619 $elmnt.css('margin', 'unset');
670 //first set css to computed values to avoid CSS NaN results from 'auto', etc620 $elmnt.css('height', height);
671 elmnt.css('left', (elmnt.offset().left) + 'px');621 $elmnt.css('width', 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;
687 }622 }
688623
689 function closeDragElement() {624 function closeDragElement() {
690 console.debug('drag finished');
691 isHeaderBeingDragged = false;
692 isMouseDown = false;625 isMouseDown = false;
626 actionType = null;
693 $(document).off('mouseup', closeDragElement);627 $(document).off('mouseup', closeDragElement);
694 $(document).off('mousemove', elementDrag);628 $(document).off('mousemove', elementDrag);
695 $('body').css('overflow', '');629 $elmnt.attr('data-dragged', 'false');
696 // Clear the "data-dragged" attribute
697 elmnt.attr('data-dragged', 'false');
698 observer.disconnect();630 observer.disconnect();
699 console.debug(`Saving ${elmntName} UI position`);631 savePositionAndSize();
700 saveSettingsDebounced();632 }
701 top = null;633
702 left = null;634 // Setup event listeners
703 right = null;635 if ($elmntHeader.length) {
704 bottom = null;636 $elmntHeader.off('mousedown').on('mousedown', (e) => {
705 maxX = null;637 if ($(e.target).hasClass('drag-grabber')) {
706 maxY = null;638 actionType = 'drag';
639 isMouseDown = true;
640 observer.observe($elmnt[0], { attributes: true, attributeFilter: ['style'] });
641 dragMouseDown(e);
642 }
643 });
707 }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 });
708}663}
709664
710export async function initMovingUI() {665export async function initMovingUI() {
@@ -775,9 +730,8 @@ export function initRossMods() {
775 $(RightNavDrawerIcon).removeClass('drawerPinnedOpen');730 $(RightNavDrawerIcon).removeClass('drawerPinnedOpen');
776731
777 if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {732 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');
781 }735 }
782 }736 }
783 });737 });
@@ -793,14 +747,13 @@ export function initRossMods() {
793 $(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');747 $(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');
794748
795 if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {749 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');
799 }752 }
800 }753 }
801 });754 });
802755
803 $(WIPanelPin).on('click', function () {756 $(WIPanelPin).on('click', async function () {
804 accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));757 accountStorage.setItem('WINavLockOn', $(WIPanelPin).prop('checked'));
805 if ($(WIPanelPin).prop('checked') == true) {758 if ($(WIPanelPin).prop('checked') == true) {
806 console.debug('adding pin class to WI');759 console.debug('adding pin class to WI');
@@ -813,9 +766,8 @@ export function initRossMods() {
813766
814 if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {767 if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
815 console.debug('closing WI after lock removal');768 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');
819 }771 }
820 }772 }
821 });773 });
public/scripts/world-info.js+448 -633
@@ -966,6 +966,7 @@ export function reloadEditor(file, loadIfNotSelected = false) {
966 }966 }
967}967}
968968
969//MARK: regWISlashCommands
969function registerWorldInfoSlashCommands() {970function registerWorldInfoSlashCommands() {
970 /**971 /**
971 * Gets a *rough* approximation of the current chat context.972 * Gets a *rough* approximation of the current chat context.
@@ -1998,6 +1999,7 @@ function clearEntryList() {
1998 console.timeEnd('clearEntryList');1999 console.timeEnd('clearEntryList');
1999}2000}
20002001
2002//MARK: displayWorldEntries
2001async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {2003async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
2002 updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);2004 updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);
20032005
@@ -2537,147 +2539,378 @@ export function parseRegexFromString(input) {
2537 }2539 }
2538}2540}
25392541
2540export async function getWorldEntry(name, data, entry) {2542//MARK: getWorldEntry
2541 if (!data.entries[entry.uid]) {2543function enableKeysInputHelper({ template, entry, entryPropName, originalDataValueName, name, data }) {
2542 return;2544 const isFancyInput = !isMobile() && !power_user.wi_key_input_plaintext;
2545 const input = isFancyInput ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`);
2546 input.data('uid', entry.uid);
2547 input.on('click', function (event) {
2548 event.stopPropagation();
2549 });
2550
2551 function templateStyling(item, { searchStyle = false } = {}) {
2552 const content = $('<span>').addClass('item').text(item.text).attr('title', `${item.text}\n\nClick to edit`);
2553 const isRegex = isValidRegex(item.text);
2554 if (isRegex) {
2555 content.html(highlightRegex(item.text));
2556 content.addClass('regex_item').prepend($('<span>').addClass('regex_icon').text('•*').attr('title', 'Regex'));
2557 }
2558 if (searchStyle && item.count) {
2559 const wrapper = $('<span>').addClass('result_block').append(content);
2560 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`));
2561 return wrapper;
2562 }
2563 return content;
2564 }
2565
2566 if (isFancyInput) {
2567 select2ModifyOptions(input, entry[entryPropName], { select: true, changeEventArgs: { skipReset: true, noSave: true } });
2568 input.select2({
2569 ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),
2570 tags: true,
2571 tokenSeparators: [','],
2572 tokenizer: customTokenizer,
2573 placeholder: input.attr('placeholder'),
2574 templateResult: item => templateStyling(item, { searchStyle: true }),
2575 templateSelection: item => templateStyling(item),
2576 });
2577
2578 // TypeScript-safe event handler
2579 /**
2580 * @param {Event} _event
2581 * @param {{ skipReset?: boolean, noSave?: boolean }} [arg]
2582 */
2583 input.on('change', async function (_event, arg) {
2584 const uid = $(this).data('uid');
2585 const keys = ($(this).select2('data')).map(x => x.text);
2586 const skipReset = arg?.skipReset ?? false;
2587 const noSave = arg?.noSave ?? false;
2588 if (!skipReset) await resetScrollHeight(this);
2589 if (!noSave) {
2590 data.entries[uid][entryPropName] = keys;
2591 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2592 await saveWorldInfo(name, data);
2593 }
2594 $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);
2595 });
2596
2597 input.toggleClass('empty', !entry[entryPropName].length);
2598 input.on('select2:select', event => updateWorldEntryKeyOptionsCache([event.params.data]));
2599 input.on('select2:unselect', event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true }));
2600
2601 select2ChoiceClickSubscribe(input, target => {
2602 const key = $(target.closest('.regex-highlight, .item')).text();
2603 const selected = input.val();
2604 if (!Array.isArray(selected)) return;
2605 var index = selected.indexOf(getSelect2OptionId(key));
2606 if (index > -1) selected.splice(index, 1);
2607 input.val(selected).trigger('change');
2608 updateWorldEntryKeyOptionsCache([key], { remove: true });
2609 input.next('span.select2-container').find('textarea').val(key).trigger('input');
2610 }, { openDrawer: true });
2611 } else {
2612 template.find(`select[name="${entryPropName}"]`).hide();
2613 input.show();
2614 /**
2615 * @param {Event} _event
2616 * @param {{ skipReset?: boolean, noSave?: boolean }} [arg]
2617 */
2618 input.on('change', async function (_event, arg) {
2619 const uid = $(this).data('uid');
2620 const value = String($(this).val());
2621 const skipReset = arg?.skipReset ?? false;
2622 const noSave = arg?.noSave ?? false;
2623 if (!skipReset) await resetScrollHeight(this);
2624 if (!noSave) {
2625 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
2626 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2627 await saveWorldInfo(name, data);
2628 $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);
2629 }
2630 });
2631 input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
2543 }2632 }
2633 return { isFancy: isFancyInput, control: input };
2634}
25442635
2545 const template = WI_ENTRY_EDIT_TEMPLATE.clone();2636/**
2546 template.data('uid', entry.uid);2637 * Helper to handle match checkboxes for WI entries.
2547 template.attr('uid', entry.uid);2638 */
2639function handleMatchCheckboxHelper({ template, entry, fieldName, data, name }) {
2640 const key = originalWIDataKeyMap[fieldName];
2641 const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`);
2642 checkBoxElem.data('uid', entry.uid);
2643 checkBoxElem.on('input', async function () {
2644 const uid = $(this).data('uid');
2645 const value = $(this).prop('checked');
2646 data.entries[uid][fieldName] = value;
2647 setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]);
2648 await saveWorldInfo(name, data);
2649 });
2650 checkBoxElem.prop('checked', !!entry[fieldName]).trigger('input');
2651}
25482652
2549 // Init default state of WI Key toggle (=> true)2653/**
2550 if (typeof power_user.wi_key_input_plaintext === 'undefined') power_user.wi_key_input_plaintext = true;2654 * Helper to update position/order display.
2655 */
2656function updatePosOrdDisplayHelper({ template, data, uid }) {
2657 let entry = data.entries[uid];
2658 let posText = entry.position;
2659 switch (entry.position) {
2660 case 0: posText = '↑CD'; break;
2661 case 1: posText = 'CD↓'; break;
2662 case 2: posText = '↑AN'; break;
2663 case 3: posText = 'AN↓'; break;
2664 case 4: posText = `@D${entry.depth}`; break;
2665 }
2666 template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`);
2667}
25512668
2552 /** Function to build the keys input controls @param {string} entryPropName @param {string} originalDataValueName */2669/**
2553 function enableKeysInput(entryPropName, originalDataValueName) {2670 * Helper to initialize character filter select2.
2554 const isFancyInput = !isMobile() && !power_user.wi_key_input_plaintext;2671 */
2555 const input = isFancyInput ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`);2672function initCharacterFilterSelect2Helper(characterFilter, t) {
2556 input.data('uid', entry.uid);2673 if (!isMobile()) {
2557 input.on('click', function (event) {2674 $(characterFilter).select2({
2558 // Prevent closing the drawer on clicking the input2675 width: '100%',
2559 event.stopPropagation();2676 placeholder: t`Tie this entry to specific characters or characters with specific tags`,
2677 allowClear: true,
2678 closeOnSelect: false,
2560 });2679 });
2680 }
2681}
25612682
2562 function templateStyling(/** @type {Select2Option} */ item, { searchStyle = false } = {}) {2683/**
2563 const content = $('<span>').addClass('item').text(item.text).attr('title', `${item.text}\n\nClick to edit`);2684 * Helper to fill character and tag options for character filter.
2564 const isRegex = isValidRegex(item.text);2685 */
2565 if (isRegex) {2686function fillCharacterAndTagOptionsHelper({ characterFilter, entry, getContext }) {
2566 content.html(highlightRegex(item.text));2687 const characters = getContext().characters;
2567 content.addClass('regex_item').prepend($('<span>').addClass('regex_icon').text('•*').attr('title', 'Regex'));2688 characters.forEach((character) => {
2568 }2689 const option = document.createElement('option');
2690 const name = character.avatar.replace(/\.[^/.]+$/, '') ?? character.name;
2691 option.innerText = name;
2692 option.selected = entry.characterFilter?.names?.includes(name);
2693 option.setAttribute('data-type', 'character');
2694 characterFilter.append(option);
2695 });
2696 const tags = getContext().tags;
2697 tags.forEach((tag) => {
2698 const option = document.createElement('option');
2699 option.innerText = `[Tag] ${tag.name}`;
2700 option.selected = entry.characterFilter?.tags?.includes(tag.id);
2701 option.value = tag.id;
2702 option.setAttribute('data-type', 'tag');
2703 characterFilter.append(option);
2704 });
2705}
2706
2707/**
2708 * Helper to handle character filter changes.
2709 */
2710function handleCharacterFilterChangeHelper({ characterFilter, data, entry, name, world_names, getContext, setWIOriginalDataValue, saveWorldInfo, t }) {
2711 characterFilter.on('mousedown change', async function (e) {
2712 if (world_names.length === 0) {
2713 e.preventDefault();
2714 return;
2715 }
2716 const uid = $(this).data('uid');
2717 const selected = $(this).find(':selected');
2718 if ((!selected || selected?.length === 0) && !data.entries[uid].characterFilter?.isExclude) {
2719 delete data.entries[uid].characterFilter;
2720 } else {
2721 const names = selected.filter('[data-type="character"]').map((_, e) => e instanceof HTMLOptionElement && e.innerText).toArray();
2722 const tags = selected.filter('[data-type="tag"]').map((_, e) => e instanceof HTMLOptionElement && e.value).toArray();
2723 Object.assign(
2724 data.entries[uid],
2725 {
2726 characterFilter: {
2727 isExclude: data.entries[uid].characterFilter?.isExclude ?? false,
2728 names: names,
2729 tags: tags,
2730 },
2731 },
2732 );
2733 }
2734 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2735 await saveWorldInfo(name, data);
2736 });
2737}
25692738
2570 if (searchStyle && item.count) {2739/**
2571 // Build a wrapping element2740 * Helper to handle probability input.
2572 const wrapper = $('<span>').addClass('result_block')2741 */
2573 .append(content);2742function handleProbabilityInputHelper({ probabilityInput, data, entry, name, setWIOriginalDataValue, saveWorldInfo }) {
2574 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`));2743 probabilityInput.data('uid', entry.uid);
2575 return wrapper;2744 probabilityInput.on('input', async function () {
2745 const uid = $(this).data('uid');
2746 const value = Number($(this).val());
2747 data.entries[uid].probability = !isNaN(value) ? value : null;
2748 if (data.entries[uid].probability !== null) {
2749 data.entries[uid].probability = Math.min(100, Math.max(0, data.entries[uid].probability));
2750 if (data.entries[uid].probability !== value) {
2751 $(this).val(data.entries[uid].probability);
2576 }2752 }
2753 }
2754 setWIOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
2755 await saveWorldInfo(name, data);
2756 });
2757 probabilityInput.val(entry.probability).trigger('input');
2758 probabilityInput.css('width', 'calc(3em + 15px)');
2759}
25772760
2578 return content;2761/**
2762 * Helper to handle probability toggle.
2763 */
2764function handleProbabilityToggleHelper({ probabilityToggle, data, entry, name, probabilityInput, setWIOriginalDataValue, saveWorldInfo }) {
2765 probabilityToggle.data('uid', entry.uid);
2766 probabilityToggle.on('input', async function () {
2767 const uid = $(this).data('uid');
2768 const value = $(this).prop('checked');
2769 data.entries[uid].useProbability = value;
2770 const probabilityContainer = $(this).closest('.world_entry').find('.probabilityContainer');
2771 await saveWorldInfo(name, data);
2772 value ? probabilityContainer.show() : probabilityContainer.hide();
2773 if (value && data.entries[uid].probability === null) {
2774 data.entries[uid].probability = 100;
2579 }2775 }
2776 if (!value) {
2777 data.entries[uid].probability = null;
2778 }
2779 probabilityInput.val(data.entries[uid].probability).trigger('input');
2780 });
2781 probabilityToggle.prop('checked', true).trigger('input');
2782 probabilityToggle.parent().hide();
2783}
25802784
2581 if (isFancyInput) {2785/**
2582 // First initialize existing values as options, before initializing select2, to speed up performance2786 * Helper to handle select2 dropdowns for boolean selects.
2583 select2ModifyOptions(input, entry[entryPropName], { select: true, changeEventArgs: { skipReset: true, noSave: true } });2787 */
2788function handleBooleanSelectHelper({ selectElem, entry, entryKey, data, name, setWIOriginalDataValue, saveWorldInfo }) {
2789 selectElem.data('uid', entry.uid);
2790 selectElem.on('input', async function () {
2791 const uid = $(this).data('uid');
2792 const value = $(this).val();
2793 data.entries[uid][entryKey] = value === 'null' ? null : value === 'true';
2794 setWIOriginalDataValue(data, uid, `extensions.${entryKey.replace(/[A-Z]/g, m => `_${m.toLowerCase()}`)}`, data.entries[uid][entryKey]);
2795 await saveWorldInfo(name, data);
2796 });
2797 selectElem.val((entry[entryKey] === null || entry[entryKey] === undefined) ? 'null' : entry[entryKey] ? 'true' : 'false').trigger('input');
2798}
25842799
2585 input.select2({2800/**
2586 ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),2801 * Helper to handle input fields for numbers.
2587 tags: true,2802 */
2588 tokenSeparators: [','],2803function handleNumberInputHelper({ inputElem, entry, entryKey, data, name, setWIOriginalDataValue, saveWorldInfo, min, max, clamp = false }) {
2589 tokenizer: customTokenizer,2804 inputElem.data('uid', entry.uid);
2590 placeholder: input.attr('placeholder'),2805 inputElem.on('input', async function () {
2591 templateResult: item => templateStyling(item, { searchStyle: true }),2806 const uid = $(this).data('uid');
2592 templateSelection: item => templateStyling(item),2807 let value = Number($(this).val());
2593 });2808 if (clamp) {
2594 input.on('change', async function (_, { skipReset, noSave } = {}) {2809 if (value < min) {
2595 const uid = $(this).data('uid');2810 value = min;
2596 /** @type {string[]} */2811 $(this).val(min);
2597 const keys = ($(this).select2('data')).map(x => x.text);2812 } else if (value > max) {
25982813 value = max;
2599 !skipReset && await resetScrollHeight(this);2814 $(this).val(max);
2600 if (!noSave) {2815 }
2601 data.entries[uid][entryPropName] = keys;
2602 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2603 await saveWorldInfo(name, data);
2604 }
2605 $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);
2606 });
2607 input.toggleClass('empty', !entry[entryPropName].length);
2608 input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data]));
2609 input.on('select2:unselect', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true }));
2610
2611 select2ChoiceClickSubscribe(input, target => {
2612 const key = $(target.closest('.regex-highlight, .item')).text();
2613 console.debug('Editing WI key', key);
2614
2615 // Remove the current key from the actual selection
2616 const selected = input.val();
2617 if (!Array.isArray(selected)) return;
2618 var index = selected.indexOf(getSelect2OptionId(key));
2619 if (index > -1) selected.splice(index, 1);
2620 input.val(selected).trigger('change');
2621 // Manually update the cache, that change event is not gonna trigger it
2622 updateWorldEntryKeyOptionsCache([key], { remove: true });
2623
2624 // We need to "hack" the actual text input into the currently open textarea
2625 input.next('span.select2-container').find('textarea')
2626 .val(key).trigger('input');
2627 }, { openDrawer: true });
2628 }2816 }
2629 else {2817 data.entries[uid][entryKey] = !isNaN(value) ? value : null;
2630 // Compatibility with mobile devices. On mobile we need a text input field, not a select option control, so we need its own event handlers2818 setWIOriginalDataValue(data, uid, `extensions.${entryKey.replace(/[A-Z]/g, m => `_${m.toLowerCase()}`)}`, data.entries[uid][entryKey]);
2631 template.find(`select[name="${entryPropName}"]`).hide();2819 await saveWorldInfo(name, data);
2632 input.show();2820 });
2821 inputElem.val(entry[entryKey] ?? (clamp ? min : '')).trigger('input');
2822}
26332823
2634 input.on('input', async function (_, { skipReset, noSave } = {}) {2824/**
2635 const uid = $(this).data('uid');2825 * Helper to handle tri-state selector for constant/normal/vectorized.
2636 const value = String($(this).val());2826 */
2637 !skipReset && await resetScrollHeight(this);2827function handleEntryStateSelectorHelper({ entryStateSelector, entry, data, name, setWIOriginalDataValue, saveWorldInfo }) {
2638 if (!noSave) {2828 entryStateSelector.data('uid', entry.uid);
2639 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);2829 entryStateSelector.on('click', function (event) {
2640 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);2830 event.stopPropagation();
2641 await saveWorldInfo(name, data);2831 });
2642 $(this).toggleClass('empty', !data.entries[uid][entryPropName].length);2832 entryStateSelector.on('input', async function () {
2643 }2833 const uid = entry.uid;
2644 });2834 const value = $(this).val();
2645 input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });2835 switch (value) {
2836 case 'constant':
2837 data.entries[uid].constant = true;
2838 data.entries[uid].vectorized = false;
2839 setWIOriginalDataValue(data, uid, 'constant', true);
2840 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2841 break;
2842 case 'normal':
2843 data.entries[uid].constant = false;
2844 data.entries[uid].vectorized = false;
2845 setWIOriginalDataValue(data, uid, 'constant', false);
2846 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2847 break;
2848 case 'vectorized':
2849 data.entries[uid].constant = false;
2850 data.entries[uid].vectorized = true;
2851 setWIOriginalDataValue(data, uid, 'constant', false);
2852 setWIOriginalDataValue(data, uid, 'extensions.vectorized', true);
2853 break;
2646 }2854 }
2647 return { isFancy: isFancyInput, control: input };2855 await saveWorldInfo(name, data);
2648 }2856 });
2857 const entryState = () => entry.constant === true ? 'constant' : entry.vectorized === true ? 'vectorized' : 'normal';
2858 entryStateSelector.find(`option[value=${entryState()}]`).prop('selected', true).trigger('input');
2859}
2860
2861/**
2862 * Helper to handle kill switch toggle.
2863 */
2864function handleEntryKillSwitchHelper({ entryKillSwitch, entry, data, name, setWIOriginalDataValue, saveWorldInfo, template }) {
2865 entryKillSwitch.data('uid', entry.uid);
2866 entryKillSwitch.on('click', async function () {
2867 const uid = entry.uid;
2868 data.entries[uid].disable = !data.entries[uid].disable;
2869 const isActive = !data.entries[uid].disable;
2870 setWIOriginalDataValue(data, uid, 'enabled', isActive);
2871 template.toggleClass('disabledWIEntry', !isActive);
2872 entryKillSwitch.toggleClass('fa-toggle-off', !isActive);
2873 entryKillSwitch.toggleClass('fa-toggle-on', isActive);
2874 await saveWorldInfo(name, data);
2875 });
2876 const isActive = !entry.disable;
2877 template.toggleClass('disabledWIEntry', !isActive);
2878 entryKillSwitch.toggleClass('fa-toggle-off', !isActive);
2879 entryKillSwitch.toggleClass('fa-toggle-on', isActive);
2880}
26492881
2650 // key2882/**
2651 const keyInput = enableKeysInput('key', 'keys');2883 * Main function to build the WI entry editor template.
2884 */
2885export async function getWorldEntry(name, data, entry) {
2886 if (!data.entries[entry.uid]) return;
2887
2888 const template = WI_ENTRY_EDIT_TEMPLATE.clone();
2889 template.data('uid', entry.uid);
2890 template.attr('uid', entry.uid);
2891
2892 if (typeof power_user.wi_key_input_plaintext === 'undefined') power_user.wi_key_input_plaintext = true;
26522893
2653 // keysecondary2894 // Key inputs
2654 const keySecondaryInput = enableKeysInput('keysecondary', 'secondary_keys');2895 const keyInput = enableKeysInputHelper({ template, entry, entryPropName: 'key', originalDataValueName: 'keys', name, data });
2896 const keySecondaryInput = enableKeysInputHelper({ template, entry, entryPropName: 'keysecondary', originalDataValueName: 'secondary_keys', name, data });
26552897
2656 // draw key input switch button2898 // Key input switch
2657 template.find('.switch_input_type_icon').on('click', function () {2899 template.find('.switch_input_type_icon').on('click', function () {
2658 power_user.wi_key_input_plaintext = !power_user.wi_key_input_plaintext;2900 power_user.wi_key_input_plaintext = !power_user.wi_key_input_plaintext;
2659 saveSettingsDebounced();2901 saveSettingsDebounced();
2660
2661 // Just redraw the panel
2662 const uid = ($(this).parents('.world_entry')).data('uid');2902 const uid = ($(this).parents('.world_entry')).data('uid');
2663 updateEditor(uid, false);2903 updateEditor(uid, false);
2664
2665 $(`.world_entry[uid="${uid}"] .inline-drawer-icon`).trigger('click');2904 $(`.world_entry[uid="${uid}"] .inline-drawer-icon`).trigger('click');
2666 // setTimeout(() => {
2667 // }, debounce_timeout.standard);
2668 }).each((_, icon) => {2905 }).each((_, icon) => {
2669 $(icon).attr('title', $(icon).data(power_user.wi_key_input_plaintext ? 'tooltip-on' : 'tooltip-off'));2906 $(icon).attr('title', $(icon).data(power_user.wi_key_input_plaintext ? 'tooltip-on' : 'tooltip-off'));
2670 $(icon).text($(icon).data(power_user.wi_key_input_plaintext ? 'icon-on' : 'icon-off'));2907 $(icon).text($(icon).data(power_user.wi_key_input_plaintext ? 'icon-on' : 'icon-off'));
2671 });2908 });
26722909
2673 // logic AND/NOT2910 // Logic AND/NOT
2674 const selectiveLogicDropdown = template.find('select[name="entryLogicType"]');2911 const selectiveLogicDropdown = template.find('select[name="entryLogicType"]');
2675 selectiveLogicDropdown.data('uid', entry.uid);2912 selectiveLogicDropdown.data('uid', entry.uid);
26762913 selectiveLogicDropdown.on('click', e => e.stopPropagation());
2677 selectiveLogicDropdown.on('click', function (event) {
2678 event.stopPropagation();
2679 });
2680
2681 selectiveLogicDropdown.on('input', async function () {2914 selectiveLogicDropdown.on('input', async function () {
2682 const uid = $(this).data('uid');2915 const uid = $(this).data('uid');
2683 const value = Number($(this).val());2916 const value = Number($(this).val());
@@ -2685,17 +2918,11 @@ export async function getWorldEntry(name, data, entry) {
2685 setWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);2918 setWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
2686 await saveWorldInfo(name, data);2919 await saveWorldInfo(name, data);
2687 });2920 });
26882921 template.find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`).prop('selected', true).trigger('input');
2689 template
2690 .find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`)
2691 .prop('selected', true)
2692 .trigger('input');
26932922
2694 // Character filter2923 // Character filter
2695 const characterFilterLabel = template.find('label[for="characterFilter"] > small');2924 const characterFilterLabel = template.find('label[for="characterFilter"] > small');
2696 characterFilterLabel.text(entry.characterFilter?.isExclude ? 'Exclude Character(s)' : 'Filter to Character(s)');2925 characterFilterLabel.text(entry.characterFilter?.isExclude ? 'Exclude Character(s)' : 'Filter to Character(s)');
2697
2698 // exclude characters checkbox
2699 const characterExclusionInput = template.find('input[name="character_exclusion"]');2926 const characterExclusionInput = template.find('input[name="character_exclusion"]');
2700 characterExclusionInput.data('uid', entry.uid);2927 characterExclusionInput.data('uid', entry.uid);
2701 characterExclusionInput.on('input', async function () {2928 characterExclusionInput.on('input', async function () {
@@ -2709,28 +2936,15 @@ export async function getWorldEntry(name, data, entry) {
2709 data.entries[uid].characterFilter.isExclude = value;2936 data.entries[uid].characterFilter.isExclude = value;
2710 }2937 }
2711 } else if (value) {2938 } else if (value) {
2712 Object.assign(2939 Object.assign(data.entries[uid], { characterFilter: { isExclude: true, names: [], tags: [] } });
2713 data.entries[uid],
2714 {
2715 characterFilter: {
2716 isExclude: true,
2717 names: [],
2718 tags: [],
2719 },
2720 },
2721 );
2722 }2940 }
2723
2724 // Verify names to exist in the system
2725 if (data.entries[uid]?.characterFilter?.names?.length > 0) {2941 if (data.entries[uid]?.characterFilter?.names?.length > 0) {
2726 for (const name of [...data.entries[uid].characterFilter.names]) {2942 for (const name of [...data.entries[uid].characterFilter.names]) {
2727 if (!getContext().characters.find(x => x.avatar.replace(/\.[^/.]+$/, '') === name)) {2943 if (!getContext().characters.find(x => x.avatar.replace(/\.[^/.]+$/, '') === name)) {
2728 console.warn(`World Info: Character ${name} not found. Removing from the entry filter.`, entry);
2729 data.entries[uid].characterFilter.names = data.entries[uid].characterFilter.names.filter(x => x !== name);2944 data.entries[uid].characterFilter.names = data.entries[uid].characterFilter.names.filter(x => x !== name);
2730 }2945 }
2731 }2946 }
2732 }2947 }
2733
2734 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);2948 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2735 await saveWorldInfo(name, data);2949 await saveWorldInfo(name, data);
2736 });2950 });
@@ -2738,66 +2952,13 @@ export async function getWorldEntry(name, data, entry) {
27382952
2739 const characterFilter = template.find('select[name="characterFilter"]');2953 const characterFilter = template.find('select[name="characterFilter"]');
2740 characterFilter.data('uid', entry.uid);2954 characterFilter.data('uid', entry.uid);
27412955 initCharacterFilterSelect2Helper(characterFilter, t);
2742 if (!isMobile()) {2956 fillCharacterAndTagOptionsHelper({ characterFilter, entry, getContext });
2743 $(characterFilter).select2({2957 handleCharacterFilterChangeHelper({
2744 width: '100%',2958 characterFilter, data, entry, name, world_names, getContext, setWIOriginalDataValue, saveWorldInfo, t,
2745 placeholder: t`Tie this entry to specific characters or characters with specific tags`,
2746 allowClear: true,
2747 closeOnSelect: false,
2748 });
2749 }
2750
2751 const characters = getContext().characters;
2752 characters.forEach((character) => {
2753 const option = document.createElement('option');
2754 const name = character.avatar.replace(/\.[^/.]+$/, '') ?? character.name;
2755 option.innerText = name;
2756 option.selected = entry.characterFilter?.names?.includes(name);
2757 option.setAttribute('data-type', 'character');
2758 characterFilter.append(option);
2759 });
2760
2761 const tags = getContext().tags;
2762 tags.forEach((tag) => {
2763 const option = document.createElement('option');
2764 option.innerText = `[Tag] ${tag.name}`;
2765 option.selected = entry.characterFilter?.tags?.includes(tag.id);
2766 option.value = tag.id;
2767 option.setAttribute('data-type', 'tag');
2768 characterFilter.append(option);
2769 });
2770
2771 characterFilter.on('mousedown change', async function (e) {
2772 // If there's no world names, don't do anything
2773 if (world_names.length === 0) {
2774 e.preventDefault();
2775 return;
2776 }
2777
2778 const uid = $(this).data('uid');
2779 const selected = $(this).find(':selected');
2780 if ((!selected || selected?.length === 0) && !data.entries[uid].characterFilter?.isExclude) {
2781 delete data.entries[uid].characterFilter;
2782 } else {
2783 const names = selected.filter('[data-type="character"]').map((_, e) => e instanceof HTMLOptionElement && e.innerText).toArray();
2784 const tags = selected.filter('[data-type="tag"]').map((_, e) => e instanceof HTMLOptionElement && e.value).toArray();
2785 Object.assign(
2786 data.entries[uid],
2787 {
2788 characterFilter: {
2789 isExclude: data.entries[uid].characterFilter?.isExclude ?? false,
2790 names: names,
2791 tags: tags,
2792 },
2793 },
2794 );
2795 }
2796 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2797 await saveWorldInfo(name, data);
2798 });2959 });
27992960
2800 // comment2961 // Comment
2801 const commentInput = template.find('textarea[name="comment"]');2962 const commentInput = template.find('textarea[name="comment"]');
2802 const commentToggle = template.find('input[name="addMemo"]');2963 const commentToggle = template.find('input[name="addMemo"]');
2803 commentInput.data('uid', entry.uid);2964 commentInput.data('uid', entry.uid);
@@ -2806,7 +2967,6 @@ export async function getWorldEntry(name, data, entry) {
2806 const value = $(this).val();2967 const value = $(this).val();
2807 !skipReset && await resetScrollHeight(this);2968 !skipReset && await resetScrollHeight(this);
2808 data.entries[uid].comment = value;2969 data.entries[uid].comment = value;
2809
2810 setWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);2970 setWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
2811 await saveWorldInfo(name, data);2971 await saveWorldInfo(name, data);
2812 });2972 });
@@ -2814,27 +2974,21 @@ export async function getWorldEntry(name, data, entry) {
2814 commentToggle.on('input', async function () {2974 commentToggle.on('input', async function () {
2815 const uid = $(this).data('uid');2975 const uid = $(this).data('uid');
2816 const value = $(this).prop('checked');2976 const value = $(this).prop('checked');
2817 //console.log(value)2977 const commentContainer = $(this).closest('.world_entry').find('.commentContainer');
2818 const commentContainer = $(this)
2819 .closest('.world_entry')
2820 .find('.commentContainer');
2821 data.entries[uid].addMemo = value;2978 data.entries[uid].addMemo = value;
2822 await saveWorldInfo(name, data);2979 await saveWorldInfo(name, data);
2823 value ? commentContainer.show() : commentContainer.hide();2980 value ? commentContainer.show() : commentContainer.hide();
2824 });2981 });
2825
2826 commentInput.val(entry.comment).trigger('input', { skipReset: true });2982 commentInput.val(entry.comment).trigger('input', { skipReset: true });
2827 //initScrollHeight(commentInput);2983 commentToggle.prop('checked', true).trigger('input');
2828 commentToggle.prop('checked', true /* entry.addMemo */).trigger('input');
2829 commentToggle.parent().hide();2984 commentToggle.parent().hide();
28302985
2831 // content2986 // Content
2832 const counter = template.find('.world_entry_form_token_counter');2987 const counter = template.find('.world_entry_form_token_counter');
2833 const countTokensDebounced = debounce(async function (counter, value) {2988 const countTokensDebounced = debounce(async function (counter, value) {
2834 const numberOfTokens = await getTokenCountAsync(value);2989 const numberOfTokens = await getTokenCountAsync(value);
2835 $(counter).text(numberOfTokens);2990 $(counter).text(numberOfTokens);
2836 }, debounce_timeout.relaxed);2991 }, debounce_timeout.relaxed);
2837
2838 const contentInputId = `world_entry_content_${entry.uid}`;2992 const contentInputId = `world_entry_content_${entry.uid}`;
2839 const contentInput = template.find('textarea[name="content"]');2993 const contentInput = template.find('textarea[name="content"]');
2840 contentInput.data('uid', entry.uid);2994 contentInput.data('uid', entry.uid);
@@ -2843,22 +2997,12 @@ export async function getWorldEntry(name, data, entry) {
2843 const uid = $(this).data('uid');2997 const uid = $(this).data('uid');
2844 const value = $(this).val();2998 const value = $(this).val();
2845 data.entries[uid].content = value;2999 data.entries[uid].content = value;
2846
2847 setWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);3000 setWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);
2848 await saveWorldInfo(name, data);3001 await saveWorldInfo(name, data);
28493002 if (!skipCount) countTokensDebounced(counter, value);
2850 if (skipCount) {
2851 return;
2852 }
2853
2854 // count tokens
2855 countTokensDebounced(counter, value);
2856 });3003 });
2857 contentInput.val(entry.content).trigger('input', { skipCount: true });3004 contentInput.val(entry.content).trigger('input', { skipCount: true });
28583005 template.find('.editor_maximize').attr('data-for', contentInputId);
2859 const contentExpandButton = template.find('.editor_maximize');
2860 contentExpandButton.attr('data-for', contentInputId);
2861
2862 template.find('.inline-drawer-toggle').on('click', function () {3006 template.find('.inline-drawer-toggle').on('click', function () {
2863 if (counter.data('first-run')) {3007 if (counter.data('first-run')) {
2864 counter.data('first-run', false);3008 counter.data('first-run', false);
@@ -2868,76 +3012,45 @@ export async function getWorldEntry(name, data, entry) {
2868 }3012 }
2869 });3013 });
28703014
2871 // selective3015 // Selective
2872 const selectiveInput = template.find('input[name="selective"]');3016 const selectiveInput = template.find('input[name="selective"]');
2873 selectiveInput.data('uid', entry.uid);3017 selectiveInput.data('uid', entry.uid);
2874 selectiveInput.on('input', async function () {3018 selectiveInput.on('input', async function () {
2875 const uid = $(this).data('uid');3019 const uid = $(this).data('uid');
2876 const value = $(this).prop('checked');3020 const value = $(this).prop('checked');
2877 data.entries[uid].selective = value;3021 data.entries[uid].selective = value;
2878
2879 setWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);3022 setWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
2880 await saveWorldInfo(name, data);3023 await saveWorldInfo(name, data);
28813024 const keysecondary = $(this).closest('.world_entry').find('.keysecondary');
2882 const keysecondary = $(this)3025 const keysecondarytextpole = $(this).closest('.world_entry').find('.keysecondarytextpole');
2883 .closest('.world_entry')3026 const keyprimaryselect = $(this).closest('.world_entry').find('.keyprimaryselect');
2884 .find('.keysecondary');
2885
2886 const keysecondarytextpole = $(this)
2887 .closest('.world_entry')
2888 .find('.keysecondarytextpole');
2889
2890 const keyprimaryselect = $(this)
2891 .closest('.world_entry')
2892 .find('.keyprimaryselect');
2893
2894 const keyprimaryHeight = keyprimaryselect.outerHeight();3027 const keyprimaryHeight = keyprimaryselect.outerHeight();
2895 keysecondarytextpole.css('height', keyprimaryHeight + 'px');3028 keysecondarytextpole.css('height', keyprimaryHeight + 'px');
2896
2897 value ? keysecondary.show() : keysecondary.hide();3029 value ? keysecondary.show() : keysecondary.hide();
2898
2899 });3030 });
2900 //forced on, ignored if empty3031 selectiveInput.prop('checked', true).trigger('input');
2901 selectiveInput.prop('checked', true /* entry.selective */).trigger('input');
2902 selectiveInput.parent().hide();3032 selectiveInput.parent().hide();
29033033
29043034 // Order
2905 // constant
2906 /*
2907 const constantInput = template.find('input[name="constant"]');
2908 constantInput.data("uid", entry.uid);
2909 constantInput.on("input", async function () {
2910 const uid = $(this).data("uid");
2911 const value = $(this).prop("checked");
2912 data.entries[uid].constant = value;
2913 setOriginalDataValue(data, uid, "constant", data.entries[uid].constant);
2914 await saveWorldInfo(name, data);
2915 });
2916 constantInput.prop("checked", entry.constant).trigger("input");
2917 */
2918
2919 // order
2920 const orderInput = template.find('input[name="order"]');3035 const orderInput = template.find('input[name="order"]');
2921 orderInput.data('uid', entry.uid);3036 orderInput.data('uid', entry.uid);
2922 orderInput.on('input', async function () {3037 orderInput.on('input', async function () {
2923 const uid = $(this).data('uid');3038 const uid = $(this).data('uid');
2924 const value = Number($(this).val());3039 const value = Number($(this).val());
2925
2926 data.entries[uid].order = !isNaN(value) ? value : 0;3040 data.entries[uid].order = !isNaN(value) ? value : 0;
2927 updatePosOrdDisplay(uid);3041 updatePosOrdDisplayHelper({ template, data, uid });
2928 setWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);3042 setWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
2929 await saveWorldInfo(name, data);3043 await saveWorldInfo(name, data);
2930 });3044 });
2931 orderInput.val(entry.order).trigger('input');3045 orderInput.val(entry.order).trigger('input');
2932 orderInput.css('width', 'calc(3em + 15px)');3046 orderInput.css('width', 'calc(3em + 15px)');
29333047
2934 // group3048 // Group
2935 const groupInput = template.find('input[name="group"]');3049 const groupInput = template.find('input[name="group"]');
2936 groupInput.data('uid', entry.uid);3050 groupInput.data('uid', entry.uid);
2937 groupInput.on('input', async function () {3051 groupInput.on('input', async function () {
2938 const uid = $(this).data('uid');3052 const uid = $(this).data('uid');
2939 const value = String($(this).val()).trim();3053 const value = String($(this).val()).trim();
2940
2941 data.entries[uid].group = value;3054 data.entries[uid].group = value;
2942 setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);3055 setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
2943 await saveWorldInfo(name, data);3056 await saveWorldInfo(name, data);
@@ -2945,7 +3058,7 @@ export async function getWorldEntry(name, data, entry) {
2945 groupInput.val(entry.group ?? '').trigger('input');3058 groupInput.val(entry.group ?? '').trigger('input');
2946 setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);3059 setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
29473060
2948 // inclusion priority3061 // Inclusion priority
2949 const groupOverrideInput = template.find('input[name="groupOverride"]');3062 const groupOverrideInput = template.find('input[name="groupOverride"]');
2950 groupOverrideInput.data('uid', entry.uid);3063 groupOverrideInput.data('uid', entry.uid);
2951 groupOverrideInput.on('input', async function () {3064 groupOverrideInput.on('input', async function () {
@@ -2957,289 +3070,96 @@ export async function getWorldEntry(name, data, entry) {
2957 });3070 });
2958 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');3071 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
29593072
2960 // group weight3073 // Group weight
2961 const groupWeightInput = template.find('input[name="groupWeight"]');3074 handleNumberInputHelper({
2962 groupWeightInput.data('uid', entry.uid);3075 inputElem: template.find('input[name="groupWeight"]'),
2963 groupWeightInput.on('input', async function () {3076 entry, entryKey: 'groupWeight', data, name, setWIOriginalDataValue, saveWorldInfo,
2964 const uid = $(this).data('uid');3077 min: 1, max: 10000, clamp: true,
2965 let value = Number($(this).val());
2966 const min = Number($(this).attr('min'));
2967 const max = Number($(this).attr('max'));
2968
2969 // Clamp the value
2970 if (value < min) {
2971 value = min;
2972 $(this).val(min);
2973 } else if (value > max) {
2974 value = max;
2975 $(this).val(max);
2976 }
2977
2978 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
2979 setWIOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
2980 await saveWorldInfo(name, data);
2981 });3078 });
2982 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
2983
2984 // sticky
2985 const sticky = template.find('input[name="sticky"]');
2986 sticky.data('uid', entry.uid);
2987 sticky.on('input', async function () {
2988 const uid = $(this).data('uid');
2989 const value = Number($(this).val());
2990 data.entries[uid].sticky = !isNaN(value) ? value : null;
29913079
2992 setWIOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);3080 // Sticky, cooldown, delay
2993 await saveWorldInfo(name, data);3081 handleNumberInputHelper({
3082 inputElem: template.find('input[name="sticky"]'),
3083 entry, entryKey: 'sticky', data, name, setWIOriginalDataValue, saveWorldInfo,
3084 min: 1, max: 10000, clamp: false,
2994 });3085 });
2995 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');3086 handleNumberInputHelper({
29963087 inputElem: template.find('input[name="cooldown"]'),
2997 // cooldown3088 entry, entryKey: 'cooldown', data, name, setWIOriginalDataValue, saveWorldInfo,
2998 const cooldown = template.find('input[name="cooldown"]');3089 min: 1, max: 10000, clamp: false,
2999 cooldown.data('uid', entry.uid);
3000 cooldown.on('input', async function () {
3001 const uid = $(this).data('uid');
3002 const value = Number($(this).val());
3003 data.entries[uid].cooldown = !isNaN(value) ? value : null;
3004
3005 setWIOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
3006 await saveWorldInfo(name, data);
3007 });3090 });
3008 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');3091 handleNumberInputHelper({
30093092 inputElem: template.find('input[name="delay"]'),
3010 // delay3093 entry, entryKey: 'delay', data, name, setWIOriginalDataValue, saveWorldInfo,
3011 const delay = template.find('input[name="delay"]');3094 min: 1, max: 10000, clamp: false,
3012 delay.data('uid', entry.uid);
3013 delay.on('input', async function () {
3014 const uid = $(this).data('uid');
3015 const value = Number($(this).val());
3016 data.entries[uid].delay = !isNaN(value) ? value : null;
3017
3018 setWIOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
3019 await saveWorldInfo(name, data);
3020 });3095 });
3021 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
30223096
3023 // probability3097 // Probability
3024 if (entry.probability === undefined) {3098 handleProbabilityInputHelper({ probabilityInput: template.find('input[name="probability"]'), data, entry, name, setWIOriginalDataValue, saveWorldInfo });
3025 entry.probability = null;3099 handleProbabilityToggleHelper({
3026 }3100 probabilityToggle: template.find('input[name="useProbability"]'),
30273101 data, entry, name,
3028 // depth3102 probabilityInput: template.find('input[name="probability"]'),
3029 const depthInput = template.find('input[name="depth"]');3103 setWIOriginalDataValue, saveWorldInfo,
3030 depthInput.data('uid', entry.uid);
3031
3032 depthInput.on('input', async function () {
3033 const uid = $(this).data('uid');
3034 const value = Number($(this).val());
3035
3036 data.entries[uid].depth = !isNaN(value) ? value : 0;
3037 updatePosOrdDisplay(uid);
3038 setWIOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
3039 await saveWorldInfo(name, data);
3040 });3104 });
3041 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
3042 depthInput.css('width', 'calc(3em + 15px)');
3043
3044 // Hide by default unless depth is specified
3045 if (entry.position === world_info_position.atDepth) {
3046 //depthInput.parent().hide();
3047 }
3048
3049 const probabilityInput = template.find('input[name="probability"]');
3050 probabilityInput.data('uid', entry.uid);
3051 probabilityInput.on('input', async function () {
3052 const uid = $(this).data('uid');
3053 const value = Number($(this).val());
3054
3055 data.entries[uid].probability = !isNaN(value) ? value : null;
30563105
3057 // Clamp probability to 0-1003106 // Depth
3058 if (data.entries[uid].probability !== null) {3107 handleNumberInputHelper({
3059 data.entries[uid].probability = Math.min(100, Math.max(0, data.entries[uid].probability));3108 inputElem: template.find('input[name="depth"]'),
30603109 entry, entryKey: 'depth', data, name, setWIOriginalDataValue, saveWorldInfo,
3061 if (data.entries[uid].probability !== value) {3110 min: 0, max: MAX_SCAN_DEPTH, clamp: false,
3062 $(this).val(data.entries[uid].probability);
3063 }
3064 }
3065
3066 setWIOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
3067 await saveWorldInfo(name, data);
3068 });3111 });
3069 probabilityInput.val(entry.probability).trigger('input');3112 template.find('input[name="depth"]').css('width', 'calc(3em + 15px)');
3070 probabilityInput.css('width', 'calc(3em + 15px)');
3071
3072 // probability toggle
3073 if (entry.useProbability === undefined) {
3074 entry.useProbability = false;
3075 }
3076
3077 const probabilityToggle = template.find('input[name="useProbability"]');
3078 probabilityToggle.data('uid', entry.uid);
3079 probabilityToggle.on('input', async function () {
3080 const uid = $(this).data('uid');
3081 const value = $(this).prop('checked');
3082 data.entries[uid].useProbability = value;
3083 const probabilityContainer = $(this)
3084 .closest('.world_entry')
3085 .find('.probabilityContainer');
3086 await saveWorldInfo(name, data);
3087 value ? probabilityContainer.show() : probabilityContainer.hide();
3088
3089 if (value && data.entries[uid].probability === null) {
3090 data.entries[uid].probability = 100;
3091 }
3092
3093 if (!value) {
3094 data.entries[uid].probability = null;
3095 }
3096
3097 probabilityInput.val(data.entries[uid].probability).trigger('input');
3098 });
3099 //forced on, 100% by default
3100 probabilityToggle.prop('checked', true /* entry.useProbability */).trigger('input');
3101 probabilityToggle.parent().hide();
3102
3103 // position
3104 if (entry.position === undefined) {
3105 entry.position = 0;
3106 }
31073113
3114 // Position
3115 if (entry.position === undefined) entry.position = 0;
3108 const positionInput = template.find('select[name="position"]');3116 const positionInput = template.find('select[name="position"]');
3109 //initScrollHeight(positionInput);
3110 positionInput.data('uid', entry.uid);3117 positionInput.data('uid', entry.uid);
3111 positionInput.on('click', function (event) {3118 positionInput.on('click', e => e.stopPropagation());
3112 // Prevent closing the drawer on clicking the input
3113 event.stopPropagation();
3114 });
3115 positionInput.on('input', async function () {3119 positionInput.on('input', async function () {
3116 const uid = $(this).data('uid');3120 const uid = $(this).data('uid');
3117 const value = Number($(this).val());3121 const value = Number($(this).val());
3118 data.entries[uid].position = !isNaN(value) ? value : 0;3122 data.entries[uid].position = !isNaN(value) ? value : 0;
3123 const depthInput = template.find('input[name="depth"]');
3119 if (value === world_info_position.atDepth) {3124 if (value === world_info_position.atDepth) {
3120 depthInput.prop('disabled', false);3125 depthInput.prop('disabled', false);
3121 depthInput.css('visibility', 'visible');3126 depthInput.css('visibility', 'visible');
3122 //depthInput.parent().show();
3123 const role = Number($(this).find(':selected').data('role'));3127 const role = Number($(this).find(':selected').data('role'));
3124 data.entries[uid].role = role;3128 data.entries[uid].role = role;
3125 } else {3129 } else {
3126 depthInput.prop('disabled', true);3130 depthInput.prop('disabled', true);
3127 depthInput.css('visibility', 'hidden');3131 depthInput.css('visibility', 'hidden');
3128 data.entries[uid].role = null;3132 data.entries[uid].role = null;
3129 //depthInput.parent().hide();
3130 }3133 }
3131 updatePosOrdDisplay(uid);3134 updatePosOrdDisplayHelper({ template, data, uid });
3132 // Spec v2 only supports before_char and after_char
3133 setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');3135 setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
3134 // Write the original value as extensions field
3135 setWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);3136 setWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
3136 setWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);3137 setWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
3137 await saveWorldInfo(name, data);3138 await saveWorldInfo(name, data);
3138 });3139 });
3139
3140 const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';3140 const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
3141 template3141 template.find(`select[name="position"] option[value="${entry.position}"][data-role="${roleValue}"]`).prop('selected', true).trigger('input');
3142 .find(`select[name="position"] option[value="${entry.position}"][data-role="${roleValue}"]`)
3143 .prop('selected', true)
3144 .trigger('input');
31453142
3146 //add UID above content box (less important doesn't need to be always visible)3143 // UID display
3147 template.find('.world_entry_form_uid_value').text(`(UID: ${entry.uid})`);3144 template.find('.world_entry_form_uid_value').text(`(UID: ${entry.uid})`);
31483145
3149 //new tri-state selector for constant/normal/vectorized3146 // Tri-state selector
3150 const entryStateSelector = template.find('select[name="entryStateSelector"]');3147 handleEntryStateSelectorHelper({
3151 entryStateSelector.data('uid', entry.uid);3148 entryStateSelector: template.find('select[name="entryStateSelector"]'),
3152 entryStateSelector.on('click', function (event) {3149 entry, data, name, setWIOriginalDataValue, saveWorldInfo,
3153 // Prevent closing the drawer on clicking the input
3154 event.stopPropagation();
3155 });3150 });
3156 entryStateSelector.on('input', async function () {
3157 const uid = entry.uid;
3158 const value = $(this).val();
3159 switch (value) {
3160 case 'constant':
3161 data.entries[uid].constant = true;
3162 data.entries[uid].vectorized = false;
3163 setWIOriginalDataValue(data, uid, 'constant', true);
3164 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
3165 break;
3166 case 'normal':
3167 data.entries[uid].constant = false;
3168 data.entries[uid].vectorized = false;
3169 setWIOriginalDataValue(data, uid, 'constant', false);
3170 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
3171 break;
3172 case 'vectorized':
3173 data.entries[uid].constant = false;
3174 data.entries[uid].vectorized = true;
3175 setWIOriginalDataValue(data, uid, 'constant', false);
3176 setWIOriginalDataValue(data, uid, 'extensions.vectorized', true);
3177 break;
3178 }
3179 await saveWorldInfo(name, data);
3180
3181 });
3182
3183 const entryKillSwitch = template.find('div[name="entryKillSwitch"]');
3184 entryKillSwitch.data('uid', entry.uid);
3185 entryKillSwitch.on('click', async function (event) {
3186 const uid = entry.uid;
3187 data.entries[uid].disable = !data.entries[uid].disable;
3188 const isActive = !data.entries[uid].disable;
3189 setWIOriginalDataValue(data, uid, 'enabled', isActive);
3190 template.toggleClass('disabledWIEntry', !isActive);
3191 entryKillSwitch.toggleClass('fa-toggle-off', !isActive);
3192 entryKillSwitch.toggleClass('fa-toggle-on', isActive);
3193 await saveWorldInfo(name, data);
31943151
3152 // Kill switch
3153 handleEntryKillSwitchHelper({
3154 entryKillSwitch: template.find('div[name="entryKillSwitch"]'),
3155 entry, data, name, setWIOriginalDataValue, saveWorldInfo, template,
3195 });3156 });
31963157
3197 const entryState = function () {3158 // Exclude/prevent recursion
3198 if (entry.constant === true) {3159 handleMatchCheckboxHelper({ template, entry, fieldName: 'excludeRecursion', data, name });
3199 return 'constant';3160 handleMatchCheckboxHelper({ template, entry, fieldName: 'preventRecursion', data, name });
3200 } else if (entry.vectorized === true) {
3201 return 'vectorized';
3202 } else {
3203 return 'normal';
3204 }
3205 };
3206
3207 const isActive = !entry.disable;
3208 template.toggleClass('disabledWIEntry', !isActive);
3209 entryKillSwitch.toggleClass('fa-toggle-off', !isActive);
3210 entryKillSwitch.toggleClass('fa-toggle-on', isActive);
3211
3212 template
3213 .find(`select[name="entryStateSelector"] option[value=${entryState()}]`)
3214 .prop('selected', true)
3215 .trigger('input');
3216
3217 // exclude recursion
3218 const excludeRecursionInput = template.find('input[name="exclude_recursion"]');
3219 excludeRecursionInput.data('uid', entry.uid);
3220 excludeRecursionInput.on('input', async function () {
3221 const uid = $(this).data('uid');
3222 const value = $(this).prop('checked');
3223 data.entries[uid].excludeRecursion = value;
3224 setWIOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
3225 await saveWorldInfo(name, data);
3226 });
3227 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
3228
3229 // prevent recursion
3230 const preventRecursionInput = template.find('input[name="prevent_recursion"]');
3231 preventRecursionInput.data('uid', entry.uid);
3232 preventRecursionInput.on('input', async function () {
3233 const uid = $(this).data('uid');
3234 const value = $(this).prop('checked');
3235 data.entries[uid].preventRecursion = value;
3236 setWIOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
3237 await saveWorldInfo(name, data);
3238 });
3239 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
32403161
3241 // delay until recursion3162 // Delay until recursion
3242 // delay until recursion level
3243 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');3163 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
3244 delayUntilRecursionInput.data('uid', entry.uid);3164 delayUntilRecursionInput.data('uid', entry.uid);
3245 const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');3165 const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');
@@ -3247,12 +3167,8 @@ export async function getWorldEntry(name, data, entry) {
3247 delayUntilRecursionInput.on('input', async function () {3167 delayUntilRecursionInput.on('input', async function () {
3248 const uid = $(this).data('uid');3168 const uid = $(this).data('uid');
3249 const toggled = $(this).prop('checked');3169 const toggled = $(this).prop('checked');
3250
3251 // If the value contains a number, we'll take that one (set by the level input), otherwise we can use true/false switch
3252 const value = toggled ? data.entries[uid].delayUntilRecursion || true : false;3170 const value = toggled ? data.entries[uid].delayUntilRecursion || true : false;
3253
3254 if (!toggled) delayUntilRecursionLevelInput.val('');3171 if (!toggled) delayUntilRecursionLevelInput.val('');
3255
3256 data.entries[uid].delayUntilRecursion = value;3172 data.entries[uid].delayUntilRecursion = value;
3257 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);3173 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
3258 await saveWorldInfo(name, data);3174 await saveWorldInfo(name, data);
@@ -3265,30 +3181,22 @@ export async function getWorldEntry(name, data, entry) {
3265 : content === 1 ? true3181 : content === 1 ? true
3266 : !isNaN(Number(content)) ? Number(content)3182 : !isNaN(Number(content)) ? Number(content)
3267 : false;3183 : false;
3268
3269 data.entries[uid].delayUntilRecursion = value;3184 data.entries[uid].delayUntilRecursion = value;
3270 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);3185 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
3271 await saveWorldInfo(name, data);3186 await saveWorldInfo(name, data);
3272 });3187 });
3273 // No need to retrigger inpout event, we'll just set the curret current value. It was edited/saved above already
3274 delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input');3188 delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input');
32753189
3276 // duplicate button3190 // Duplicate/delete/move buttons
3277 const duplicateButton = template.find('.duplicate_entry_button');3191 template.find('.duplicate_entry_button').data('uid', entry.uid).on('click', async function () {
3278 duplicateButton.data('uid', entry.uid);
3279 duplicateButton.on('click', async function () {
3280 const uid = $(this).data('uid');3192 const uid = $(this).data('uid');
3281 const entry = duplicateWorldInfoEntry(data, uid);3193 const entryDup = duplicateWorldInfoEntry(data, uid);
3282 if (entry) {3194 if (entryDup) {
3283 await saveWorldInfo(name, data);3195 await saveWorldInfo(name, data);
3284 updateEditor(entry.uid);3196 updateEditor(entryDup.uid);
3285 }3197 }
3286 });3198 });
32873199 template.find('.delete_entry_button').data('uid', entry.uid).on('click', async function (e) {
3288 // delete button
3289 const deleteButton = template.find('.delete_entry_button');
3290 deleteButton.data('uid', entry.uid);
3291 deleteButton.on('click', async function (e) {
3292 e.stopPropagation();3200 e.stopPropagation();
3293 const uid = $(this).data('uid');3201 const uid = $(this).data('uid');
3294 const deleted = await deleteWorldInfoEntry(data, uid);3202 const deleted = await deleteWorldInfoEntry(data, uid);
@@ -3297,36 +3205,24 @@ export async function getWorldEntry(name, data, entry) {
3297 await saveWorldInfo(name, data);3205 await saveWorldInfo(name, data);
3298 updateEditor(navigation_option.previous);3206 updateEditor(navigation_option.previous);
3299 });3207 });
33003208 template.find('.move_entry_button').attr('data-uid', entry.uid).attr('data-current-world', name).on('click', async function (e) {
3301 // move button
3302 const moveButton = template.find('.move_entry_button');
3303 moveButton.attr('data-uid', entry.uid);
3304 moveButton.attr('data-current-world', name);
3305 moveButton.on('click', async function (e) {
3306 e.stopPropagation();3209 e.stopPropagation();
3307 const sourceUid = $(this).attr('data-uid');3210 const sourceUid = $(this).attr('data-uid');
3308 const sourceWorld = $(this).attr('data-current-world');3211 const sourceWorld = $(this).attr('data-current-world');
3309 const sourceWorldInfo = await loadWorldInfo(sourceWorld);3212 const sourceWorldInfo = await loadWorldInfo(sourceWorld);
3310 if (!sourceWorldInfo) {3213 if (!sourceWorldInfo) return;
3311 return;
3312 }
3313 const sourceName = sourceWorldInfo.entries[sourceUid]?.comment;3214 const sourceName = sourceWorldInfo.entries[sourceUid]?.comment;
3314 if (sourceName === undefined) {3215 if (sourceName === undefined) return;
3315 return;
3316 }
3317
3318 const select = document.createElement('select');3216 const select = document.createElement('select');
3319 select.id = 'move_entry_target_select';3217 select.id = 'move_entry_target_select';
3320 select.classList.add('text_pole', 'wide100p', 'marginTop10');3218 select.classList.add('text_pole', 'wide100p', 'marginTop10');
3321
3322 const defaultOption = document.createElement('option');3219 const defaultOption = document.createElement('option');
3323 defaultOption.value = '';3220 defaultOption.value = '';
3324 defaultOption.textContent = `-- ${t`Select Target Lorebook`} --`;3221 defaultOption.textContent = `-- ${t`Select Target Lorebook`} --`;
3325 select.appendChild(defaultOption);3222 select.appendChild(defaultOption);
3326
3327 let selectableWorldCount = 0;3223 let selectableWorldCount = 0;
3328 world_names.forEach(worldName => {3224 world_names.forEach(worldName => {
3329 if (worldName !== sourceWorld) { // Exclude current world3225 if (worldName !== sourceWorld) {
3330 const option = document.createElement('option');3226 const option = document.createElement('option');
3331 option.value = world_names.indexOf(worldName).toString();3227 option.value = world_names.indexOf(worldName).toString();
3332 option.textContent = worldName;3228 option.textContent = worldName;
@@ -3334,143 +3230,84 @@ export async function getWorldEntry(name, data, entry) {
3334 selectableWorldCount++;3230 selectableWorldCount++;
3335 }3231 }
3336 });3232 });
3337
3338 if (selectableWorldCount === 0) {3233 if (selectableWorldCount === 0) {
3339 toastr.warning(t`There are no other lorebooks to move to.`);3234 toastr.warning(t`There are no other lorebooks to move to.`);
3340 return;3235 return;
3341 }3236 }
3342
3343 // Create wrapper div
3344 const wrapper = document.createElement('div');3237 const wrapper = document.createElement('div');
3345 wrapper.textContent = t`Move '${sourceName}' to:`;3238 wrapper.textContent = t`Move '${sourceName}' to:`;
3346
3347 // Create container and append elements
3348 const container = document.createElement('div');3239 const container = document.createElement('div');
3349 container.appendChild(wrapper);3240 container.appendChild(wrapper);
3350 container.appendChild(select);3241 container.appendChild(select);
3351
3352 let selectedWorldIndex = -1;3242 let selectedWorldIndex = -1;
3353 select.addEventListener('change', function () {3243 select.addEventListener('change', function () {
3354 selectedWorldIndex = this.value === '' ? -1 : Number(this.value);3244 selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
3355 });3245 });
3356
3357 const popupConfirm = await callGenericPopup(container, POPUP_TYPE.CONFIRM, '', {3246 const popupConfirm = await callGenericPopup(container, POPUP_TYPE.CONFIRM, '', {
3358 okButton: t`Move`,3247 okButton: t`Move`,
3359 cancelButton: t`Cancel`,3248 cancelButton: t`Cancel`,
3360 });3249 });
3361 if (!popupConfirm) {3250 if (!popupConfirm) return;
3362 return;3251 if (selectedWorldIndex === -1) return;
3363 }
3364
3365 if (selectedWorldIndex === -1) {
3366 return;
3367 }
3368
3369 const selectedValue = world_names[selectedWorldIndex];3252 const selectedValue = world_names[selectedWorldIndex];
3370
3371 if (!selectedValue) {3253 if (!selectedValue) {
3372 toastr.warning(t`Please select a target lorebook.`);3254 toastr.warning(t`Please select a target lorebook.`);
3373 return;3255 return;
3374 }3256 }
3375
3376 await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid);3257 await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid);
3377 });3258 });
33783259
3379 // scan depth3260 // Scan depth
3380 const scanDepthInput = template.find('input[name="scanDepth"]');3261 const scanDepthInput = template.find('input[name="scanDepth"]');
3381 scanDepthInput.data('uid', entry.uid);3262 scanDepthInput.data('uid', entry.uid);
3382 scanDepthInput.on('input', async function () {3263 scanDepthInput.on('input', async function () {
3383 const uid = $(this).data('uid');3264 const uid = $(this).data('uid');
3384 const isEmpty = $(this).val() === '';3265 const isEmpty = $(this).val() === '';
3385 const value = Number($(this).val());3266 const value = Number($(this).val());
3386
3387 // Clamp if necessary
3388 if (value < 0) {3267 if (value < 0) {
3389 $(this).val(0).trigger('input');3268 $(this).val(0).trigger('input');
3390 toastr.warning('Scan depth cannot be negative');3269 toastr.warning('Scan depth cannot be negative');
3391 return;3270 return;
3392 }3271 }
3393
3394 if (value > MAX_SCAN_DEPTH) {3272 if (value > MAX_SCAN_DEPTH) {
3395 $(this).val(MAX_SCAN_DEPTH).trigger('input');3273 $(this).val(MAX_SCAN_DEPTH).trigger('input');
3396 toastr.warning(`Scan depth cannot exceed ${MAX_SCAN_DEPTH}`);3274 toastr.warning(`Scan depth cannot exceed ${MAX_SCAN_DEPTH}`);
3397 return;3275 return;
3398 }3276 }
3399
3400 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;3277 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
3401 setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);3278 setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
3402 await saveWorldInfo(name, data);3279 await saveWorldInfo(name, data);
3403 });3280 });
3404 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');3281 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
34053282
3406 // case sensitive select3283 // Boolean selects
3407 const caseSensitiveSelect = template.find('select[name="caseSensitive"]');3284 handleBooleanSelectHelper({
3408 caseSensitiveSelect.data('uid', entry.uid);3285 selectElem: template.find('select[name="caseSensitive"]'),
3409 caseSensitiveSelect.on('input', async function () {3286 entry, entryKey: 'caseSensitive', data, name, setWIOriginalDataValue, saveWorldInfo,
3410 const uid = $(this).data('uid');
3411 const value = $(this).val();
3412
3413 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
3414 setWIOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
3415 await saveWorldInfo(name, data);
3416 });3287 });
3417 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');3288 handleBooleanSelectHelper({
34183289 selectElem: template.find('select[name="matchWholeWords"]'),
3419 // match whole words select3290 entry, entryKey: 'matchWholeWords', data, name, setWIOriginalDataValue, saveWorldInfo,
3420 const matchWholeWordsSelect = template.find('select[name="matchWholeWords"]');
3421 matchWholeWordsSelect.data('uid', entry.uid);
3422 matchWholeWordsSelect.on('input', async function () {
3423 const uid = $(this).data('uid');
3424 const value = $(this).val();
3425
3426 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
3427 setWIOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
3428 await saveWorldInfo(name, data);
3429 });3291 });
3430 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');3292 handleBooleanSelectHelper({
34313293 selectElem: template.find('select[name="useGroupScoring"]'),
3432 // use group scoring select3294 entry, entryKey: 'useGroupScoring', data, name, setWIOriginalDataValue, saveWorldInfo,
3433 const useGroupScoringSelect = template.find('select[name="useGroupScoring"]');
3434 useGroupScoringSelect.data('uid', entry.uid);
3435 useGroupScoringSelect.on('input', async function () {
3436 const uid = $(this).data('uid');
3437 const value = $(this).val();
3438
3439 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
3440 setWIOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
3441 await saveWorldInfo(name, data);
3442 });3295 });
3443 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
34443296
3445 function handleMatchCheckbox(fieldName) {3297 // Match checkboxes
3446 const key = originalWIDataKeyMap[fieldName];3298 handleMatchCheckboxHelper({ template, entry, fieldName: 'matchPersonaDescription', data, name });
3447 const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`);3299 handleMatchCheckboxHelper({ template, entry, fieldName: 'matchCharacterDescription', data, name });
3448 checkBoxElem.data('uid', entry.uid);3300 handleMatchCheckboxHelper({ template, entry, fieldName: 'matchCharacterPersonality', data, name });
3449 checkBoxElem.on('input', async function () {3301 handleMatchCheckboxHelper({ template, entry, fieldName: 'matchCharacterDepthPrompt', data, name });
3450 const uid = $(this).data('uid');3302 handleMatchCheckboxHelper({ template, entry, fieldName: 'matchScenario', data, name });
3451 const value = $(this).prop('checked');3303 handleMatchCheckboxHelper({ template, entry, fieldName: 'matchCreatorNotes', data, name });
34523304
3453 data.entries[uid][fieldName] = value;3305 // Automation ID
3454 setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]);
3455 await saveWorldInfo(name, data);
3456 });
3457 checkBoxElem.prop('checked', !!entry[fieldName]).trigger('input');
3458 }
3459
3460 handleMatchCheckbox('matchPersonaDescription');
3461 handleMatchCheckbox('matchCharacterDescription');
3462 handleMatchCheckbox('matchCharacterPersonality');
3463 handleMatchCheckbox('matchCharacterDepthPrompt');
3464 handleMatchCheckbox('matchScenario');
3465 handleMatchCheckbox('matchCreatorNotes');
3466
3467 // automation id
3468 const automationIdInput = template.find('input[name="automationId"]');3306 const automationIdInput = template.find('input[name="automationId"]');
3469 automationIdInput.data('uid', entry.uid);3307 automationIdInput.data('uid', entry.uid);
3470 automationIdInput.on('input', async function () {3308 automationIdInput.on('input', async function () {
3471 const uid = $(this).data('uid');3309 const uid = $(this).data('uid');
3472 const value = $(this).val();3310 const value = $(this).val();
3473
3474 data.entries[uid].automationId = value;3311 data.entries[uid].automationId = value;
3475 setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);3312 setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
3476 await saveWorldInfo(name, data);3313 await saveWorldInfo(name, data);
@@ -3478,31 +3315,7 @@ export async function getWorldEntry(name, data, entry) {
3478 automationIdInput.val(entry.automationId ?? '').trigger('input');3315 automationIdInput.val(entry.automationId ?? '').trigger('input');
3479 setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);3316 setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);
34803317
3481 template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed3318 template.find('.inline-drawer-content').css('display', 'none');
3482
3483 function updatePosOrdDisplay(uid) {
3484 // display position/order info left of keyword box
3485 let entry = data.entries[uid];
3486 let posText = entry.position;
3487 switch (entry.position) {
3488 case 0:
3489 posText = '↑CD';
3490 break;
3491 case 1:
3492 posText = 'CD↓';
3493 break;
3494 case 2:
3495 posText = '↑AN';
3496 break;
3497 case 3:
3498 posText = 'AN↓';
3499 break;
3500 case 4:
3501 posText = `@D${entry.depth}`;
3502 break;
3503 }
3504 template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`);
3505 }
35063319
3507 return template;3320 return template;
3508}3321}
@@ -4156,6 +3969,8 @@ function parseDecorators(content) {
4156 * @property {Set<any>} allActivatedEntries All entries.3969 * @property {Set<any>} allActivatedEntries All entries.
4157 * @returns {Promise<WIActivated>} The world info activated.3970 * @returns {Promise<WIActivated>} The world info activated.
4158 */3971 */
3972
3973//MARK: checkWorldInfo
4159export async function checkWorldInfo(chat, maxContext, isDryRun, globalScanData) {3974export async function checkWorldInfo(chat, maxContext, isDryRun, globalScanData) {
4160 const context = getContext();3975 const context = getContext();
4161 const buffer = new WorldInfoBuffer(chat, globalScanData);3976 const buffer = new WorldInfoBuffer(chat, globalScanData);