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 | #smooth_streaming:checked~#smooth_streaming_speed_control { | 455 | #smooth_streaming:checked~#smooth_streaming_speed_control { |
| 456 | display: block; | 456 | display: block; |
| 457 | } | 457 | } |
| 458 | |||
| 459 | .mdhotkey_icon { | ||
| 460 | opacity: 0.6; | ||
| 461 | } | ||
| @@ -4037,6 +4037,13 @@ | |||
| 4037 | <input id="enable_auto_select_input" type="checkbox" /> | 4037 | <input id="enable_auto_select_input" type="checkbox" /> |
| 4038 | <small data-i18n="Auto-select Input Text">Auto-select Input Text</small> | 4038 | <small data-i18n="Auto-select Input Text">Auto-select Input Text</small> |
| 4039 | </label> | 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 | <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"> | 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 | <input id="restore_user_input" type="checkbox" /> | 4048 | <input id="restore_user_input" type="checkbox" /> |
| 4042 | <small data-i18n="Restore User Input">Restore User Input</small> | 4049 | <small data-i18n="Restore User Input">Restore User Input</small> |
| @@ -4720,7 +4727,7 @@ | |||
| 4720 | <hr> | 4727 | <hr> |
| 4721 | <div id="description_div" class="title_restorable"> | 4728 | <div id="description_div" class="title_restorable"> |
| 4722 | <div class="flex-container alignitemscenter"> | 4729 | <div class="flex-container alignitemscenter"> |
| 4723 | <span data-i18n="Character Description">Description</span> | 4730 | <span data-i18n="Character Description" class="mdhotkey_location">Description</span> |
| 4724 | <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> | 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 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank"> | 4732 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank"> |
| 4726 | <span class="fa-solid fa-circle-question note-link-span"></span> | 4733 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| @@ -4734,7 +4741,7 @@ | |||
| 4734 | </span> | 4741 | </span> |
| 4735 | </div> | 4742 | </div> |
| 4736 | </div> | 4743 | </div> |
| 4737 | <textarea id="description_textarea" 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> | 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 | <div class="extension_token_counter"> | 4745 | <div class="extension_token_counter"> |
| 4739 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span> | 4746 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="description_textarea" data-token-permanent="true">counting...</span> |
| 4740 | </div> | 4747 | </div> |
| @@ -4742,7 +4749,7 @@ | |||
| 4742 | <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1"> | 4749 | <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1"> |
| 4743 | <div id="first_message_div" class="title_restorable"> | 4750 | <div id="first_message_div" class="title_restorable"> |
| 4744 | <div class="flex-container alignitemscenter flex1"> | 4751 | <div class="flex-container alignitemscenter flex1"> |
| 4745 | <span data-i18n="First message">First message</span> | 4752 | <span data-i18n="First message" class="mdhotkey_location">First message</span> |
| 4746 | <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> | 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 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#first-message" class="notes-link" target="_blank"> | 4754 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#first-message" class="notes-link" target="_blank"> |
| 4748 | <span class="fa-solid fa-circle-question note-link-span"></span> | 4755 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| @@ -4754,7 +4761,7 @@ | |||
| 4754 | </span> | 4761 | </span> |
| 4755 | </div> | 4762 | </div> |
| 4756 | </div> | 4763 | </div> |
| 4757 | <textarea 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> | 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 | <div class="extension_token_counter"> | 4765 | <div class="extension_token_counter"> |
| 4759 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="firstmessage_textarea">counting...</span> | 4766 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="firstmessage_textarea">counting...</span> |
| 4760 | </div> | 4767 | </div> |
| @@ -5112,7 +5119,7 @@ | |||
| 5112 | </div> | 5119 | </div> |
| 5113 | <hr> | 5120 | <hr> |
| 5114 | <div id="personality_div"> | 5121 | <div id="personality_div"> |
| 5115 | <h4> | 5122 | <h4 class="flex-container alignItemsBaseline"> |
| 5116 | <span data-i18n="Personality summary">Personality summary</span> | 5123 | <span data-i18n="Personality summary">Personality summary</span> |
| 5117 | <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> | 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 | </h4> | 5125 | </h4> |
| @@ -5122,7 +5129,7 @@ | |||
| 5122 | </div> | 5129 | </div> |
| 5123 | </div> | 5130 | </div> |
| 5124 | <div id="scenario_div"> | 5131 | <div id="scenario_div"> |
| 5125 | <h4> | 5132 | <h4 class="flex-container alignItemsBaseline"> |
| 5126 | <span data-i18n="Scenario">Scenario</span> | 5133 | <span data-i18n="Scenario">Scenario</span> |
| 5127 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#scenario" class="notes-link" target="_blank"> | 5134 | <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#scenario" class="notes-link" target="_blank"> |
| 5128 | <span class="fa-solid fa-circle-question note-link-span"></span> | 5135 | <span class="fa-solid fa-circle-question note-link-span"></span> |
| @@ -5179,10 +5186,10 @@ | |||
| 5179 | <hr> | 5186 | <hr> |
| 5180 | <div id="mes_example_div" class="flex-container flexFlowColumn"> | 5187 | <div id="mes_example_div" class="flex-container flexFlowColumn"> |
| 5181 | <div> | 5188 | <div> |
| 5182 | <h4><span data-i18n="Examples of dialogue">Examples of dialogue</span></h4> | 5189 | <h4 class="flex-container gap5px alignItemsBaseline"><span data-i18n="Examples of dialogue" class="mdhotkey_location">Examples of dialogue</span></h4> |
| 5183 | <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> | 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 | </div> | 5191 | </div> |
| 5185 | <textarea id="mes_example_textarea" class="flexGrow" 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> | 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 | <div class="extension_token_counter"> | 5193 | <div class="extension_token_counter"> |
| 5187 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="mes_example_textarea">counting...</span> | 5194 | <span data-i18n="extension_token_counter">Tokens:</span> <span data-token-counter="mes_example_textarea">counting...</span> |
| 5188 | </div> | 5195 | </div> |
| @@ -5477,7 +5484,7 @@ | |||
| 5477 | <label for="content "> | 5484 | <label for="content "> |
| 5478 | <small> | 5485 | <small> |
| 5479 | <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween"> | 5486 | <span class="alignitemscenter flex-container flexnowrap wide100p justifySpaceBetween"> |
| 5480 | <span data-i18n="Content" class="alignitemscenter flex-container flexNoGap"> | 5487 | <span data-i18n="Content" class="alignitemscenter flex-container flexNoGap mdhotkey_location"> |
| 5481 | Content | 5488 | Content |
| 5482 | </span> | 5489 | </span> |
| 5483 | <span> | 5490 | <span> |
| @@ -5512,7 +5519,7 @@ | |||
| 5512 | </span> | 5519 | </span> |
| 5513 | </small> | 5520 | </small> |
| 5514 | </label> | 5521 | </label> |
| 5515 | <textarea class="text_pole" 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> | 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 | </div> | 5523 | </div> |
| 5517 | </div> | 5524 | </div> |
| 5518 | <div class="world_entry_thin_controls commentContainer"> | 5525 | <div class="world_entry_thin_controls commentContainer"> |
| @@ -5987,7 +5994,7 @@ | |||
| 5987 | <div id="alternate_greetings_template" class="template_element"> | 5994 | <div id="alternate_greetings_template" class="template_element"> |
| 5988 | <div class="alternate_grettings flexFlowColumn flex-container"> | 5995 | <div class="alternate_grettings flexFlowColumn flex-container"> |
| 5989 | <div class="title_restorable"> | 5996 | <div class="title_restorable"> |
| 5990 | <h3><span data-i18n="Alternate Greetings">Alternate Greetings</span></h3> | 5997 | <h3><span data-i18n="Alternate Greetings" class="mdhotkey_location">Alternate Greetings</span></h3> |
| 5991 | <div title="Add" class="menu_button fa-solid fa-plus add_alternate_greeting" data-i18n="[title]Add"></div> | 5998 | <div title="Add" class="menu_button fa-solid fa-plus add_alternate_greeting" data-i18n="[title]Add"></div> |
| 5992 | </div> | 5999 | </div> |
| 5993 | <small class="justifyLeft" data-i18n="Alternate_Greetings_desc"> | 6000 | <small class="justifyLeft" data-i18n="Alternate_Greetings_desc"> |
| @@ -6008,7 +6015,7 @@ | |||
| 6008 | <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong> | 6015 | <strong><span data-i18n="Alternate Greeting #">Alternate Greeting #</span><span class="greeting_index"></span></strong> |
| 6009 | <div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div> | 6016 | <div class="menu_button fa-solid fa-trash-alt delete_alternate_greeting"></div> |
| 6010 | </div> | 6017 | </div> |
| 6011 | <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" value="" autocomplete="off" rows="16"></textarea> | 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 | </div> | 6019 | </div> |
| 6013 | </div> | 6020 | </div> |
| 6014 | 6021 | ||
| @@ -6392,7 +6399,7 @@ | |||
| 6392 | <div id="leftSendForm" class="alignContentCenter"> | 6399 | <div id="leftSendForm" class="alignContentCenter"> |
| 6393 | <div id="options_button" class="fa-solid fa-bars interactable"></div> | 6400 | <div id="options_button" class="fa-solid fa-bars interactable"></div> |
| 6394 | </div> | 6401 | </div> |
| 6395 | <textarea id="send_textarea" name="text" 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> | 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 | <div id="rightSendForm" class="alignContentCenter"> | 6403 | <div id="rightSendForm" class="alignContentCenter"> |
| 6397 | <div id="stscript_continue" title="Continue script execution" class="stscript_btn stscript_continue" data-i18n="[title]Continue script execution"> | 6404 | <div id="stscript_continue" title="Continue script execution" class="stscript_btn stscript_continue" data-i18n="[title]Continue script execution"> |
| 6398 | <i class="fa-solid fa-play"></i> | 6405 | <i class="fa-solid fa-play"></i> |
| @@ -242,6 +242,7 @@ import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js' | |||
| 242 | import { initDynamicStyles } from './scripts/dynamic-styles.js'; | 242 | import { initDynamicStyles } from './scripts/dynamic-styles.js'; |
| 243 | import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js'; | 243 | import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js'; |
| 244 | import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js'; | 244 | import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 245 | import { initInputMarkdown } from './scripts/input-md-formatting.js'; | ||
| 245 | import { AbortReason } from './scripts/util/AbortReason.js'; | 246 | import { AbortReason } from './scripts/util/AbortReason.js'; |
| 246 | 247 | ||
| 247 | //exporting functions and vars for mods | 248 | //exporting functions and vars for mods |
| @@ -951,6 +952,7 @@ async function firstLoadInit() { | |||
| 951 | initStats(); | 952 | initStats(); |
| 952 | initCfg(); | 953 | initCfg(); |
| 953 | initLogprobs(); | 954 | initLogprobs(); |
| 955 | initInputMarkdown(); | ||
| 954 | doDailyExtensionUpdatesCheck(); | 956 | doDailyExtensionUpdatesCheck(); |
| 955 | await hideLoader(); | 957 | await hideLoader(); |
| 956 | await fixViewport(); | 958 | await fixViewport(); |
| @@ -10200,7 +10202,7 @@ jQuery(async function () { | |||
| 10200 | .closest('.mes_block') | 10202 | .closest('.mes_block') |
| 10201 | .find('.mes_text') | 10203 | .find('.mes_text') |
| 10202 | .append( | 10204 | .append( |
| 10203 | '<textarea id=\'curEditTextarea\' class=\'edit_textarea\' style=\'max-width:auto;\'></textarea>', | 10205 | '<textarea id=\'curEditTextarea\' class=\'edit_textarea mdHotkeys\' style=\'max-width:auto;\'></textarea>', |
| 10204 | ); | 10206 | ); |
| 10205 | $('#curEditTextarea').val(text); | 10207 | $('#curEditTextarea').val(text); |
| 10206 | let edit_textarea = $(this) | 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 | relaxed_api_urls: false, | 200 | relaxed_api_urls: false, |
| 201 | world_import_dialog: true, | 201 | world_import_dialog: true, |
| 202 | enable_auto_select_input: false, | 202 | enable_auto_select_input: false, |
| 203 | enable_md_hotkeys: false, | ||
| 203 | tag_import_setting: tag_import_setting.ASK, | 204 | tag_import_setting: tag_import_setting.ASK, |
| 204 | disable_group_trimming: false, | 205 | disable_group_trimming: false, |
| 205 | single_line: false, | 206 | single_line: false, |
| @@ -1452,6 +1453,7 @@ async function loadPowerUserSettings(settings, data) { | |||
| 1452 | $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls); | 1453 | $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls); |
| 1453 | $('#world_import_dialog').prop('checked', power_user.world_import_dialog); | 1454 | $('#world_import_dialog').prop('checked', power_user.world_import_dialog); |
| 1454 | $('#enable_auto_select_input').prop('checked', power_user.enable_auto_select_input); | 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 | $('#trim_spaces').prop('checked', power_user.trim_spaces); | 1457 | $('#trim_spaces').prop('checked', power_user.trim_spaces); |
| 1456 | $('#continue_on_send').prop('checked', power_user.continue_on_send); | 1458 | $('#continue_on_send').prop('checked', power_user.continue_on_send); |
| 1457 | $('#quick_continue').prop('checked', power_user.quick_continue); | 1459 | $('#quick_continue').prop('checked', power_user.quick_continue); |
| @@ -1601,6 +1603,17 @@ async function loadPowerUserSettings(settings, data) { | |||
| 1601 | switchSpoilerMode(); | 1603 | switchSpoilerMode(); |
| 1602 | loadMovingUIState(); | 1604 | loadMovingUIState(); |
| 1603 | loadCharListState(); | 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 | function loadCharListState() { | 1619 | function loadCharListState() { |
| @@ -3610,6 +3623,13 @@ $(document).ready(() => { | |||
| 3610 | saveSettingsDebounced(); | 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 | $('#spoiler_free_mode').on('input', function () { | 3633 | $('#spoiler_free_mode').on('input', function () { |
| 3614 | power_user.spoiler_free_mode = !!$(this).prop('checked'); | 3634 | power_user.spoiler_free_mode = !!$(this).prop('checked'); |
| 3615 | switchSpoilerMode(); | 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 | <ul> | 4 | <ul> |
| 3 | <li><tt data-i18n="help_hotkeys_1">Up</tt> = <span data-i18n="help_hotkeys_2">Edit last message in chat</span></li> | 5 | <li><kbd data-i18n="help_hotkeys_1">Up</kbd> = <span data-i18n="help_hotkeys_2">Edit last message in chat</span></li> |
| 4 | <li><tt data-i18n="help_hotkeys_3">Ctrl+Up</tt> = <span data-i18n="help_hotkeys_4">Edit last USER message in chat</span></li> | 6 | <li><kbd data-i18n="help_hotkeys_3">Ctrl+Up</kbd> = <span data-i18n="help_hotkeys_4">Edit last USER message in chat</span></li> |
| 5 | <li><tt data-i18n="help_hotkeys_5">Left</tt> = <span data-i18n="help_hotkeys_6">swipe left</span></li> | 7 | <li><kbd data-i18n="help_hotkeys_5">Left</kbd> = <span data-i18n="help_hotkeys_6">swipe left</span></li> |
| 6 | <li><tt data-i18n="help_hotkeys_7">Right</tt> = <span data-i18n="help_hotkeys_8">swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</span></li> | 8 | <li><kbd data-i18n="help_hotkeys_7">Right</kbd> = <span data-i18n="help_hotkeys_8">swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)</span></li> |
| 7 | <li><tt data-i18n="help_hotkeys_9">Enter</tt> <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> | 9 | <li><kbd data-i18n="help_hotkeys_9">Enter</kbd> <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 | <li><tt data-i18n="help_hotkeys_11">Ctrl+Enter</tt> = <span data-i18n="help_hotkeys_12">Regenerate the last AI response</span></li> | 10 | <li><kbd data-i18n="help_hotkeys_11">Ctrl+Enter</kbd> = <span data-i18n="help_hotkeys_12">Regenerate the last AI response</span></li> |
| 9 | <li><tt data-i18n="help_hotkeys_13">Alt+Enter</tt> = <span data-i18n="help_hotkeys_14">Continue the last AI response</span></li> | 11 | <li><kbd data-i18n="help_hotkeys_13">Alt+Enter</kbd> = <span data-i18n="help_hotkeys_14">Continue the last AI response</span></li> |
| 10 | <li><tt data-i18n="help_hotkeys_15">Escape</tt> = <span data-i18n="help_hotkeys_16">stop AI response generation, close UI panels, cancel message edit</span></li> | 12 | <li><kbd data-i18n="help_hotkeys_15">Escape</kbd> = <span data-i18n="help_hotkeys_16">stop AI response generation, close UI panels, cancel message edit</span></li> |
| 11 | <li><tt data-i18n="help_hotkeys_17">Ctrl+Shift+Up</tt> = <span data-i18n="help_hotkeys_18">Scroll to context line</span></li> | 13 | <li><kbd data-i18n="help_hotkeys_17">Ctrl+Shift+Up</kbd> = <span data-i18n="help_hotkeys_18">Scroll to context line</span></li> |
| 12 | <li><tt data-i18n="help_hotkeys_19">Ctrl+Shift+Down</tt> = <span data-i18n="help_hotkeys_20">Scroll chat to bottom</span></li> | 14 | <li><kbd data-i18n="help_hotkeys_19">Ctrl+Shift+Down</kbd> = <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 | </ul> | 31 | </ul> |