Merge pull request #2427 from SillyTavern/more-popups

More popups
This commit is contained in:
Cohee 2024-06-27 23:20:10 +03:00 committed by GitHub
commit 599c55938b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 232 additions and 155 deletions

View File

@ -92,6 +92,24 @@ dialog {
text-align: left;
}
.popup-crop-wrap {
margin: 10px auto;
max-height: 75vh;
max-height: 75svh;
max-width: 100%;
}
.popup-crop-wrap img {
max-width: 100%;
/* This rule is very important, please do not ignore this! */
}
.popup-inputs {
margin-top: 10px;
font-size: smaller;
opacity: 0.7;
}
.popup-input {
margin-top: 10px;
}

View File

@ -4883,6 +4883,7 @@
<img class="popup-crop-image" src="">
</div>
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
<div class="popup-inputs"></div>
<div class="popup-controls">
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>
<div class="popup-button-cancel menu_button result-control" data-result="0" data-i18n="Cancel">Cancel</div>

View File

@ -8551,6 +8551,39 @@ async function doImpersonate(args, prompt) {
return '';
}
export async function doNewChat({ deleteCurrentChat = false } = {}) {
//Make a new chat for selected character
if ((!selected_group && this_chid == undefined) || menu_type == 'create') {
return;
}
//Fix it; New chat doesn't create while open create character menu
await clearChat();
chat.length = 0;
chat_file_for_del = getCurrentChatDetails()?.sessionName;
// Make it easier to find in backups
if (deleteCurrentChat) {
await saveChatConditional();
}
if (selected_group) {
await createNewGroupChat(selected_group);
if (deleteCurrentChat) await deleteGroupChat(selected_group, chat_file_for_del);
}
else {
//RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
chat_metadata = {};
characters[this_chid].chat = `${name2} - ${humanizedDateTime()}`;
$('#selected_chat_pole').val(characters[this_chid].chat);
await getChat();
await createOrEditCharacter(new CustomEvent('newChat'));
if (deleteCurrentChat) await delChat(chat_file_for_del + '.jsonl');
}
}
async function doDeleteChat() {
await displayPastChats();
let currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross');
@ -8659,13 +8692,11 @@ function doCloseChat() {
* it proceeds to delete character from UI and saves settings.
* In case of error during the fetch request, it logs the error details.
*
* @param {string} popup_type - The type of popup currently active.
* @param {string} this_chid - The character ID to be deleted.
* @param {boolean} delete_chats - Whether to delete chats or not.
*/
export async function handleDeleteCharacter(popup_type, this_chid, delete_chats) {
if (popup_type !== 'del_ch' ||
!characters[this_chid]) {
export async function handleDeleteCharacter(this_chid, delete_chats) {
if (!characters[this_chid]) {
return;
}
@ -8711,6 +8742,8 @@ export async function deleteCharacter(characterKey, { deleteChats = true } = {})
await eventSource.emit(event_types.CHAT_DELETED, name);
}
}
eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] });
}
/**
@ -9212,46 +9245,9 @@ jQuery(async function () {
}, 2000);
}
}
if (popup_type == 'del_ch') {
const deleteChats = !!$('#del_char_checkbox').prop('checked');
eventSource.emit(event_types.CHARACTER_DELETED, { id: this_chid, character: characters[this_chid] });
await handleDeleteCharacter(popup_type, this_chid, deleteChats);
}
if (popup_type == 'alternate_greeting' && menu_type !== 'create') {
createOrEditCharacter();
}
//Make a new chat for selected character
if (
popup_type == 'new_chat' &&
(selected_group || this_chid !== undefined) &&
menu_type != 'create'
) {
//Fix it; New chat doesn't create while open create character menu
await clearChat();
chat.length = 0;
chat_file_for_del = getCurrentChatDetails()?.sessionName;
const isDelChatCheckbox = document.getElementById('del_chat_checkbox')?.checked;
// Make it easier to find in backups
if (isDelChatCheckbox) {
await saveChatConditional();
}
if (selected_group) {
await createNewGroupChat(selected_group);
if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del);
}
else {
//RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
chat_metadata = {};
characters[this_chid].chat = `${name2} - ${humanizedDateTime()}`;
$('#selected_chat_pole').val(characters[this_chid].chat);
await getChat();
await createOrEditCharacter(new CustomEvent('newChat'));
if (isDelChatCheckbox) await delChat(chat_file_for_del + '.jsonl');
}
}
if (dialogueResolve) {
if (popup_type == 'input') {
@ -9297,15 +9293,27 @@ jQuery(async function () {
$('#form_create').submit(createOrEditCharacter);
$('#delete_button').on('click', function () {
callPopup(`
<h3>Delete the character?</h3>
<b>THIS IS PERMANENT!<br><br>
$('#delete_button').on('click', async function () {
if (!this_chid) {
toastr.warning('No character selected.');
return;
}
let deleteChats = false;
const confirm = await Popup.show.confirm('Delete the character?', `
<b>THIS IS PERMANENT!<br><br>
<label for="del_char_checkbox" class="checkbox_label justifyCenter">
<input type="checkbox" id="del_char_checkbox" />
<small>Also delete the chat files</small>
</label><br></b>`, 'del_ch', '',
);
</label></b>`, {
onClose: () => deleteChats = !!$('#del_char_checkbox').prop('checked'),
});
if (!confirm) {
return;
}
await deleteCharacter(characters[this_chid].avatar, { deleteChats: deleteChats });
});
//////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS ///////////////
@ -9567,14 +9575,20 @@ jQuery(async function () {
else if (id == 'option_start_new_chat') {
if ((selected_group || this_chid !== undefined) && !is_send_press) {
callPopup(`
<h3>Start new chat?</h3><br>
let deleteCurrentChat = false;
const result = await Popup.show.confirm('Start new chat?', `
<label for="del_chat_checkbox" class="checkbox_label justifyCenter"
title="If necessary, you can later restore this chat file from the /backups folder">
<input type="checkbox" id="del_chat_checkbox" />
<small>Also delete the current chat file</small>
</label><br>
`, 'new_chat', '');
</label>`, {
onClose: () => deleteCurrentChat = !!$('#del_chat_checkbox').prop('checked'),
});
if (!result) {
return;
}
await doNewChat({ deleteCurrentChat: deleteCurrentChat });
}
}

View File

@ -926,8 +926,8 @@ export function initRossMods() {
return false;
}
$(document).on('keydown', function (event) {
processHotkeys(event.originalEvent);
$(document).on('keydown', async function (event) {
await processHotkeys(event.originalEvent);
});
const hotkeyTargets = {
@ -939,7 +939,7 @@ export function initRossMods() {
/**
* @param {KeyboardEvent} event
*/
function processHotkeys(event) {
async function processHotkeys(event) {
//Enter to send when send_textarea in focus
if (document.activeElement == hotkeyTargets['send_textarea']) {
const sendOnEnter = shouldSendOnEnter();
@ -1003,21 +1003,17 @@ export function initRossMods() {
if (skipConfirm) {
doRegenerate();
} else {
Popup.show.confirm('Regenerate Message', `
<span>Are you sure you want to regenerate the latest message?</span>
<label class="checkbox_label justifyCenter marginTop10" for="regenerateWithCtrlEnter">
<input type="checkbox" id="regenerateWithCtrlEnter">
Don't ask again
</label>`, {
onClose: (popup) => {
if (!popup.result) {
return;
}
const regenerateWithCtrlEnter = $('#regenerateWithCtrlEnter').prop('checked');
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
doRegenerate();
},
})
let regenerateWithCtrlEnter = false;
const result = await Popup.show.confirm('Regenerate Message', 'Are you sure you want to regenerate the latest message?', {
customInputs: [{ id: 'regenerateWithCtrlEnter', label: 'Don\'t ask again' }],
onClose: (popup) => regenerateWithCtrlEnter = popup.inputResults.get('regenerateWithCtrlEnter') ?? false,
});
if (!result) {
return;
}
SaveLocal(skipConfirmKey, regenerateWithCtrlEnter);
doRegenerate();
}
return;
} else {

View File

@ -24,6 +24,7 @@ import {
saveGroupBookmarkChat,
selected_group,
} from './group-chats.js';
import { Popup } from './popup.js';
import { createTagMapFromList } from './tags.js';
import {
@ -239,8 +240,7 @@ async function convertSoloToGroupChat() {
return;
}
const confirm = await callPopup('Are you sure you want to convert this chat to a group chat?', 'confirm');
const confirm = await Popup.show.confirm('Convert to group chat', 'Are you sure you want to convert this chat to a group chat?<br />This cannot be reverted.');
if (!confirm) {
return;
}

View File

@ -37,6 +37,7 @@ export const POPUP_RESULT = {
* @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup
* @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`.
* @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
* @property {CustomPopupInput[]?} [customInputs=null] - Custom inputs to add to the popup. The display below the content and the input box, one by one.
* @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
* @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
* @property {number?} [cropAspect=null] - Aspect ratio for the crop popup
@ -52,6 +53,14 @@ export const POPUP_RESULT = {
* @property {boolean?} [appendAtEnd] - Whether to append the button to the end of the popup - by default it will be prepended
*/
/**
* @typedef {object} CustomPopupInput
* @property {string} id - The id for the html element
* @property {string} label - The label text for the input
* @property {string?} [tooltip=null] - Optional tooltip icon displayed behind the label
* @property {boolean?} [defaultState=false] - The default state when opening the popup (false if not set)
*/
/**
* @typedef {object} ShowPopupHelper
* Local implementation of the helper functionality to show several popups.
@ -78,8 +87,8 @@ const showPopupHelper = {
/**
* Asynchronously displays a confirmation popup with the given header and text, returning the clicked result button value.
*
* @param {string} header - The header text for the popup.
* @param {string} text - The main text for the popup.
* @param {string?} header - The header text for the popup.
* @param {string?} text - The main text for the popup.
* @param {PopupOptions} [popupOptions={}] - Options for the popup.
* @return {Promise<POPUP_RESULT>} A Promise that resolves with the result of the user's interaction.
*/
@ -93,34 +102,37 @@ const showPopupHelper = {
};
export class Popup {
/** @type {POPUP_TYPE} */ type;
/** @readonly @type {POPUP_TYPE} */ type;
/** @type {string} */ id;
/** @readonly @type {string} */ id;
/** @type {HTMLDialogElement} */ dlg;
/** @type {HTMLElement} */ body;
/** @type {HTMLElement} */ content;
/** @type {HTMLTextAreaElement} */ input;
/** @type {HTMLElement} */ controls;
/** @type {HTMLElement} */ okButton;
/** @type {HTMLElement} */ cancelButton;
/** @type {HTMLElement} */ closeButton;
/** @type {HTMLElement} */ cropWrap;
/** @type {HTMLImageElement} */ cropImage;
/** @type {POPUP_RESULT|number?} */ defaultResult;
/** @type {CustomPopupButton[]|string[]?} */ customButtons;
/** @readonly @type {HTMLDialogElement} */ dlg;
/** @readonly @type {HTMLDivElement} */ body;
/** @readonly @type {HTMLDivElement} */ content;
/** @readonly @type {HTMLTextAreaElement} */ mainInput;
/** @readonly @type {HTMLDivElement} */ inputControls;
/** @readonly @type {HTMLDivElement} */ buttonControls;
/** @readonly @type {HTMLDivElement} */ okButton;
/** @readonly @type {HTMLDivElement} */ cancelButton;
/** @readonly @type {HTMLDivElement} */ closeButton;
/** @readonly @type {HTMLDivElement} */ cropWrap;
/** @readonly @type {HTMLImageElement} */ cropImage;
/** @readonly @type {POPUP_RESULT|number?} */ defaultResult;
/** @readonly @type {CustomPopupButton[]|string[]?} */ customButtons;
/** @readonly @type {CustomPopupInput[]} */ customInputs;
/** @type {(popup: Popup) => boolean?} */ onClosing;
/** @type {(popup: Popup) => void?} */ onClose;
/** @type {POPUP_RESULT|number} */ result;
/** @type {any} */ value;
/** @type {Map<string,boolean>?} */ inputResults;
/** @type {any} */ cropData;
/** @type {HTMLElement} */ lastFocus;
/** @type {Promise<any>} */ promise;
/** @type {(result: any) => any} */ resolver;
/** @type {Promise<any>} */ #promise;
/** @type {(result: any) => any} */ #resolver;
/**
* Constructs a new Popup object with the given text content, type, inputValue, and options
@ -130,7 +142,7 @@ export class Popup {
* @param {string} [inputValue=''] - The initial value of the input field
* @param {PopupOptions} [options={}] - Additional options for the popup
*/
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) {
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, customInputs = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) {
Popup.util.popups.push(this);
// Make this popup uniquely identifiable
@ -147,8 +159,9 @@ export class Popup {
this.dlg = template.content.cloneNode(true).querySelector('.popup');
this.body = this.dlg.querySelector('.popup-body');
this.content = this.dlg.querySelector('.popup-content');
this.input = this.dlg.querySelector('.popup-input');
this.controls = this.dlg.querySelector('.popup-controls');
this.mainInput = this.dlg.querySelector('.popup-input');
this.inputControls = this.dlg.querySelector('.popup-inputs');
this.buttonControls = this.dlg.querySelector('.popup-controls');
this.okButton = this.dlg.querySelector('.popup-button-ok');
this.cancelButton = this.dlg.querySelector('.popup-button-cancel');
this.closeButton = this.dlg.querySelector('.popup-button-close');
@ -165,7 +178,9 @@ export class Popup {
// If custom button captions are provided, we set them beforehand
this.okButton.textContent = typeof okButton === 'string' ? okButton : 'OK';
this.okButton.dataset.i18n = this.okButton.textContent;
this.cancelButton.textContent = typeof cancelButton === 'string' ? cancelButton : template.getAttribute('popup-button-cancel');
this.cancelButton.dataset.i18n = this.cancelButton.textContent;
this.defaultResult = defaultResult;
this.customButtons = customButtons;
@ -178,12 +193,13 @@ export class Popup {
buttonElement.classList.add(...(button.classes ?? []));
buttonElement.dataset.result = String(button.result ?? undefined);
buttonElement.textContent = button.text;
buttonElement.dataset.i18n = buttonElement.textContent;
buttonElement.tabIndex = 0;
if (button.appendAtEnd) {
this.controls.appendChild(buttonElement);
this.buttonControls.appendChild(buttonElement);
} else {
this.controls.insertBefore(buttonElement, this.okButton);
this.buttonControls.insertBefore(buttonElement, this.okButton);
}
if (typeof button.action === 'function') {
@ -191,13 +207,45 @@ export class Popup {
}
});
this.customInputs = customInputs;
this.customInputs?.forEach(input => {
if (!input.id || !(typeof input.id === 'string')) {
console.warn('Given custom input does not have a valid id set')
return;
}
const label = document.createElement('label');
label.classList.add('checkbox_label', 'justifyCenter');
label.setAttribute('for', input.id);
const inputElement = document.createElement('input');
inputElement.type = 'checkbox';
inputElement.id = input.id;
inputElement.checked = input.defaultState ?? false;
label.appendChild(inputElement);
const labelText = document.createElement('span');
labelText.innerText = input.label;
labelText.dataset.i18n = input.label;
label.appendChild(labelText);
if (input.tooltip) {
const tooltip = document.createElement('div');
tooltip.classList.add('fa-solid', 'fa-circle-info', 'opacity50p');
tooltip.title = input.tooltip;
tooltip.dataset.i18n = '[title]' + input.tooltip;
label.appendChild(tooltip);
}
this.inputControls.appendChild(label);
});
// Set the default button class
const defaultButton = this.controls.querySelector(`[data-result="${this.defaultResult}"]`);
const defaultButton = this.buttonControls.querySelector(`[data-result="${this.defaultResult}"]`);
if (defaultButton) defaultButton.classList.add('menu_button_default');
// Styling differences depending on the popup type
// General styling for all types first, that might be overriden for specific types below
this.input.style.display = 'none';
this.mainInput.style.display = 'none';
this.inputControls.style.display = customInputs ? 'block' : 'none';
this.closeButton.style.display = 'none';
this.cropWrap.style.display = 'none';
@ -212,12 +260,12 @@ export class Popup {
break;
}
case POPUP_TYPE.INPUT: {
this.input.style.display = 'block';
this.mainInput.style.display = 'block';
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-save');
break;
}
case POPUP_TYPE.DISPLAY: {
this.controls.style.display = 'none';
this.buttonControls.style.display = 'none';
this.closeButton.style.display = 'block';
break;
}
@ -243,8 +291,8 @@ export class Popup {
}
}
this.input.value = inputValue;
this.input.rows = rows ?? 1;
this.mainInput.value = inputValue;
this.mainInput.rows = rows ?? 1;
this.content.innerHTML = '';
if (content instanceof jQuery) {
@ -335,10 +383,10 @@ export class Popup {
this.dlg.removeAttribute('opening');
});
this.promise = new Promise((resolve) => {
this.resolver = resolve;
this.#promise = new Promise((resolve) => {
this.#resolver = resolve;
});
return this.promise;
return this.#promise;
}
setAutoFocus({ applyAutoFocus = false } = {}) {
@ -352,12 +400,12 @@ export class Popup {
if (!control) {
switch (this.type) {
case POPUP_TYPE.INPUT: {
control = this.input;
control = this.mainInput;
break;
}
default:
// Select default button
control = this.controls.querySelector(`[data-result="${this.defaultResult}"]`);
control = this.buttonControls.querySelector(`[data-result="${this.defaultResult}"]`);
break;
}
}
@ -389,7 +437,7 @@ export class Popup {
let value = result;
// Input type have special results, so the input can be accessed directly without the need to save the popup and access both result and value
if (this.type === POPUP_TYPE.INPUT) {
if (result >= POPUP_RESULT.AFFIRMATIVE) value = this.input.value;
if (result >= POPUP_RESULT.AFFIRMATIVE) value = this.mainInput.value;
else if (result === POPUP_RESULT.NEGATIVE) value = false;
else if (result === POPUP_RESULT.CANCELLED) value = null;
else value = false; // Might a custom negative value?
@ -402,6 +450,14 @@ export class Popup {
: null;
}
if (this.customInputs?.length) {
this.inputResults = new Map(this.customInputs.map(input => {
/** @type {HTMLInputElement} */
const inputControl = this.dlg.querySelector(`#${input.id}`);
return [inputControl.id, inputControl.checked];
}));
}
this.value = value;
this.result = result;
@ -410,15 +466,14 @@ export class Popup {
if (!shouldClose) return;
}
Popup.util.lastResult = { value, result };
this.hide();
Popup.util.lastResult = { value, result, inputResults: this.inputResults };
this.#hide();
}
/**
* Hides the popup, using the internal resolver to return the value to the original show promise
* @private
*/
hide() {
#hide() {
// We close the dialog, first running the animation
this.dlg.setAttribute('closing', '');
@ -451,9 +506,9 @@ export class Popup {
else popup.setAutoFocus();
}
}
});
this.resolver(this.value);
this.#resolver(this.value);
});
}
/**
@ -467,10 +522,10 @@ export class Popup {
* Contains the list of all currently open popups, and it'll remember the result of the last closed popup.
*/
static util = {
/** @type {Popup[]} Remember all popups */
/** @readonly @type {Popup[]} Remember all popups */
popups: [],
/** @type {{value: any, result: POPUP_RESULT|number?}?} Last popup result */
/** @type {{value: any, result: POPUP_RESULT|number?, inputResults: Map<string, boolean>?}?} Last popup result */
lastResult: null,
/** @returns {boolean} Checks if any modal popup dialog is open */
@ -491,9 +546,17 @@ export class Popup {
}
class PopupUtils {
/**
* Builds popup content with header and text below
*
* @param {string} header - The header to be added to the text
* @param {string} text - The main text content
*/
static BuildTextWithHeader(header, text) {
return `
<h3>${header}</h1>
if (!header) {
return text;
}
return `<h3>${header}</h3>
${text}`;
}
}

View File

@ -22,6 +22,7 @@ import {
setActiveGroup,
setActiveCharacter,
entitiesFilter,
doNewChat,
} from '../script.js';
import { isMobile, initMovingUI, favsToHotswap } from './RossAscends-mods.js';
import {
@ -39,11 +40,11 @@ import { tokenizers } from './tokenizers.js';
import { BIAS_CACHE } from './logit-bias.js';
import { renderTemplateAsync } from './templates.js';
import { countOccurrences, debounce, delay, download, getFileText, isOdd, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
import { countOccurrences, debounce, delay, download, getFileText, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js';
import { FILTER_TYPES } from './filters.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js';
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
@ -2530,14 +2531,6 @@ async function resetMovablePanels(type) {
});
}
async function doNewChat() {
$('#option_start_new_chat').trigger('click');
await delay(1);
$('#dialogue_popup_ok').trigger('click');
await delay(1);
return '';
}
/**
* Finds the ID of the tag with the given name.
* @param {string} name
@ -3926,7 +3919,20 @@ $(document).ready(() => {
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'newchat',
callback: doNewChat,
/** @type {(args: { delete: string?}, string) => Promise<''>} */
callback: async (args, _) => {
await doNewChat({ deleteCurrentChat: isTrueBoolean(args.delete) });
return '';
},
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'delete',
description: 'delete the current chat',
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'false',
enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
helpString: 'Start a new chat with the current character',
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({

View File

@ -801,8 +801,7 @@ async function showTagImportPopup(character, existingTags, newTags, folderTags)
if (folderTags.length === 0) popupContent.find('#folder_tags_block').hide();
function onCloseRemember(/** @type {Popup} */ popup) {
const rememberCheckbox = document.getElementById('import_remember_option');
if (rememberCheckbox instanceof HTMLInputElement && rememberCheckbox.checked) {
if (popup.result && popup.inputResults.get('import_remember_option')) {
const setting = buttonSettingsMap[popup.result];
if (!setting) return;
power_user.tag_import_setting = setting;
@ -812,7 +811,12 @@ async function showTagImportPopup(character, existingTags, newTags, folderTags)
}
}
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, { wider: true, okButton: 'Import', cancelButton: true, customButtons: Object.values(importButtons), onClose: onCloseRemember });
const result = await callGenericPopup(popupContent, POPUP_TYPE.TEXT, null, {
wider: true, okButton: 'Import', cancelButton: true,
customButtons: Object.values(importButtons),
customInputs: [{ id: 'import_remember_option', label: 'Remember my choice', tooltip: 'Remember the chosen import option\nIf anything besides \'Cancel\' is selected, this dialog will not show up anymore.\nTo change this, go to the settings and modify "Tag Import Option".\n\nIf the "Import" option is chosen, the global setting will stay on "Ask".' }],
onClose: onCloseRemember
});
if (!result) {
return [];
}

View File

@ -19,17 +19,4 @@
</small>
<div id="import_folder_tags_list" class="tags" style="margin-top: 5px;"></div>
</div>
<small>
<label class="checkbox flex-container alignitemscenter flexNoGap m-t-3" for="import_remember_option">
<input type="checkbox" id="import_remember_option" name="import_remember_option" />
<span>
<span data-i18n="Remember my choice">Remember my choice</span>
<div class="fa-solid fa-circle-info opacity50p"
data-i18n="[title]Remember the chosen import option If anything besides 'Cancel' is selected, this dialog will not show up anymore. To change this, go to the settings and modify &quot;Tag Import Option&quot;. If the &quot;Import&quot; option is chosen, the global setting will stay on &quot;Ask&quot;."
title="Remember the chosen import option&#010;If anything besides 'Cancel' is selected, this dialog will not show up anymore.&#010;To change this, go to the settings and modify &quot;Tag Import Option&quot;.&#010;&#010;If the &quot;Import&quot; option is chosen, the global setting will stay on &quot;Ask&quot;.">
</div>
</span>
</label>
</small>
</div>

View File

@ -4150,18 +4150,6 @@ h5 {
grid-template-columns: 340px auto;
}
.popup-crop-wrap {
margin: 10px auto;
max-height: 75vh;
max-height: 75svh;
max-width: 100%;
}
.popup-crop-wrap img {
max-width: 100%;
/* This rule is very important, please do not ignore this! */
}
body .ui-autocomplete {
max-height: 300px;
overflow-y: auto;