Tags: Add unused tags pruning to tags management

cc169fffc28f1e124f3a7403decad8b9e36d0a6d

Cohee <18619528+Cohee1207@users.noreply.github.com>

2 files changed, +47 -0Ignore whitespace
public/scripts/tags.js+43 -0
@@ -393,6 +393,10 @@ function createTagMapFromList(listElement, key) {
393393 * @returns {Tag[]} A list of tags
394394 */
395395function getTagsList(key, sort = true) {
396+ if (key === null || key === undefined) {
397+ return [];
398+ }
399+
396400 if (!Array.isArray(tag_map[key])) {
397401 tag_map[key] = [];
398402 return [];
@@ -1547,6 +1551,44 @@ function onTagsBackupClick() {
15471551 download(blob, filename, 'application/json');
15481552}
15491553
1554+async function onTagsPruneClick() {
1555+ // Get tags which have zero tag map entries
1556+ const allTagsInTagMaps = new Set(Object.values(tag_map).flat());
1557+ const tagsToPrune = tags.filter(tag => !allTagsInTagMaps.has(tag.id));
1558+
1559+ // Get tag maps referring to deleted entities
1560+ const allEntityKeys = new Set([...characters.map(c => String(c.avatar)), ...groups.map(g => String(g.id))]);
1561+ const tagMapsToPrune = Object.keys(tag_map).filter(key => !allEntityKeys.has(key));
1562+
1563+ if (!tagsToPrune.length && !tagMapsToPrune.length) {
1564+ toastr.info(t`No unused tags or references found.`);
1565+ return;
1566+ }
1567+
1568+ const confirm = await Popup.show.confirm(t`Prune ${tagsToPrune.length} tags and ${tagMapsToPrune.length} references`, t`Are you sure you want to remove all unused tags and references to missing or deleted characters and groups?`);
1569+
1570+ if (!confirm) {
1571+ return;
1572+ }
1573+
1574+ for (const tag of tagsToPrune) {
1575+ tags.splice(tags.indexOf(tag), 1);
1576+ }
1577+
1578+ for (const key of tagMapsToPrune) {
1579+ delete tag_map[key];
1580+ }
1581+
1582+ printCharactersDebounced();
1583+ saveSettingsDebounced();
1584+
1585+ // Reprint the tag management popup, without having it to be opened again
1586+ const tagContainer = $('#tag_view_list .tag_view_list_tags');
1587+ printViewTagList(tagContainer);
1588+
1589+ toastr.success(t`Unused tags pruned successfully.`);
1590+}
1591+
15501592function onTagCreateClick() {
15511593 const tagName = getFreeName('New Tag', tags.map(x => x.name));
15521594 const tag = createNewTag(tagName);
@@ -2198,6 +2240,7 @@ export function initTags() {
21982240 $(document).on('click', '.tag_view_create', onTagCreateClick);
21992241 $(document).on('click', '.tag_view_backup', onTagsBackupClick);
22002242 $(document).on('click', '.tag_view_restore', onBackupRestoreClick);
2243+ $(document).on('click', '.tag_view_prune', onTagsPruneClick);
22012244 eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
22022245 eventSource.makeFirst(event_types.CHAT_CHANGED, () => selected_group ? applyTagsOnGroupSelect() : applyTagsOnCharacterSelect());
22032246
public/scripts/templates/tagManagement.html+4 -0
@@ -1,6 +1,10 @@
11<div class="title_restorable alignItemsBaseline">
22 <h3 data-i18n="Tag Management">Tag Management</h3>
33 <div class="flex-container alignItemsBaseline">
4+ <div class="menu_button menu_button_icon tag_view_prune" data-i18n="[title]Remove unused tags" title="Remove unused tags">
5+ <i class="fa-solid fa-scissors"></i>
6+ <span data-i18n="Prune">Prune</span>
7+ </div>
48 <div class="menu_button menu_button_icon tag_view_backup" data-i18n="[title]Save your tags to a file" title="Save your tags to a file">
59 <i class="fa-solid fa-file-export"></i>
610 <span data-i18n="Backup">Backup</span>