Add pagination for WI entries #964

This commit is contained in:
Cohee 2023-08-21 21:10:11 +03:00
parent 4a30875030
commit 069a07a139
6 changed files with 77 additions and 87 deletions

View File

@ -39,7 +39,6 @@
#world_popup_bottom_holder div {
width: fit-content;
user-select: none;
opacity: 0.8;
}
.world_popup_logo_block {
@ -60,7 +59,6 @@
#form_rename_world {
display: flex;
align-items: center;
opacity: 0.8;
gap: 5px;
}
@ -179,3 +177,9 @@
/* .world_entry_form .inline-drawer-header {
background-color: var(--SmartThemeShadowColor);
} */
#world_editor_select {
text-overflow: ellipsis;
white-space: nowrap;
width: 10em;
}

View File

@ -2507,8 +2507,8 @@
<div id="world_popup_text">
<div id="world_popup_header" class="flex-container flexGap5">
<div class="world_popup_logo_block">
<h3 data-i18n="World/Lore Editor">
World/Lore Editor
<h3 data-i18n="Editor">
Editor
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#world-info-entry" class="notes-link" target="_blank"><span class="note-link-span">?</span></a>
</h3>
</div>
@ -2524,13 +2524,13 @@
<div id="world_import_button" class="menu_button fa-solid fa-file-import fa-fw" title="Import World Info" data-i18n="[title]Import World Info"></div>
<div id="world_popup_export" class="menu_button fa-solid fa-file-export margin0 fa-fw" title="Export World Info" data-i18n="[title]Export World Info"></div>
<div id="world_popup_delete" class="menu_button fa-solid fa-trash-can redWarningBG margin0 fa-fw" title="Delete World Info" data-i18n="[title]Delete World Info"></div>
<span data-i18n="Editing:">&nbsp;Editing:</span>
<small data-i18n="Editing:">&nbsp;Editing:</small>
<select id="world_editor_select" class="margin0">
<option value="" data-i18n="--- None ---">--- None ---</option>
</select>
<div id="world_popup_name_button" class="menu_button fa-solid fa-i-cursor fa-fw" title="Rename World Info" data-i18n="[title]Rename World Info"></div>
<div id="world_info_pagination"></div>
</form>
</div>
</div>
</div>
@ -3547,61 +3547,6 @@
</div>
<!-- templates for JS to reuse when needed -->
<div id="context_editor_template" class="template_element">
<div class="context_editor">
<h3>Context Template Editor</h3>
<h4 class="template_name"></h4>
<div class="inline-drawer wide100p">
<div class="inline-drawer-toggle inline-drawer-header">
Substitution Parameters
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content">
<i>Click to copy.</i>
<ul class="template_parameters_list justifyLeft margin0">
<li><code>{{char}}</code> - current character name</li>
<li><code>{{user}}</code> - current user name</li>
<li><code>{{description}}</code> - character description</li>
<li><code>{{scenario}}</code> - character or group scenario</li>
<li><code>{{personality}}</code> - character personality</li>
<li><code>{{mesExamples}}</code> - message examples</li>
<li><code>{{wiBeforeCharacter}}</code> - activated World Info entries (Before Char)</li>
<li><code>{{wiAfterCharacter}}</code> - activated World Info entries (After Char)</li>
<li><code>{{instructSystemPrompt}}</code> - system prompt (Instruct mode only)</li>
</ul>
</div>
</div>
<div>
<div class="margin-bot-10px wide100p justifyLeft">
Story String Template
</div>
<textarea class="wide100p textarea_compact story_string_template" rows="8"></textarea>
<div>
<small>Lines containing parameters resolving to an empty value will be removed from the template
string.</small>
</div>
</div>
<div>
<div class="title_restorable">
<span>Chat Injections</span>
<div title="Add chat injection" data-i18n="[title]Add chat injection" class="menu_button chat_injection_add">
<div class="fa-solid fa-plus"></div>
</div>
</div>
<div class="chat_injections_list flex-container flexFlowColumn flexGap5 wide100p"></div>
</div>
</div>
</div>
<div id="chat_injection_template" class="template_element">
<div class="chat_injection flex-container wide100p flexGap5 flexnowrap">
<input class="chat_injection_text textarea_compact text_pole flex2" data-i18n="[placeholder]Injection text (supports parameters)" placeholder="Injection text (supports parameters)" type="text" />
<input class="chat_injection_depth textarea_compact text_pole flex1" data-i18n="[placeholder]Injection depth" placeholder="Injection depth" type="number" min="0" max="100" />
<div title="Remove injection" data-i18n="[title]Remove injection" class="menu_button fa-solid fa-xmark chat_injection_remove"></div>
</div>
</div>
<div id="scenario_override_template" class="template_element">
<div class="scenario_override range-block flexFlowColumn flex-container">
<div class="range-block-title title_restorable">
@ -3743,7 +3688,7 @@
<textarea class="text_pole keysecondarytextpole" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated (ignored if empty)" placeholder="Comma separated (ignored if empty)" maxlength="1000"></textarea>
</div>
</div>
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
<div class="fa-fw fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
</div>
<div class="inline-drawer-content flex-container">
<div class="WIEntryContentAndMemo flex-container">

View File

@ -133,6 +133,7 @@ import {
isDataURL,
getCharaFilename,
isDigitsOnly,
PAGINATION_TEMPLATE,
} from "./scripts/utils.js";
import { extension_settings, getContext, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
@ -1048,6 +1049,7 @@ async function printCharacters(fullRefresh = false) {
showSizeChanger: true,
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
callback: function (data) {
$("#rm_print_characters_block").empty();

View File

@ -6,7 +6,8 @@ import {
isDataURL,
createThumbnail,
extractAllWords,
saveBase64AsFile
saveBase64AsFile,
PAGINATION_TEMPLATE,
} from './utils.js';
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js";
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
@ -989,6 +990,7 @@ function printGroupCandidates() {
showSizeChanger: false,
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
showSizeChanger: true,
pageSize: Number(localStorage.getItem(storageKey)) || 5,
@ -1016,6 +1018,7 @@ function printGroupMembers() {
showSizeChanger: false,
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
showSizeChanger: true,
pageSize: Number(localStorage.getItem(storageKey)) || 5,

View File

@ -1,6 +1,8 @@
import { getContext } from "./extensions.js";
import { getRequestHeaders } from "../script.js";
export const PAGINATION_TEMPLATE = '<%= rangeStart %>-<%= rangeEnd %> of <%= totalNumber %>';
export function onlyUnique(value, index, array) {
return array.indexOf(value) === index;
}

View File

@ -1,5 +1,5 @@
import { saveSettings, callPopup, substituteParams, getTokenCount, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types } from "../script.js";
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, deepClone, getSortableDelay, escapeRegex } from "./utils.js";
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, deepClone, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE } from "./utils.js";
import { getContext } from "./extensions.js";
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from "./authors-note.js";
import { registerSlashCommand } from "./slash-commands.js";
@ -45,11 +45,8 @@ const saveSettingsDebounced = debounce(() => {
saveSettings()
}, 1000);
const sortFn = (a, b) => b.order - a.order;
const countTokensDebounced = debounce(function (counter, value) {
const numberOfTokens = getTokenCount(value);
$(counter).text(numberOfTokens);
}, 1000);
const navigation_option = { none: 0, previous: 1, last: 2, };
let updateEditor = (navigation) => { navigation; };
export function getWorldInfoSettings() {
return {
@ -228,7 +225,9 @@ function nullWorldInfo() {
toastr.info("Create or import a new World Info file first.", "World Info is not set", { timeOut: 10000, preventDuplicates: true });
}
function displayWorldEntries(name, data) {
function displayWorldEntries(name, data, navigation = navigation_option.none) {
updateEditor = (navigation) => displayWorldEntries(name, data, navigation);
$("#world_popup_entries_list").empty().show();
if (!data || !("entries" in data)) {
@ -237,22 +236,55 @@ function displayWorldEntries(name, data) {
$("#world_popup_export").off('click').on('click', nullWorldInfo);
$("#world_popup_delete").off('click').on('click', nullWorldInfo);
$("#world_popup_entries_list").hide();
$("#world_info_pagination").pagination('destroy');
return;
}
// Convert the data.entries object into an array
const entriesArray = Object.keys(data.entries).map(uid => {
const entry = data.entries[uid];
entry.displayIndex = entry.displayIndex ?? entry.uid;
return entry;
function getDataArray(callback) {
// Convert the data.entries object into an array
const entriesArray = Object.keys(data.entries).map(uid => {
const entry = data.entries[uid];
entry.displayIndex = entry.displayIndex ?? entry.uid;
return entry;
});
// Sort the entries array by displayIndex and uid
entriesArray.sort((a, b) => a.displayIndex - b.displayIndex || a.uid - b.uid);
callback(entriesArray);
}
let startPage = 1;
if (navigation === navigation_option.previous) {
startPage = $("#world_info_pagination").pagination('getCurrentPageNum');
}
const storageKey = 'WI_PerPage';
$("#world_info_pagination").pagination({
dataSource: getDataArray,
pageSize: Number(localStorage.getItem(storageKey)) || 10,
sizeChangerOptions: [10, 25, 50, 100],
pageRange: 1,
pageNumber: startPage,
position: 'top',
showPageNumbers: false,
showSizeChanger: true,
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
callback: function (page) {
$("#world_popup_entries_list").empty();
const blocks = page.map(entry => getWorldEntry(name, data, entry));
$("#world_popup_entries_list").append(blocks);
},
afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value);
}
});
// Sort the entries array by displayIndex and uid
entriesArray.sort((a, b) => a.displayIndex - b.displayIndex || a.uid - b.uid);
// Loop through the sorted array and call appendWorldEntry
for (const entry of entriesArray) {
appendWorldEntry(name, data, entry);
if (navigation === navigation_option.last) {
$("#world_info_pagination").pagination('go', $("#world_info_pagination").pagination('getTotalPage'));
}
$("#world_popup_new").off('click').on('click', () => {
@ -365,7 +397,7 @@ function deleteOriginalDataValue(data, uid) {
}
}
function appendWorldEntry(name, data, entry) {
function getWorldEntry(name, data, entry) {
const template = $("#entry_edit_template .world_entry").clone();
template.data("uid", entry.uid);
@ -460,6 +492,10 @@ function appendWorldEntry(name, data, entry) {
// content
const counter = template.find(".world_entry_form_token_counter");
const countTokensDebounced = debounce(function (counter, value) {
const numberOfTokens = getTokenCount(value);
$(counter).text(numberOfTokens);
}, 1000);
const contentInput = template.find('textarea[name="content"]');
contentInput.data("uid", entry.uid);
@ -661,11 +697,10 @@ function appendWorldEntry(name, data, entry) {
const uid = $(this).data("uid");
deleteWorldInfoEntry(data, uid);
deleteOriginalDataValue(data, uid);
$(this).closest(".world_entry").remove();
saveWorldInfo(name, data);
updateEditor(navigation_option.previous);
});
template.appendTo("#world_popup_entries_list");
template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed
return template;
@ -706,8 +741,7 @@ function createWorldInfoEntry(name, data) {
const newEntry = { uid: newUid, ...newEntryTemplate };
data.entries[newUid] = newEntry;
const entryTemplate = appendWorldEntry(name, data, newEntry);
entryTemplate.get(0).scrollIntoView({ behavior: "smooth" });
updateEditor(navigation_option.last);
}
async function _save(name, data) {