mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #2152 from Wolfsblvt/auto-sort-tags-option
Option to auto-sort tags (+UI improvements)
This commit is contained in:
@ -152,6 +152,7 @@ import {
|
|||||||
Stopwatch,
|
Stopwatch,
|
||||||
isValidUrl,
|
isValidUrl,
|
||||||
ensureImageFormatSupported,
|
ensureImageFormatSupported,
|
||||||
|
flashHighlight,
|
||||||
} from './scripts/utils.js';
|
} from './scripts/utils.js';
|
||||||
|
|
||||||
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
import { ModuleWorkerWrapper, doDailyExtensionUpdatesCheck, extension_settings, getContext, loadExtensionSettings, renderExtensionTemplate, renderExtensionTemplateAsync, runGenerationInterceptors, saveMetadataDebounced, writeExtensionField } from './scripts/extensions.js';
|
||||||
@ -6799,10 +6800,7 @@ function select_rm_info(type, charId, previousCharId = null) {
|
|||||||
|
|
||||||
const scrollOffset = element.offset().top - element.parent().offset().top;
|
const scrollOffset = element.offset().top - element.parent().offset().top;
|
||||||
element.parent().scrollTop(scrollOffset);
|
element.parent().scrollTop(scrollOffset);
|
||||||
element.addClass('flash animated');
|
flashHighlight(element, 5000);
|
||||||
setTimeout(function () {
|
|
||||||
element.removeClass('flash animated');
|
|
||||||
}, 5000);
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -6828,10 +6826,7 @@ function select_rm_info(type, charId, previousCharId = null) {
|
|||||||
const element = $(selector);
|
const element = $(selector);
|
||||||
const scrollOffset = element.offset().top - element.parent().offset().top;
|
const scrollOffset = element.offset().top - element.parent().offset().top;
|
||||||
element.parent().scrollTop(scrollOffset);
|
element.parent().scrollTop(scrollOffset);
|
||||||
$(element).addClass('flash animated');
|
flashHighlight(element, 5000);
|
||||||
setTimeout(function () {
|
|
||||||
$(element).removeClass('flash animated');
|
|
||||||
}, 5000);
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -7100,57 +7095,49 @@ function onScenarioOverrideRemoveClick() {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function callPopup(text, type, inputValue = '', { okButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {
|
function callPopup(text, type, inputValue = '', { okButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling, cropAspect } = {}) {
|
||||||
|
function getOkButtonText() {
|
||||||
|
if (['avatarToCrop'].includes(popup_type)) {
|
||||||
|
return okButton ?? 'Accept';
|
||||||
|
} else if (['text', 'alternate_greeting', 'char_not_selected'].includes(popup_type)) {
|
||||||
|
$dialoguePopupCancel.css('display', 'none');
|
||||||
|
return okButton ?? 'Ok';
|
||||||
|
} else if (['delete_extension'].includes(popup_type)) {
|
||||||
|
return okButton ?? 'Ok';
|
||||||
|
} else if (['new_chat', 'confirm'].includes(popup_type)) {
|
||||||
|
return okButton ?? 'Yes';
|
||||||
|
} else if (['input'].includes(popup_type)) {
|
||||||
|
return okButton ?? 'Save';
|
||||||
|
}
|
||||||
|
return okButton ?? 'Delete';
|
||||||
|
}
|
||||||
|
|
||||||
dialogueCloseStop = true;
|
dialogueCloseStop = true;
|
||||||
if (type) {
|
if (type) {
|
||||||
popup_type = type;
|
popup_type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#dialogue_popup').toggleClass('wide_dialogue_popup', !!wide);
|
const $dialoguePopup = $('#dialogue_popup');
|
||||||
$('#dialogue_popup').toggleClass('large_dialogue_popup', !!large);
|
const $dialoguePopupCancel = $('#dialogue_popup_cancel');
|
||||||
$('#dialogue_popup').toggleClass('horizontal_scrolling_dialogue_popup', !!allowHorizontalScrolling);
|
const $dialoguePopupOk = $('#dialogue_popup_ok');
|
||||||
$('#dialogue_popup').toggleClass('vertical_scrolling_dialogue_popup', !!allowVerticalScrolling);
|
const $dialoguePopupInput = $('#dialogue_popup_input');
|
||||||
|
const $dialoguePopupText = $('#dialogue_popup_text');
|
||||||
|
const $shadowPopup = $('#shadow_popup');
|
||||||
|
|
||||||
$('#dialogue_popup_cancel').css('display', 'inline-block');
|
$dialoguePopup.toggleClass('wide_dialogue_popup', !!wide)
|
||||||
switch (popup_type) {
|
.toggleClass('large_dialogue_popup', !!large)
|
||||||
case 'avatarToCrop':
|
.toggleClass('horizontal_scrolling_dialogue_popup', !!allowHorizontalScrolling)
|
||||||
$('#dialogue_popup_ok').text(okButton ?? 'Accept');
|
.toggleClass('vertical_scrolling_dialogue_popup', !!allowVerticalScrolling);
|
||||||
break;
|
|
||||||
case 'text':
|
|
||||||
case 'alternate_greeting':
|
|
||||||
case 'char_not_selected':
|
|
||||||
$('#dialogue_popup_ok').text(okButton ?? 'Ok');
|
|
||||||
$('#dialogue_popup_cancel').css('display', 'none');
|
|
||||||
break;
|
|
||||||
case 'delete_extension':
|
|
||||||
$('#dialogue_popup_ok').text(okButton ?? 'Ok');
|
|
||||||
break;
|
|
||||||
case 'new_chat':
|
|
||||||
case 'confirm':
|
|
||||||
$('#dialogue_popup_ok').text(okButton ?? 'Yes');
|
|
||||||
break;
|
|
||||||
case 'del_group':
|
|
||||||
case 'rename_chat':
|
|
||||||
case 'del_chat':
|
|
||||||
default:
|
|
||||||
$('#dialogue_popup_ok').text(okButton ?? 'Delete');
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#dialogue_popup_input').val(inputValue);
|
$dialoguePopupCancel.css('display', 'inline-block');
|
||||||
$('#dialogue_popup_input').attr('rows', rows ?? 1);
|
$dialoguePopupOk.text(getOkButtonText());
|
||||||
|
$dialoguePopupInput.toggle(popup_type === 'input').val(inputValue).attr('rows', rows ?? 1);
|
||||||
|
$dialoguePopupText.empty().append(text);
|
||||||
|
$shadowPopup.css('display', 'block');
|
||||||
|
|
||||||
if (popup_type == 'input') {
|
if (popup_type == 'input') {
|
||||||
$('#dialogue_popup_input').css('display', 'block');
|
$dialoguePopupInput.trigger('focus');
|
||||||
$('#dialogue_popup_ok').text(okButton ?? 'Save');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$('#dialogue_popup_input').css('display', 'none');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#dialogue_popup_text').empty().append(text);
|
|
||||||
$('#shadow_popup').css('display', 'block');
|
|
||||||
if (popup_type == 'input') {
|
|
||||||
$('#dialogue_popup_input').focus();
|
|
||||||
}
|
|
||||||
if (popup_type == 'avatarToCrop') {
|
if (popup_type == 'avatarToCrop') {
|
||||||
// unset existing data
|
// unset existing data
|
||||||
crop_data = undefined;
|
crop_data = undefined;
|
||||||
@ -7166,7 +7153,8 @@ function callPopup(text, type, inputValue = '', { okButton, rows, wide, large, a
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$('#shadow_popup').transition({
|
|
||||||
|
$shadowPopup.transition({
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
duration: animation_duration,
|
duration: animation_duration,
|
||||||
easing: animation_easing,
|
easing: animation_easing,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
|
import { callPopup, chat_metadata, eventSource, event_types, generateQuietPrompt, getCurrentChatId, getRequestHeaders, getThumbnailUrl, saveSettingsDebounced } from '../script.js';
|
||||||
import { saveMetadataDebounced } from './extensions.js';
|
import { saveMetadataDebounced } from './extensions.js';
|
||||||
import { registerSlashCommand } from './slash-commands.js';
|
import { registerSlashCommand } from './slash-commands.js';
|
||||||
import { stringFormat } from './utils.js';
|
import { flashHighlight, stringFormat } from './utils.js';
|
||||||
|
|
||||||
const BG_METADATA_KEY = 'custom_background';
|
const BG_METADATA_KEY = 'custom_background';
|
||||||
const LIST_METADATA_KEY = 'chat_backgrounds';
|
const LIST_METADATA_KEY = 'chat_backgrounds';
|
||||||
@ -453,8 +453,7 @@ function highlightNewBackground(bg) {
|
|||||||
const newBg = $(`.bg_example[bgfile="${bg}"]`);
|
const newBg = $(`.bg_example[bgfile="${bg}"]`);
|
||||||
const scrollOffset = newBg.offset().top - newBg.parent().offset().top;
|
const scrollOffset = newBg.offset().top - newBg.parent().offset().top;
|
||||||
$('#Backgrounds').scrollTop(scrollOffset);
|
$('#Backgrounds').scrollTop(scrollOffset);
|
||||||
newBg.addClass('flash animated');
|
flashHighlight(newBg);
|
||||||
setTimeout(() => newBg.removeClass('flash animated'), 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBackgroundFilterInput() {
|
function onBackgroundFilterInput() {
|
||||||
|
@ -15,7 +15,7 @@ import {
|
|||||||
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
|
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
|
||||||
|
|
||||||
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
|
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
|
||||||
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay } from './utils.js';
|
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight } from './utils.js';
|
||||||
import { power_user } from './power-user.js';
|
import { power_user } from './power-user.js';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -350,18 +350,20 @@ function createTagMapFromList(listElement, key) {
|
|||||||
* If you have an entity, you can get it's key easily via `getTagKeyForEntity(entity)`.
|
* If you have an entity, you can get it's key easily via `getTagKeyForEntity(entity)`.
|
||||||
*
|
*
|
||||||
* @param {string} key - The key for which to get tags via the tag map
|
* @param {string} key - The key for which to get tags via the tag map
|
||||||
|
* @param {boolean} [sort=true] -
|
||||||
* @returns {Tag[]} A list of tags
|
* @returns {Tag[]} A list of tags
|
||||||
*/
|
*/
|
||||||
function getTagsList(key) {
|
function getTagsList(key, sort = true) {
|
||||||
if (!Array.isArray(tag_map[key])) {
|
if (!Array.isArray(tag_map[key])) {
|
||||||
tag_map[key] = [];
|
tag_map[key] = [];
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return tag_map[key]
|
const list = tag_map[key]
|
||||||
.map(x => tags.find(y => y.id === x))
|
.map(x => tags.find(y => y.id === x))
|
||||||
.filter(x => x)
|
.filter(x => x);
|
||||||
.sort(compareTagsForSort);
|
if (sort) list.sort(compareTagsForSort);
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInlineListSelector() {
|
function getInlineListSelector() {
|
||||||
@ -644,6 +646,7 @@ function createNewTag(tagName) {
|
|||||||
* @property {Tag} [addTag=undefined] - Optionally provide a tag that should be manually added to this print. Either to the overriden tag list or the found tags based on the entity/key. Will respect the tag exists check.
|
* @property {Tag} [addTag=undefined] - Optionally provide a tag that should be manually added to this print. Either to the overriden tag list or the found tags based on the entity/key. Will respect the tag exists check.
|
||||||
* @property {object|number|string} [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.
|
* @property {object|number|string} [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.
|
||||||
* @property {boolean|string} [empty=true] - Whether the list should be initially empty. If a string string is provided, 'always' will always empty the list, otherwise it'll evaluate to a boolean.
|
* @property {boolean|string} [empty=true] - Whether the list should be initially empty. If a string string is provided, 'always' will always empty the list, otherwise it'll evaluate to a boolean.
|
||||||
|
* @property {boolean} [sort=true] - Whether the tags should be sorted via the sort function, or kept as is.
|
||||||
* @property {function(object): function} [tagActionSelector=undefined] - An optional override for the action property that can be assigned to each tag via tagOptions.
|
* @property {function(object): function} [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.
|
* 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.
|
||||||
* @property {TagOptions} [tagOptions={}] - Options for tag behavior. (Same object will be passed into "appendTagToList")
|
* @property {TagOptions} [tagOptions={}] - Options for tag behavior. (Same object will be passed into "appendTagToList")
|
||||||
@ -655,10 +658,10 @@ function createNewTag(tagName) {
|
|||||||
* @param {JQuery<HTMLElement>|string} element - The container element where the tags are to be printed. (Optionally can also be a string selector for the element, which will then be resolved)
|
* @param {JQuery<HTMLElement>|string} element - The container element where the tags are to be printed. (Optionally can also be a string selector for the element, which will then be resolved)
|
||||||
* @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list.
|
* @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list.
|
||||||
*/
|
*/
|
||||||
function printTagList(element, { tags = undefined, addTag = undefined, forEntityOrKey = undefined, empty = true, tagActionSelector = undefined, tagOptions = {} } = {}) {
|
function printTagList(element, { tags = undefined, addTag = undefined, forEntityOrKey = undefined, empty = true, sort = true, tagActionSelector = undefined, tagOptions = {} } = {}) {
|
||||||
const $element = (typeof element === 'string') ? $(element) : element;
|
const $element = (typeof element === 'string') ? $(element) : element;
|
||||||
const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey();
|
const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey();
|
||||||
let printableTags = tags ? (typeof tags === 'function' ? tags() : tags) : getTagsList(key);
|
let printableTags = tags ? (typeof tags === 'function' ? tags() : tags) : getTagsList(key, sort);
|
||||||
|
|
||||||
if (empty === 'always' || (empty && (printableTags?.length > 0 || key))) {
|
if (empty === 'always' || (empty && (printableTags?.length > 0 || key))) {
|
||||||
$element.empty();
|
$element.empty();
|
||||||
@ -669,7 +672,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
|
|||||||
}
|
}
|
||||||
|
|
||||||
// one last sort, because we might have modified the tag list or manually retrieved it from a function
|
// one last sort, because we might have modified the tag list or manually retrieved it from a function
|
||||||
printableTags = printableTags.sort(compareTagsForSort);
|
if (sort) printableTags = printableTags.sort(compareTagsForSort);
|
||||||
|
|
||||||
const customAction = typeof tagActionSelector === 'function' ? tagActionSelector : null;
|
const customAction = typeof tagActionSelector === 'function' ? tagActionSelector : null;
|
||||||
|
|
||||||
@ -872,10 +875,10 @@ function printTagFilters(type = tag_filter_types.character) {
|
|||||||
|
|
||||||
// Print all action tags. (Exclude folder if that setting isn't chosen)
|
// 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);
|
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 } });
|
printTagList($(FILTER_SELECTOR), { empty: false, sort: false, tags: actionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } });
|
||||||
|
|
||||||
const inListActionTags = Object.values(InListActionable);
|
const inListActionTags = Object.values(InListActionable);
|
||||||
printTagList($(FILTER_SELECTOR), { empty: false, tags: inListActionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } });
|
printTagList($(FILTER_SELECTOR), { empty: false, sort: false, tags: inListActionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } });
|
||||||
|
|
||||||
const characterTagIds = Object.values(tag_map).flat();
|
const characterTagIds = Object.values(tag_map).flat();
|
||||||
const tagsToDisplay = tags.filter(x => characterTagIds.includes(x.id)).sort(compareTagsForSort);
|
const tagsToDisplay = tags.filter(x => characterTagIds.includes(x.id)).sort(compareTagsForSort);
|
||||||
@ -992,11 +995,11 @@ export function createTagInput(inputSelector, listSelector, tagListOptions = {})
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onViewTagsListClick() {
|
function onViewTagsListClick() {
|
||||||
$('#dialogue_popup').addClass('large_dialogue_popup');
|
const popup = $('#dialogue_popup');
|
||||||
const list = $(document.createElement('div'));
|
popup.addClass('large_dialogue_popup');
|
||||||
list.attr('id', 'tag_view_list');
|
const html = $(document.createElement('div'));
|
||||||
const everything = Object.values(tag_map).flat();
|
html.attr('id', 'tag_view_list');
|
||||||
$(list).append(`
|
html.append(`
|
||||||
<div class="title_restorable alignItemsBaseline">
|
<div class="title_restorable alignItemsBaseline">
|
||||||
<h3>Tag Management</h3>
|
<h3>Tag Management</h3>
|
||||||
<div class="flex-container alignItemsBaseline">
|
<div class="flex-container alignItemsBaseline">
|
||||||
@ -1017,25 +1020,57 @@ function onViewTagsListClick() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="justifyLeft m-b-1">
|
<div class="justifyLeft m-b-1">
|
||||||
<small>
|
<small>
|
||||||
Drag the handle to reorder.<br>
|
Drag handle to reorder. Click name to rename. Click color to change display.<br>
|
||||||
${(power_user.bogus_folders ? 'Click on the folder icon to use this tag as a folder.<br>' : '')}
|
${(power_user.bogus_folders ? 'Click on the folder icon to use this tag as a folder.<br>' : '')}
|
||||||
Click on the tag name to edit it.<br>
|
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-1" for="auto_sort_tags">
|
||||||
Click on color box to assign new color.
|
<input type="checkbox" id="auto_sort_tags" name="auto_sort_tags" ${power_user.auto_sort_tags ? ' checked' : ''} />
|
||||||
|
<span data-i18n="Use alphabetical sorting">
|
||||||
|
Use alphabetical sorting
|
||||||
|
<div class="fa-solid fa-circle-info opacity50p" data-i18n="[title]If enabled, tags will automatically be sorted alphabetically on creation or rename.\nIf disabled, new tags will be appended at the end.\n\nIf a tag is manually reordered by dragging, automatic sorting will be disabled."
|
||||||
|
title="If enabled, tags will automatically be sorted alphabetically on creation or rename.\nIf disabled, new tags will be appended at the end.\n\nIf a tag is manually reordered by dragging, automatic sorting will be disabled.">
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
</small>
|
</small>
|
||||||
</div>`);
|
</div>`);
|
||||||
|
|
||||||
const tagContainer = $('<div class="tag_view_list_tags ui-sortable"></div>');
|
const tagContainer = $('<div class="tag_view_list_tags ui-sortable"></div>');
|
||||||
list.append(tagContainer);
|
html.append(tagContainer);
|
||||||
|
|
||||||
const sortedTags = sortTags(tags);
|
callPopup(html, 'text', null, { allowVerticalScrolling: true });
|
||||||
|
|
||||||
for (const tag of sortedTags) {
|
|
||||||
appendViewTagToList(tagContainer, tag, everything);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
printViewTagList();
|
||||||
makeTagListDraggable(tagContainer);
|
makeTagListDraggable(tagContainer);
|
||||||
|
|
||||||
callPopup(list, 'text');
|
$('#dialogue_popup .tag-color').on('change', (evt) => onTagColorize(evt));
|
||||||
|
$('#dialogue_popup .tag-color2').on('change', (evt) => onTagColorize2(evt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print the list of tags in the tag management view
|
||||||
|
* @param {Event} event Event that triggered the color change
|
||||||
|
* @param {boolean} toggle State of the toggle
|
||||||
|
*/
|
||||||
|
function toggleAutoSortTags(event, toggle) {
|
||||||
|
if (toggle === power_user.auto_sort_tags) return;
|
||||||
|
|
||||||
|
// Ask user to confirm if enabling and it was manually sorted before
|
||||||
|
if (toggle && isManuallySorted() && !confirm('Are you sure you want to automatically sort alphabetically?')) {
|
||||||
|
if (event.target instanceof HTMLInputElement) {
|
||||||
|
event.target.checked = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
power_user.auto_sort_tags = toggle;
|
||||||
|
|
||||||
|
printCharactersDebounced();
|
||||||
|
saveSettingsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This function goes over all existing tags and checks whether they were reorderd in the past. @returns {boolean} */
|
||||||
|
function isManuallySorted() {
|
||||||
|
return tags.some((tag, index) => tag.sort_order !== index);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTagListDraggable(tagContainer) {
|
function makeTagListDraggable(tagContainer) {
|
||||||
@ -1067,6 +1102,13 @@ function makeTagListDraggable(tagContainer) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If tags were dragged manually, we have to disable auto sorting
|
||||||
|
if (power_user.auto_sort_tags) {
|
||||||
|
power_user.auto_sort_tags = false;
|
||||||
|
$('#dialogue_popup input[name="auto_sort_tags"]').prop('checked', false);
|
||||||
|
toastr.info('Automatic sorting of tags deactivated.');
|
||||||
|
}
|
||||||
|
|
||||||
// If the order of tags in display has changed, we need to redraw some UI elements. Do it debounced so it doesn't block and you can drag multiple tags.
|
// If the order of tags in display has changed, we need to redraw some UI elements. Do it debounced so it doesn't block and you can drag multiple tags.
|
||||||
printCharactersDebounced();
|
printCharactersDebounced();
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
@ -1098,6 +1140,11 @@ function sortTags(tags) {
|
|||||||
* @returns {number} The compare result
|
* @returns {number} The compare result
|
||||||
*/
|
*/
|
||||||
function compareTagsForSort(a, b) {
|
function compareTagsForSort(a, b) {
|
||||||
|
const defaultSort = a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||||
|
if (power_user.auto_sort_tags) {
|
||||||
|
return defaultSort;
|
||||||
|
}
|
||||||
|
|
||||||
if (a.sort_order !== undefined && b.sort_order !== undefined) {
|
if (a.sort_order !== undefined && b.sort_order !== undefined) {
|
||||||
return a.sort_order - b.sort_order;
|
return a.sort_order - b.sort_order;
|
||||||
} else if (a.sort_order !== undefined) {
|
} else if (a.sort_order !== undefined) {
|
||||||
@ -1105,7 +1152,7 @@ function compareTagsForSort(a, b) {
|
|||||||
} else if (b.sort_order !== undefined) {
|
} else if (b.sort_order !== undefined) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
return defaultSort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1208,7 +1255,10 @@ function onTagsBackupClick() {
|
|||||||
|
|
||||||
function onTagCreateClick() {
|
function onTagCreateClick() {
|
||||||
const tag = createNewTag('New Tag');
|
const tag = createNewTag('New Tag');
|
||||||
appendViewTagToList($('#tag_view_list .tag_view_list_tags'), tag, []);
|
printViewTagList();
|
||||||
|
|
||||||
|
const tagElement = ($('#dialogue_popup .tag_view_list_tags')).find(`.tag_view_item[id="${tag.id}"]`);
|
||||||
|
flashHighlight(tagElement);
|
||||||
|
|
||||||
printCharactersDebounced();
|
printCharactersDebounced();
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
@ -1248,18 +1298,6 @@ function appendViewTagToList(list, tag, everything) {
|
|||||||
|
|
||||||
list.append(template);
|
list.append(template);
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
document.querySelector(`.tag-color[id="${colorPickerId}"`).addEventListener('change', (evt) => {
|
|
||||||
onTagColorize(evt);
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
document.querySelector(`.tag-color2[id="${colorPicker2Id}"`).addEventListener('change', (evt) => {
|
|
||||||
onTagColorize2(evt);
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
updateDrawTagFolder(template, tag);
|
updateDrawTagFolder(template, tag);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -1394,6 +1432,17 @@ function copyTags(data) {
|
|||||||
tag_map[data.newAvatar] = Array.from(new Set([...prevTagMap, ...newTagMap]));
|
tag_map[data.newAvatar] = Array.from(new Set([...prevTagMap, ...newTagMap]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function printViewTagList(empty = true) {
|
||||||
|
const tagContainer = $('#dialogue_popup .tag_view_list_tags');
|
||||||
|
|
||||||
|
if (empty) tagContainer.empty();
|
||||||
|
const everything = Object.values(tag_map).flat();
|
||||||
|
const sortedTags = sortTags(tags);
|
||||||
|
for (const tag of sortedTags) {
|
||||||
|
appendViewTagToList(tagContainer, tag, everything);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function initTags() {
|
export function initTags() {
|
||||||
createTagInput('#tagInput', '#tagList', { tagOptions: { removable: true } });
|
createTagInput('#tagInput', '#tagList', { tagOptions: { removable: true } });
|
||||||
createTagInput('#groupTagInput', '#groupTagList', { tagOptions: { removable: true } });
|
createTagInput('#groupTagInput', '#groupTagList', { tagOptions: { removable: true } });
|
||||||
@ -1412,4 +1461,31 @@ export function initTags() {
|
|||||||
$(document).on('click', '.tag_view_backup', onTagsBackupClick);
|
$(document).on('click', '.tag_view_backup', onTagsBackupClick);
|
||||||
$(document).on('click', '.tag_view_restore', onBackupRestoreClick);
|
$(document).on('click', '.tag_view_restore', onBackupRestoreClick);
|
||||||
eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
|
eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
|
||||||
|
|
||||||
|
$(document).on('input', '#dialogue_popup input[name="auto_sort_tags"]', (evt) => {
|
||||||
|
const toggle = $(evt.target).is(':checked');
|
||||||
|
toggleAutoSortTags(evt.originalEvent, toggle);
|
||||||
|
printViewTagList();
|
||||||
|
});
|
||||||
|
$(document).on('focusout', `#dialogue_popup .tag_view_name`, (evt) => {
|
||||||
|
// Remember the order, so we can flash highlight if it changed after reprinting
|
||||||
|
const tagId = $(evt.target).parent('.tag_view_item').attr('id');
|
||||||
|
const oldOrder = $(`#dialogue_popup .tag_view_item`).map((_, el) => el.id).get();
|
||||||
|
|
||||||
|
printViewTagList();
|
||||||
|
|
||||||
|
const newOrder = $(`#dialogue_popup .tag_view_item`).map((_, el) => el.id).get();
|
||||||
|
const orderChanged = !oldOrder.every((id, index) => id === newOrder[index]);
|
||||||
|
if (orderChanged) {
|
||||||
|
flashHighlight($(`#dialogue_popup .tag_view_item[id="${tagId}"]`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize auto sort setting based on whether it was sorted before
|
||||||
|
if (power_user.auto_sort_tags === undefined || power_user.auto_sort_tags === null) {
|
||||||
|
power_user.auto_sort_tags = !isManuallySorted();
|
||||||
|
if (power_user.auto_sort_tags) {
|
||||||
|
printCharactersDebounced();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1419,3 +1419,13 @@ export function setValueByPath(obj, path, value) {
|
|||||||
|
|
||||||
currentObject[keyParts[keyParts.length - 1]] = value;
|
currentObject[keyParts[keyParts.length - 1]] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flashes the given HTML element via CSS flash animation for a defined period
|
||||||
|
* @param {JQuery<HTMLElement>} element - The element to flash
|
||||||
|
* @param {number} timespan - A numer in milliseconds how the flash should last
|
||||||
|
*/
|
||||||
|
export function flashHighlight(element, timespan = 2000) {
|
||||||
|
element.addClass('flash animated');
|
||||||
|
setTimeout(() => element.removeClass('flash animated'), timespan);
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
|
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
|
||||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath } from './utils.js';
|
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight } from './utils.js';
|
||||||
import { extension_settings, getContext } from './extensions.js';
|
import { extension_settings, getContext } from './extensions.js';
|
||||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
|
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
|
||||||
import { registerSlashCommand } from './slash-commands.js';
|
import { registerSlashCommand } from './slash-commands.js';
|
||||||
@ -854,8 +854,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none) {
|
|||||||
const parentOffset = element.parent().offset();
|
const parentOffset = element.parent().offset();
|
||||||
const scrollOffset = elementOffset.top - parentOffset.top;
|
const scrollOffset = elementOffset.top - parentOffset.top;
|
||||||
$('#WorldInfo').scrollTop(scrollOffset);
|
$('#WorldInfo').scrollTop(scrollOffset);
|
||||||
element.addClass('flash animated');
|
flashHighlight(element);
|
||||||
setTimeout(() => element.removeClass('flash animated'), 2000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user