Blame Raw
Cohee · e3f41666 · · 151 lines (7.2 KB)
1 contributor
1import { power_user } from './power-user.js';
2
3export 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) {
63 //if text is selected
64 selectedText = textarea.value.substring(start, end);
65 if (selectedTextandPossibleFormatting === charsToAdd + selectedText + charsToAdd) {
66 // If the selected text is already formatted, remove the formatting
67
68 let expandedStart = start - charsToAdd.length;
69 let expandedEnd = end + charsToAdd.length;
70
71 // Ensure expanded range is within the bounds of the text
72 if (expandedStart < 0) expandedStart = 0;
73 if (expandedEnd > textarea.value.length) expandedEnd = textarea.value.length;
74
75 // Select the expanded range
76 textarea.setSelectionRange(expandedStart, expandedEnd);
77
78 // Replace the expanded selection with the original selected text
79 document.execCommand('insertText', false, selectedText);
80 // Adjust cursor position
81 cursorShift = -charsToAdd.length;
82 } else {
83 // Add formatting to the selected text
84 let possibleAddedSpace = '';
85 if (selectedText.endsWith(' ')) {
86 possibleAddedSpace = ' ';
87 selectedText = selectedText.substring(0, selectedText.length - 1);
88 end--; // Adjust the end index since we removed the space
89 }
90 // To add the formatting, we need to select the text first
91 textarea.focus();
92 document.execCommand('insertText', false, charsToAdd + selectedText + charsToAdd + possibleAddedSpace);
93 }
94 } else {
95 // No text is selected
96 //check 1 character before and after the cursor for non-space characters
97
98 if (beforeCaret !== ' ' && afterCaret !== ' ' && afterCaret !== '' && beforeCaret !== '') { //look for caret in the middle of a word
99 //expand the selection range until the next space on both sides
100 let midCaretExpandedStart = start - 1;
101 let midCaretExpandedEnd = end + 1;
102 while (midCaretExpandedStart > 0 && textarea.value.substring(midCaretExpandedStart - 1, midCaretExpandedStart) !== ' ') {
103 midCaretExpandedStart--;
104 }
105 while (midCaretExpandedEnd < textarea.value.length && textarea.value.substring(midCaretExpandedEnd, midCaretExpandedEnd + 1) !== ' ') {
106 midCaretExpandedEnd++;
107 }
108 //make a selection of the discovered word
109 textarea.setSelectionRange(midCaretExpandedStart, midCaretExpandedEnd);
110 //set variables for comparison
111 let discoveredWordWithPossibleFormatting = textarea.value.substring(midCaretExpandedStart, midCaretExpandedEnd).trim();
112 let discoveredWord = '';
113
114 if (discoveredWordWithPossibleFormatting.endsWith(charsToAdd) && discoveredWordWithPossibleFormatting.startsWith(charsToAdd)) {
115 discoveredWord = textarea.value.substring(midCaretExpandedStart + charsToAdd.length, midCaretExpandedEnd - charsToAdd.length).trim();
116 } else {
117 discoveredWord = textarea.value.substring(midCaretExpandedStart, midCaretExpandedEnd).trim();
118 }
119
120 if (charsToAdd + discoveredWord + charsToAdd === discoveredWordWithPossibleFormatting) {
121 // Replace the expanded selection with the original discovered word
122 textarea.focus();
123 document.execCommand('insertText', false, discoveredWord);
124 // Adjust cursor position
125 cursorShift = -charsToAdd.length;
126 } else { //format did not previously exist, so add it
127 textarea.focus();
128 document.execCommand('insertText', false, charsToAdd + discoveredWord + charsToAdd);
129 }
130 } else { //caret is not inside a word, so just add the formatting
131 textarea.focus();
132 textarea.setSelectionRange(start, end);
133 selectedText = textarea.value.substring(start, end);
134 document.execCommand('insertText', false, charsToAdd + selectedText + charsToAdd);
135 }
136 }
137
138 // Manually trigger the 'input' event to make undo/redo work
139 let event = new Event('input', { bubbles: true });
140 textarea.dispatchEvent(event); // This notifies the browser of a change, allowing undo/redo to function.
141
142 // Update the cursor position
143 if (isTextSelected) {
144 textarea.selectionStart = start + cursorShift;
145 textarea.selectionEnd = start + cursorShift + selectedText.length;
146 } else {
147 textarea.selectionStart = start + cursorShift;
148 textarea.selectionEnd = start + cursorShift;
149 }
150 });
151}