Markdown hotkeys for textareas (#2800) * initial commit * dont hijack all ctrl keybinds * change strikethrough bind, convert to subscribable class, target key textareas * better early return, hotkey reversiblility * possible undo solution, key checks to switch * execCommand alternate, perfect Undo * format full word when caret is in the middle of a word * double backticks do nothing, dummy. * ctrl+K for ....'K'ode snippet... * remove console logs * Add new hotkeys to help * Allow hotkeys in message edit textarea * add markdown hotkey help text * help text addition to mention hotkeys work in message edits * add markdown hotkeys to WI entry content box * disengage if alt/win pressed, universal prevent default * disengage if shiftKey pressed * re-allow shift for one special case * add MD hotkeys toggle in user settings * add markdown enabled icon on relevant textareas when appropriate * Add icon to help * Uniform formatting * Add opacity to icon --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -455,3 +455,7 @@ body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint { | ||
| 455 | 455 | #smooth_streaming:checked~#smooth_streaming_speed_control { |
| 456 | 456 | display: block; |
| 457 | 457 | } |
| 458 | + | |
| 459 | +.mdhotkey_icon { | |
| 460 | + opacity: 0.6; | |
| 461 | +} | |
| @@ -4033,10 +4033,17 @@ | ||
| 4033 | 4033 | <input id="world_import_dialog" type="checkbox" /> |
| 4034 | 4034 | <small data-i18n="Lorebook Import Dialog">Lorebook Import Dialog</small> |
| 4035 | 4035 | </label> |
| 4036 | 4036 | <label class="checkbox_label" for=" enable_auto_select_input" title="Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields." data-i18n="[title]Enable auto-select of input text in some text fields when clicking/selecting them. Applies to popup input textboxes, and possible other custom input fields."> |
| 4037 | 4037 | <input id="enable_auto_select_input" type="checkbox" /> |
| 4038 | 4038 | <small data-i18n="Auto-select Input Text">Auto-select Input Text</small> |
| 4039 | 4039 | </label> |
| 4040 | + <label class="checkbox_label alignItemsCenter" for="enable_md_hotkeys" title="Enable hotkeys for inserting markdown format characters in certain text input boxes. See '/help hotkeys'."> | |
| 4041 | + <input id="enable_md_hotkeys" type="checkbox" /> | |
| 4042 | + <small> | |
| 4043 | + <span data-i18n="Markdown Hotkeys">Markdown Hotkeys</span> | |
| 4044 | + <i class="fa-brands fa-markdown"></i> | |
| 4045 | + </small> | |
| 4046 | + </label> | |
| 4040 | 4047 | <label class="checkbox_label" for="restore_user_input" title="Restore unsaved user input on page refresh." data-i18n="[title]Restore unsaved user input on page refresh"> |
| 4041 | 4048 | <input id="restore_user_input" type="checkbox" /> |
| 4042 | 4049 | <small data-i18n="Restore User Input">Restore User Input</small> |
| @@ -4720,7 +4727,7 @@ | ||
| 4720 | 4727 | <hr> |
| 4721 | 4728 | <div id="description_div" class="title_restorable"> |
| 4722 | 4729 | <div class="flex-container alignitemscenter"> |
| 4723 | 4730 | <span data-i18n="Character Description" class="mdhotkey_location">Description</span> |
| 4724 | 4731 | <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i> |
| 4725 | 4732 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank"> |
| 4726 | 4733 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| @@ -4734,7 +4741,7 @@ | ||
| 4734 | 4741 | </span> |
| 4735 | 4742 | </div> |
| 4736 | 4743 | </div> |
| 4737 | 4744 | <textarea id="description_textarea" class="mdHotkeys" data-i18n="[placeholder]Describe your character's physical and mental traits here." placeholder="Describe your character's physical and mental traits here." name="description" placeholder=""></textarea> |
| 4738 | 4745 | <div class="extension_token_counter"> |
| 4739 | 4746 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span> |
| 4740 | 4747 | </div> |
| @@ -4742,7 +4749,7 @@ | ||
| 4742 | 4749 | <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1"> |
| 4743 | 4750 | <div id="first_message_div" class="title_restorable"> |
| 4744 | 4751 | <div class="flex-container alignitemscenter flex1"> |
| 4745 | 4752 | <span data-i18n="First message" class="mdhotkey_location">First message</span> |
| 4746 | 4753 | <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textarea" title="Expand the editor" data-i18n="[title]Expand the editor"></i> |
| 4747 | 4754 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#first-message" class="notes-link" target="_blank"> |
| 4748 | 4755 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| @@ -4754,7 +4761,7 @@ | ||
| 4754 | 4761 | </span> |
| 4755 | 4762 | </div> |
| 4756 | 4763 | </div> |
| 4757 | 4764 | <textarea class="mdHotkeys" id="firstmessage_textarea" data-i18n="[placeholder]This will be the first message from the character that starts every chat." placeholder="This will be the first message from the character that starts every chat." name="first_mes" placeholder=""></textarea> |
| 4758 | 4765 | <div class="extension_token_counter"> |
| 4759 | 4766 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="firstmessage_textarea">counting...</span> |
| 4760 | 4767 | </div> |
| @@ -5112,7 +5119,7 @@ | ||
| 5112 | 5119 | </div> |
| 5113 | 5120 | <hr> |
| 5114 | 5121 | <div id="personality_div"> |
| 5115 | - <h4> | |
| 5122 | + <h4 class="flex-container alignItemsBaseline"> | |
| 5116 | 5123 | <span data-i18n="Personality summary">Personality summary</span> |
| 5117 | 5124 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#personality-summary" class="notes-link" target="_blank"><span class="fa-solid fa-circle-question note-link-span"></span></a> |
| 5118 | 5125 | </h4> |
| @@ -5122,7 +5129,7 @@ | ||
| 5122 | 5129 | </div> |
| 5123 | 5130 | </div> |
| 5124 | 5131 | <div id="scenario_div"> |
| 5125 | - <h4> | |
| 5132 | + <h4 class="flex-container alignItemsBaseline"> | |
| 5126 | 5133 | <span data-i18n="Scenario">Scenario</span> |
| 5127 | 5134 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#scenario" class="notes-link" target="_blank"> |
| 5128 | 5135 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| @@ -5179,10 +5186,10 @@ | ||
| 5179 | 5186 | <hr> |
| 5180 | 5187 | <div id="mes_example_div" class="flex-container flexFlowColumn"> |
| 5181 | 5188 | <div> |
| 5182 | 5189 | <h4 class="flex-container gap5px alignItemsBaseline"><span data-i18n="Examples of dialogue" class="mdhotkey_location">Examples of dialogue</span></h4> |
| 5183 | 5190 | <h5 data-i18n="Important to set the character's writing style.">Important to set the character's writing style. <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#examples-of-dialogue" class="notes-link" target="_blank"><span class="fa-solid fa-circle-question note-link-span"></span></a></h5> |
| 5184 | 5191 | </div> |
| 5185 | 5192 | <textarea id="mes_example_textarea" class="flexGrow mdHotkeys" name="mes_example" data-i18n="[placeholder](Examples of chat dialog. Begin each example with START on a new line.)" placeholder="(Examples of chat dialog. Begin each example with <START> on a new line.)" form="form_create" rows="6"></textarea> |
| 5186 | 5193 | <div class="extension_token_counter"> |
| 5187 | 5194 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="mes_example_textarea">counting...</span> |
| 5188 | 5195 | </div> |
| @@ -5477,7 +5484,7 @@ | ||
| 5477 | 5484 | <label for="content "> |
| 5478 | 5485 | <small> |
| 5479 | 5486 | <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween"> |
| 5480 | 5487 | <span data-i18n="Content" class="alignitemscenter flex-container flexNoGap mdhotkey_location"> |
| 5481 | 5488 | Content |
| 5482 | 5489 | </span> |
| 5483 | 5490 | <span> |
| @@ -5512,7 +5519,7 @@ | ||
| 5512 | 5519 | </span> |
| 5513 | 5520 | </small> |
| 5514 | 5521 | </label> |
| 5515 | 5522 | <textarea class="text_pole mdHotkeys" name="content" rows="8" data-i18n="[placeholder]What this keyword should mean to the AI, sent verbatim" placeholder="What this keyword should mean to the AI, sent verbatim"></textarea> |
| 5516 | 5523 | </div> |
| 5517 | 5524 | </div> |
| 5518 | 5525 | <div class="world_entry_thin_controls commentContainer"> |
| @@ -5987,7 +5994,7 @@ | ||
| 5987 | 5994 | <div id="alternate_greetings_template" class="template_element"> |
| 5988 | 5995 | <div class="alternate_grettings flexFlowColumn flex-container"> |
| 5989 | 5996 | <div class="title_restorable"> |
| 5990 | 5997 | <h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3> |
| 5991 | 5998 | <div title="Add" class="menu_button fa-solid fa-plus add_alternate_greeting" data-i18n="[title]Add"></div> |
| 5992 | 5999 | </div> |
| 5993 | 6000 | <small class="justifyLeft" data-i18n="Alternate_Greetings_desc"> |
| @@ -6008,7 +6015,7 @@ | ||
| 6008 | 6015 | <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong> |
| 6009 | 6016 | <div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div> |
| 6010 | 6017 | </div> |
| 6011 | 6018 | <textarea name="alternate_greetings" data-i18n="[placeholder](This will be the first message from the character that starts every chat)" placeholder="(This will be the first message from the character that starts every chat)" class="text_pole textarea_compact alternate_greeting_text mdHotkeys" value="" autocomplete="off" rows="16"></textarea> |
| 6012 | 6019 | </div> |
| 6013 | 6020 | </div> |
| 6014 | 6021 | |
| @@ -6392,7 +6399,7 @@ | ||
| 6392 | 6399 | <div id="leftSendForm" class="alignContentCenter"> |
| 6393 | 6400 | <div id="options_button" class="fa-solid fa-bars interactable"></div> |
| 6394 | 6401 | </div> |
| 6395 | 6402 | <textarea id="send_textarea" name="text" class="mdHotkeys" data-i18n="[no_connection_text]Not connected to API!;[connected_text]Type a message, or /? for help" placeholder="Not connected to API!" no_connection_text="Not connected to API!" connected_text="Type a message, or /? for help"></textarea> |
| 6396 | 6403 | <div id="rightSendForm" class="alignContentCenter"> |
| 6397 | 6404 | <div id="stscript_continue" title="Continue script execution" class="stscript_btn stscript_continue" data-i18n="[title]Continue script execution"> |
| 6398 | 6405 | <i class="fa-solid fa-play"></i> |
| @@ -242,6 +242,7 @@ import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js' | ||
| 242 | 242 | import { initDynamicStyles } from './scripts/dynamic-styles.js'; |
| 243 | 243 | import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js'; |
| 244 | 244 | import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 245 | +import { initInputMarkdown } from './scripts/input-md-formatting.js'; | |
| 245 | 246 | import { AbortReason } from './scripts/util/AbortReason.js'; |
| 246 | 247 | |
| 247 | 248 | //exporting functions and vars for mods |
| @@ -951,6 +952,7 @@ async function firstLoadInit() { | ||
| 951 | 952 | initStats(); |
| 952 | 953 | initCfg(); |
| 953 | 954 | initLogprobs(); |
| 955 | + initInputMarkdown(); | |
| 954 | 956 | doDailyExtensionUpdatesCheck(); |
| 955 | 957 | await hideLoader(); |
| 956 | 958 | await fixViewport(); |
| @@ -10200,7 +10202,7 @@ jQuery(async function () { | ||
| 10200 | 10202 | .closest('.mes_block') |
| 10201 | 10203 | .find('.mes_text') |
| 10202 | 10204 | .append( |
| 10203 | 10205 | '<textarea id=\'curEditTextarea\' class=\'edit_textarea mdHotkeys\' style=\'max-width:auto;\'></textarea>', |
| 10204 | 10206 | ); |
| 10205 | 10207 | $('#curEditTextarea').val(text); |
| 10206 | 10208 | let edit_textarea = $(this) |
| @@ -0,0 +1,152 @@ | ||
| 1 | +import { power_user } from './power-user.js'; | |
| 2 | + | |
| 3 | +export function initInputMarkdown() { | |
| 4 | + $(document).on('keydown', 'textarea.mdHotkeys', function (e) { | |
| 5 | + if (!power_user.enable_md_hotkeys) { return; } | |
| 6 | + | |
| 7 | + // Ensure that the element is a textarea | |
| 8 | + let textarea = this; | |
| 9 | + if (!(textarea instanceof HTMLTextAreaElement)) { | |
| 10 | + return; | |
| 11 | + } | |
| 12 | + | |
| 13 | + // Early return on only control or no control, alt key, and win/cmd key | |
| 14 | + if (e.key === 'Control' || !e.ctrlKey || e.altKey || e.metaKey || (e.shiftKey && !(e.ctrlKey && e.shiftKey && e.code === 'Backquote'))) { | |
| 15 | + return; | |
| 16 | + } | |
| 17 | + let charsToAdd = ''; | |
| 18 | + let possiblePreviousFormattingMargin = 1; | |
| 19 | + | |
| 20 | + switch (true) { | |
| 21 | + case e.ctrlKey && e.shiftKey && e.code === 'Backquote': | |
| 22 | + e.preventDefault(); | |
| 23 | + e.stopPropagation(); | |
| 24 | + charsToAdd = '~~'; | |
| 25 | + possiblePreviousFormattingMargin = 2; | |
| 26 | + break; | |
| 27 | + case e.ctrlKey && e.code === 'KeyB': | |
| 28 | + e.preventDefault(); | |
| 29 | + e.stopPropagation(); | |
| 30 | + charsToAdd = '**'; | |
| 31 | + possiblePreviousFormattingMargin = 2; | |
| 32 | + break; | |
| 33 | + case e.ctrlKey && e.code === 'KeyI': | |
| 34 | + e.preventDefault(); | |
| 35 | + e.stopPropagation(); | |
| 36 | + charsToAdd = '*'; | |
| 37 | + break; | |
| 38 | + case e.ctrlKey && e.code === 'KeyU': | |
| 39 | + e.preventDefault(); | |
| 40 | + e.stopPropagation(); | |
| 41 | + charsToAdd = '__'; | |
| 42 | + possiblePreviousFormattingMargin = 2; | |
| 43 | + break; | |
| 44 | + case e.ctrlKey && e.code === 'KeyK': | |
| 45 | + e.preventDefault(); | |
| 46 | + e.stopPropagation(); | |
| 47 | + charsToAdd = '`'; | |
| 48 | + break; | |
| 49 | + default: | |
| 50 | + return; // Early return if no key matches | |
| 51 | + } | |
| 52 | + | |
| 53 | + let selectedText = ''; | |
| 54 | + let start = textarea.selectionStart; | |
| 55 | + let end = textarea.selectionEnd; | |
| 56 | + let beforeCaret = textarea.value.substring(start - 1, start); | |
| 57 | + let afterCaret = textarea.value.substring(end, end + 1); | |
| 58 | + let isTextSelected = (start !== end); | |
| 59 | + let cursorShift = charsToAdd.length; | |
| 60 | + let selectedTextandPossibleFormatting = textarea.value.substring(start - possiblePreviousFormattingMargin, end + possiblePreviousFormattingMargin).trim(); | |
| 61 | + | |
| 62 | + if (isTextSelected) { //if text is selected | |
| 63 | + selectedText = textarea.value.substring(start, end); | |
| 64 | + if (selectedTextandPossibleFormatting === charsToAdd + selectedText + charsToAdd) { | |
| 65 | + // If the selected text is already formatted, remove the formatting | |
| 66 | + | |
| 67 | + let expandedStart = start - charsToAdd.length; | |
| 68 | + let expandedEnd = end + charsToAdd.length; | |
| 69 | + | |
| 70 | + // Ensure expanded range is within the bounds of the text | |
| 71 | + if (expandedStart < 0) expandedStart = 0; | |
| 72 | + if (expandedEnd > textarea.value.length) expandedEnd = textarea.value.length; | |
| 73 | + | |
| 74 | + // Select the expanded range | |
| 75 | + textarea.setSelectionRange(expandedStart, expandedEnd); | |
| 76 | + | |
| 77 | + // Replace the expanded selection with the original selected text | |
| 78 | + document.execCommand('insertText', false, selectedText); | |
| 79 | + // Adjust cursor position | |
| 80 | + cursorShift = -charsToAdd.length; | |
| 81 | + } else { | |
| 82 | + // Add formatting to the selected text | |
| 83 | + let possibleAddedSpace = ''; | |
| 84 | + if (selectedText.endsWith(' ')) { | |
| 85 | + possibleAddedSpace = ' '; | |
| 86 | + selectedText = selectedText.substring(0, selectedText.length - 1); | |
| 87 | + end--; // Adjust the end index since we removed the space | |
| 88 | + } | |
| 89 | + // To add the formatting, we need to select the text first | |
| 90 | + textarea.focus(); | |
| 91 | + document.execCommand('insertText', false, charsToAdd + selectedText + charsToAdd + possibleAddedSpace); | |
| 92 | + } | |
| 93 | + } else {// No text is selected | |
| 94 | + //check 1 character before and after the cursor for non-space characters | |
| 95 | + | |
| 96 | + if (beforeCaret !== ' ' && afterCaret !== ' ' && afterCaret !== '' && beforeCaret !== '') { //look for caret in the middle of a word | |
| 97 | + //expand the selection range until the next space on both sides | |
| 98 | + let midCaretExpandedStart = start - 1; | |
| 99 | + let midCaretExpandedEnd = end + 1; | |
| 100 | + while (midCaretExpandedStart > 0 && textarea.value.substring(midCaretExpandedStart - 1, midCaretExpandedStart) !== ' ') { | |
| 101 | + midCaretExpandedStart--; | |
| 102 | + } | |
| 103 | + while (midCaretExpandedEnd < textarea.value.length && textarea.value.substring(midCaretExpandedEnd, midCaretExpandedEnd + 1) !== ' ') { | |
| 104 | + midCaretExpandedEnd++; | |
| 105 | + } | |
| 106 | + //make a selection of the discovered word | |
| 107 | + textarea.setSelectionRange(midCaretExpandedStart, midCaretExpandedEnd); | |
| 108 | + //set variables for comparison | |
| 109 | + let discoveredWordWithPossibleFormatting = textarea.value.substring(midCaretExpandedStart, midCaretExpandedEnd).trim(); | |
| 110 | + let discoveredWord = ''; | |
| 111 | + | |
| 112 | + if (discoveredWordWithPossibleFormatting.endsWith(charsToAdd) && discoveredWordWithPossibleFormatting.startsWith(charsToAdd)) { | |
| 113 | + discoveredWord = textarea.value.substring(midCaretExpandedStart + charsToAdd.length, midCaretExpandedEnd - charsToAdd.length).trim(); | |
| 114 | + } else { | |
| 115 | + discoveredWord = textarea.value.substring(midCaretExpandedStart, midCaretExpandedEnd).trim(); | |
| 116 | + } | |
| 117 | + | |
| 118 | + if (charsToAdd + discoveredWord + charsToAdd === discoveredWordWithPossibleFormatting) { | |
| 119 | + | |
| 120 | + // Replace the expanded selection with the original discovered word | |
| 121 | + textarea.focus(); | |
| 122 | + document.execCommand('insertText', false, discoveredWord); | |
| 123 | + // Adjust cursor position | |
| 124 | + cursorShift = -charsToAdd.length; | |
| 125 | + } else { //format did not previously exist, so add it | |
| 126 | + textarea.focus(); | |
| 127 | + document.execCommand('insertText', false, charsToAdd + discoveredWord + charsToAdd); | |
| 128 | + } | |
| 129 | + | |
| 130 | + | |
| 131 | + } else { //caret is not inside a word, so just add the formatting | |
| 132 | + textarea.focus(); | |
| 133 | + textarea.setSelectionRange(start, end); | |
| 134 | + selectedText = textarea.value.substring(start, end); | |
| 135 | + document.execCommand('insertText', false, charsToAdd + selectedText + charsToAdd); | |
| 136 | + } | |
| 137 | + } | |
| 138 | + | |
| 139 | + // Manually trigger the 'input' event to make undo/redo work | |
| 140 | + let event = new Event('input', { bubbles: true }); | |
| 141 | + textarea.dispatchEvent(event); // This notifies the browser of a change, allowing undo/redo to function. | |
| 142 | + | |
| 143 | + // Update the cursor position | |
| 144 | + if (isTextSelected) { | |
| 145 | + textarea.selectionStart = start + cursorShift; | |
| 146 | + textarea.selectionEnd = start + cursorShift + selectedText.length; | |
| 147 | + } else { | |
| 148 | + textarea.selectionStart = start + cursorShift; | |
| 149 | + textarea.selectionEnd = start + cursorShift; | |
| 150 | + } | |
| 151 | + }); | |
| 152 | +} | |
| @@ -200,6 +200,7 @@ let power_user = { | ||
| 200 | 200 | relaxed_api_urls: false, |
| 201 | 201 | world_import_dialog: true, |
| 202 | 202 | enable_auto_select_input: false, |
| 203 | + enable_md_hotkeys: false, | |
| 203 | 204 | tag_import_setting: tag_import_setting.ASK, |
| 204 | 205 | disable_group_trimming: false, |
| 205 | 206 | single_line: false, |
| @@ -1452,6 +1453,7 @@ async function loadPowerUserSettings(settings, data) { | ||
| 1452 | 1453 | $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls); |
| 1453 | 1454 | $('#world_import_dialog').prop('checked', power_user.world_import_dialog); |
| 1454 | 1455 | $('#enable_auto_select_input').prop('checked', power_user.enable_auto_select_input); |
| 1456 | + $('#enable_md_hotkeys').prop('checked', power_user.enable_md_hotkeys); | |
| 1455 | 1457 | $('#trim_spaces').prop('checked', power_user.trim_spaces); |
| 1456 | 1458 | $('#continue_on_send').prop('checked', power_user.continue_on_send); |
| 1457 | 1459 | $('#quick_continue').prop('checked', power_user.quick_continue); |
| @@ -1601,6 +1603,17 @@ async function loadPowerUserSettings(settings, data) { | ||
| 1601 | 1603 | switchSpoilerMode(); |
| 1602 | 1604 | loadMovingUIState(); |
| 1603 | 1605 | loadCharListState(); |
| 1606 | + toggleMDHotkeyIconDisplay(); | |
| 1607 | +} | |
| 1608 | + | |
| 1609 | +function toggleMDHotkeyIconDisplay() { | |
| 1610 | + if (power_user.enable_md_hotkeys) { | |
| 1611 | + $('.mdhotkey_location').each(function () { | |
| 1612 | + $(this).parent().append('<i class="fa-brands fa-markdown mdhotkey_icon"></i>'); | |
| 1613 | + }); | |
| 1614 | + } else { | |
| 1615 | + $('.mdhotkey_icon').remove(); | |
| 1616 | + } | |
| 1604 | 1617 | } |
| 1605 | 1618 | |
| 1606 | 1619 | function loadCharListState() { |
| @@ -3610,6 +3623,13 @@ $(document).ready(() => { | ||
| 3610 | 3623 | saveSettingsDebounced(); |
| 3611 | 3624 | }); |
| 3612 | 3625 | |
| 3626 | + $('#enable_md_hotkeys').on('input', function () { | |
| 3627 | + const value = !!$(this).prop('checked'); | |
| 3628 | + power_user.enable_md_hotkeys = value; | |
| 3629 | + toggleMDHotkeyIconDisplay(); | |
| 3630 | + saveSettingsDebounced(); | |
| 3631 | + }); | |
| 3632 | + | |
| 3613 | 3633 | $('#spoiler_free_mode').on('input', function () { |
| 3614 | 3634 | power_user.spoiler_free_mode = !!$(this).prop('checked'); |
| 3615 | 3635 | switchSpoilerMode(); |
| @@ -1,13 +1,31 @@ | ||
| 1 | -<span data-i18n="help_hotkeys_0">Hotkeys/Keybinds</span>: | |
| 1 | +<div> | |
| 2 | + <strong data-i18n="help_hotkeys_0">Chat Hotkeys</strong> | |
| 3 | +</div> | |
| 2 | 4 | <ul> |
| 3 | 5 | <li><ttkbd data-i18n="help_hotkeys_1">Up</ttkbd> = <span data-i18n="help_hotkeys_2">Edit last message in chat</span></li> |
| 4 | 6 | <li><ttkbd data-i18n="help_hotkeys_3">Ctrl+Up</ttkbd> = <span data-i18n="help_hotkeys_4">Edit last USER message in chat</span></li> |
| 5 | 7 | <li><ttkbd data-i18n="help_hotkeys_5">Left</ttkbd> = <span data-i18n="help_hotkeys_6">swipe left</span></li> |
| 6 | 8 | <li><ttkbd data-i18n="help_hotkeys_7">Right</ttkbd> = <span data-i18n="help_hotkeys_8">swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</span></li> |
| 7 | 9 | <li><ttkbd data-i18n="help_hotkeys_9">Enter</ttkbd> <span data-i18n="help_hotkeys_10">(with chat bar selected)</span> = <span data-i18n="help_hotkeys_10_1">send your message to AI</span></li> |
| 8 | 10 | <li><ttkbd data-i18n="help_hotkeys_11">Ctrl+Enter</ttkbd> = <span data-i18n="help_hotkeys_12">Regenerate the last AI response</span></li> |
| 9 | 11 | <li><ttkbd data-i18n="help_hotkeys_13">Alt+Enter</ttkbd> = <span data-i18n="help_hotkeys_14">Continue the last AI response</span></li> |
| 10 | 12 | <li><ttkbd data-i18n="help_hotkeys_15">Escape</ttkbd> = <span data-i18n="help_hotkeys_16">stop AI response generation, close UI panels, cancel message edit</span></li> |
| 11 | 13 | <li><ttkbd data-i18n="help_hotkeys_17">Ctrl+Shift+Up</ttkbd> = <span data-i18n="help_hotkeys_18">Scroll to context line</span></li> |
| 12 | 14 | <li><ttkbd data-i18n="help_hotkeys_19">Ctrl+Shift+Down</ttkbd> = <span data-i18n="help_hotkeys_20">Scroll chat to bottom</span></li> |
| 15 | +</ul> | |
| 16 | +<div> | |
| 17 | + <strong>Markdown Hotkeys</strong> | |
| 18 | +</div> | |
| 19 | +<div> | |
| 20 | + <small> | |
| 21 | + <span>Works in the chatbar and textareas marked with this icon:</span> | |
| 22 | + <code><i class="fa-brands fa-markdown"></i></code> | |
| 23 | + </small> | |
| 24 | +</div> | |
| 25 | +<ul> | |
| 26 | + <li><kbd>Ctrl+B</kbd> = <span>**bold**</span></li> | |
| 27 | + <li><kbd>Ctrl+I</kbd> = <span>*italic*</span></li> | |
| 28 | + <li><kbd>Ctrl+U</kbd> = <span>__underline__</span></li> | |
| 29 | + <li><kbd>Ctrl+K</kbd> = <span>`inline code`</span></li> | |
| 30 | + <li><kbd>Ctrl+Shift+~</kbd> = <span>~~strikethrough~~</span></li> | |
| 13 | 31 | </ul> |