mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Tag Folders: hidden/closed folders
- Implement folder types: Open, Closed, None - Closed folders hide characters from most places - "character(s)" singular wording on entity list - small refactoring for that code
This commit is contained in:
@ -153,8 +153,10 @@ import {
|
||||
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
|
||||
import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, processChatSlashCommands, registerSlashCommand } from './scripts/slash-commands.js';
|
||||
import {
|
||||
TAG_FOLDER_DEFAULT_TYPE,
|
||||
tag_map,
|
||||
tags,
|
||||
filterByTagState,
|
||||
loadTagsSettings,
|
||||
printTagFilters,
|
||||
getTagsList,
|
||||
@ -164,6 +166,7 @@ import {
|
||||
importTags,
|
||||
tag_filter_types,
|
||||
compareTagsForSort,
|
||||
TAG_FOLDER_TYPES,
|
||||
} from './scripts/tags.js';
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
@ -273,6 +276,7 @@ export {
|
||||
isOdd,
|
||||
countOccurrences,
|
||||
chooseBogusFolder,
|
||||
isBogusFolder,
|
||||
};
|
||||
|
||||
showLoader();
|
||||
@ -1158,25 +1162,24 @@ export async function selectCharacterById(id) {
|
||||
}
|
||||
|
||||
function getTagBlock(item, entities) {
|
||||
let count = 0;
|
||||
let subEntities = [];
|
||||
let count = entities.length;
|
||||
|
||||
for (const entity of entities) {
|
||||
if (entitiesFilter.isElementTagged(entity, item.id)) {
|
||||
count++;
|
||||
subEntities.push(entity);
|
||||
}
|
||||
}
|
||||
const tagFolder = TAG_FOLDER_TYPES[item.folder_type];
|
||||
|
||||
const template = $('#bogus_folder_template .bogus_folder_select').clone();
|
||||
template.addClass(tagFolder.class);
|
||||
template.attr({ 'tagid': item.id, 'id': `BogusFolder${item.id}` });
|
||||
template.find('.avatar').css({ 'background-color': item.color, 'color': item.color2 }).attr('title', `[Folder] ${item.name}`);
|
||||
template.find('.ch_name').text(item.name).attr('title', `[Folder] ${item.name}`);;
|
||||
template.find('.ch_name').text(item.name).attr('title', `[Folder] ${item.name}`);
|
||||
template.find('.bogus_folder_counter').text(count);
|
||||
template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
|
||||
if (count == 1) {
|
||||
template.find('.character_unit_name').text('character');
|
||||
}
|
||||
|
||||
// Fill inline character images
|
||||
const inlineAvatars = template.find('.bogus_folder_avatars_block');
|
||||
for (const entitiy of subEntities) {
|
||||
for (const entitiy of entities) {
|
||||
const id = entitiy.id;
|
||||
|
||||
// Populate the template
|
||||
@ -1314,7 +1317,7 @@ async function printCharacters(fullRefresh = false) {
|
||||
$(listId).append(getGroupBlock(i.item));
|
||||
break;
|
||||
case 'tag':
|
||||
$(listId).append(getTagBlock(i.item, entities));
|
||||
$(listId).append(getTagBlock(i.item, i.entities ?? entities));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1334,6 +1337,14 @@ async function printCharacters(fullRefresh = false) {
|
||||
favsToHotswap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a given tag is defined as a folder. Meaning it's neither undefined nor 'NONE'.
|
||||
* @returns {boolean} If it's a tag folder
|
||||
*/
|
||||
function isBogusFolder(tag) {
|
||||
return tag?.folder_type !== undefined && tag.folder_type !== TAG_FOLDER_DEFAULT_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a user is currently in a bogus folder.
|
||||
* @returns {boolean} If currently viewing a folder
|
||||
@ -1341,7 +1352,7 @@ async function printCharacters(fullRefresh = false) {
|
||||
function isBogusFolderOpen() {
|
||||
const anyIsFolder = entitiesFilter.getFilterData(FILTER_TYPES.TAG)?.selected
|
||||
.map(tagId => tags.find(x => x.id === tagId))
|
||||
.some(x => !!x.is_folder);
|
||||
.some(isBogusFolder);
|
||||
|
||||
return !!anyIsFolder;
|
||||
}
|
||||
@ -1356,33 +1367,33 @@ export function getEntitiesList({ doFilter } = {}) {
|
||||
}
|
||||
|
||||
function tagToEntity(tag) {
|
||||
return { item: structuredClone(tag), id: tag.id, type: 'tag' };
|
||||
return { item: structuredClone(tag), id: tag.id, type: 'tag', entities: [] };
|
||||
}
|
||||
|
||||
let entities = [
|
||||
...characters.map((item, index) => characterToEntity(item, index)),
|
||||
...groups.map(item => groupToEntity(item)),
|
||||
...(power_user.bogus_folders ? tags.filter(x => x.is_folder).sort(compareTagsForSort).map(item => tagToEntity(item)) : []),
|
||||
...(power_user.bogus_folders ? tags.filter(isBogusFolder).sort(compareTagsForSort).map(item => tagToEntity(item)) : []),
|
||||
];
|
||||
|
||||
// First run filters, that will hide what should never be displayed
|
||||
if (doFilter) {
|
||||
entities = entitiesFilter.applyFilters(entities);
|
||||
entities = filterByTagState(entities);
|
||||
}
|
||||
|
||||
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
|
||||
|
||||
entities = entities.filter(entity => {
|
||||
// Run over all entities between first and second filter to save some states
|
||||
for(const entity of entities) {
|
||||
// For folders, we remember the sub entities so they can be displayed later, even if they might be filtered
|
||||
if (entity.type === 'tag') {
|
||||
// Remove filtered tags/bogus folders
|
||||
if (filterData.selected.includes(entity.id) || filterData.excluded.includes(entity.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if tag is used in any other entities, removing 0 count folders
|
||||
return entities.some(e => e.type !== 'tag' && entitiesFilter.isElementTagged(e, entity.id));
|
||||
entity.entities = filterByTagState(entities, { subForEntity: entity });
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Second run filters, hiding whatever should be filtered later
|
||||
if (doFilter) {
|
||||
entities = filterByTagState(entities, { globalDisplayFilters: true });
|
||||
}
|
||||
|
||||
sortEntitiesList(entities);
|
||||
return entities;
|
||||
@ -8089,22 +8100,12 @@ function doTogglePanels() {
|
||||
}
|
||||
|
||||
function chooseBogusFolder(source, tagId, remove = false) {
|
||||
// Take the filter as the base on what bogus is currently selected
|
||||
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
|
||||
|
||||
if (!Array.isArray(filterData.selected)) {
|
||||
filterData.selected = [];
|
||||
filterData.excluded = [];
|
||||
}
|
||||
|
||||
const filteredFolders = filterData.selected
|
||||
.map(tagId => tags.find(x => x.id === tagId))
|
||||
.filter(x => !!x.is_folder);
|
||||
|
||||
// If we are here via the 'back' action, we implicitly take the last filtered folder as one to remove
|
||||
const isBack = tagId === 'back';
|
||||
if (isBack) {
|
||||
tagId = filteredFolders?.[filteredFolders.length - 1].id;
|
||||
const drilldown = $(source).closest('#rm_characters_block').find('.rm_tag_bogus_drilldown');
|
||||
const lastTag = drilldown.find('.tag:last').last();
|
||||
tagId = lastTag.attr('id');
|
||||
remove = true;
|
||||
}
|
||||
|
||||
@ -8293,7 +8294,7 @@ jQuery(async function () {
|
||||
|
||||
$(document).on('click', '.bogus_folder_select', function () {
|
||||
const tagId = $(this).attr('tagid');
|
||||
console.log('Bogus folder clicked', tagId);
|
||||
console.debug('Bogus folder clicked', tagId);
|
||||
chooseBogusFolder($(this), tagId);
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user