mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-09 00:17:47 +01:00
Tag Folders: Additional Fixes
- Refactored to one general printTagList method - Made a robust getTagKeyForEntity function - Fixed group not displaying tags if autoloaded - Fixed added tags on character/groups not being auto sorted - Fixed autoload of group/character that I killed - Fixed typo
This commit is contained in:
parent
7b49290fec
commit
5ac7826fec
@ -163,8 +163,8 @@ import {
|
||||
getTagBlock,
|
||||
loadTagsSettings,
|
||||
printTagFilters,
|
||||
getTagsList,
|
||||
appendTagToList,
|
||||
getTagKeyForEntity,
|
||||
printTagList,
|
||||
createTagMapFromList,
|
||||
renameTagKey,
|
||||
importTags,
|
||||
@ -803,8 +803,11 @@ let token;
|
||||
|
||||
var PromptArrayItemForRawPromptDisplay;
|
||||
|
||||
/** The tag of the active character. (NOT the id) */
|
||||
export let active_character = '';
|
||||
/** The tag of the active group. (Coincidentally also the id) */
|
||||
export let active_group = '';
|
||||
|
||||
export const entitiesFilter = new FilterHelper(debounce(printCharacters, 100));
|
||||
export const personasFilter = new FilterHelper(debounce(getUserAvatars, 100));
|
||||
|
||||
@ -877,12 +880,12 @@ export function setAnimationDuration(ms = null) {
|
||||
animation_duration = ms ?? ANIMATION_DURATION_DEFAULT;
|
||||
}
|
||||
|
||||
export function setActiveCharacter(character) {
|
||||
active_character = character;
|
||||
export function setActiveCharacter(entityOrKey) {
|
||||
active_character = getTagKeyForEntity(entityOrKey);
|
||||
}
|
||||
|
||||
export function setActiveGroup(group) {
|
||||
active_group = group;
|
||||
export function setActiveGroup(entityOrKey) {
|
||||
active_group = getTagKeyForEntity(entityOrKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1187,14 +1190,14 @@ function getEmptyBlock() {
|
||||
* @param {number} hidden Number of hidden characters
|
||||
*/
|
||||
function getHiddenBlock(hidden) {
|
||||
const hiddenBlick = `
|
||||
const hiddenBlock = `
|
||||
<div class="text_block hidden_block">
|
||||
<small>
|
||||
<p>${hidden} ${hidden > 1 ? 'characters' : 'character'} hidden.</p>
|
||||
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]Characters and groups hidden by filters or closed folders" title="Characters and groups hidden by filters or closed folders"></div>
|
||||
</small>
|
||||
</div>`;
|
||||
return $(hiddenBlick);
|
||||
return $(hiddenBlock);
|
||||
}
|
||||
|
||||
function getCharacterBlock(item, id) {
|
||||
@ -1233,9 +1236,8 @@ function getCharacterBlock(item, id) {
|
||||
}
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(item.avatar);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
printTagList(tagsElement, { forEntityOrKey: id });
|
||||
|
||||
// Add to the list
|
||||
return template;
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
import { favsToHotswap } from './RossAscends-mods.js';
|
||||
import { hideLoader, showLoader } from './loader.js';
|
||||
import { convertCharacterToPersona } from './personas.js';
|
||||
import { createTagInput, getTagKeyForCharacter, tag_map } from './tags.js';
|
||||
import { createTagInput, getTagKeyForEntity, tag_map } from './tags.js';
|
||||
|
||||
// Utility object for popup messages.
|
||||
const popupMessage = {
|
||||
@ -243,7 +243,7 @@ class BulkTagPopupHandler {
|
||||
*/
|
||||
static resetTags(characterIds) {
|
||||
characterIds.forEach((characterId) => {
|
||||
const key = getTagKeyForCharacter(characterId);
|
||||
const key = getTagKeyForEntity(characterId);
|
||||
if (key) tag_map[key] = [];
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
setActiveGroup,
|
||||
setActiveCharacter,
|
||||
getEntitiesList,
|
||||
getThumbnailUrl,
|
||||
buildAvatarList,
|
||||
selectCharacterById,
|
||||
eventSource,
|
||||
@ -27,7 +26,8 @@ import {
|
||||
} from './power-user.js';
|
||||
|
||||
import { LoadLocal, SaveLocal, LoadLocalBool } from './f-localStorage.js';
|
||||
import { selected_group, is_group_generating, getGroupAvatar, groups, openGroupById } from './group-chats.js';
|
||||
import { selected_group, is_group_generating, openGroupById } from './group-chats.js';
|
||||
import { getTagKeyForEntity } from './tags.js';
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
@ -248,8 +248,7 @@ export function RA_CountCharTokens() {
|
||||
async function RA_autoloadchat() {
|
||||
if (document.querySelector('#rm_print_characters_block .character_select') !== null) {
|
||||
// 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);
|
||||
|
||||
const active_character_id = characters.findIndex(x => getTagKeyForEntity(x) == active_character);
|
||||
if (active_character_id !== null) {
|
||||
await selectCharacterById(String(active_character_id));
|
||||
}
|
||||
@ -805,14 +804,14 @@ export function initRossMods() {
|
||||
|
||||
// when a char is selected from the list, save their name as the auto-load character for next page load
|
||||
$(document).on('click', '.character_select', function () {
|
||||
const characterId = $(this).find('.avatar').attr('title') || $(this).attr('title');
|
||||
const characterId = $(this).attr('chid') || $(this).data('id');
|
||||
setActiveCharacter(characterId);
|
||||
setActiveGroup(null);
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(document).on('click', '.group_select', function () {
|
||||
const groupId = $(this).data('id') || $(this).attr('grid');
|
||||
const groupId = $(this).attr('grid') || $(this).data('id');
|
||||
setActiveCharacter(null);
|
||||
setActiveGroup(groupId);
|
||||
saveSettingsDebounced();
|
||||
|
@ -69,7 +69,7 @@ import {
|
||||
loadItemizedPrompts,
|
||||
animation_duration,
|
||||
} from '../script.js';
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||
import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
|
||||
export {
|
||||
@ -546,9 +546,8 @@ export function getGroupBlock(group) {
|
||||
template.find('.group_select_block_list').append(namesList.join(''));
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(group.id);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
printTagList(tagsElement, { forEntityOrKey: group.id });
|
||||
|
||||
const avatar = getGroupAvatar(group);
|
||||
if (avatar) {
|
||||
@ -579,7 +578,7 @@ function isValidImageUrl(url) {
|
||||
|
||||
function getGroupAvatar(group) {
|
||||
if (!group) {
|
||||
return $(`<div class="avatar" title="[Group] ${group.name}"><img src="${default_avatar}"></div>`);
|
||||
return $(`<div class="avatar"><img src="${default_avatar}"></div>`);
|
||||
}
|
||||
// if isDataURL or if it's a valid local file url
|
||||
if (isValidImageUrl(group.avatar_url)) {
|
||||
@ -1185,9 +1184,8 @@ function getGroupCharacterBlock(character) {
|
||||
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, {}));
|
||||
printTagList(tagsElement, { forEntityOrKey: characters.indexOf(character) });
|
||||
|
||||
if (!openGroupId) {
|
||||
template.find('[data-action="speak"]').hide();
|
||||
@ -1263,6 +1261,9 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
selectRightMenuWithAnimation('rm_group_chats_block');
|
||||
}
|
||||
|
||||
// render tags
|
||||
printTagList($('#groupTagList'), { forEntityOrKey: groupId, tagOptions: { removable: true } });
|
||||
|
||||
// render characters list
|
||||
printGroupCandidates();
|
||||
printGroupMembers();
|
||||
|
@ -29,6 +29,7 @@ export {
|
||||
loadTagsSettings,
|
||||
printTagFilters,
|
||||
getTagsList,
|
||||
printTagList,
|
||||
appendTagToList,
|
||||
createTagMapFromList,
|
||||
renameTagKey,
|
||||
@ -308,12 +309,37 @@ function getTagKey() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getTagKeyForCharacter(characterId = null) {
|
||||
return characters[characterId]?.avatar;
|
||||
/**
|
||||
* Gets the tag key for any provided entity/id/key. If a valid tag key is provided, it just returns this.
|
||||
* Robust method to find a valid tag key for any entity
|
||||
* @param {object|number|string} entityOrKey An entity with id property (character, group, tag), or directly an id or tag key.
|
||||
* @returns {string} The tag key that can be found.
|
||||
*/
|
||||
export function getTagKeyForEntity(entityOrKey) {
|
||||
let x = entityOrKey;
|
||||
|
||||
// If it's an object and has an 'id' property, we take this for further processing
|
||||
if (typeof x === 'object' && x !== null && 'id' in x) {
|
||||
x = x.id;
|
||||
}
|
||||
|
||||
// Next lets check if its a valid character or character id, so we can swith it to its tag
|
||||
const character = characters.indexOf(x) > 0 ? x : characters[x];
|
||||
if (character) {
|
||||
x = character.avatar;
|
||||
}
|
||||
|
||||
// We should hopefully have a key now. Let's check
|
||||
if (x in tag_map) {
|
||||
return x;
|
||||
}
|
||||
|
||||
// If none of the above, we cannot find a valid tag key
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function addTagToMap(tagId, characterId = null) {
|
||||
const key = getTagKey() ?? getTagKeyForCharacter(characterId);
|
||||
const key = getTagKey() ?? getTagKeyForEntity(characterId);
|
||||
|
||||
if (!key) {
|
||||
return;
|
||||
@ -329,7 +355,7 @@ function addTagToMap(tagId, characterId = null) {
|
||||
}
|
||||
|
||||
function removeTagFromMap(tagId, characterId = null) {
|
||||
const key = getTagKey() ?? getTagKeyForCharacter(characterId);
|
||||
const key = getTagKey() ?? getTagKeyForEntity(characterId);
|
||||
|
||||
if (!key) {
|
||||
return;
|
||||
@ -370,10 +396,6 @@ function selectTag(event, ui, listSelector) {
|
||||
// unfocus and clear the input
|
||||
$(event.target).val('').trigger('input');
|
||||
|
||||
// add tag to the UI and internal map
|
||||
appendTagToList(listSelector, tag, { removable: true });
|
||||
appendTagToList(getInlineListSelector(), tag, { removable: false });
|
||||
|
||||
// Optional, check for multiple character ids being present.
|
||||
const characterData = event.target.closest('#bulk_tags_div')?.dataset.characters;
|
||||
const characterIds = characterData ? JSON.parse(characterData).characterIds : null;
|
||||
@ -385,6 +407,11 @@ function selectTag(event, ui, listSelector) {
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
|
||||
// add tag to the UI and internal map - we reprint so sorting and new markup is done correctly
|
||||
printTagList(listSelector, { tagOptions: { removable: true } });
|
||||
printTagList($(getInlineListSelector()));
|
||||
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
|
||||
@ -458,18 +485,63 @@ function createNewTag(tagName) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} TagOptions
|
||||
* @property {boolean} [removable=false] - Whether tags can be removed.
|
||||
* @property {boolean} [selectable=false] - Whether tags can be selected.
|
||||
* @property {function} [action=undefined] - Action to perform on tag interaction.
|
||||
* @property {boolean} [isGeneralList=false] - If true, indicates that this is the general list of tags.
|
||||
* @property {boolean} [skipExistsCheck=false] - If true, the tag gets added even if a tag with the same id already exists.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prints the list of tags.
|
||||
* @param {JQuery<HTMLElement>} element - The container element where the tags are to be printed.
|
||||
* @param {object} [options] - Optional parameters for printing the tag list.
|
||||
* @param {Array<object>} [options.tags] Optional override of tags that should be printed. Those will not be sorted. If no supplied, tags for the relevant character are printed.
|
||||
* @param {object|number|string} [options.forEntityOrKey=undefined] - Optional override for the chosen entity, otherwise the currently selected is chosen. Can be an entity with id property (character, group, tag), or directly an id or tag key.
|
||||
* @param {boolean} [options.empty=true] - Whether the list should be initially empty.
|
||||
* @param {function(object): function} [options.tagActionSelector=undefined] - An optional override for the action property that can be assigned to each tag via tagOptions.
|
||||
* If set, the selector is executed on each tag as input argument. This allows a list of tags to be provided and each tag can have it's action based on the tag object itself.
|
||||
* @param {TagOptions} [options.tagOptions={}] - Options for tag behavior. (Same object will be passed into "appendTagToList")
|
||||
*/
|
||||
function printTagList(element, { tags = undefined, forEntityOrKey = undefined, empty = true, tagActionSelector = undefined, tagOptions = {} } = {}) {
|
||||
const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey();
|
||||
const printableTags = tags ?? getTagsList(key);
|
||||
|
||||
if (empty) {
|
||||
$(element).empty();
|
||||
}
|
||||
|
||||
for (const tag of printableTags) {
|
||||
// If we have a custom action selector, we override that tag options for each tag
|
||||
if (tagActionSelector && typeof tagActionSelector === 'function') {
|
||||
const action = tagActionSelector(tag);
|
||||
if (action && typeof action !== 'function') {
|
||||
console.error('The action parameter must return a function for tag.', tag);
|
||||
} else {
|
||||
tagOptions.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
appendTagToList(element, tag, tagOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a tag to the list element.
|
||||
* @param {string} listElement List element selector.
|
||||
* @param {object} tag Tag object.
|
||||
* @param {TagOptions} options Options for the tag.
|
||||
* @typedef {{removable?: boolean, selectable?: boolean, action?: function, isGeneralList?: boolean}} TagOptions
|
||||
* @param {JQuery<HTMLElement>} listElement List element.
|
||||
* @param {object} tag Tag object to append.
|
||||
* @param {TagOptions} [options={}] - Options for tag behavior.
|
||||
* @returns {void}
|
||||
*/
|
||||
function appendTagToList(listElement, tag, { removable, selectable, action, isGeneralList }) {
|
||||
function appendTagToList(listElement, tag, { removable = false, selectable = false, action = undefined, isGeneralList = false, skipExistsCheck = false } = {}) {
|
||||
if (!listElement) {
|
||||
return;
|
||||
}
|
||||
if (!skipExistsCheck && $(listElement).find(`.tag[id="${tag.id}"]`).length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tagElement = $('#tag_template .tag').clone();
|
||||
tagElement.attr('id', tag.id);
|
||||
@ -527,7 +599,7 @@ function onTagFilterClick(listElement) {
|
||||
if (isBogusFolder(existingTag)) {
|
||||
// Update bogus drilldown
|
||||
if ($(this).hasClass('selected')) {
|
||||
appendTagToList('.rm_tag_controls .rm_tag_bogus_drilldown', existingTag, { removable: true, selectable: false, isGeneralList: false });
|
||||
appendTagToList($('.rm_tag_controls .rm_tag_bogus_drilldown'), existingTag, { removable: true });
|
||||
} else {
|
||||
$(listElement).closest('.rm_tag_controls').find(`.rm_tag_bogus_drilldown .tag[id=${tagId}]`).remove();
|
||||
}
|
||||
@ -574,7 +646,6 @@ function toggleTagThreeState(element, { stateOverride = undefined, simulateClick
|
||||
return states[targetStateIndex];
|
||||
}
|
||||
|
||||
|
||||
function runTagFilters(listElement) {
|
||||
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')))];
|
||||
@ -583,35 +654,29 @@ function runTagFilters(listElement) {
|
||||
}
|
||||
|
||||
function printTagFilters(type = tag_filter_types.character) {
|
||||
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
|
||||
const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
|
||||
const selectedTagIds = [...($(FILTER_SELECTOR).find('.tag.selected').map((_, el) => $(el).attr('id')))];
|
||||
$(FILTER_SELECTOR).empty();
|
||||
$(FILTER_SELECTOR).siblings('.rm_tag_bogus_drilldown').empty();
|
||||
|
||||
// Print all action tags. (Exclude folder if that setting isn't chosen)
|
||||
const actionTags = Object.values(ACTIONABLE_TAGS).filter(tag => power_user.bogus_folders || tag.id != ACTIONABLE_TAGS.FOLDER.id);
|
||||
printTagList($(FILTER_SELECTOR), { empty: false, tags: actionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } });
|
||||
|
||||
const inListActionTags = Object.values(InListActionable);
|
||||
printTagList($(FILTER_SELECTOR), { empty: false, tags: inListActionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } });
|
||||
|
||||
const characterTagIds = Object.values(tag_map).flat();
|
||||
const tagsToDisplay = tags
|
||||
.filter(x => characterTagIds.includes(x.id))
|
||||
.sort(compareTagsForSort);
|
||||
printTagList($(FILTER_SELECTOR), { empty: false, tags: tagsToDisplay, tagOptions: { selectable: true, isGeneralList: true } });
|
||||
|
||||
for (const tag of Object.values(ACTIONABLE_TAGS)) {
|
||||
if (!power_user.bogus_folders && tag.id == ACTIONABLE_TAGS.FOLDER.id) {
|
||||
continue;
|
||||
}
|
||||
runTagFilters(FILTER_SELECTOR);
|
||||
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action, isGeneralList: true });
|
||||
}
|
||||
|
||||
for (const tag of Object.values(InListActionable)) {
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action, isGeneralList: true });
|
||||
}
|
||||
for (const tag of tagsToDisplay) {
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: true, isGeneralList: true });
|
||||
if (tag.excluded) {
|
||||
runTagFilters(FILTER_SELECTOR);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tagId of selectedTagIds) {
|
||||
$(`${FILTER_SELECTOR} .tag[id="${tagId}"]`).trigger('click');
|
||||
// Simulate clicks on all "selected" tags when we reprint, otherwise their filter gets lost. "excluded" is persisted.
|
||||
for (const tagId of filterData.selected) {
|
||||
toggleTagThreeState($(`${FILTER_SELECTOR} .tag[id="${tagId}"]`), { stateOverride: FILTER_STATES.SELECTED, simulateClick: true });
|
||||
}
|
||||
|
||||
if (power_user.show_tag_filters) {
|
||||
@ -679,36 +744,18 @@ function onCharacterCreateClick() {
|
||||
}
|
||||
|
||||
function onGroupCreateClick() {
|
||||
$('#groupTagList').empty();
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
// Nothing to do here at the moment. Tags in group interface get automatically redrawn.
|
||||
}
|
||||
|
||||
export function applyTagsOnCharacterSelect() {
|
||||
//clearTagsFilter();
|
||||
const chid = Number($(this).attr('chid'));
|
||||
const key = characters[chid].avatar;
|
||||
const tags = getTagsList(key);
|
||||
|
||||
$('#tagList').empty();
|
||||
|
||||
for (const tag of tags) {
|
||||
appendTagToList('#tagList', tag, { removable: true });
|
||||
}
|
||||
printTagList($('#tagList'), { forEntityOrKey: chid, tagOptions: { removable: true } });
|
||||
}
|
||||
|
||||
function applyTagsOnGroupSelect() {
|
||||
//clearTagsFilter();
|
||||
const key = $(this).attr('grid');
|
||||
const tags = getTagsList(key);
|
||||
|
||||
$('#groupTagList').empty();
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
|
||||
for (const tag of tags) {
|
||||
appendTagToList('#groupTagList', tag, { removable: true });
|
||||
}
|
||||
// Nothing to do here at the moment. Tags in group interface get automatically redrawn.
|
||||
}
|
||||
|
||||
export function createTagInput(inputSelector, listSelector) {
|
||||
|
@ -7,7 +7,7 @@ import { isMobile } from './RossAscends-mods.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
import { getTokenCount } from './tokenizers.js';
|
||||
import { power_user } from './power-user.js';
|
||||
import { getTagKeyForCharacter } from './tags.js';
|
||||
import { getTagKeyForEntity } from './tags.js';
|
||||
import { resolveVariable } from './variables.js';
|
||||
|
||||
export {
|
||||
@ -2068,7 +2068,7 @@ async function checkWorldInfo(chat, maxContext) {
|
||||
}
|
||||
|
||||
if (entry.characterFilter && entry.characterFilter?.tags?.length > 0) {
|
||||
const tagKey = getTagKeyForCharacter(this_chid);
|
||||
const tagKey = getTagKeyForEntity(this_chid);
|
||||
|
||||
if (tagKey) {
|
||||
const tagMapEntry = context.tagMap[tagKey];
|
||||
|
Loading…
x
Reference in New Issue
Block a user