mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
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:
@ -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);
|
||||||
|
}
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
Reference in New Issue
Block a user