diff --git a/public/css/world-info.css b/public/css/world-info.css index f3363b701..6244d9c2e 100644 --- a/public/css/world-info.css +++ b/public/css/world-info.css @@ -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; -} \ No newline at end of file +} + +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); +} diff --git a/public/index.html b/public/index.html index 01ba613cc..bbf0f8515 100644 --- a/public/index.html +++ b/public/index.html @@ -5206,6 +5206,9 @@ Primary Keywords +
Logic @@ -5225,6 +5228,9 @@ Optional Filter +
diff --git a/public/scripts/utils.js b/public/scripts/utils.js index f8d91e5a8..d612bba8a 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -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} 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} 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 diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index fe1f9b759..22935dfd9 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -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);