├── .gitignore ├── index.html ├── readme.md └── textexpander.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Text Expander 9 | 10 | 19 | 20 | 21 | 22 |
23 |

Text Expander

24 |
25 |
26 | 30 |
31 |
32 |

Type any of these words:

33 |
omg
34 |

And then press 35 | space or any of these: 36 | 37 | , 38 | . 39 | ! 40 | ? 41 | 42 |

43 | 44 |

For example, type this sentence manually(don't copy-paste): 45 |
46 | "Omg roflmao, idk what I'm doing. Gn." 47 |

48 |
49 | 54 | 55 | 56 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Just have a look at the [Example File](https://hasinhayder.github.io/javascript-text-expander/) and you will understand how it works :) 2 | 3 | Released under MIT License. -------------------------------------------------------------------------------- /textexpander.js: -------------------------------------------------------------------------------- 1 | const textExpander = (textObjects, dictionary) => { 2 | // textObjects: A single text object or an array of text objects 3 | // dictionary: An object with keys and values to replace 4 | if (!dictionary || !textObjects) return; 5 | 6 | // Convert textObjects to an array if it isn't already 7 | const textObjectsArray = Array.isArray(textObjects) ? textObjects : [textObjects]; 8 | 9 | // Define the keys that will trigger the text expansion 10 | // Different browser support different key codes, so we'll use the key property if it's available 11 | // Otherwise, we'll use the keyCode property and convert it to a string 12 | const actionKeysByType = { 13 | true: " ,.!?;:", 14 | false: "r32xr188xr190xr49xr191xr186x" // Space, comma, period, exclamation point, question mark, semicolon, colon 15 | }; 16 | 17 | const getActionKeys = (key) => actionKeysByType[key !== undefined]; 18 | 19 | // Expand text on keydown 20 | const expandText = event => { 21 | const { key, keyCode, ctrlKey, metaKey, target } = event; 22 | const dataKey = key ?? `r${keyCode}x`; 23 | const actionKeys = getActionKeys(key); 24 | 25 | if ((keyCode === 90) && (ctrlKey || metaKey) && target.dataset.lastReplaced && target.dataset.lastKeystroke) { 26 | const regexp = new RegExp(dictionary[target.dataset.lastReplaced] + target.dataset.lastKeystroke + '$'); 27 | if (regexp.test(target.value)) { 28 | event.preventDefault(); 29 | target.value = target.value.replace(regexp, target.dataset.lastReplaced + target.dataset.lastKeystroke); 30 | } 31 | delete target.dataset.lastReplaced; 32 | delete target.dataset.lastKeystroke; 33 | return; 34 | } 35 | 36 | if (actionKeys.includes(dataKey)) { 37 | const selection = getCaretPosition(target); 38 | const result = /\S+$/.exec(target.value.slice(0, selection.end)); 39 | if (result) replaceLastWord(target, result.input.length - result[0].length, result.input.length, result[0]); 40 | } 41 | }; 42 | 43 | // Store the last keystroke for later use 44 | const keyHistory = ({ key, keyCode, target }) => { 45 | if (getActionKeys(key).includes(key ?? `r${keyCode}x`)) { 46 | target.dataset.lastKeystroke = target.value.slice(-1); 47 | } else { 48 | delete target.dataset.lastReplaced; 49 | } 50 | }; 51 | 52 | // Add event listeners to all text objects 53 | for (const textObject of textObjectsArray) { 54 | if (!textObject) continue; 55 | 56 | ["keydown", "keyup"].forEach(evtName => { 57 | const listener = evtName === "keydown" ? expandText : keyHistory; 58 | textObject.removeEventListener(evtName, listener); 59 | textObject.addEventListener(evtName, listener); 60 | }); 61 | } 62 | 63 | // Helper function to get the caret position in a text field 64 | const getCaretPosition = ctrl => { 65 | if (ctrl.setSelectionRange) return { start: ctrl.selectionStart, end: ctrl.selectionEnd }; 66 | 67 | if (document.selection && document.selection.createRange) { 68 | const range = document.selection.createRange(); 69 | const start = 0 - range.duplicate().moveStart('character', -100000); 70 | return { start, end: start + range.text.length }; 71 | } 72 | 73 | return { start: 0, end: 0 }; 74 | }; 75 | 76 | // Helper function to replace the last word in a text field 77 | const replaceLastWord = (ctrl, start, end, key) => { 78 | const replaceWith = dictionary[key.toLowerCase()]; 79 | if (!replaceWith) return; 80 | 81 | // Determine the correct replacement based on the original word's case. 82 | const replaced = key.charAt(0) === key.charAt(0).toUpperCase() ? 83 | capitalizeFirstLetter(replaceWith) : 84 | replaceWith; 85 | 86 | const preText = ctrl.value.substring(0, start); 87 | const postText = ctrl.value.substr(end); 88 | ctrl.value = preText + replaced + postText; 89 | 90 | const adjustedPosition = end + replaced.length - key.length; 91 | ctrl.setSelectionRange(adjustedPosition, adjustedPosition); 92 | ctrl.dataset.lastReplaced = key; 93 | }; 94 | 95 | // Helper function to capitalize the first letter of a string 96 | const capitalizeFirstLetter = (string) => { 97 | return string.charAt(0).toUpperCase() + string.slice(1); 98 | }; 99 | 100 | }; 101 | --------------------------------------------------------------------------------