├── .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 |
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 |
--------------------------------------------------------------------------------