mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
8 Commits
staging
...
OptimizedW
Author | SHA1 | Date | |
---|---|---|---|
|
e08d1b522c | ||
|
62fdce8cad | ||
|
bbe62527d2 | ||
|
b7323715b2 | ||
|
0f9cd5f48d | ||
|
e1a1fdb0da | ||
|
42b2637707 | ||
|
b55de85243 |
@@ -1,7 +1,7 @@
|
||||
import { Fuse } from '../lib.js';
|
||||
|
||||
import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
|
||||
import { 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';
|
||||
import { 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';
|
||||
import { extension_settings, getContext } from './extensions.js';
|
||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
|
||||
import { isMobile } from './RossAscends-mods.js';
|
||||
@@ -85,6 +85,7 @@ const saveSettingsDebounced = debounce(() => {
|
||||
}, debounce_timeout.relaxed);
|
||||
const sortFn = (a, b) => b.order - a.order;
|
||||
let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
|
||||
let isSaveWorldInfoDisabled = false;
|
||||
|
||||
// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
|
||||
export const worldInfoFilter = new FilterHelper(() => updateEditor());
|
||||
@@ -1754,12 +1755,12 @@ function registerWorldInfoSlashCommands() {
|
||||
*/
|
||||
export async function showWorldEditor(name) {
|
||||
if (!name) {
|
||||
hideWorldEditor();
|
||||
await hideWorldEditor();
|
||||
return;
|
||||
}
|
||||
|
||||
const wiData = await loadWorldInfo(name);
|
||||
displayWorldEntries(name, wiData);
|
||||
await displayWorldEntries(name, wiData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1815,8 +1816,8 @@ export async function updateWorldInfoList() {
|
||||
}
|
||||
}
|
||||
|
||||
function hideWorldEditor() {
|
||||
displayWorldEntries(null, null);
|
||||
async function hideWorldEditor() {
|
||||
await displayWorldEntries(null, null);
|
||||
}
|
||||
|
||||
function getWIElement(name) {
|
||||
@@ -1937,15 +1938,75 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f
|
||||
worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
|
||||
}
|
||||
|
||||
function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
|
||||
updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
|
||||
function clearEntryList() {
|
||||
console.time('clearEntryList');
|
||||
const $list = $('#world_popup_entries_list');
|
||||
|
||||
if (!$list.children().length) {
|
||||
console.debug('List already empty, skipping cleanup.');
|
||||
console.timeEnd('clearEntryList');
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Clean all <option> elements within <select>
|
||||
$list.find('option').each(function () {
|
||||
const $option = $(this);
|
||||
$option.off();
|
||||
$.cleanData([$option[0]]);
|
||||
$option.remove();
|
||||
});
|
||||
|
||||
// Step 2: Clean all <select> elements
|
||||
$list.find('select').each(function () {
|
||||
const $select = $(this);
|
||||
// Remove Select2-related data and container if present
|
||||
if ($select.data('select2')) {
|
||||
try {
|
||||
$select.select2('destroy');
|
||||
} catch (e) {
|
||||
console.debug('Select2 destroy failed:', e);
|
||||
}
|
||||
}
|
||||
const $container = $select.parent();
|
||||
if ($container.length) {
|
||||
$container.find('*').off();
|
||||
$.cleanData($container.find('*').get());
|
||||
$container.remove();
|
||||
}
|
||||
|
||||
$select.off();
|
||||
$.cleanData([$select[0]]);
|
||||
});
|
||||
|
||||
// Step 3: Clean <div>, <span>, <input>
|
||||
$list.find('div, span, input').each(function () {
|
||||
const $elem = $(this);
|
||||
$elem.off();
|
||||
$.cleanData([$elem[0]]);
|
||||
$elem.remove();
|
||||
});
|
||||
|
||||
const totalElementsOfAnyKindLeftInList = $list.children().length;
|
||||
|
||||
// Final cleanup
|
||||
if (totalElementsOfAnyKindLeftInList) {
|
||||
console.time('empty');
|
||||
$list.empty();
|
||||
console.timeEnd('empty');
|
||||
}
|
||||
|
||||
console.timeEnd('clearEntryList');
|
||||
}
|
||||
|
||||
async function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
|
||||
updateEditor = async (navigation, flashOnNav = true) => await displayWorldEntries(name, data, navigation, flashOnNav);
|
||||
|
||||
const worldEntriesList = $('#world_popup_entries_list');
|
||||
|
||||
// We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
|
||||
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
|
||||
worldEntriesList.find('*').off();
|
||||
worldEntriesList.empty().show();
|
||||
worldEntriesList.css({ 'opacity': 0, 'transition': 'opacity 250ms ease-in-out' });
|
||||
await delay(250);
|
||||
clearEntryList();
|
||||
worldEntriesList.show();
|
||||
|
||||
if (!data || !('entries' in data)) {
|
||||
$('#world_popup_new').off('click').on('click', nullWorldInfo);
|
||||
@@ -2038,22 +2099,39 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
|
||||
formatNavigator: PAGINATION_TEMPLATE,
|
||||
showNavigator: true,
|
||||
callback: async function (/** @type {object[]} */ page) {
|
||||
// We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
|
||||
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
|
||||
worldEntriesList.find('*').off();
|
||||
worldEntriesList.empty();
|
||||
try {
|
||||
// Prevent saveWorldInfo from firing timeouts while rendering the list
|
||||
isSaveWorldInfoDisabled = true;
|
||||
clearEntryList();
|
||||
|
||||
const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
|
||||
const blocksPromises = page.map(async (entry) => await getWorldEntry(name, data, entry)).filter(x => x);
|
||||
const blocks = await Promise.all(blocksPromises);
|
||||
const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
|
||||
if (!isCustomOrder) {
|
||||
blocks.forEach(block => {
|
||||
block.find('.drag-handle').remove();
|
||||
});
|
||||
const keywordHeaders = await renderTemplateAsync('worldInfoKeywordHeaders');
|
||||
const blocks = [];
|
||||
|
||||
for (const entry of page) {
|
||||
try {
|
||||
const block = await getWorldEntry(name, data, entry);
|
||||
if (block) {
|
||||
blocks.push(block);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error while processing entry ${entry.uid}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
|
||||
if (!isCustomOrder) {
|
||||
blocks.forEach(block => {
|
||||
block.find('.drag-handle').remove();
|
||||
});
|
||||
}
|
||||
|
||||
worldEntriesList.append(keywordHeaders);
|
||||
worldEntriesList.append(blocks);
|
||||
} catch (error) {
|
||||
console.error('Error while rendering WI entries:', error);
|
||||
} finally {
|
||||
isSaveWorldInfoDisabled = false;
|
||||
}
|
||||
worldEntriesList.append(keywordHeaders);
|
||||
worldEntriesList.append(blocks);
|
||||
},
|
||||
afterSizeSelectorChange: function (e) {
|
||||
accountStorage.setItem(storageKey, e.target.value);
|
||||
@@ -2173,7 +2251,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
|
||||
if (selectedIndex !== -1) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
} else {
|
||||
hideWorldEditor();
|
||||
await hideWorldEditor();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -2211,6 +2289,12 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
|
||||
await saveWorldInfo(name, data);
|
||||
},
|
||||
});
|
||||
|
||||
worldEntriesList.css('opacity', '1');
|
||||
await delay(250);
|
||||
worldEntriesList.removeAttr('style');
|
||||
|
||||
|
||||
//$("#world_popup_entries_list").disableSelection();
|
||||
}
|
||||
|
||||
@@ -3266,7 +3350,7 @@ export async function getWorldEntry(name, data, entry) {
|
||||
container.appendChild(select);
|
||||
|
||||
let selectedWorldIndex = -1;
|
||||
select.addEventListener('change', function() {
|
||||
select.addEventListener('change', function () {
|
||||
selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
|
||||
});
|
||||
|
||||
@@ -3661,6 +3745,11 @@ async function _save(name, data) {
|
||||
* @return {Promise<void>} A promise that resolves when the world info is saved
|
||||
*/
|
||||
export async function saveWorldInfo(name, data, immediately = false) {
|
||||
// Saving is temporarily disabled
|
||||
if (isSaveWorldInfoDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name || !data) {
|
||||
return;
|
||||
}
|
||||
@@ -3819,7 +3908,7 @@ export async function createNewWorldInfo(worldName, { interactive = false } = {}
|
||||
if (selectedIndex !== -1) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
} else {
|
||||
hideWorldEditor();
|
||||
await hideWorldEditor();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -5385,7 +5474,7 @@ export function initWorldInfo() {
|
||||
const selectedIndex = String($('#world_editor_select').find(':selected').val());
|
||||
|
||||
if (selectedIndex === '') {
|
||||
hideWorldEditor();
|
||||
await hideWorldEditor();
|
||||
} else {
|
||||
const worldName = world_names[selectedIndex];
|
||||
showWorldEditor(worldName);
|
||||
@@ -5537,9 +5626,12 @@ export function initWorldInfo() {
|
||||
select2ChoiceClickSubscribe($('#world_info'), target => {
|
||||
const name = $(target).text();
|
||||
const selectedIndex = world_names.indexOf(name);
|
||||
if (selectedIndex !== -1) {
|
||||
const alreadySelectedInEditor = $('#world_editor_select option:selected').text() === name;
|
||||
if (selectedIndex !== -1 && !alreadySelectedInEditor) {
|
||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||
console.log('Quick selection of world', name);
|
||||
} else {
|
||||
console.warn('lets not reload an already loaded list yes?');
|
||||
}
|
||||
}, { buttonStyle: true, closeDrawer: true });
|
||||
}
|
||||
|
Reference in New Issue
Block a user