Tags: Add unused tags pruning to tags management
| @@ -393,6 +393,10 @@ function createTagMapFromList(listElement, key) { | |||
| 393 | * @returns {Tag[]} A list of tags | 393 | * @returns {Tag[]} A list of tags |
| 394 | */ | 394 | */ |
| 395 | function getTagsList(key, sort = true) { | 395 | function getTagsList(key, sort = true) { |
| 396 | if (key === null || key === undefined) { | ||
| 397 | return []; | ||
| 398 | } | ||
| 399 | |||
| 396 | if (!Array.isArray(tag_map[key])) { | 400 | if (!Array.isArray(tag_map[key])) { |
| 397 | tag_map[key] = []; | 401 | tag_map[key] = []; |
| 398 | return []; | 402 | return []; |
| @@ -1547,6 +1551,44 @@ function onTagsBackupClick() { | |||
| 1547 | download(blob, filename, 'application/json'); | 1551 | download(blob, filename, 'application/json'); |
| 1548 | } | 1552 | } |
| 1549 | 1553 | ||
| 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 | |||
| 1550 | function onTagCreateClick() { | 1592 | function onTagCreateClick() { |
| 1551 | const tagName = getFreeName('New Tag', tags.map(x => x.name)); | 1593 | const tagName = getFreeName('New Tag', tags.map(x => x.name)); |
| 1552 | const tag = createNewTag(tagName); | 1594 | const tag = createNewTag(tagName); |
| @@ -2198,6 +2240,7 @@ export function initTags() { | |||
| 2198 | $(document).on('click', '.tag_view_create', onTagCreateClick); | 2240 | $(document).on('click', '.tag_view_create', onTagCreateClick); |
| 2199 | $(document).on('click', '.tag_view_backup', onTagsBackupClick); | 2241 | $(document).on('click', '.tag_view_backup', onTagsBackupClick); |
| 2200 | $(document).on('click', '.tag_view_restore', onBackupRestoreClick); | 2242 | $(document).on('click', '.tag_view_restore', onBackupRestoreClick); |
| 2243 | $(document).on('click', '.tag_view_prune', onTagsPruneClick); | ||
| 2201 | eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags); | 2244 | eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags); |
| 2202 | eventSource.makeFirst(event_types.CHAT_CHANGED, () => selected_group ? applyTagsOnGroupSelect() : applyTagsOnCharacterSelect()); | 2245 | eventSource.makeFirst(event_types.CHAT_CHANGED, () => selected_group ? applyTagsOnGroupSelect() : applyTagsOnCharacterSelect()); |
| 2203 | 2246 | ||
| @@ -1,6 +1,10 @@ | |||
| 1 | <div class="title_restorable alignItemsBaseline"> | 1 | <div class="title_restorable alignItemsBaseline"> |
| 2 | <h3 data-i18n="Tag Management">Tag Management</h3> | 2 | <h3 data-i18n="Tag Management">Tag Management</h3> |
| 3 | <div class="flex-container alignItemsBaseline"> | 3 | <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> | ||
| 4 | <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"> | 8 | <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"> |
| 5 | <i class="fa-solid fa-file-export"></i> | 9 | <i class="fa-solid fa-file-export"></i> |
| 6 | <span data-i18n="Backup">Backup</span> | 10 | <span data-i18n="Backup">Backup</span> |