Add pagination for characters list view

This commit is contained in:
Cohee 2023-08-18 23:13:15 +03:00
parent 303f961ee2
commit 602c5cd791
9 changed files with 1610 additions and 347 deletions

View File

@ -42,6 +42,7 @@
<script src="scripts/droll.js"></script>
<script src="scripts/localforage.min.js"></script>
<script src="scripts/handlebars.js"></script>
<script src="scripts/pagination.js"></script>
<script type="module" src="scripts/eventemitter.js"></script>
<script type="module" src="scripts/power-user.js"></script>
<script type="module" src="scripts/swiped-events.js"></script>
@ -98,6 +99,7 @@
<script type="module" src="scripts/extensions.js"></script>
<script type="module" src="scripts/authors-note.js"></script>
<script type="module" src="scripts/preset-manager.js"></script>
<script type="module" src="scripts/filters.js"></script>
<script type="text/javascript" src="scripts/toolcool-color-picker.js"></script>
<title>SillyTavern</title>
@ -2988,7 +2990,7 @@
<div id="rm_button_selected_ch">
<h2></h2>
</div>
<i id="hideCharPanelAvatarButton" class="fa-solid fa-eye menu_button"></i>
<i id="hideCharPanelAvatarButton" class="fa-solid fa-eye right_menu_button"></i>
</div>
</div>
<!-- end group peeking cope structure-->
@ -3255,14 +3257,12 @@
<div class="rm_tag_controls">
<div class="tags rm_tag_filter"></div>
</div>
<!-- a div containing a dynamically updated count of characters currently displayed -->
<div class="flex-container alignitemscenter">
<div id="rm_character_count"></div>
<i id="charListGridToggle" class="fa-solid fa-table-cells-large menu_button" title="Toggle character grid view"></i>
</div>
<hr>
</div>
<div id="rm_print_characters_pagination">
<i id="charListGridToggle" class="fa-solid fa-table-cells-large menu_button" title="Toggle character grid view"></i>
</div>
<div id="rm_print_characters_block" class="flexFlowColumn"></div>
</div>
@ -4286,4 +4286,4 @@
</script>
</body>
</html>
</html>

View File

@ -40,7 +40,6 @@ import {
generateGroupWrapper,
deleteGroup,
is_group_generating,
printGroups,
resetSelectedGroup,
select_group_chats,
regenerateGroup,
@ -55,13 +54,13 @@ import {
deleteGroupChat,
renameGroupChat,
importGroupChat,
getGroupBlock,
} from "./scripts/group-chats.js";
import {
collapseNewlines,
loadPowerUserSettings,
playMessageSound,
sortCharactersList,
fixMarkdown,
power_user,
pygmalion_options,
@ -72,10 +71,9 @@ import {
persona_description_positions,
loadMovingUIState,
getCustomStoppingStrings,
fuzzySearchCharacters,
MAX_CONTEXT_DEFAULT,
fuzzySearchGroups,
renderStoryString,
sortEntitiesList,
} from "./scripts/power-user.js";
import {
@ -165,6 +163,7 @@ import { NOTE_MODULE_NAME, metadata_keys, setFloatingPrompt, shouldWIAddPrompt }
import { deviceInfo } from "./scripts/RossAscends-mods.js";
import { registerPromptManagerMigration } from "./scripts/PromptManager.js";
import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js";
import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js";
//exporting functions and vars for mods
export {
@ -233,7 +232,6 @@ export {
talkativeness_default,
default_ch_mes,
extension_prompt_types,
updateVisibleDivs,
mesForShowdownParse,
printCharacters,
}
@ -817,8 +815,9 @@ let token;
var PromptArrayItemForRawPromptDisplay;
export let active_character = ""
export let active_group = ""
export let active_character = "";
export let active_group = "";
export const entitiesFilter = new FilterHelper(debounce(printCharacters, 100));
export function getRequestHeaders() {
return {
@ -965,60 +964,129 @@ function resultCheckStatus() {
$("#api_button_textgenerationwebui").css("display", "inline-block");
}
async function printCharacters() {
$("#rm_print_characters_block").empty();
characters.forEach(function (item, i, arr) {
let this_avatar = default_avatar;
if (item.avatar != "none") {
this_avatar = getThumbnailUrl('avatar', item.avatar);
}
// Populate the template
const template = $('#character_template .character_select').clone();
template.attr({ 'chid': i, 'id': `CharID${i}` });
template.find('img').attr('src', this_avatar);
template.find('.avatar').attr('title', item.avatar);
template.find('.ch_name').text(item.name);
if (power_user.show_card_avatar_urls) {
template.find('.ch_avatar_url').text(item.avatar);
}
template.find('.ch_fav_icon').css("display", 'none');
template.toggleClass('is_fav', item.fav || item.fav == 'true');
template.find('.ch_fav').val(item.fav);
export function selectCharacterById(id) {
if (characters[id] == undefined) {
return;
}
const description = item.data?.creator_notes?.split('\n', 1)[0] || '';
if (description) {
template.find('.ch_description').text(description);
}
else {
template.find('.ch_description').hide();
}
if (selected_group && is_group_generating) {
return;
}
const version = item.data?.character_version || '';
if (version) {
template.find('.character_version').text(version);
if (selected_group || this_chid !== id) {
//if clicked on a different character from what was currently selected
if (!is_send_press) {
cancelTtsPlay();
resetSelectedGroup();
this_edit_mes_id = undefined;
selected_button = "character_edit";
this_chid = id;
clearChat();
chat.length = 0;
chat_metadata = {};
getChat();
}
else {
template.find('.character_version').hide();
} else {
//if clicked on character that was already selected
selected_button = "character_edit";
select_selected_character(this_chid);
}
}
function getCharacterBlock(item, id) {
let this_avatar = default_avatar;
if (item.avatar != "none") {
this_avatar = getThumbnailUrl('avatar', item.avatar);
}
// Populate the template
const template = $('#character_template .character_select').clone();
template.attr({ 'chid': id, 'id': `CharID${id}` });
template.find('img').attr('src', this_avatar);
template.find('.avatar').attr('title', item.avatar);
template.find('.ch_name').text(item.name);
if (power_user.show_card_avatar_urls) {
template.find('.ch_avatar_url').text(item.avatar);
}
template.find('.ch_fav_icon').css("display", 'none');
template.toggleClass('is_fav', item.fav || item.fav == 'true');
template.find('.ch_fav').val(item.fav);
const description = item.data?.creator_notes?.split('\n', 1)[0] || '';
if (description) {
template.find('.ch_description').text(description);
}
else {
template.find('.ch_description').hide();
}
const version = item.data?.character_version || '';
if (version) {
template.find('.character_version').text(version);
}
else {
template.find('.character_version').hide();
}
// Display inline tags
const tags = getTagsList(item.avatar);
const tagsElement = template.find('.tags');
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
// Add to the list
return template;
}
async function printCharacters(fullRefresh = false) {
const storageKey = 'Characters_PerPage';
$("#rm_print_characters_pagination").pagination({
dataSource: getEntitiesList({ doFilter: true }),
pageSize: Number(localStorage.getItem(storageKey)) || 50,
sizeChangerOptions: [25, 50, 100, 250, 500, 1000],
pageRange: 1,
position: 'top',
showPageNumbers: false,
showSizeChanger: true,
prevText: '<',
nextText: '>',
showNavigator: true,
callback: function (data) {
$("#rm_print_characters_block").empty();
for (const i of data) {
if (i.type === 'character') {
$("#rm_print_characters_block").append(getCharacterBlock(i.item, i.id));
}
if (i.type === 'group') {
$("#rm_print_characters_block").append(getGroupBlock(i.item));
}
}
},
afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value);
}
// Display inline tags
const tags = getTagsList(item.avatar);
const tagsElement = template.find('.tags');
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
// Add to the list
$("#rm_print_characters_block").append(template);
});
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
printGroups();
sortCharactersList();
favsToHotswap();
await delay(300);
updateVisibleDivs('#rm_print_characters_block', true);
displayOverrideWarnings();
if (fullRefresh) {
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
await delay(300);
displayOverrideWarnings();
}
}
export function getEntitiesList({ doFilter } = {}) {
let entities = [];
entities.push(...characters.map((item, index) => ({ item, id: index, type: 'character' })));
entities.push(...groups.map((item) => ({ item, id: item.id, type: 'group' })));
if (doFilter) {
entities = entitiesFilter.applyFilters(entities);
}
sortEntitiesList(entities);
return entities;
}
async function getCharacters() {
@ -1050,10 +1118,7 @@ async function getCharacters() {
}
await getGroups();
await printCharacters();
updateCharacterCount('#rm_print_characters_block > div');
await printCharacters(true);
}
}
@ -4209,7 +4274,7 @@ async function renameCharacter() {
if (newChId !== -1) {
// Select the character after the renaming
this_chid = -1;
$(`.character_select[chid="${newChId}"]`).click();
selectCharacterById(String(newChId));
// Async delay to update UI
await delay(1);
@ -4303,7 +4368,6 @@ async function saveChat(chat_name, withMetadata, mesId) {
const metadata = { ...chat_metadata, ...(withMetadata || {}) };
let file_name = chat_name ?? characters[this_chid].chat;
characters[this_chid]['date_last_chat'] = Date.now();
sortCharactersList();
chat.forEach(function (item, i) {
if (item["is_group"]) {
toastr.error('Trying to save group chat with regular saveChat function. Aborting to prevent corruption.');
@ -5852,7 +5916,6 @@ function select_rm_characters() {
menu_type = "characters";
selectRightMenuWithAnimation('rm_characters_block');
setRightTabSelectedClass('rm_button_characters');
updateVisibleDivs('#rm_print_characters_block', true);
}
function setExtensionPrompt(key, value, position, depth) {
@ -6199,7 +6262,6 @@ async function deleteMessageImage() {
mesBlock.find('.mes_img_container').removeClass('img_extra');
mesBlock.find('.mes_img').attr('src', '');
saveChatConditional();
/*updateVisibleDivs('#chat', false);*/
}
function enlargeMessageImage() {
@ -6932,51 +6994,6 @@ const swipe_right = () => {
}
}
export function updateCharacterCount(characterSelector) {
const visibleCharacters = $(characterSelector)
.not(".hiddenBySearch")
.not(".hiddenByTag")
.not(".hiddenByGroup")
.not(".hiddenByGroupMember")
.not(".hiddenByFav");
const visibleCharacterCount = visibleCharacters.length;
const totalCharacterCount = $(characterSelector).length;
$("#rm_character_count").text(
`(${visibleCharacterCount} / ${totalCharacterCount}) Characters`
);
console.log("visibleCharacters.length: " + visibleCharacters.length);
}
function updateVisibleDivs(containerSelector, resizecontainer) {
var $container = $(containerSelector);
var $children = $container.children();
var totalHeight = 0;
$children.each(function () {
totalHeight += $(this).outerHeight();
});
if (resizecontainer) {
$container.css({
height: totalHeight,
});
}
var containerTop = $container.offset() ? $container.offset().top : 0;
var firstVisibleIndex = null;
var lastVisibleIndex = null;
$children.each(function (index) {
var $child = $(this);
var childTop = $child.offset().top - containerTop;
var childBottom = childTop + $child.outerHeight();
if (childTop <= $container.height() && childBottom >= 0) {
if (firstVisibleIndex === null) {
firstVisibleIndex = index;
}
lastVisibleIndex = index;
}
$child.toggleClass('hiddenByScroll', childTop > $container.height() || childBottom < 0);
});
}
function displayOverrideWarnings() {
if (!this_chid || !selected_group) {
$('.prompt_overridden').hide();
@ -7161,8 +7178,7 @@ function doCharListDisplaySwitch() {
console.debug('toggling body charListGrid state')
$("body").toggleClass('charListGrid')
power_user.charListGrid = $("body").hasClass("charListGrid") ? true : false;
saveSettingsDebounced()
updateVisibleDivs('#rm_print_characters_block', true);
saveSettingsDebounced();
}
function doCloseChat() {
@ -7256,18 +7272,11 @@ $(document).ready(function () {
registerSlashCommand('closechat', doCloseChat, [], "- closes the current chat", true, true);
registerSlashCommand('panels', doTogglePanels, ['togglepanels'], "- toggle UI panels on/off", true, true);
setTimeout(function () {
$("#groupControlsToggle").trigger('click');
$("#groupCurrentMemberListToggle .inline-drawer-icon").trigger('click');
}, 200);
$("#rm_print_characters_block").on('scroll', debounce(() => {
updateVisibleDivs('#rm_print_characters_block', true);
}, 5));
$('#chat').on('scroll', async () => {
// if on the start of the chat and has hidden messages
if ($('#chat').scrollTop() === 0 && $('#chat').children('.mes').not(':visible').length > 0) {
@ -7330,43 +7339,8 @@ $(document).ready(function () {
$(document).on('click', '.swipe_left', swipe_left);
$("#character_search_bar").on("input", function () {
const selector = ['#rm_print_characters_block .character_select', '#rm_print_characters_block .group_select'].join(',');
const searchValue = $(this).val().trim().toLowerCase();
const fuzzySearchCharactersResults = power_user.fuzzy_search ? fuzzySearchCharacters(searchValue) : [];
const fuzzySearchGroupsResults = power_user.fuzzy_search ? fuzzySearchGroups(searchValue) : [];
function getIsValidSearch(_this) {
const name = $(_this).find(".ch_name").text().toLowerCase();
const chid = $(_this).attr("chid");
const grid = $(_this).attr("grid");
if (power_user.fuzzy_search) {
if (chid !== undefined) {
return fuzzySearchCharactersResults.includes(parseInt(chid));
} else if (grid !== undefined) {
return fuzzySearchGroupsResults.includes(String(grid));
} else {
return false;
}
}
else {
return name.includes(searchValue);
}
}
if (!searchValue) {
$(selector).removeClass('hiddenBySearch');
updateVisibleDivs('#rm_print_characters_block', true);
} else {
$(selector).each(function () {
const isValidSearch = getIsValidSearch(this);
$(this).toggleClass('hiddenBySearch', !isValidSearch);
});
updateVisibleDivs('#rm_print_characters_block', true);
}
updateCharacterCount(selector);
const searchValue = $(this).val().toLowerCase();
entitiesFilter.setFilterData(FILTER_TYPES.SEARCH, searchValue);
});
$("#send_but").click(function () {
@ -7406,32 +7380,11 @@ $(document).ready(function () {
$("#character_search_bar").val("").trigger("input");
});
$(document).on("click", ".character_select", function () {
if (selected_group && is_group_generating) {
return;
}
if (selected_group || this_chid !== $(this).attr("chid")) {
//if clicked on a different character from what was currently selected
if (!is_send_press) {
cancelTtsPlay();
resetSelectedGroup();
this_edit_mes_id = undefined;
selected_button = "character_edit";
this_chid = $(this).attr("chid");
clearChat();
chat.length = 0;
chat_metadata = {};
getChat();
}
} else {
//if clicked on character that was already selected
selected_button = "character_edit";
select_selected_character(this_chid);
}
$(document).on("click", ".character_select", function() {
const id = $(this).attr("chid");
selectCharacterById(id);
});
$(document).on("input", ".edit_textarea", function () {
scroll_holder = $("#chat").scrollTop();
$(this).height(0).height(this.scrollHeight);

View File

@ -13,11 +13,13 @@ import {
menu_type,
max_context,
saveSettingsDebounced,
eventSource,
active_group,
active_character,
setActiveGroup,
setActiveCharacter,
getEntitiesList,
getThumbnailUrl,
selectCharacterById,
} from "../script.js";
import {
@ -348,12 +350,13 @@ async function RA_autoloadchat() {
// active character is the name, we should look it up in the character list and get the id
let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character);
var charToAutoLoad = document.getElementById('CharID' + active_character_id);
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
if (charToAutoLoad != null) {
$(charToAutoLoad).click();
if (active_character_id !== null) {
selectCharacterById(String(active_character_id));
}
else if (groupToAutoLoad != null) {
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
if (groupToAutoLoad != null) {
$(groupToAutoLoad).click();
}
@ -362,53 +365,60 @@ async function RA_autoloadchat() {
}
export async function favsToHotswap() {
const selector = ['#rm_print_characters_block .character_select', '#rm_print_characters_block .group_select'].join(',');
const entities = getEntitiesList({ doFilter: false });
const container = $('#right-nav-panel .hotswap');
const template = $('#hotswap_template .hotswapAvatar');
container.empty();
const maxCount = 6;
let count = 0;
$(selector).sort(sortByCssOrder).each(function () {
if ($(this).hasClass('is_fav') && count < maxCount) {
const isCharacter = $(this).hasClass('character_select');
const isGroup = $(this).hasClass('group_select');
const grid = Number($(this).attr('grid'));
const chid = Number($(this).attr('chid'));
let thisHotSwapSlot = template.clone();
thisHotSwapSlot.toggleClass('character_select', isCharacter);
thisHotSwapSlot.toggleClass('group_select', isGroup);
thisHotSwapSlot.attr('grid', isGroup ? grid : '');
thisHotSwapSlot.attr('chid', isCharacter ? chid : '');
thisHotSwapSlot.data('id', isGroup ? grid : chid);
thisHotSwapSlot.attr('title', '');
if (isGroup) {
const group = groups.find(x => x.id === grid);
const avatar = getGroupAvatar(group);
$(thisHotSwapSlot).find('img').replaceWith(avatar);
}
if (isCharacter) {
const avatarUrl = $(this).find('img').attr('src');
$(thisHotSwapSlot).find('img').attr('src', avatarUrl);
}
$(thisHotSwapSlot).css('cursor', 'pointer');
container.append(thisHotSwapSlot);
count++;
for (const entity of entities) {
if (count >= maxCount) {
break;
}
});
//console.log('about to check for leftover selectors...')
const isFavorite = entity.item.fav || entity.item.fav == 'true';
if (!isFavorite) {
continue;
}
const isCharacter = entity.type === 'character';
const isGroup = entity.type === 'group';
const grid = isGroup ? entity.id : '';
const chid = isCharacter ? entity.id : '';
let slot = template.clone();
slot.toggleClass('character_select', isCharacter);
slot.toggleClass('group_select', isGroup);
slot.attr('grid', isGroup ? grid : '');
slot.attr('chid', isCharacter ? chid : '');
slot.data('id', isGroup ? grid : chid);
slot.attr('title', '');
if (isGroup) {
const group = groups.find(x => x.id === grid);
const avatar = getGroupAvatar(group);
$(slot).find('img').replaceWith(avatar);
}
if (isCharacter) {
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
$(slot).find('img').attr('src', avatarUrl);
}
$(slot).css('cursor', 'pointer');
container.append(slot);
count++;
}
// there are 6 slots in total,
if (count < maxCount) { //if any are left over
let leftOverSlots = maxCount - count;
for (let i = 1; i <= leftOverSlots; i++) {
container.append(template.clone());
}
} else {
//console.log(`count was ${count} so no need to knock off any selectors!`);
}
}

129
public/scripts/filters.js Normal file
View File

@ -0,0 +1,129 @@
import { fuzzySearchCharacters, fuzzySearchGroups, power_user } from "./power-user.js";
import { tag_map } from "./tags.js";
export const FILTER_TYPES = {
SEARCH: 'search',
TAG: 'tag',
FAV: 'fav',
GROUP: 'group',
};
export class FilterHelper {
constructor(onDataChanged) {
this.onDataChanged = onDataChanged;
}
filterFunctions = {
[FILTER_TYPES.SEARCH]: this.searchFilter.bind(this),
[FILTER_TYPES.GROUP]: this.groupFilter.bind(this),
[FILTER_TYPES.FAV]: this.favFilter.bind(this),
[FILTER_TYPES.TAG]: this.tagFilter.bind(this),
}
filterData = {
[FILTER_TYPES.SEARCH]: '',
[FILTER_TYPES.GROUP]: false,
[FILTER_TYPES.FAV]: false,
[FILTER_TYPES.TAG]: { excluded: [], selected: [] },
}
tagFilter(data) {
const TAG_LOGIC_AND = true; // switch to false to use OR logic for combining tags
const { selected, excluded } = this.filterData[FILTER_TYPES.TAG];
if (!selected.length && !excluded.length) {
return data;
}
function isElementTagged(entity, tagId) {
const isCharacter = entity.type === 'character';
const lookupValue = isCharacter ? entity.item.avatar : String(entity.id);
const isTagged = Array.isArray(tag_map[lookupValue]) && tag_map[lookupValue].includes(tagId);
return isTagged;
}
function getIsTagged(entity) {
const tagFlags = selected.map(tagId => isElementTagged(entity, tagId));
const trueFlags = tagFlags.filter(x => x);
const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0;
const excludedTagFlags = excluded.map(tagId => isElementTagged(entity, tagId));
const isExcluded = excludedTagFlags.includes(true);
if (isExcluded) {
return false;
} else if (selected.length > 0 && !isTagged) {
return false;
} else {
return true;
}
}
return data.filter(entity => getIsTagged(entity));
}
favFilter(data) {
if (!this.filterData[FILTER_TYPES.FAV]) {
return data;
}
return data.filter(entity => entity.item.fav || entity.item.fav == "true");
}
groupFilter(data) {
if (!this.filterData[FILTER_TYPES.GROUP]) {
return data;
}
return data.filter(entity => entity.type === 'group');
}
searchFilter(data) {
if (!this.filterData[FILTER_TYPES.SEARCH]) {
return data;
}
const searchValue = this.filterData[FILTER_TYPES.SEARCH].trim().toLowerCase();
const fuzzySearchCharactersResults = power_user.fuzzy_search ? fuzzySearchCharacters(searchValue) : [];
const fuzzySearchGroupsResults = power_user.fuzzy_search ? fuzzySearchGroups(searchValue) : [];
function getIsValidSearch(entity) {
const isGroup = entity.type === 'group';
const isCharacter = entity.type === 'character';
if (power_user.fuzzy_search) {
if (isCharacter) {
return fuzzySearchCharactersResults.includes(parseInt(entity.id));
} else if (isGroup) {
return fuzzySearchGroupsResults.includes(String(entity.id));
} else {
return false;
}
}
else {
return entity.item?.name?.toLowerCase()?.includes(searchValue) || false;
}
}
return data.filter(entity => getIsValidSearch(entity));
}
setFilterData(filterType, data) {
const oldData = this.filterData[filterType];
this.filterData[filterType] = data;
// only trigger a data change if the data actually changed
if (JSON.stringify(oldData) !== JSON.stringify(data)) {
this.onDataChanged();
}
}
getFilterData(filterType) {
return this.filterData[filterType];
}
applyFilters(data) {
return Object.values(this.filterFunctions)
.reduce((data, fn) => fn(data), data);
}
}

View File

@ -8,7 +8,7 @@ import {
extractAllWords,
} from './utils.js';
import { RA_CountCharTokens, humanizedDateTime, dragElement } from "./RossAscends-mods.js";
import { sortCharactersList, sortGroupMembers, loadMovingUIState } from './power-user.js';
import { sortGroupMembers, loadMovingUIState } from './power-user.js';
import {
chat,
@ -63,6 +63,7 @@ import {
getCropPopup,
} from "../script.js";
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
import { FilterHelper } from './filters.js';
export {
selected_group,
@ -75,7 +76,6 @@ export {
deleteGroup,
getGroupAvatar,
getGroups,
printGroups,
regenerateGroup,
resetSelectedGroup,
select_group_chats,
@ -94,9 +94,15 @@ export const group_activation_strategy = {
LIST: 1,
};
export const groupCandidatesFilter = new FilterHelper(debounce(printGroupCandidates, 100));
const groupAutoModeInterval = setInterval(groupChatAutoModeWorker, 5000);
const saveGroupDebounced = debounce(async (group) => await _save(group), 500);
function printGroupCandidates(fullRefresh = false) {
toastr.info('Group candidates tags filter is temporarily unavailable.');
console.log('TODO: implement printGroupCandidates');
}
async function _save(group, reload = true) {
await fetch("/editgroup", {
method: "POST",
@ -228,7 +234,6 @@ async function saveGroupChat(groupId, shouldSaveGroup) {
if (shouldSaveGroup && response.ok) {
await editGroup(groupId);
}
sortCharactersList();
}
export async function renameGroupMember(oldAvatar, newAvatar, newName) {
@ -330,8 +335,7 @@ async function getGroups() {
}
}
function printGroups() {
for (let group of groups) {
export function getGroupBlock(group) {
const template = $("#group_list_template .group_select").clone();
template.data("id", group.id);
template.attr("grid", group.id);
@ -345,10 +349,14 @@ function printGroups() {
const tagsElement = template.find('.tags');
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
$("#rm_print_characters_block").prepend(template);
updateGroupAvatar(group);
}
const avatar = getGroupAvatar(group);
if (avatar) {
$(template).find(".avatar").replaceWith(avatar);
}
return template;
}
function updateGroupAvatar(group) {
$("#rm_print_characters_block .group_select").each(function () {
if ($(this).data("id") == group.id) {
@ -1353,7 +1361,6 @@ export async function openGroupChat(groupId, chatId) {
await editGroup(groupId, true);
await getGroupChat(groupId);
sortCharactersList();
}
export async function renameGroupChat(groupId, oldChatId, newChatId) {

1190
public/scripts/pagination.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,6 @@ import {
reloadCurrentChat,
getRequestHeaders,
substituteParams,
updateVisibleDivs,
eventSource,
event_types,
getCurrentChatId,
@ -18,7 +17,7 @@ import {
setCharacterId,
setEditedMessageId
} from "../script.js";
import { favsToHotswap, isMobile, initMovingUI } from "./RossAscends-mods.js";
import { isMobile, initMovingUI } from "./RossAscends-mods.js";
import {
groups,
resetSelectedGroup,
@ -35,7 +34,7 @@ export {
collapseNewlines,
playMessageSound,
sortGroupMembers,
sortCharactersList,
sortEntitiesList,
fixMarkdown,
power_user,
pygmalion_options,
@ -803,7 +802,6 @@ function loadPowerUserSettings(settings, data) {
$(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop("selected", true);
sortCharactersList();
reloadMarkdownProcessor(power_user.render_formulas);
loadInstructMode();
loadContextSettings();
@ -812,7 +810,6 @@ function loadPowerUserSettings(settings, data) {
switchSpoilerMode();
loadMovingUIState();
loadCharListState();
}
async function loadCharListState() {
@ -1129,32 +1126,12 @@ const compareFunc = (first, second) => {
}
};
function sortCharactersList() {
const arr1 = groups.map(x => ({
item: x,
id: x.id,
selector: '.group_select',
attribute: 'grid',
}))
const arr2 = characters.map((x, index) => ({
item: x,
id: index,
selector: '.character_select',
attribute: 'chid',
}));
const array = [...arr1, ...arr2];
if (power_user.sort_field == undefined || array.length === 0) {
function sortEntitiesList(entities) {
if (power_user.sort_field == undefined || entities.length === 0) {
return;
}
let orderedList = array.slice().sort((a, b) => sortFunc(a.item, b.item));
for (const item of array) {
$(`${item.selector}[${item.attribute}="${item.id}"]`).css({ 'order': orderedList.indexOf(item) });
}
updateVisibleDivs('#rm_print_characters_block', true);
entities.sort((a, b) => sortFunc(a.item, b.item));
}
function sortGroupMembers(selector) {
@ -1900,6 +1877,7 @@ $(document).ready(() => {
power_user.never_resize_avatars = !!$(this).prop('checked');
saveSettingsDebounced();
});
$("#show_card_avatar_urls").on('input', function () {
power_user.show_card_avatar_urls = !!$(this).prop('checked');
printCharacters();
@ -1925,8 +1903,7 @@ $(document).ready(() => {
power_user.sort_field = $(this).find(":selected").data('field');
power_user.sort_order = $(this).find(":selected").data('order');
power_user.sort_rule = $(this).find(":selected").data('rule');
sortCharactersList();
favsToHotswap();
printCharacters();
saveSettingsDebounced();
});
@ -2038,12 +2015,6 @@ $(document).ready(() => {
saveSettingsDebounced();
});
/* $("#removeXML").on("input", function () {
power_user.removeXML = !!$(this).prop('checked');
reloadCurrentChat();
saveSettingsDebounced();
}); */
$("#token_padding").on("input", function () {
power_user.token_padding = Number($(this).val());
saveSettingsDebounced();

View File

@ -4,12 +4,12 @@ import {
this_chid,
callPopup,
menu_type,
updateVisibleDivs,
getCharacters,
updateCharacterCount,
entitiesFilter,
} from "../script.js";
import { FILTER_TYPES } from "./filters.js";
import { selected_group } from "./group-chats.js";
import { groupCandidatesFilter, selected_group } from "./group-chats.js";
import { uuidv4 } from "./utils.js";
export {
@ -17,7 +17,6 @@ export {
tag_map,
loadTagsSettings,
printTagFilters,
isElementTagged,
getTagsList,
appendTagToList,
createTagMapFromList,
@ -26,18 +25,11 @@ export {
};
const random_id = () => uuidv4();
const TAG_LOGIC_AND = true; // switch to false to use OR logic for combining tags
const CHARACTER_SELECTOR = '#rm_print_characters_block > div';
const GROUP_MEMBER_SELECTOR = '#rm_group_add_members > div';
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
function getCharacterSelector(listSelector) {
if ($(listSelector).is(GROUP_FILTER_SELECTOR)) {
return GROUP_MEMBER_SELECTOR;
}
return CHARACTER_SELECTOR;
function getFilterHelper(listSelector) {
return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter;
}
export const tag_filter_types = {
@ -68,37 +60,20 @@ const DEFAULT_TAGS = [
let tags = [];
let tag_map = {};
function applyFavFilter(characterSelector) {
function applyFavFilter(filterHelper) {
const isSelected = $(this).hasClass('selected');
const displayFavoritesOnly = !isSelected;
$(this).toggleClass('selected', displayFavoritesOnly);
$(characterSelector).removeClass('hiddenByFav');
$(characterSelector).each(function () {
if (displayFavoritesOnly) {
if ($(this).find(".ch_fav").length !== 0) {
const shouldBeDisplayed = $(this).find(".ch_fav").val().toLowerCase().includes(true);
$(this).toggleClass('hiddenByFav', !shouldBeDisplayed);
}
}
});
updateCharacterCount(characterSelector);
updateVisibleDivs('#rm_print_characters_block', true);
filterHelper.setFilterData(FILTER_TYPES.FAV, displayFavoritesOnly);
}
function filterByGroups(characterSelector) {
function filterByGroups(filterHelper) {
const isSelected = $(this).hasClass('selected');
const displayGroupsOnly = !isSelected;
$(this).toggleClass('selected', displayGroupsOnly);
$(characterSelector).removeClass('hiddenByGroup');
$(characterSelector).each((_, element) => {
$(element).toggleClass('hiddenByGroup', displayGroupsOnly && !$(element).hasClass('group_select'));
});
updateCharacterCount(characterSelector);
updateVisibleDivs('#rm_print_characters_block', true);
filterHelper.setFilterData(FILTER_TYPES.GROUP, displayGroupsOnly);
}
function loadTagsSettings(settings) {
@ -291,7 +266,6 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
return;
}
const characterSelector = getCharacterSelector($(listElement));
let tagElement = $('#tag_template .tag').clone();
tagElement.attr('id', tag.id);
@ -316,11 +290,12 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
}
if (selectable) {
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement, characterSelector));
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement));
}
if (action) {
tagElement.on('click', () => action.bind(tagElement)(characterSelector));
const filter = getFilterHelper($(listElement));
tagElement.on('click', () => action.bind(tagElement)(filter));
tagElement.addClass('actionable');
}
if (action && tag.id === 2) {
@ -330,7 +305,7 @@ function appendTagToList(listElement, tag, { removable, selectable, action, isGe
$(listElement).append(tagElement);
}
function onTagFilterClick(listElement, characterSelector) {
function onTagFilterClick(listElement) {
let excludeTag;
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
@ -356,44 +331,10 @@ function onTagFilterClick(listElement, characterSelector) {
}
}
// TODO: Overhaul this somehow to use settings tag IDs instead
const tagIds = [...($(listElement).find(".tag.selected:not(.actionable)").map((_, el) => $(el).attr("id")))];
const excludedTagIds = [...($(listElement).find(".tag.excluded:not(.actionable)").map((_, el) => $(el).attr("id")))];
$(characterSelector).each((_, element) => applyFilterToElement(tagIds, excludedTagIds, element));
updateCharacterCount(characterSelector);
updateVisibleDivs('#rm_print_characters_block', true);
}
function applyFilterToElement(tagIds, excludedTagIds, element) {
const tagFlags = tagIds.map(tagId => isElementTagged(element, tagId));
const trueFlags = tagFlags.filter(x => x);
const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0;
const excludedTagFlags = excludedTagIds.map(tagId => isElementTagged(element, tagId));
const isExcluded = excludedTagFlags.includes(true);
if (isExcluded) {
$(element).addClass('hiddenByTag');
} else if (tagIds.length > 0 && !isTagged) {
$(element).addClass('hiddenByTag');
} else {
$(element).removeClass('hiddenByTag');
}
}
function isElementTagged(element, tagId) {
const isGroup = $(element).hasClass('group_select');
const isCharacter = $(element).hasClass('character_select') || $(element).hasClass('group_member');
const idAttr = isGroup ? 'grid' : 'chid';
const elementId = $(element).attr(idAttr);
const lookupValue = isCharacter ? characters[elementId].avatar : elementId;
const isTagged = Array.isArray(tag_map[lookupValue]) && tag_map[lookupValue].includes(tagId);
return isTagged;
}
function clearTagsFilter(characterSelector) {
$('.rm_tag_filter .tag').removeClass('selected');
$(characterSelector).removeClass('hiddenByTag');
const filterHelper = getFilterHelper($(listElement));
filterHelper.setFilterData(FILTER_TYPES.TAG, { excluded: excludedTagIds, selected: tagIds });
}
function printTagFilters(type = tag_filter_types.character) {

View File

@ -1270,18 +1270,18 @@ input[type="file"] {
filter: brightness(150%);
}
#rm_character_count {
padding: 5px;
font-size: calc(var(--mainFontSize) * .8);
font-weight: bold;
#rm_print_characters_pagination {
display: flex;
flex-direction: row;
gap: 5px;
justify-content: center;
}
#rm_print_characters_block {
/* padding: 5px 0; */
overflow-y: auto;
flex-grow: 1;
display: flex;
/* row-gap: 5px; */
height: 100%;
}
body.charListGrid #rm_print_characters_block {
@ -1501,6 +1501,7 @@ select option:not(:checked) {
display: flex;
overflow-y: auto;
flex-direction: column;
height: 100%;
}
#rm_characters_block .right_menu_button {
@ -5616,3 +5617,64 @@ body.waifuMode .zoomed_avatar {
vertical-align: middle;
/* To align with adjacent text */
}
.paginationjs {
display: flex;
align-items: center;
flex-direction: row;
}
/* Pagination */
.paginationsjs-pages {
margin: 0.5em 0;
display: flex;
justify-content: center;
align-items: center;
}
.paginationjs-pages ul {
list-style-type: none;
margin: 0.25em;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
}
.paginationjs-size-changer select {
width: unset;
margin: 0;
}
.paginationjs-pages ul li a {
padding: 0.05em 0.5em;
text-decoration: none;
color: var(--SmartThemeBodyColor);
border: 1px solid var(--white30a);
border-radius: 5px;
transition: opacity 0.2s;
opacity: 0.8;
cursor: pointer;
}
.paginationjs-pages ul li a:hover {
opacity: 1;
}
.paginationjs-pages ul li.active a {
color: var(--SmartThemeQuoteColor);
border-color: var(--SmartThemeQuoteColor);
opacity: 1;
}
.paginationjs-pages ul li.disabled a {
opacity: 0.5;
cursor: not-allowed;
}
.paginationjs-nav {
padding: 5px;
font-size: calc(var(--mainFontSize) * .8);
font-weight: bold;
}