| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | /** |
| b69ab31 | | | 9 | * Replace text in a text area. |
| b69ab31 | | | 10 | * Re-adjust cursor & selection to be the same as before the replacement. |
| b69ab31 | | | 11 | */ |
| b69ab31 | | | 12 | export function replaceInTextArea(textArea: HTMLTextAreaElement, oldText: string, newText: string) { |
| b69ab31 | | | 13 | const {selectionStart, selectionEnd} = textArea; |
| b69ab31 | | | 14 | const insertionSpot = textArea.value.indexOf(oldText); |
| b69ab31 | | | 15 | textArea.value = textArea.value.replace(oldText, newText); |
| b69ab31 | | | 16 | // re-select whatever we had selected before. |
| b69ab31 | | | 17 | // if the new text is longer, we may need to add additional length |
| b69ab31 | | | 18 | if (selectionStart) { |
| b69ab31 | | | 19 | textArea.selectionStart = |
| b69ab31 | | | 20 | selectionStart > insertionSpot |
| b69ab31 | | | 21 | ? selectionStart + (newText.length - oldText.length) |
| b69ab31 | | | 22 | : selectionStart; |
| b69ab31 | | | 23 | } |
| b69ab31 | | | 24 | if (selectionEnd) { |
| b69ab31 | | | 25 | textArea.selectionEnd = |
| b69ab31 | | | 26 | selectionEnd > insertionSpot |
| b69ab31 | | | 27 | ? selectionEnd + (newText.length - oldText.length) |
| b69ab31 | | | 28 | : selectionEnd; |
| b69ab31 | | | 29 | } |
| b69ab31 | | | 30 | } |
| b69ab31 | | | 31 | |
| b69ab31 | | | 32 | /** |
| b69ab31 | | | 33 | * Insert text into a text area at the cursor location. |
| b69ab31 | | | 34 | * Add spaces before/after as necessary so the new text does not neighbor the existing content. |
| b69ab31 | | | 35 | * If text is selected, replace the selected text with the new text. |
| b69ab31 | | | 36 | * If nothing is selected, append to the end. |
| b69ab31 | | | 37 | */ |
| b69ab31 | | | 38 | export function insertAtCursor(textArea: HTMLTextAreaElement, text: string) { |
| b69ab31 | | | 39 | if (textArea.selectionStart != null) { |
| b69ab31 | | | 40 | const startPos = textArea.selectionStart; |
| b69ab31 | | | 41 | const endPos = textArea.selectionEnd; |
| b69ab31 | | | 42 | const nextCharPos = endPos ?? startPos; |
| b69ab31 | | | 43 | const previousChar = textArea.value[startPos - 1]; |
| b69ab31 | | | 44 | const nextChar = textArea.value[nextCharPos]; |
| b69ab31 | | | 45 | const isWhitespace = (s: string | undefined) => !s || /[ \n\t]/.test(s); |
| b69ab31 | | | 46 | // if inserting next to whitespace, no need to add more. |
| b69ab31 | | | 47 | // if inserting next to text, add a space before to avoid the link becoming invalid. |
| b69ab31 | | | 48 | const prefix = isWhitespace(previousChar) ? '' : ' '; |
| b69ab31 | | | 49 | // similarly for suffix |
| b69ab31 | | | 50 | const suffix = isWhitespace(nextChar) ? '' : ' '; |
| b69ab31 | | | 51 | const totalAddedLength = text.length + prefix.length + suffix.length; |
| b69ab31 | | | 52 | textArea.value = |
| b69ab31 | | | 53 | textArea.value.substring(0, startPos) + |
| b69ab31 | | | 54 | prefix + |
| b69ab31 | | | 55 | text + |
| b69ab31 | | | 56 | suffix + |
| b69ab31 | | | 57 | (endPos != null ? textArea.value.substring(endPos, textArea.value.length) : ''); |
| b69ab31 | | | 58 | const newPos = startPos + totalAddedLength; |
| b69ab31 | | | 59 | textArea.selectionStart = newPos; |
| b69ab31 | | | 60 | textArea.selectionEnd = newPos; |
| b69ab31 | | | 61 | } else { |
| b69ab31 | | | 62 | // no selection => append to the end |
| b69ab31 | | | 63 | const prefix = /\s/.test(textArea.value[textArea.value.length - 1]) ? '' : ' '; |
| b69ab31 | | | 64 | textArea.value += prefix + text; |
| b69ab31 | | | 65 | } |
| b69ab31 | | | 66 | } |