mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	* 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>
		
			
				
	
	
		
			153 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { power_user } from './power-user.js';
 | |
| 
 | |
| export function initInputMarkdown() {
 | |
|     $(document).on('keydown', 'textarea.mdHotkeys', function (e) {
 | |
|         if (!power_user.enable_md_hotkeys) { return; }
 | |
| 
 | |
|         // Ensure that the element is a textarea
 | |
|         let textarea = this;
 | |
|         if (!(textarea instanceof HTMLTextAreaElement)) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         // Early return on only control or no control, alt key, and win/cmd key
 | |
|         if (e.key === 'Control' || !e.ctrlKey || e.altKey || e.metaKey || (e.shiftKey && !(e.ctrlKey && e.shiftKey && e.code === 'Backquote'))) {
 | |
|             return;
 | |
|         }
 | |
|         let charsToAdd = '';
 | |
|         let possiblePreviousFormattingMargin = 1;
 | |
| 
 | |
|         switch (true) {
 | |
|             case e.ctrlKey && e.shiftKey && e.code === 'Backquote':
 | |
|                 e.preventDefault();
 | |
|                 e.stopPropagation();
 | |
|                 charsToAdd = '~~';
 | |
|                 possiblePreviousFormattingMargin = 2;
 | |
|                 break;
 | |
|             case e.ctrlKey && e.code === 'KeyB':
 | |
|                 e.preventDefault();
 | |
|                 e.stopPropagation();
 | |
|                 charsToAdd = '**';
 | |
|                 possiblePreviousFormattingMargin = 2;
 | |
|                 break;
 | |
|             case e.ctrlKey && e.code === 'KeyI':
 | |
|                 e.preventDefault();
 | |
|                 e.stopPropagation();
 | |
|                 charsToAdd = '*';
 | |
|                 break;
 | |
|             case e.ctrlKey && e.code === 'KeyU':
 | |
|                 e.preventDefault();
 | |
|                 e.stopPropagation();
 | |
|                 charsToAdd = '__';
 | |
|                 possiblePreviousFormattingMargin = 2;
 | |
|                 break;
 | |
|             case e.ctrlKey && e.code === 'KeyK':
 | |
|                 e.preventDefault();
 | |
|                 e.stopPropagation();
 | |
|                 charsToAdd = '`';
 | |
|                 break;
 | |
|             default:
 | |
|                 return; // Early return if no key matches
 | |
|         }
 | |
| 
 | |
|         let selectedText = '';
 | |
|         let start = textarea.selectionStart;
 | |
|         let end = textarea.selectionEnd;
 | |
|         let beforeCaret = textarea.value.substring(start - 1, start);
 | |
|         let afterCaret = textarea.value.substring(end, end + 1);
 | |
|         let isTextSelected = (start !== end);
 | |
|         let cursorShift = charsToAdd.length;
 | |
|         let selectedTextandPossibleFormatting = textarea.value.substring(start - possiblePreviousFormattingMargin, end + possiblePreviousFormattingMargin).trim();
 | |
| 
 | |
|         if (isTextSelected) { //if text is selected
 | |
|             selectedText = textarea.value.substring(start, end);
 | |
|             if (selectedTextandPossibleFormatting === charsToAdd + selectedText + charsToAdd) {
 | |
|                 // If the selected text is already formatted, remove the formatting
 | |
| 
 | |
|                 let expandedStart = start - charsToAdd.length;
 | |
|                 let expandedEnd = end + charsToAdd.length;
 | |
| 
 | |
|                 // Ensure expanded range is within the bounds of the text
 | |
|                 if (expandedStart < 0) expandedStart = 0;
 | |
|                 if (expandedEnd > textarea.value.length) expandedEnd = textarea.value.length;
 | |
| 
 | |
|                 // Select the expanded range
 | |
|                 textarea.setSelectionRange(expandedStart, expandedEnd);
 | |
| 
 | |
|                 // Replace the expanded selection with the original selected text
 | |
|                 document.execCommand('insertText', false, selectedText);
 | |
|                 // Adjust cursor position
 | |
|                 cursorShift = -charsToAdd.length;
 | |
|             } else {
 | |
|                 // Add formatting to the selected text
 | |
|                 let possibleAddedSpace = '';
 | |
|                 if (selectedText.endsWith(' ')) {
 | |
|                     possibleAddedSpace = ' ';
 | |
|                     selectedText = selectedText.substring(0, selectedText.length - 1);
 | |
|                     end--; // Adjust the end index since we removed the space
 | |
|                 }
 | |
|                 // To add the formatting, we need to select the text first
 | |
|                 textarea.focus();
 | |
|                 document.execCommand('insertText', false, charsToAdd + selectedText + charsToAdd + possibleAddedSpace);
 | |
|             }
 | |
|         } else {// No text is selected
 | |
|             //check 1 character before and after the cursor for non-space characters
 | |
| 
 | |
|             if (beforeCaret !== ' ' && afterCaret !== ' ' && afterCaret !== '' && beforeCaret !== '') { //look for caret in the middle of a word
 | |
|                 //expand the selection range until the next space on both sides
 | |
|                 let midCaretExpandedStart = start - 1;
 | |
|                 let midCaretExpandedEnd = end + 1;
 | |
|                 while (midCaretExpandedStart > 0 && textarea.value.substring(midCaretExpandedStart - 1, midCaretExpandedStart) !== ' ') {
 | |
|                     midCaretExpandedStart--;
 | |
|                 }
 | |
|                 while (midCaretExpandedEnd < textarea.value.length && textarea.value.substring(midCaretExpandedEnd, midCaretExpandedEnd + 1) !== ' ') {
 | |
|                     midCaretExpandedEnd++;
 | |
|                 }
 | |
|                 //make a selection of the discovered word
 | |
|                 textarea.setSelectionRange(midCaretExpandedStart, midCaretExpandedEnd);
 | |
|                 //set variables for comparison
 | |
|                 let discoveredWordWithPossibleFormatting = textarea.value.substring(midCaretExpandedStart, midCaretExpandedEnd).trim();
 | |
|                 let discoveredWord = '';
 | |
| 
 | |
|                 if (discoveredWordWithPossibleFormatting.endsWith(charsToAdd) && discoveredWordWithPossibleFormatting.startsWith(charsToAdd)) {
 | |
|                     discoveredWord = textarea.value.substring(midCaretExpandedStart + charsToAdd.length, midCaretExpandedEnd - charsToAdd.length).trim();
 | |
|                 } else {
 | |
|                     discoveredWord = textarea.value.substring(midCaretExpandedStart, midCaretExpandedEnd).trim();
 | |
|                 }
 | |
| 
 | |
|                 if (charsToAdd + discoveredWord + charsToAdd === discoveredWordWithPossibleFormatting) {
 | |
| 
 | |
|                     // Replace the expanded selection with the original discovered word
 | |
|                     textarea.focus();
 | |
|                     document.execCommand('insertText', false, discoveredWord);
 | |
|                     // Adjust cursor position
 | |
|                     cursorShift = -charsToAdd.length;
 | |
|                 } else { //format did not previously exist, so add it
 | |
|                     textarea.focus();
 | |
|                     document.execCommand('insertText', false, charsToAdd + discoveredWord + charsToAdd);
 | |
|                 }
 | |
| 
 | |
| 
 | |
|             } else { //caret is not inside a word, so just add the formatting
 | |
|                 textarea.focus();
 | |
|                 textarea.setSelectionRange(start, end);
 | |
|                 selectedText = textarea.value.substring(start, end);
 | |
|                 document.execCommand('insertText', false, charsToAdd + selectedText + charsToAdd);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Manually trigger the 'input' event to make undo/redo work
 | |
|         let event = new Event('input', { bubbles: true });
 | |
|         textarea.dispatchEvent(event); // This notifies the browser of a change, allowing undo/redo to function.
 | |
| 
 | |
|         // Update the cursor position
 | |
|         if (isTextSelected) {
 | |
|             textarea.selectionStart = start + cursorShift;
 | |
|             textarea.selectionEnd = start + cursorShift + selectedText.length;
 | |
|         } else {
 | |
|             textarea.selectionStart = start + cursorShift;
 | |
|             textarea.selectionEnd = start + cursorShift;
 | |
|         }
 | |
|     });
 | |
| }
 |