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 {
display: flex;
flex-direction: column;
position: relative;
}
.world_entry_thin_controls {
@ -222,4 +223,26 @@ span.select2-container .select2-results__option:has(> .result_block .regex_item)
.select2-results__option .item_count {
margin-left: 10px;
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>
<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>
<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 class="world_entry_form_control">
<small class="textAlignCenter" data-i18n="Logic">Logic</small>
@ -5225,6 +5228,9 @@
<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>
<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 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.
*
* @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) {
function dynamicSelect2DataTransport(params, success, failure) {
@ -1543,11 +1543,21 @@ export function dynamicSelect2DataViaAjax(dataProvider) {
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
*
* @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 {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
@ -1561,8 +1571,8 @@ export function select2ChoiceClickSubscribe(control, action, { buttonStyle = fal
// Get the real container below and create a click handler on that one
const select2Container = control.next('span.select2-container');
select2Container.on('click', function (event) {
const $target = $(event.target);
if ($target.hasClass('select2-selection__choice__display') || $target.parents('.select2-selection__choice__display')) {
const isChoice = isSelect2ChoiceElement(event.target);
if (isChoice) {
event.preventDefault();
// 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 { 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 { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
import { isMobile } from './RossAscends-mods.js';
@ -69,7 +69,7 @@ const saveSettingsDebounced = debounce(() => {
saveSettings();
}, debounce_timeout.relaxed);
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.
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));
}
function displayWorldEntries(name, data, navigation = navigation_option.none) {
updateEditor = (navigation) => displayWorldEntries(name, data, navigation);
function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
const worldEntriesList = $('#world_popup_entries_list');
@ -1156,7 +1156,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none) {
const parentOffset = element.parent().offset();
const scrollOffset = elementOffset.top - parentOffset.top;
$('#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);
addFindCallback({ id: getSelect2OptionId(term.trim()), text: term.trim() });
const finalTerm = term.trim();
if (finalTerm) {
addFindCallback({ id: getSelect2OptionId(finalTerm), text: finalTerm });
}
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.
if (token.startsWith('/') && !isRegex) {
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 {
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
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 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.on('click', function (event) {
// Prevent closing the drawer on clicking the input
@ -1507,7 +1512,7 @@ function getWorldEntry(name, data, entry) {
return content;
}
if (!isMobile()) {
if (isFancyInput) {
input.select2({
ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),
tags: true,
@ -1557,14 +1562,15 @@ function getWorldEntry(name, data, entry) {
template.find(`select[name="${entryPropName}"]`).hide();
input.show();
input.on('input', function (_, { skipReset } = {}) {
input.on('input', function (_, { skipReset, noSave } = {}) {
const uid = $(this).data('uid');
const value = String($(this).val());
!skipReset && resetScrollHeight(this);
data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
saveWorldInfo(name, data);
if (!noSave) {
data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
saveWorldInfo(name, data);
}
});
input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
}
@ -1576,6 +1582,23 @@ function getWorldEntry(name, data, entry) {
// keysecondary
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
const selectiveLogicDropdown = template.find('select[name="entryLogicType"]');
selectiveLogicDropdown.data('uid', entry.uid);