Removes memory heap bloat by improving DOM and JQUI datacache clearing when swapping WI in the editor, and all other 'reloadEditor' calls via slashcommands etc. Results in a slight delay depending on entries visible via pagination (about 2s for 50 visible entries), but I added a pleasant fade transition to make it feel organic.

b55de85243bdae4e1202f3642b077712ba957f33

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

1 files changed, +109 -15Ignore whitespace
public/scripts/world-info.js+109 -15
@@ -1,7 +1,7 @@
1import { Fuse } from '../lib.js';1import { Fuse } from '../lib.js';
22
3import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';3import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
4import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';4import { download, debounce, delay, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents } from './utils.js';
5import { extension_settings, getContext } from './extensions.js';5import { extension_settings, getContext } from './extensions.js';
6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
7import { isMobile } from './RossAscends-mods.js';7import { isMobile } from './RossAscends-mods.js';
@@ -1754,12 +1754,12 @@ function registerWorldInfoSlashCommands() {
1754 */1754 */
1755export async function showWorldEditor(name) {1755export async function showWorldEditor(name) {
1756 if (!name) {1756 if (!name) {
1757 hideWorldEditor();1757 await hideWorldEditor();
1758 return;1758 return;
1759 }1759 }
17601760
1761 const wiData = await loadWorldInfo(name);1761 const wiData = await loadWorldInfo(name);
1762 displayWorldEntries(name, wiData);1762 await displayWorldEntries(name, wiData);
1763}1763}
17641764
1765/**1765/**
@@ -1815,8 +1815,8 @@ export async function updateWorldInfoList() {
1815 }1815 }
1816}1816}
18171817
1818function hideWorldEditor() {1818async function hideWorldEditor() {
1819 displayWorldEntries(null, null);1819 await displayWorldEntries(null, null);
1820}1820}
18211821
1822function getWIElement(name) {1822function getWIElement(name) {
@@ -1937,15 +1937,102 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f
1937 worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));1937 worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
1938}1938}
19391939
1940function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {1940function clearEntryList() {
1941 updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);1941 console.time('clearEntryList');
1942 const $list = $('#world_popup_entries_list');
1943
1944
1945 if (!$list.children().length) {
1946 console.warn('List already empty, skipping cleanup.');
1947 console.timeEnd('clearEntryList');
1948 return;
1949 }
1950
1951 // Step 1: Clean all <option> elements within <select>
1952 console.warn(`Before cleaning: ${$list.find('option').length} options in list`);
1953 $list.find('option').each(function () {
1954 const $option = $(this);
1955 $option.off();
1956 $.cleanData([$option[0]]);
1957 $option.remove();
1958 });
1959 console.warn(`Post-clean: ${$list.find('option').length} options in list`);
1960
1961 // Step 2: Clean all <select> elements
1962 console.warn(`Before cleaning: ${$list.find('select').length} selects in list`);
1963 $list.find('select').each(function () {
1964 const $select = $(this);
1965 // Remove Select2-related data and container if present
1966 if ($select.hasClass('select2-hidden-accessible')) {
1967 try {
1968 $select.select2('destroy');
1969 } catch (e) {
1970 console.warn('Select2 destroy failed:', e);
1971 }
1972 }
1973 const $container = $select.parent();
1974 if ($container.length) {
1975 $container.find('*').off();
1976 $.cleanData($container.find('*').get());
1977 $container.remove();
1978 } else {
1979 console.warn('container not found');
1980 console.warn($select.parent().html());
1981 }
1982 // Destroy Select2 if applicable
1983
1984 $select.off();
1985 $.cleanData([$select[0]]);
1986
1987 });
1988 console.warn(`Post-clean: ${$list.find('select').length} selects in list`);
1989
1990 // Step 3: Clean <div>, <span>, <input>
1991 console.warn(`Before cleaning: ${$list.find('div, span, input').length} divs, spans, inputs in list`);
1992 $list.find('div, span, input').each(function () {
1993 const $elem = $(this);
1994 $elem.off();
1995 $.cleanData([$elem[0]]);
1996 $elem.remove();
1997 });
1998 console.warn(`Post-clean: ${$list.find('div, span, input').length} divs, spans, inputs in list`);
1999
2000 // Destroy Sortable
2001 console.warn(`Before cleaning: ${$list.sortable('instance') ? 1 : 0} sortable instance in list`);
2002 if ($list.sortable('instance')) {
2003 $list.sortable('destroy');
2004 }
2005 console.warn(`Post-cleaning: ${$list.sortable('instance') ? 1 : 0} sortable instance in list`);
2006
2007 let totalElementsOfanyKindLeftInList = $list.children().length;
2008
2009 // Final cleanup
2010 if (totalElementsOfanyKindLeftInList !== 0) {
2011 console.time('empty');
2012 console.warn(`Before .empty(): ${totalElementsOfanyKindLeftInList} elements left in list`);
2013 $list.empty();
2014 totalElementsOfanyKindLeftInList = $list.children().length;
2015 console.warn(`After .empty(): ${totalElementsOfanyKindLeftInList} elements left in list`);
2016 console.timeEnd('empty');
2017 } else {
2018 console.warn('Entry List is already totally empty, no need to call .empty()');
2019 }
2020
2021 console.timeEnd('clearEntryList');
2022}
2023
2024async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
2025 updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);
19422026
1943 const worldEntriesList = $('#world_popup_entries_list');2027 const worldEntriesList = $('#world_popup_entries_list');
19442028
1945 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements2029 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
1946 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it2030 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
1947 worldEntriesList.find('*').off();2031 //worldEntriesList.find('*').off();
1948 worldEntriesList.empty().show();2032 worldEntriesList.css({ 'opacity': 0, 'transition': 'opacity 250ms ease-in-out' });
2033 await delay(250);
2034 clearEntryList(); // Use enhanced cleanup
2035 worldEntriesList.show();
19492036
1950 if (!data || !('entries' in data)) {2037 if (!data || !('entries' in data)) {
1951 $('#world_popup_new').off('click').on('click', nullWorldInfo);2038 $('#world_popup_new').off('click').on('click', nullWorldInfo);
@@ -2040,8 +2127,9 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
2040 callback: async function (/** @type {object[]} */ page) {2127 callback: async function (/** @type {object[]} */ page) {
2041 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements2128 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
2042 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it2129 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
2043 worldEntriesList.find('*').off();2130 //worldEntriesList.find('*').off();
2044 worldEntriesList.empty();2131 clearEntryList();
2132 //worldEntriesList.empty();
20452133
2046 const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');2134 const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
2047 const blocksPromises = page.map(async (entry) => await getWorldEntry(name, data, entry)).filter(x => x);2135 const blocksPromises = page.map(async (entry) => await getWorldEntry(name, data, entry)).filter(x => x);
@@ -2173,7 +2261,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
2173 if (selectedIndex !== -1) {2261 if (selectedIndex !== -1) {
2174 $('#world_editor_select').val(selectedIndex).trigger('change');2262 $('#world_editor_select').val(selectedIndex).trigger('change');
2175 } else {2263 } else {
2176 hideWorldEditor();2264 await hideWorldEditor();
2177 }2265 }
2178 }2266 }
2179 });2267 });
@@ -2211,6 +2299,12 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
2211 await saveWorldInfo(name, data);2299 await saveWorldInfo(name, data);
2212 },2300 },
2213 });2301 });
2302
2303 worldEntriesList.css('opacity', '1');
2304 //remove inline CSS on the list
2305 worldEntriesList.removeAttr('style');
2306
2307
2214 //$("#world_popup_entries_list").disableSelection();2308 //$("#world_popup_entries_list").disableSelection();
2215}2309}
22162310
@@ -3266,7 +3360,7 @@ export async function getWorldEntry(name, data, entry) {
3266 container.appendChild(select);3360 container.appendChild(select);
32673361
3268 let selectedWorldIndex = -1;3362 let selectedWorldIndex = -1;
3269 select.addEventListener('change', function() {3363 select.addEventListener('change', function () {
3270 selectedWorldIndex = this.value === '' ? -1 : Number(this.value);3364 selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
3271 });3365 });
32723366
@@ -3819,7 +3913,7 @@ export async function createNewWorldInfo(worldName, { interactive = false } = {}
3819 if (selectedIndex !== -1) {3913 if (selectedIndex !== -1) {
3820 $('#world_editor_select').val(selectedIndex).trigger('change');3914 $('#world_editor_select').val(selectedIndex).trigger('change');
3821 } else {3915 } else {
3822 hideWorldEditor();3916 await hideWorldEditor();
3823 }3917 }
38243918
3825 return true;3919 return true;
@@ -5385,7 +5479,7 @@ export function initWorldInfo() {
5385 const selectedIndex = String($('#world_editor_select').find(':selected').val());5479 const selectedIndex = String($('#world_editor_select').find(':selected').val());
53865480
5387 if (selectedIndex === '') {5481 if (selectedIndex === '') {
5388 hideWorldEditor();5482 await hideWorldEditor();
5389 } else {5483 } else {
5390 const worldName = world_names[selectedIndex];5484 const worldName = world_names[selectedIndex];
5391 showWorldEditor(worldName);5485 showWorldEditor(worldName);