Update and fix default expressions + emojis - Move "use default emojis" from its own toggle into the fallback dropdown - Add "no fallback" to the fallback dropdown - fix fallback expressions not being consistently used - Switching fallback will now reroll/reset the relevant expression - Add html attributes on the expression image

fb2ee756dd2db7fe7441c5acb6733aa1db126979

Wolfsblvt <wolfsblvt@gmail.com>

2 files changed, +128 -85Showing whitespace changes
public/scripts/extensions/expressions/index.js+128 -81
@@ -70,6 +70,12 @@ const DEFAULT_EXPRESSIONS = [
70 'surprise',70 'surprise',
71 'neutral',71 'neutral',
72];72];
73
74const OPTION_NO_FALLBACK = '#none';
75const OPTION_EMOJI_FALLBACK = '#emoji';
76const RESET_SPRITE_LABEL = '#reset';
77
78
73/** @enum {number} */79/** @enum {number} */
74const EXPRESSION_API = {80const EXPRESSION_API = {
75 local: 0,81 local: 0,
@@ -106,15 +112,6 @@ function getPlaceholderImage(expression, isCustom = false) {
106 };112 };
107}113}
108114
109
110/**
111 * Returns the fallback expression if explicitly chosen, otherwise the default one
112 * @returns {string} expression name
113 */
114function getFallbackExpression() {
115 return extension_settings.expressions.fallback_expression ?? DEFAULT_FALLBACK_EXPRESSION;
116}
117
118function isVisualNovelMode() {115function isVisualNovelMode() {
119 return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);116 return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);
120}117}
@@ -127,12 +124,12 @@ async function forceUpdateVisualNovelMode() {
127124
128const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, debounce_timeout.quick);125const updateVisualNovelModeDebounced = debounce(forceUpdateVisualNovelMode, debounce_timeout.quick);
129126
130async function updateVisualNovelMode(name, expression) {127async function updateVisualNovelMode(spriteFolderName, expression) {
131 const vnContainer = $('#visual-novel-wrapper');128 const vnContainer = $('#visual-novel-wrapper');
132129
133 await visualNovelRemoveInactive(vnContainer);130 await visualNovelRemoveInactive(vnContainer);
134131
135 const setSpritePromises = await visualNovelSetCharacterSprites(vnContainer, name, expression);132 const setSpritePromises = await visualNovelSetCharacterSprites(vnContainer, spriteFolderName, expression);
136133
137 // calculate layer indices based on recent messages134 // calculate layer indices based on recent messages
138 await visualNovelUpdateLayers(vnContainer);135 await visualNovelUpdateLayers(vnContainer);
@@ -414,23 +411,6 @@ async function setImage(img, path) {
414 });411 });
415}412}
416413
417function onExpressionsShowDefaultInput() {
418 const value = $(this).prop('checked');
419 extension_settings.expressions.showDefault = value;
420 saveSettingsDebounced();
421
422 const existingImageSrc = $('img.expression').prop('src');
423 if (existingImageSrc !== undefined) { //if we have an image in src
424 if (!value && existingImageSrc.includes('/img/default-expressions/')) { //and that image is from /img/ (default)
425 $('img.expression').prop('src', ''); //remove it
426 lastMessage = null;
427 }
428 if (value) {
429 lastMessage = null;
430 }
431 }
432}
433
434async function moduleWorker() {414async function moduleWorker() {
435 const context = getContext();415 const context = getContext();
436416
@@ -546,8 +526,8 @@ async function moduleWorker() {
546 const force = !!context.groupId;526 const force = !!context.groupId;
547527
548 // Character won't be angry on you for swiping528 // Character won't be angry on you for swiping
549 if (currentLastMessage.mes == '...' && expressionsList.includes(getFallbackExpression())) {529 if (currentLastMessage.mes == '...' && expressionsList.includes(extension_settings.expressions.fallback_expression)) {
550 expression = getFallbackExpression();530 expression = extension_settings.expressions.fallback_expression;
551 }531 }
552532
553 await sendExpressionCall(spriteFolderName, expression, { force: force, vnMode: vnMode });533 await sendExpressionCall(spriteFolderName, expression, { force: force, vnMode: vnMode });
@@ -599,23 +579,23 @@ function getFolderNameByMessage(message) {
599/**579/**
600 * Update the expression for the given character.580 * Update the expression for the given character.
601 *581 *
602 * @param {string} name The character name, optionally with a sprite folder override, e.g. "folder/expression".582 * @param {string} spriteFolderName The character name, optionally with a sprite folder override, e.g. "folder/expression".
603 * @param {string} expression The expression label, e.g. "amusement", "joy", etc.583 * @param {string} expression The expression label, e.g. "amusement", "joy", etc.
604 * @param {Object} [options] Additional options584 * @param {Object} [options] Additional options
605 * @param {boolean} [options.force=false] If true, the expression will be sent even if it is the same as the current expression.585 * @param {boolean} [options.force=false] If true, the expression will be sent even if it is the same as the current expression.
606 * @param {boolean} [options.vnMode=null] If true, the expression will be sent in Visual Novel mode. If null, it will be determined by the current chat mode.586 * @param {boolean} [options.vnMode=null] If true, the expression will be sent in Visual Novel mode. If null, it will be determined by the current chat mode.
607 * @param {string?} [options.overrideSpriteFile=null] - Set if a specific sprite file should be used. Must be sprite file name.587 * @param {string?} [options.overrideSpriteFile=null] - Set if a specific sprite file should be used. Must be sprite file name.
608 */588 */
609async function sendExpressionCall(name, expression, { force = false, vnMode = null, overrideSpriteFile = null } = {}) {589async function sendExpressionCall(spriteFolderName, expression, { force = false, vnMode = null, overrideSpriteFile = null } = {}) {
610 lastExpression[name.split('/')[0]] = expression;590 lastExpression[spriteFolderName.split('/')[0]] = expression;
611 if (vnMode === null) {591 if (vnMode === null) {
612 vnMode = isVisualNovelMode();592 vnMode = isVisualNovelMode();
613 }593 }
614594
615 if (vnMode) {595 if (vnMode) {
616 await updateVisualNovelMode(name, expression);596 await updateVisualNovelMode(spriteFolderName, expression);
617 } else {597 } else {
618 setExpression(name, expression, { force: force, overrideSpriteFile: overrideSpriteFile });598 setExpression(spriteFolderName, expression, { force: force, overrideSpriteFile: overrideSpriteFile });
619 }599 }
620}600}
621601
@@ -679,7 +659,7 @@ async function setSpriteSlashCommand({ type }, searchTerm) {
679 await validateImages(spriteFolderName);659 await validateImages(spriteFolderName);
680660
681 // Handle reset as a special term and just reset the sprite via expression call661 // Handle reset as a special term and just reset the sprite via expression call
682 if (searchTerm === '#reset') {662 if (searchTerm === RESET_SPRITE_LABEL) {
683 await sendExpressionCall(spriteFolderName, label, { force: true });663 await sendExpressionCall(spriteFolderName, label, { force: true });
684 return lastExpression[spriteFolderName] ?? '';664 return lastExpression[spriteFolderName] ?? '';
685 }665 }
@@ -933,12 +913,12 @@ function onTextGenSettingsReady(args) {
933 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.913 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.
934 * @param {object} [options={}] - Optional arguments.914 * @param {object} [options={}] - Optional arguments.
935 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.915 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.
936 * @returns {Promise<string>} - The label of the expression.916 * @returns {Promise<string?>} - The label of the expression.
937 */917 */
938export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) {918export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) {
939 // Return if text is undefined, saving a costly fetch request919 // Return if text is undefined, saving a costly fetch request
940 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {920 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {
941 return getFallbackExpression();921 return extension_settings.expressions.fallback_expression;
942 }922 }
943923
944 if (extension_settings.expressions.translate && typeof window['translate'] === 'function') {924 if (extension_settings.expressions.translate && typeof window['translate'] === 'function') {
@@ -968,7 +948,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
968 await waitUntilCondition(() => online_status !== 'no_connection', 3000, 250);948 await waitUntilCondition(() => online_status !== 'no_connection', 3000, 250);
969 } catch (error) {949 } catch (error) {
970 console.warn('No LLM connection. Using fallback expression', error);950 console.warn('No LLM connection. Using fallback expression', error);
971 return getFallbackExpression();951 return extension_settings.expressions.fallback_expression;
972 }952 }
973953
974 const expressionsList = await getExpressionsList();954 const expressionsList = await getExpressionsList();
@@ -981,7 +961,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
981 case EXPRESSION_API.webllm: {961 case EXPRESSION_API.webllm: {
982 if (!isWebLlmSupported()) {962 if (!isWebLlmSupported()) {
983 console.warn('WebLLM is not supported. Using fallback expression');963 console.warn('WebLLM is not supported. Using fallback expression');
984 return getFallbackExpression();964 return extension_settings.expressions.fallback_expression;
985 }965 }
986966
987 const expressionsList = await getExpressionsList();967 const expressionsList = await getExpressionsList();
@@ -1014,9 +994,9 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1014 } break;994 } break;
1015 }995 }
1016 } catch (error) {996 } catch (error) {
1017 toastr.info('Could not classify expression. Check the console or your backend for more information.');997 toastr.error('Could not classify expression. Check the console or your backend for more information.');
1018 console.error(error);998 console.error(error);
1019 return getFallbackExpression();999 return extension_settings.expressions.fallback_expression;
1020 }1000 }
1021}1001}
10221002
@@ -1231,13 +1211,20 @@ async function renderFallbackExpressionPicker() {
1231 const defaultPicker = $('#expression_fallback');1211 const defaultPicker = $('#expression_fallback');
1232 defaultPicker.empty();1212 defaultPicker.empty();
12331213
1234 const fallbackExpression = getFallbackExpression();1214
1215 addOption(OPTION_NO_FALLBACK, '[ No fallback ]', !extension_settings.expressions.fallback_expression);
1216 addOption(OPTION_EMOJI_FALLBACK, '[ Default emojis ]', !!extension_settings.expressions.showDefault);
12351217
1236 for (const expression of expressions) {1218 for (const expression of expressions) {
1219 addOption(expression, expression, expression == extension_settings.expressions.fallback_expression);
1220 }
1221
1222 /** @type {(value: string, label: string, isSelected: boolean) => void} */
1223 function addOption(value, label, isSelected) {
1237 const option = document.createElement('option');1224 const option = document.createElement('option');
1238 option.value = expression;1225 option.value = value;
1239 option.text = expression;1226 option.text = label;
1240 option.selected = expression == fallbackExpression;1227 option.selected = isSelected;
1241 defaultPicker.append(option);1228 defaultPicker.append(option);
1242 }1229 }
1243}1230}
@@ -1326,12 +1313,19 @@ export async function getExpressionsList() {
1326 * @param {object} [options] - Options to select the sprite1313 * @param {object} [options] - Options to select the sprite
1327 * @param {string} [options.prevExpressionSrc=null] - The source of the previous expression1314 * @param {string} [options.prevExpressionSrc=null] - The source of the previous expression
1328 * @param {string} [options.overrideSpriteFile=null] - The file name of the sprite to select1315 * @param {string} [options.overrideSpriteFile=null] - The file name of the sprite to select
1329 * @returns {ExpressionImage} - The selected sprite1316 * @returns {ExpressionImage?} - The selected sprite
1330 */1317 */
1331function chooseSpriteForExpression(spriteFolderName, expression, { prevExpressionSrc = null, overrideSpriteFile = null } = {}) {1318function chooseSpriteForExpression(spriteFolderName, expression, { prevExpressionSrc = null, overrideSpriteFile = null } = {}) {
1332 const sprite = (spriteCache[spriteFolderName] && spriteCache[spriteFolderName].find(x => x.label === expression));1319 if (!spriteCache[spriteFolderName]) return null;
1333 if (!(sprite?.files.length > 0))1320 if (expression === RESET_SPRITE_LABEL) return null;
1334 return null;1321
1322 // Search for sprites of that expression - or fallback expression sprites if enabled
1323 let sprite = spriteCache[spriteFolderName].find(x => x.label === expression);
1324 if (!(sprite?.files.length > 0) && extension_settings.expressions.fallback_expression) {
1325 sprite = spriteCache[spriteFolderName].find(x => x.label === extension_settings.expressions.fallback_expression);
1326 console.debug('Expression', expression, 'not found. Using fallback expression', extension_settings.expressions.fallback_expression);
1327 }
1328 if (!(sprite?.files.length > 0)) return null;
13351329
1336 let spriteFile = sprite.files[0];1330 let spriteFile = sprite.files[0];
13371331
@@ -1397,6 +1391,10 @@ async function setExpression(spriteFolderName, expression, { force = false, over
1397 expressionClone.attr('id', '').css({ opacity: 0 });1391 expressionClone.attr('id', '').css({ opacity: 0 });
1398 //add new sprite path to clone src1392 //add new sprite path to clone src
1399 expressionClone.attr('src', spriteFile.imageSrc);1393 expressionClone.attr('src', spriteFile.imageSrc);
1394 //set relevant data tags
1395 expressionClone.attr('data-sprite-folder-name', spriteFolderName);
1396 expressionClone.attr('data-expression', expression);
1397 expressionClone.attr('data-sprite-filename', spriteFile.fileName);
1400 //add invisible clone to html1398 //add invisible clone to html
1401 expressionClone.appendTo($('#expression-holder'));1399 expressionClone.appendTo($('#expression-holder'));
14021400
@@ -1442,12 +1440,12 @@ async function setExpression(spriteFolderName, expression, { force = false, over
14421440
1443 expressionClone.removeClass('default');1441 expressionClone.removeClass('default');
1444 expressionClone.off('error');1442 expressionClone.off('error');
1445 expressionClone.on('error', function () {1443 expressionClone.on('error', function (error) {
1446 console.debug('Expression image error', spriteFile.imageSrc);1444 console.debug('Expression image error', spriteFile.imageSrc, error);
1447 $(this).attr('src', '');1445 $(this).attr('src', '');
1448 $(this).off('error');1446 $(this).off('error');
1449 if (force && extension_settings.expressions.showDefault) {1447 if (force && extension_settings.expressions.showDefault) {
1450 setDefault();1448 setDefaultEmojiForImage(img, expression);
1451 }1449 }
1452 });1450 });
1453 }1451 }
@@ -1455,30 +1453,52 @@ async function setExpression(spriteFolderName, expression, { force = false, over
1455 console.info('Expression set', { expression: spriteFile.expression, file: spriteFile.fileName });1453 console.info('Expression set', { expression: spriteFile.expression, file: spriteFile.fileName });
1456 }1454 }
1457 else {1455 else {
1458 if (extension_settings.expressions.showDefault) {1456 img.attr('data-sprite-folder-name', spriteFolderName);
1459 setDefault();1457 img.attr('data-expression', expression);
1458
1459 $(img).off('error');
1460
1461 if (extension_settings.expressions.showDefault && expression !== RESET_SPRITE_LABEL) {
1462 setDefaultEmojiForImage(img, expression);
1460 } else {1463 } else {
1461 setNone();1464 setNoneForImage(img, expression);
1462 }1465 }
1463 console.debug('Expression unset - No sprite found', { expression: expression });1466 console.debug('Expression unset - No sprite found', { expression: expression });
1464 }1467 }
14651468
1466 function setDefault() {1469 document.getElementById('expression-holder').style.display = '';
1467 console.debug('setting default expression');1470}
1471
1472/**
1473 * Sets the default expression image for the given image element and expression
1474 * @param {JQuery<HTMLElement>} img - The image element to set the default expression for
1475 * @param {string} expression - The expression label to use for the default image
1476 */
1477function setDefaultEmojiForImage(img, expression) {
1478 if (extension_settings.expressions.custom?.includes(expression)) {
1479 console.debug(`Can't set default emoji for a custom expression (${expression}). setting to ${DEFAULT_FALLBACK_EXPRESSION} instead.`);
1480 expression = DEFAULT_FALLBACK_EXPRESSION;
1481 }
1482
1468 const defImgUrl = `/img/default-expressions/${expression}.png`;1483 const defImgUrl = `/img/default-expressions/${expression}.png`;
1469 //console.log(defImgUrl);
1470 img.attr('src', defImgUrl);1484 img.attr('src', defImgUrl);
1485 img.attr('data-expression', expression);
1486 img.attr('data-sprite-filename', null);
1471 img.addClass('default');1487 img.addClass('default');
1472}1488}
1473 function setNone() {1489
1474 console.debug('setting no expression');1490/**
1491 * Sets the image element to display no expression by clearing its source attribute.
1492 * @param {JQuery<HTMLElement>} img - The image element to clear the expression for
1493 * @param {string} expression - The expression label to use
1494 */
1495function setNoneForImage(img, expression) {
1475 img.attr('src', '');1496 img.attr('src', '');
1497 img.attr('data-expression', expression);
1498 img.attr('data-sprite-filename', null);
1476 img.removeClass('default');1499 img.removeClass('default');
1477}1500}
14781501
1479 document.getElementById('expression-holder').style.display = '';
1480}
1481
1482function onClickExpressionImage() {1502function onClickExpressionImage() {
1483 // If there is no expression image and we clicked on the placeholder, we remove the sprite by calling via the expression label1503 // If there is no expression image and we clicked on the placeholder, we remove the sprite by calling via the expression label
1484 if ($(this).attr('data-expression-type') === 'failure') {1504 if ($(this).attr('data-expression-type') === 'failure') {
@@ -1504,19 +1524,15 @@ async function onClickExpressionAddCustom() {
15041524
1505 // a-z, 0-9, dashes and underscores only1525 // a-z, 0-9, dashes and underscores only
1506 if (!/^[a-z0-9-_]+$/.test(expressionName)) {1526 if (!/^[a-z0-9-_]+$/.test(expressionName)) {
1507 toastr.info('Invalid custom expression name provided');1527 toastr.warning('Invalid custom expression name provided', 'Add Custom Expression');
1508 return;1528 return;
1509 }1529 }
1510
1511 // Check if expression name already exists in default expressions
1512 if (DEFAULT_EXPRESSIONS.includes(expressionName)) {1530 if (DEFAULT_EXPRESSIONS.includes(expressionName)) {
1513 toastr.info('Expression name already exists');1531 toastr.warning('Expression name already exists', 'Add Custom Expression');
1514 return;1532 return;
1515 }1533 }
1516
1517 // Check if expression name already exists in custom expressions
1518 if (extension_settings.expressions.custom.includes(expressionName)) {1534 if (extension_settings.expressions.custom.includes(expressionName)) {
1519 toastr.info('Custom expression already exists');1535 toastr.warning('Custom expression already exists', 'Add Custom Expression');
1520 return;1536 return;
1521 }1537 }
15221538
@@ -1550,8 +1566,8 @@ async function onClickExpressionRemoveCustom() {
1550 // Remove custom expression from settings1566 // Remove custom expression from settings
1551 const index = extension_settings.expressions.custom.indexOf(selectedExpression);1567 const index = extension_settings.expressions.custom.indexOf(selectedExpression);
1552 extension_settings.expressions.custom.splice(index, 1);1568 extension_settings.expressions.custom.splice(index, 1);
1553 if (selectedExpression == getFallbackExpression()) {1569 if (selectedExpression == extension_settings.expressions.fallback_expression) {
1554 toastr.warning(`Deleted custom expression '${selectedExpression}' that was also selected as the fallback expression.\nFallback expression has been reset to '${DEFAULT_FALLBACK_EXPRESSION}'.`);1570 toastr.warning(`Deleted custom expression '${selectedExpression}' that was also selected as the fallback expression.\nFallback expression has been reset to '${DEFAULT_FALLBACK_EXPRESSION}'.`, 'Remove Custom Expression');
1555 extension_settings.expressions.fallback_expression = DEFAULT_FALLBACK_EXPRESSION;1571 extension_settings.expressions.fallback_expression = DEFAULT_FALLBACK_EXPRESSION;
1556 }1572 }
1557 await renderAdditionalExpressionSettings();1573 await renderAdditionalExpressionSettings();
@@ -1575,12 +1591,35 @@ function onExpressionApiChanged() {
1575 }1591 }
1576}1592}
15771593
1578function onExpressionFallbackChanged() {1594async function onExpressionFallbackChanged() {
1579 const expression = this.value;1595 /** @type {HTMLSelectElement} */
1580 if (expression) {1596 const select = this;
1581 extension_settings.expressions.fallback_expression = expression;1597 const selectedValue = select.value;
1582 saveSettingsDebounced();1598
1599 switch (selectedValue) {
1600 case OPTION_NO_FALLBACK:
1601 extension_settings.expressions.fallback_expression = null;
1602 extension_settings.expressions.showDefault = false;
1603 break;
1604 case OPTION_EMOJI_FALLBACK:
1605 extension_settings.expressions.fallback_expression = null;
1606 extension_settings.expressions.showDefault = true;
1607 break;
1608 default:
1609 extension_settings.expressions.fallback_expression = selectedValue;
1610 extension_settings.expressions.showDefault = false;
1611 break;
1583 }1612 }
1613
1614 const img = $('img.expression');
1615 const spriteFolderName = img.attr('data-sprite-folder-name');
1616 const expression = img.attr('data-expression');
1617
1618 if (spriteFolderName && expression) {
1619 await sendExpressionCall(spriteFolderName, expression, { force: true });
1620 }
1621
1622 saveSettingsDebounced();
1584}1623}
15851624
1586async function handleFileUpload(url, formData) {1625async function handleFileUpload(url, formData) {
@@ -1924,6 +1963,16 @@ function migrateSettings() {
1924 extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT;1963 extension_settings.expressions.llmPrompt = DEFAULT_LLM_PROMPT;
1925 saveSettingsDebounced();1964 saveSettingsDebounced();
1926 }1965 }
1966
1967 if (extension_settings.expressions.allowMultiple === undefined) {
1968 extension_settings.expressions.allowMultiple = true;
1969 saveSettingsDebounced();
1970 }
1971
1972 if (extension_settings.expressions.showDefault && extension_settings.expressions.fallback_expression !== undefined) {
1973 extension_settings.expressions.showDefault = false;
1974 saveSettingsDebounced();
1975 }
1927}1976}
19281977
1929(async function () {1978(async function () {
@@ -1950,9 +1999,7 @@ function migrateSettings() {
1950 const template = await renderExtensionTemplateAsync(MODULE_NAME, 'settings');1999 const template = await renderExtensionTemplateAsync(MODULE_NAME, 'settings');
1951 $('#expressions_container').append(template);2000 $('#expressions_container').append(template);
1952 $('#expression_override_button').on('click', onClickExpressionOverrideButton);2001 $('#expression_override_button').on('click', onClickExpressionOverrideButton);
1953 $('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
1954 $('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);2002 $('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
1955 $('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input');
1956 $('#expression_translate').prop('checked', extension_settings.expressions.translate).on('input', function () {2003 $('#expression_translate').prop('checked', extension_settings.expressions.translate).on('input', function () {
1957 extension_settings.expressions.translate = !!$(this).prop('checked');2004 extension_settings.expressions.translate = !!$(this).prop('checked');
1958 saveSettingsDebounced();2005 saveSettingsDebounced();
@@ -2080,7 +2127,7 @@ function migrateSettings() {
2080 if (type == 'sprite') return localEnumProviders.sprites();2127 if (type == 'sprite') return localEnumProviders.sprites();
2081 else return [2128 else return [
2082 ...localEnumProviders.expressions(),2129 ...localEnumProviders.expressions(),
2083 new SlashCommandEnumValue('#reset', 'Resets the expression (to either default or no sprite)', enumTypes.enum, '❌'),2130 new SlashCommandEnumValue(RESET_SPRITE_LABEL, 'Resets the expression (to either default or no sprite)', enumTypes.enum, '❌'),
2084 ];2131 ];
2085 },2132 },
2086 }),2133 }),
public/scripts/extensions/expressions/settings.html+0 -4
@@ -10,10 +10,6 @@
10 <input id="expression_translate" type="checkbox">10 <input id="expression_translate" type="checkbox">
11 <span data-i18n="Translate text to English before classification">Translate text to English before classification</span>11 <span data-i18n="Translate text to English before classification">Translate text to English before classification</span>
12 </label>12 </label>
13 <label class="checkbox_label" for="expressions_show_default">
14 <input id="expressions_show_default" type="checkbox">
15 <span data-i18n="Show default images (emojis) if sprite missing">Show default images (emojis) if sprite missing</span>
16 </label>
17 <label class="checkbox_label" for="expressions_allow_multiple" title="A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected." data-i18n="[title]A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected.">13 <label class="checkbox_label" for="expressions_allow_multiple" title="A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected." data-i18n="[title]A single expression can have multiple sprites. Whenever the expression is chosen, a random sprite for this expression will be selected.">
18 <input id="expressions_allow_multiple" type="checkbox">14 <input id="expressions_allow_multiple" type="checkbox">
19 <span data-i18n="Allow multiple sprites per expression">Allow multiple sprites per expression</span>15 <span data-i18n="Allow multiple sprites per expression">Allow multiple sprites per expression</span>