WI key input mode switch fancy/plaintext

- Implemented switch between fancy and plaintext input controls
- Fixed splitting keys into regexes index issue
- Fixed focus falsely adding text as key
This commit is contained in:
Wolfsblvt
2024-05-14 04:51:22 +02:00
parent 5426431adf
commit 00ce078630
4 changed files with 81 additions and 19 deletions

View File

@ -76,6 +76,7 @@
.world_entry_form_control { .world_entry_form_control {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
} }
.world_entry_thin_controls { .world_entry_thin_controls {
@ -223,3 +224,25 @@ span.select2-container .select2-results__option:has(> .result_block .regex_item)
margin-left: 10px; margin-left: 10px;
float: right; float: right;
} }
select.keyselect+span.select2-container .select2-selection--multiple {
padding-right: 30px;
}
.switch_input_type_icon {
cursor: pointer;
font-weight: bold;
height: calc(100% - var(--mainFontSize));
width: 25px;
margin-right: 5px;
position: absolute;
right: 0;
bottom: 0;
padding: 1px;
background-color: transparent;
border: none;
font-size: 1em;
color: var(--SmartThemeBodyColor);
}

View File

@ -5206,6 +5206,9 @@
<small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small> <small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small>
<select class="keyprimaryselect keyselect select2_multi_sameline" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select> <select class="keyprimaryselect keyselect select2_multi_sameline" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select>
<textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" maxlength="2000" style="display: none;"></textarea> <textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" maxlength="2000" style="display: none;"></textarea>
<button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
⌨️
</button>
</div> </div>
<div class="world_entry_form_control"> <div class="world_entry_form_control">
<small class="textAlignCenter" data-i18n="Logic">Logic</small> <small class="textAlignCenter" data-i18n="Logic">Logic</small>
@ -5225,6 +5228,9 @@
<small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small> <small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small>
<select class="keysecondaryselect keyselect select2_multi_sameline" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select> <select class="keysecondaryselect keyselect select2_multi_sameline" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select>
<textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" maxlength="2000" style="display: none;"></textarea> <textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" maxlength="2000" style="display: none;"></textarea>
<button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode">
⌨️
</button>
</div> </div>
</div> </div>
<div name="perEntryOverridesBlock" class="flex-container wide100p alignitemscenter"> <div name="perEntryOverridesBlock" class="flex-container wide100p alignitemscenter">

View File

@ -1520,7 +1520,7 @@ export function select2ModifyOptions(element, items, { select = false, changeEve
* Can be used on a single global array, querying data from the server or anything similar. * Can be used on a single global array, querying data from the server or anything similar.
* *
* @param {function():Select2Option[]} dataProvider - The provider/function to retrieve the data - can be as simple as "() => myData" for arrays * @param {function():Select2Option[]} dataProvider - The provider/function to retrieve the data - can be as simple as "() => myData" for arrays
* @return {{transport: function}} The ajax object with the transport function to use on the select2 ajax property * @return {{transport: (params, success, failure) => any}} The ajax object with the transport function to use on the select2 ajax property
*/ */
export function dynamicSelect2DataViaAjax(dataProvider) { export function dynamicSelect2DataViaAjax(dataProvider) {
function dynamicSelect2DataTransport(params, success, failure) { function dynamicSelect2DataTransport(params, success, failure) {
@ -1543,11 +1543,21 @@ export function dynamicSelect2DataViaAjax(dataProvider) {
return ajax; return ajax;
} }
/**
* Checks whether a given control is a select2 choice element - meaning one of the results being displayed in the select multi select box
* @param {JQuery<HTMLElement>|HTMLElement} element - The element to check
* @returns {boolean} Whether this is a choice element
*/
export function isSelect2ChoiceElement(element) {
const $element = $(element);
return ($element.hasClass('select2-selection__choice__display') || $element.parents('.select2-selection__choice__display').length > 0);
}
/** /**
* Subscribes a 'click' event handler to the choice elements of a select2 multi-select control * Subscribes a 'click' event handler to the choice elements of a select2 multi-select control
* *
* @param {JQuery<HTMLElement>} control The original control the select2 was applied to * @param {JQuery<HTMLElement>} control The original control the select2 was applied to
* @param {function(EventTarget):void} action - The action to execute when a choice element is clicked * @param {function(HTMLElement):void} action - The action to execute when a choice element is clicked
* @param {object} options - Optional parameters * @param {object} options - Optional parameters
* @param {boolean} [options.buttonStyle=false] - Whether the choices should be styles as a clickable button with color and hover transition, instead of just changed cursor * @param {boolean} [options.buttonStyle=false] - Whether the choices should be styles as a clickable button with color and hover transition, instead of just changed cursor
* @param {boolean} [options.closeDrawer=false] - Whether the drawer should be closed and focus removed after the choice item was clicked * @param {boolean} [options.closeDrawer=false] - Whether the drawer should be closed and focus removed after the choice item was clicked
@ -1561,8 +1571,8 @@ export function select2ChoiceClickSubscribe(control, action, { buttonStyle = fal
// Get the real container below and create a click handler on that one // Get the real container below and create a click handler on that one
const select2Container = control.next('span.select2-container'); const select2Container = control.next('span.select2-container');
select2Container.on('click', function (event) { select2Container.on('click', function (event) {
const $target = $(event.target); const isChoice = isSelect2ChoiceElement(event.target);
if ($target.hasClass('select2-selection__choice__display') || $target.parents('.select2-selection__choice__display')) { if (isChoice) {
event.preventDefault(); event.preventDefault();
// select2 still bubbles the event to open the dropdown. So we close it here and remove focus if we want that // select2 still bubbles the event to open the dropdown. So we close it here and remove focus if we want that

View File

@ -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, flashHighlight, select2ModifyOptions, getStringHash, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe } from './utils.js'; import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe } 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 { isMobile } from './RossAscends-mods.js'; import { isMobile } from './RossAscends-mods.js';
@ -69,7 +69,7 @@ const saveSettingsDebounced = debounce(() => {
saveSettings(); saveSettings();
}, debounce_timeout.relaxed); }, debounce_timeout.relaxed);
const sortFn = (a, b) => b.order - a.order; const sortFn = (a, b) => b.order - a.order;
let updateEditor = (navigation) => { console.debug('Triggered WI navigation', navigation); }; let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data. // Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
const worldInfoFilter = new FilterHelper(() => updateEditor()); const worldInfoFilter = new FilterHelper(() => updateEditor());
@ -1022,8 +1022,8 @@ function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = f
worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text)); worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
} }
function displayWorldEntries(name, data, navigation = navigation_option.none) { function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
updateEditor = (navigation) => displayWorldEntries(name, data, navigation); updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
const worldEntriesList = $('#world_popup_entries_list'); const worldEntriesList = $('#world_popup_entries_list');
@ -1156,7 +1156,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);
flashHighlight(element); if (flashOnNav) flashHighlight(element);
}); });
} }
@ -1364,7 +1364,10 @@ function splitKeywordsAndRegexes(input) {
} }
const { term } = customTokenizer({ _type: 'custom_call', term: input }, undefined, addFindCallback); const { term } = customTokenizer({ _type: 'custom_call', term: input }, undefined, addFindCallback);
addFindCallback({ id: getSelect2OptionId(term.trim()), text: term.trim() }); const finalTerm = term.trim();
if (finalTerm) {
addFindCallback({ id: getSelect2OptionId(finalTerm), text: finalTerm });
}
return keywordsAndRegexes; return keywordsAndRegexes;
} }
@ -1407,7 +1410,7 @@ function customTokenizer(input, _selection, callback) {
// Last chance to check for valid regex again. Because it might have been valid while typing, but now is not valid anymore and contains commas we need to split. // Last chance to check for valid regex again. Because it might have been valid while typing, but now is not valid anymore and contains commas we need to split.
if (token.startsWith('/') && !isRegex) { if (token.startsWith('/') && !isRegex) {
const tokens = token.split(',').map(x => x.trim()); const tokens = token.split(',').map(x => x.trim());
tokens.forEach(x => callback({ id: x, text: x })); tokens.forEach(x => callback({ id: getSelect2OptionId(x), text: x }));
} else { } else {
callback({ id: getSelect2OptionId(token), text: token }); callback({ id: getSelect2OptionId(token), text: token });
} }
@ -1415,6 +1418,7 @@ function customTokenizer(input, _selection, callback) {
// Now remove the token from the current input, and the comma too // Now remove the token from the current input, and the comma too
current = current.slice(i + 1); current = current.slice(i + 1);
i = 0;
} }
} }
@ -1481,7 +1485,8 @@ function getWorldEntry(name, data, entry) {
/** Function to build the keys input controls @param {string} entryPropName @param {string} originalDataValueName */ /** Function to build the keys input controls @param {string} entryPropName @param {string} originalDataValueName */
function enableKeysInput(entryPropName, originalDataValueName) { function enableKeysInput(entryPropName, originalDataValueName) {
const input = !isMobile() ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`); const isFancyInput = !isMobile() && !power_user.wi_key_input_plaintext;
const input = isFancyInput ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`);
input.data('uid', entry.uid); input.data('uid', entry.uid);
input.on('click', function (event) { input.on('click', function (event) {
// Prevent closing the drawer on clicking the input // Prevent closing the drawer on clicking the input
@ -1507,7 +1512,7 @@ function getWorldEntry(name, data, entry) {
return content; return content;
} }
if (!isMobile()) { if (isFancyInput) {
input.select2({ input.select2({
ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache), ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),
tags: true, tags: true,
@ -1557,14 +1562,15 @@ function getWorldEntry(name, data, entry) {
template.find(`select[name="${entryPropName}"]`).hide(); template.find(`select[name="${entryPropName}"]`).hide();
input.show(); input.show();
input.on('input', function (_, { skipReset } = {}) { input.on('input', function (_, { skipReset, noSave } = {}) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = String($(this).val()); const value = String($(this).val());
!skipReset && resetScrollHeight(this); !skipReset && resetScrollHeight(this);
if (!noSave) {
data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value); data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
saveWorldInfo(name, data); saveWorldInfo(name, data);
}
}); });
input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true }); input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
} }
@ -1576,6 +1582,23 @@ function getWorldEntry(name, data, entry) {
// keysecondary // keysecondary
enableKeysInput("keysecondary", "secondary_keys"); enableKeysInput("keysecondary", "secondary_keys");
// draw key input switch button
template.find('.switch_input_type_icon').on('click', function () {
power_user.wi_key_input_plaintext = !power_user.wi_key_input_plaintext;
saveSettingsDebounced();
// Just redraw the panel
const uid = ($(this).parents('.world_entry')).data('uid');
updateEditor(uid, false);
$(`.world_entry[uid="${uid}"] .inline-drawer-icon`).trigger('click');
// setTimeout(() => {
// }, debounce_timeout.standard);
}).each((_, icon) => {
$(icon).attr('title', $(icon).data(power_user.wi_key_input_plaintext ? 'tooltip-on' : 'tooltip-off'));
$(icon).text($(icon).data(power_user.wi_key_input_plaintext ? 'icon-on' : 'icon-off'));
});
// logic AND/NOT // logic AND/NOT
const selectiveLogicDropdown = template.find('select[name="entryLogicType"]'); const selectiveLogicDropdown = template.find('select[name="entryLogicType"]');
selectiveLogicDropdown.data('uid', entry.uid); selectiveLogicDropdown.data('uid', entry.uid);