Refactor and optimize group members display

This commit is contained in:
Cohee 2023-08-19 02:53:05 +03:00
parent 602c5cd791
commit f27107e0ef
5 changed files with 323 additions and 285 deletions

View File

@ -3201,6 +3201,7 @@
</div>
<div class="inline-drawer-content">
<div name="Current Group Members" class="flex-container flexFlowColumn overflowYAuto flex1">
<div id="rm_group_members_pagination" class="group_pagination"></div>
<div id="rm_group_members" class="overflowYAuto flex-container"></div>
</div>
</div>
@ -3218,6 +3219,7 @@
<div class="rm_tag_controls">
<div class="tags rm_tag_filter"></div>
</div>
<div id="rm_group_add_members_pagination" class="group_pagination"></div>
<div id="rm_group_add_members" class="overflowYAuto flex-container"></div>
</div>
</div>

View File

@ -5916,6 +5916,7 @@ function select_rm_characters() {
menu_type = "characters";
selectRightMenuWithAnimation('rm_characters_block');
setRightTabSelectedClass('rm_button_characters');
printCharacters(false); // Do a quick refresh of the characters list
}
function setExtensionPrompt(key, value, position, depth) {

View File

@ -7,8 +7,8 @@ import {
createThumbnail,
extractAllWords,
} from './utils.js';
import { RA_CountCharTokens, humanizedDateTime, dragElement } from "./RossAscends-mods.js";
import { sortGroupMembers, loadMovingUIState } from './power-user.js';
import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap } from "./RossAscends-mods.js";
import { loadMovingUIState, sortEntitiesList } from './power-user.js';
import {
chat,
@ -87,7 +87,8 @@ let groups = [];
let selected_group = null;
let group_generation_id = null;
let fav_grp_checked = false;
let fav_filter_on = false;
let openGroupId = null;
let newGroupMembers = [];
export const group_activation_strategy = {
NATURAL: 0,
@ -96,12 +97,7 @@ export const group_activation_strategy = {
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');
}
const saveGroupDebounced = debounce(async (group, reload) => await _save(group, reload), 500);
async function _save(group, reload = true) {
await fetch("/editgroup", {
@ -232,7 +228,7 @@ async function saveGroupChat(groupId, shouldSaveGroup) {
});
if (shouldSaveGroup && response.ok) {
await editGroup(groupId);
await editGroup(groupId, false, false);
}
}
@ -250,7 +246,7 @@ export async function renameGroupMember(oldAvatar, newAvatar, newName) {
// Replace group member avatar id and save the changes
group.members[memberIndex] = newAvatar;
await editGroup(group.id, true);
await editGroup(group.id, true, false);
console.log(`Renamed character ${newName} in group: ${group.name}`)
// Load all chats from this group
@ -791,8 +787,6 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
return memberIds;
}
async function deleteGroup(id) {
const response = await fetch("/deletegroup", {
method: "POST",
@ -831,7 +825,7 @@ export async function editGroup(id, immediately, reload = true) {
return await _save(group, reload);
}
saveGroupDebounced(group);
saveGroupDebounced(group, reload);
}
let groupAutoModeAbortController = null;
@ -857,104 +851,222 @@ async function groupChatAutoModeWorker() {
async function modifyGroupMember(chat_id, groupMember, isDelete) {
const id = groupMember.data("id");
const template = groupMember.clone();
let _thisGroup = groups.find((x) => x.id == chat_id);
template.data("id", id);
const thisGroup = groups.find((x) => x.id == chat_id);
const membersArray = thisGroup?.members ?? newGroupMembers;
if (isDelete) {
$("#rm_group_add_members").prepend(template);
} else {
$("#rm_group_members").prepend(template);
}
if (_thisGroup) {
if (isDelete) {
const index = _thisGroup.members.findIndex((x) => x === id);
if (index !== -1) {
_thisGroup.members.splice(index, 1);
}
} else {
_thisGroup.members.push(id);
template.css({ 'order': _thisGroup.members.length });
const index = membersArray.findIndex((x) => x === id);
if (index !== -1) {
membersArray.splice(membersArray.indexOf(id), 1);
}
await editGroup(selected_group);
updateGroupAvatar(_thisGroup);
}
else {
template.css({ 'order': 'unset' });
} else {
membersArray.unshift(id);
}
groupMember.remove();
const groupHasMembers = !!$("#rm_group_members").children().length;
if (openGroupId) {
await editGroup(openGroupId, false, false);
updateGroupAvatar(thisGroup);
}
printGroupCandidates();
printGroupMembers();
const groupHasMembers = getGroupCharacters({ doFilter: false, onlyMembers: true }).length > 0;
$("#rm_group_submit").prop("disabled", !groupHasMembers);
}
async function reorderGroupMember(chat_id, groupMember, direction) {
const id = groupMember.data("id");
const group = groups.find((x) => x.id == chat_id);
const memberArray = group?.members ?? newGroupMembers;
const indexOf = memberArray.indexOf(id);
if (direction == 'down') {
const next = memberArray[indexOf + 1];
if (next) {
memberArray[indexOf + 1] = memberArray[indexOf];
memberArray[indexOf] = next;
}
}
if (direction == 'up') {
const prev = memberArray[indexOf - 1];
if (prev) {
memberArray[indexOf - 1] = memberArray[indexOf];
memberArray[indexOf] = prev;
}
}
printGroupMembers();
// Existing groups need to modify members list
if (group && group.members.length > 1) {
const indexOf = group.members.indexOf(id);
if (direction == 'down') {
const next = group.members[indexOf + 1];
if (next) {
group.members[indexOf + 1] = group.members[indexOf];
group.members[indexOf] = next;
}
}
if (direction == 'up') {
const prev = group.members[indexOf - 1];
if (prev) {
group.members[indexOf - 1] = group.members[indexOf];
group.members[indexOf] = prev;
}
}
await editGroup(chat_id);
if (openGroupId) {
await editGroup(chat_id, false, false);
updateGroupAvatar(group);
// stupid but lifts the manual reordering
select_group_chats(chat_id, true);
}
// New groups just can't be DOM-ordered
else {
if (direction == 'down') {
groupMember.insertAfter(groupMember.next());
}
if (direction == 'up') {
groupMember.insertBefore(groupMember.prev());
}
}
async function onGroupActivationStrategyInput(e) {
if (openGroupId) {
let _thisGroup = groups.find((x) => x.id == openGroupId);
_thisGroup.activation_strategy = Number(e.target.value);
await editGroup(openGroupId, false, false);
}
}
async function onGroupNameInput() {
if (openGroupId) {
let _thisGroup = groups.find((x) => x.id == openGroupId);
_thisGroup.name = $(this).val();
$("#rm_button_selected_ch").children("h2").text(_thisGroup.name);
await editGroup(openGroupId);
}
}
function isGroupMember(group, avatarId) {
if (group && Array.isArray(group.members)) {
return group.members.includes(avatarId);
} else {
return newGroupMembers.includes(avatarId);
}
}
function getGroupCharacters({ doFilter, onlyMembers } = {}) {
function sortMembersFn(a, b) {
const membersArray = thisGroup?.members ?? newGroupMembers;
const aIndex = membersArray.indexOf(a.item.avatar);
const bIndex = membersArray.indexOf(b.item.avatar);
return aIndex - bIndex;
}
const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
let candidates = characters.map((x, index) => ({ item: x, id: index, type: 'character' }));
candidates = candidates.filter((x) => isGroupMember(thisGroup, x.item.avatar) == onlyMembers);
if (onlyMembers) {
candidates.sort(sortMembersFn);
} else {
sortEntitiesList(candidates);
}
if (doFilter) {
candidates = groupCandidatesFilter.applyFilters(candidates);
}
return candidates;
}
function printGroupCandidates() {
$("#rm_group_add_members_pagination").pagination({
dataSource: getGroupCharacters({ doFilter: true, onlyMembers: false }),
pageSize: 5,
pageRange: 1,
position: 'top',
showPageNumbers: false,
showSizeChanger: false,
prevText: '<',
nextText: '>',
showNavigator: true,
callback: function (data) {
$("#rm_group_add_members").empty();
for (const i of data) {
$("#rm_group_add_members").append(getGroupCharacterBlock(i.item));
}
},
});
}
function printGroupMembers() {
$("#rm_group_members_pagination").pagination({
dataSource: getGroupCharacters({ doFilter: false, onlyMembers: true }),
pageSize: 5,
pageRange: 1,
position: 'top',
showPageNumbers: false,
showSizeChanger: false,
prevText: '<',
nextText: '>',
showNavigator: true,
callback: function (data) {
$("#rm_group_members").empty();
for (const i of data) {
$("#rm_group_members").append(getGroupCharacterBlock(i.item));
}
},
});
}
function getGroupCharacterBlock(character) {
const avatar = getThumbnailUrl('avatar', character.avatar);
const template = $("#group_member_template .group_member").clone();
const isFav = character.fav || character.fav == 'true';
template.data("id", character.avatar);
template.find(".avatar img").attr({ "src": avatar, "title": character.avatar });
template.find(".ch_name").text(character.name);
template.attr("chid", characters.indexOf(character));
template.find('.ch_fav').val(isFav);
template.toggleClass('is_fav', isFav);
template.toggleClass('disabled', isGroupMemberDisabled(character.avatar));
// Display inline tags
const tags = getTagsList(character.avatar);
const tagsElement = template.find('.tags');
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
if (!openGroupId) {
template.find('[data-action="speak"]').hide();
template.find('[data-action="enable"]').hide();
template.find('[data-action="disable"]').hide();
}
return template;
}
function isGroupMemberDisabled(avatarId) {
const thisGroup = openGroupId && groups.find((x) => x.id == openGroupId);
return Boolean(thisGroup && thisGroup.disabled_members.includes(avatarId));
}
function onDeleteGroupClick() {
if (is_group_generating) {
toastr.warning('Not so fast! Wait for the characters to stop typing before deleting the group.');
return;
}
$("#dialogue_popup").data("group_id", openGroupId);
callPopup('<h3>Delete the group?</h3><p>This will also delete all your chats with that group. If you want to delete a single conversation, select a "View past chats" option in the lower left menu.</p>', "del_group");
}
async function onFavoriteGroupClick() {
updateFavButtonState(!fav_grp_checked);
if (openGroupId) {
let _thisGroup = groups.find((x) => x.id == openGroupId);
_thisGroup.fav = fav_grp_checked;
await editGroup(openGroupId, false, false);
favsToHotswap();
}
}
async function onGroupSelfResponsesClick() {
if (openGroupId) {
let _thisGroup = groups.find((x) => x.id == openGroupId);
const value = $(this).prop("checked");
_thisGroup.allow_self_responses = value;
await editGroup(openGroupId, false, false);
}
}
function select_group_chats(groupId, skipAnimation) {
const group = groupId && groups.find((x) => x.id == groupId);
openGroupId = groupId;
newGroupMembers = [];
const group = openGroupId && groups.find((x) => x.id == openGroupId);
const groupName = group?.name ?? "";
const replyStrategy = Number(group?.activation_strategy ?? group_activation_strategy.NATURAL);
setMenuType(!!group ? 'group_edit' : 'group_create');
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
$("#rm_group_chat_name").val(groupName);
$("#rm_group_chat_name").off();
$("#rm_group_chat_name").on("input", async function () {
if (groupId) {
let _thisGroup = groups.find((x) => x.id == groupId);
_thisGroup.name = $(this).val();
$("#rm_button_selected_ch").children("h2").text(_thisGroup.name);
await editGroup(groupId);
}
});
$("#rm_group_filter").val("").trigger("input");
$('input[name="rm_group_activation_strategy"]').off();
$('input[name="rm_group_activation_strategy"]').on("input", async function (e) {
if (groupId) {
let _thisGroup = groups.find((x) => x.id == groupId);
_thisGroup.activation_strategy = Number(e.target.value);
await editGroup(groupId);
}
});
const replyStrategy = Number(group?.activation_strategy ?? group_activation_strategy.NATURAL);
$(`input[name="rm_group_activation_strategy"][value="${replyStrategy}"]`).prop('checked', true);
if (!skipAnimation) {
@ -962,53 +1074,15 @@ function select_group_chats(groupId, skipAnimation) {
}
// render characters list
$("#rm_group_add_members").empty();
$("#rm_group_members").empty();
for (let character of characters) {
const avatar =
character.avatar != "none"
? getThumbnailUrl('avatar', character.avatar)
: default_avatar;
const template = $("#group_member_template .group_member").clone();
const isFav = character.fav || character.fav == 'true';
template.data("id", character.avatar);
template.find(".avatar img").attr("src", avatar);
template.find(".avatar img").attr("title", character.avatar);
template.find(".ch_name").text(character.name);
template.attr("chid", characters.indexOf(character));
template.find('.ch_fav').val(isFav);
template.toggleClass('is_fav', isFav);
// Display inline tags
const tags = getTagsList(character.avatar);
const tagsElement = template.find('.tags');
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
if (!group) {
template.find('[data-action="speak"]').hide();
}
if (
group &&
Array.isArray(group.members) &&
group.members.includes(character.avatar)
) {
template.css({ 'order': group.members.indexOf(character.avatar) });
template.toggleClass('disabled', group.disabled_members.includes(character.avatar));
$("#rm_group_members").append(template);
} else {
$("#rm_group_add_members").append(template);
}
}
sortGroupMembers("#rm_group_add_members .group_member");
printGroupCandidates();
printGroupMembers();
const groupHasMembers = !!$("#rm_group_members").children().length;
$("#rm_group_submit").prop("disabled", !groupHasMembers);
$("#rm_group_allow_self_responses").prop("checked", group && group.allow_self_responses);
// bottom buttons
if (groupId) {
if (openGroupId) {
$("#rm_group_submit").hide();
$("#rm_group_delete").show();
$("#rm_group_scenario").show();
@ -1021,39 +1095,8 @@ function select_group_chats(groupId, skipAnimation) {
$("#rm_group_scenario").hide();
}
$("#rm_group_delete").off();
$("#rm_group_delete").on("click", function () {
if (is_group_generating) {
toastr.warning('Not so fast! Wait for the characters to stop typing before deleting the group.');
return;
}
$("#dialogue_popup").data("group_id", groupId);
callPopup('<h3>Delete the group?</h3><p>This will also delete all your chats with that group. If you want to delete a single conversation, select a "View past chats" option in the lower left menu.</p>', "del_group");
});
updateFavButtonState(group?.fav ?? false);
$("#group_favorite_button").off('click');
$("#group_favorite_button").on('click', async function () {
updateFavButtonState(!fav_grp_checked);
if (group) {
let _thisGroup = groups.find((x) => x.id == groupId);
_thisGroup.fav = fav_grp_checked;
await editGroup(groupId);
}
});
$("#rm_group_allow_self_responses").off();
$("#rm_group_allow_self_responses").on("input", async function () {
if (group) {
let _thisGroup = groups.find((x) => x.id == groupId);
const value = $(this).prop("checked");
_thisGroup.allow_self_responses = value;
await editGroup(groupId);
}
});
// top bar
if (group) {
$("#rm_group_automode_label").show();
@ -1064,118 +1107,112 @@ function select_group_chats(groupId, skipAnimation) {
$("#rm_group_automode_label").hide();
}
$("#group_avatar_button").off('input').on("input", uploadGroupAvatar);
$("#rm_group_restore_avatar").off('click').on("click", restoreGroupAvatar);
eventSource.emit('groupSelected', {detail: {id: openGroupId, group: group}});
}
async function uploadGroupAvatar(event) {
const file = event.target.files[0];
async function uploadGroupAvatar(event) {
const file = event.target.files[0];
if (!file) {
return;
}
const e = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = resolve;
reader.onerror = reject;
reader.readAsDataURL(file);
});
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
if (!croppedImage) {
return;
}
const thumbnail = await createThumbnail(croppedImage, 96, 144);
if (!groupId) {
$('#group_avatar_preview img').attr('src', thumbnail);
$('#rm_group_restore_avatar').show();
return;
}
let _thisGroup = groups.find((x) => x.id == groupId);
_thisGroup.avatar_url = thumbnail;
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
$("#rm_group_restore_avatar").show();
await editGroup(groupId, true, true);
if (!file) {
return;
}
async function restoreGroupAvatar() {
const confirm = await callPopup('<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.', 'confirm');
if (!confirm) {
return;
}
if (!groupId) {
$("#group_avatar_preview img").attr("src", default_avatar);
$("#rm_group_restore_avatar").hide();
return;
}
let _thisGroup = groups.find((x) => x.id == groupId);
_thisGroup.avatar_url = '';
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
$("#rm_group_restore_avatar").hide();
await editGroup(groupId, true, true);
}
$(document).off("click", ".group_member .right_menu_button");
$(document).on("click", ".group_member .right_menu_button", async function (event) {
event.stopPropagation();
const action = $(this).data('action');
const member = $(this).closest('.group_member');
if (action === 'remove') {
await modifyGroupMember(groupId, member, true);
}
if (action === 'add') {
await modifyGroupMember(groupId, member, false);
}
if (action === 'enable') {
member.removeClass('disabled');
const _thisGroup = groups.find(x => x.id === groupId);
const index = _thisGroup.disabled_members.indexOf(member.data('id'));
if (index !== -1) {
_thisGroup.disabled_members.splice(index, 1);
}
await editGroup(groupId);
}
if (action === 'disable') {
member.addClass('disabled');
const _thisGroup = groups.find(x => x.id === groupId);
_thisGroup.disabled_members.push(member.data('id'));
await editGroup(groupId);
}
if (action === 'up' || action === 'down') {
await reorderGroupMember(groupId, member, action);
}
if (action === 'view') {
openCharacterDefinition(member);
}
if (action === 'speak') {
const chid = Number(member.attr('chid'));
if (Number.isInteger(chid)) {
Generate('normal', { force_chid: chid });
}
}
sortGroupMembers("#rm_group_add_members .group_member");
await eventSource.emit(event_types.GROUP_UPDATED);
const e = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = resolve;
reader.onerror = reject;
reader.readAsDataURL(file);
});
eventSource.emit('groupSelected', {detail: {id: groupId, group: group}});
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
if (!croppedImage) {
return;
}
const thumbnail = await createThumbnail(croppedImage, 96, 144);
if (!openGroupId) {
$('#group_avatar_preview img').attr('src', thumbnail);
$('#rm_group_restore_avatar').show();
return;
}
let _thisGroup = groups.find((x) => x.id == openGroupId);
_thisGroup.avatar_url = thumbnail;
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
$("#rm_group_restore_avatar").show();
await editGroup(openGroupId, true, true);
}
async function restoreGroupAvatar() {
const confirm = await callPopup('<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.', 'confirm');
if (!confirm) {
return;
}
if (!openGroupId) {
$("#group_avatar_preview img").attr("src", default_avatar);
$("#rm_group_restore_avatar").hide();
return;
}
let _thisGroup = groups.find((x) => x.id == openGroupId);
_thisGroup.avatar_url = '';
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
$("#rm_group_restore_avatar").hide();
await editGroup(openGroupId, true, true);
}
async function onGroupActionClick(event) {
event.stopPropagation();
const action = $(this).data('action');
const member = $(this).closest('.group_member');
if (action === 'remove') {
await modifyGroupMember(openGroupId, member, true);
}
if (action === 'add') {
await modifyGroupMember(openGroupId, member, false);
}
if (action === 'enable') {
member.removeClass('disabled');
const _thisGroup = groups.find(x => x.id === openGroupId);
const index = _thisGroup.disabled_members.indexOf(member.data('id'));
if (index !== -1) {
_thisGroup.disabled_members.splice(index, 1);
}
await editGroup(openGroupId, false, false);
}
if (action === 'disable') {
member.addClass('disabled');
const _thisGroup = groups.find(x => x.id === openGroupId);
_thisGroup.disabled_members.push(member.data('id'));
await editGroup(openGroupId, false, false);
}
if (action === 'up' || action === 'down') {
await reorderGroupMember(openGroupId, member, action);
}
if (action === 'view') {
openCharacterDefinition(member);
}
if (action === 'speak') {
const chid = Number(member.attr('chid'));
if (Number.isInteger(chid)) {
Generate('normal', { force_chid: chid });
}
}
await eventSource.emit(event_types.GROUP_UPDATED);
}
function updateFavButtonState(state) {
@ -1243,10 +1280,7 @@ async function createGroup() {
let name = $("#rm_group_chat_name").val();
let allow_self_responses = !!$("#rm_group_allow_self_responses").prop("checked");
let activation_strategy = $('input[name="rm_group_activation_strategy"]:checked').val() ?? group_activation_strategy.NATURAL;
const members = $("#rm_group_members .group_member")
.map((_, x) => $(x).data("id"))
.toArray();
const members = newGroupMembers;
const memberNames = characters.filter(x => members.includes(x.avatar)).map(x => x.name).join(", ");
if (!name) {
@ -1276,6 +1310,7 @@ async function createGroup() {
});
if (createGroupResponse.ok) {
newGroupMembers = [];
const data = await createGroupResponse.json();
createTagMapFromList("#groupTagList", data.id);
await getCharacters();
@ -1359,7 +1394,7 @@ export async function openGroupChat(groupId, chatId) {
group['date_last_chat'] = Date.now();
updateChatMetadata(group.chat_metadata, true);
await editGroup(groupId, true);
await editGroup(groupId, true, false);
await getGroupChat(groupId);
}
@ -1452,7 +1487,7 @@ export async function saveGroupBookmarkChat(groupId, name, metadata, mesId) {
? chat.slice(0, parseInt(mesId) + 1)
: chat;
await editGroup(groupId, true);
await editGroup(groupId, true, false);
await fetch("/savegroupchat", {
method: "POST",
@ -1520,4 +1555,12 @@ jQuery(() => {
});
$("#send_textarea").on("keyup", onSendTextareaInput);
$("#groupCurrentMemberPopoutButton").on('click', doCurMemberListPopout);
$("#rm_group_chat_name").on("input", onGroupNameInput)
$("#rm_group_delete").off().on("click", onDeleteGroupClick);
$("#group_favorite_button").on('click', onFavoriteGroupClick);
$("#rm_group_allow_self_responses").on("input", onGroupSelfResponsesClick);
$('input[name="rm_group_activation_strategy"]').on("input", onGroupActivationStrategyInput);
$("#group_avatar_button").on("input", uploadGroupAvatar);
$("#rm_group_restore_avatar").on("click", restoreGroupAvatar);
$(document).on("click", ".group_member .right_menu_button", onGroupActionClick);
});

View File

@ -33,7 +33,6 @@ export {
loadMovingUIState,
collapseNewlines,
playMessageSound,
sortGroupMembers,
sortEntitiesList,
fixMarkdown,
power_user,
@ -1133,19 +1132,6 @@ function sortEntitiesList(entities) {
entities.sort((a, b) => sortFunc(a.item, b.item));
}
function sortGroupMembers(selector) {
if (power_user.sort_field == undefined || characters.length === 0) {
return;
}
let orderedList = characters.slice().sort(sortFunc);
for (let i = 0; i < characters.length; i++) {
$(`${selector}[chid="${i}"]`).css({ 'order': orderedList.indexOf(characters[i]) });
}
}
async function saveTheme() {
const name = await callPopup('Enter a theme preset name:', 'input');

View File

@ -3234,6 +3234,12 @@ h5 {
display: none;
}
.group_pagination {
display: flex;
justify-content: center;
align-items: center;
}
#rm_group_chats_block .tag.filterByGroups {
display: none;
}