UNPKG

easy-typing-input-tool

Version:

A tool utilizing google input tool for the use of easy typing repositories such as ENT, EHt & EBT

996 lines (836 loc) 36.7 kB
/** * Pre-requirement : JQuery * * Usage: * * Minimum Requirement: * 1. On HTML: * =================================== * * Minimum: * ---------------------------------- * * <!-- REQUIRED! for displaying message --> * <div id="input_tool_helper"> * <em>Additional options appears here!</em> * </div> * <textarea id="input_tool" <!-- REQUIRED! --> * dir="ltr|rtl" <!-- Optional (required to specify script with right to left for e.g. Urdu, Arabic)--> * > * <!-- REQUIRED! --> * <input type="hidden" name="googleInputKeyCode" value="specify input key code for e.g., (ne-t-i0-und)"> * * * For ENT (Based on Mobile Version): * ---------------------------------- * * <div id="input_tool_helper"> * <em>Additional options appears here!</em> * </div> * <div id="TextareaWrapper"> * <div id="CopyTool" * class="tool-button-theme light-grey" * style="right: 10px; bottom: 8px" * >Copy</div> * * <textarea id="input_tool" * class="mobileLanguageTextarea copiable storable" * dir="<?= $textDirection ?>" * ></textarea> * <input type="hidden" name="googleInputKeyCode" value="<?= $androidInputKeyCode; ?>"> * </div> * * * * * 2. Javascript: * =================================== * <script type="text/javascript" src="dist/google-input-tool-bundle.js?ver=<?=$version?>"></script> * * * * * 3. Style Sheet: * =================================== * Apply own style * * * OR (ENT Mobile Style): * ---------------------------------- * #input_tool_helper { * height: 33px; * width: 100%; * text-align: right; * color: #ccc; * } * #input_tool_helper > .btn { * margin: 0px 1px; * * // Remove on select highlight border * outline: 0; * } * #input_tool_helper > em { * font-size: 12px; * } */ /** * npm install google-input-tool --save */ const googleInputTool = require('google-input-tool'); // $(document).ready(googleInputTool.transliterate()); module.exports = {transliterate: googleTransliterationTool}; function googleTransliterationTool(args) { args = args || {}; const inputTextareaIds = args.inputTextareaIds || '#input_tool, #languageTextarea, #emailContents, #translationTextarea'; const inputHelperToolId = args.inputHelperTool || '#input_tool_helper'; const additionalPopUpTopMargin = args.additionalPopUpTopMargin || 0; const debug = args.debug || false; if (debug) { console.log('google transliteration tool', args); } /** * Applies to both space and backspace. * @type {boolean} */ const suggestionPopupAfterSpace = args.suggestionPopupAfterSpace || false; const suggestionPopupOnClick = args.suggestionPopupOnClick || false; let googleInputKeyCode = args.googleInputKeyCode || $("input[name=googleInputKeyCode]").val(); let googleTransliterationArea = $(inputTextareaIds); const inputToolHelper = $(inputHelperToolId); const maxResult = args.maxResult || 6; let request = new XMLHttpRequest(); let ctrlGPressed = false; const suggestionDropdown = document.createElement('div'); suggestionDropdown.id = 'InputSuggestions'; const suggestionDropdownContent = document.createTextNode('Option'); googleTransliterationArea.on('input', function (inputEvent) { suggestionDropdown.remove(); }); if (!googleInputKeyCode) { googleInputKeyCode = 'ne-t-i0-und'; console.error('Google Input Key Code not defined. So defaulting to Nepali (ne-t-i0-und). ' + 'Please specify hidden input: ' + '<input type="hidden" name="googleInputKeyCode" value="inputKeyCode(ne-t-i0-und)">' ) } /** * Detect if CTRL + G key is pressed */ googleTransliterationArea.on('keydown', function(event) { if (isCtrlG(event)) { ctrlGPressed = !ctrlGPressed; } }); /** * Keeps the mapping of transliterated input word and its english word * * { * सम्भु : "sambhu", * राज : "raj" * } */ let transliteratedWordEnglishMapping = {}; /** * Backspace is not detected on keypress, so key up used */ googleTransliterationArea.on('keyup', function (e) { // On clicking backspace remove the helper if (e.which === 8) { inputToolHelper.empty(); if (ctrlGPressed) { inputToolHelper.append('<em>English mode enabled. Press (CTRL + G) to switch back.</em>'); } else { inputToolHelper.append('<em>Additional options appears here!</em>'); } } }); /** * On clicking the word on the textarea. * 1. Get the selected word for e.g.: सम्भु * 2. Get the english version of the selected word from the transliteratedWordEnglishMapping. * In this case, it will be 'sambhu' * 3. Get the additional options as an array: * For example, ["सम्भु", "संभु", "समभु", "सम्भू", "संभू", "sambhu"] * 4. Now on clicking the option : * a) Update the transliteratedWordEnglishMapping * b) replaced the selectedWord with the one clicked. */ googleTransliterationArea.click(function(e) { console.log('1. Backup Google Input 1...'); removeSuggestionDropdown(); const stopCharacters = [' ', '\n', '\r', '\t']; const text = $(this).val(); let start = $(this)[0].selectionStart; let end = $(this)[0].selectionEnd; while (start > 0) { if (stopCharacters.indexOf(text[start]) == -1) { --start; } else { break; } } ++start; while (end < text.length) { if (stopCharacters.indexOf(text[end]) == -1) { ++end; } else { break; } } // 1. Get the selected word for e.g.: सम्भु const selectedWord = text.substr(start, end - start); if (selectedWord) { //2. Get the english version of the selected word from the transliteratedWordEnglishMapping. let selectedWordEnglishVersion = transliteratedWordEnglishMapping[selectedWord]; /** * Some time user would choose to enter the text in english and click it. * In this case, selected word is the selected word in english version. */ if (typeof selectedWordEnglishVersion === 'undefined') { selectedWordEnglishVersion = selectedWord; } //3. Get the additional options as an array: googleInputTool(request, selectedWordEnglishVersion, googleInputKeyCode, maxResult) .then(function(response) { // makeTransliterationInventoryPostRequest( // 'https://www.api.phplab.info/v1/transliteration-inventory', // transliterationPostData(response, lastWord, googleInputKeyCode) // ) // .then(data => { // console.log('Success', data); // }) // .catch(error => { // console.error('Error:', errors) // }); /** * Add all the response word to a mapping */ response.forEach(function(responseWordAsKey) { transliteratedWordEnglishMapping[responseWordAsKey[0]] = selectedWordEnglishVersion; }); const selectedWordWithSpecialCharacter = selectedWordEnglishVersion.match(/[A-Za-z0-9\u00C0-\u024F\u1E00-\u1EFF]+/)[0]; /** * Add english word on the list to be displayed on additional options. */ response.push([selectedWordEnglishVersion, selectedWordWithSpecialCharacter]); /** * Clear the content of the div */ inputToolHelper.empty(); response.forEach(function(input) { const inputHelperToolBtnOnClick = document.createElement('button'); inputHelperToolBtnOnClick.setAttribute('value', input[0]); inputHelperToolBtnOnClick.innerHTML = input[1]; inputHelperToolBtnOnClick.classList.add('btn'); inputToolHelper.append(inputHelperToolBtnOnClick); }); if (suggestionPopupOnClick === true) { popUpSuggestionDropdown( response, googleTransliterationArea, suggestionDropdown, suggestionDropdownContent, additionalPopUpTopMargin ); } //4. Now on clicking the option : $(inputHelperToolId + ' .btn, #InputSuggestions .suggestion-btn').on('click', function(optionClickedEvent) { let buttonClickedValue = (typeof this.value !== 'undefined') ? this.value : this.innerHTML; //4 a) Update the transliteratedWordEnglishMapping transliteratedWordEnglishMapping[buttonClickedValue] = selectedWordEnglishVersion; /** * Using start and end value slice the text and add the buttonClickedValue */ const modifiedTexts = text.slice(0, start) + buttonClickedValue + text.slice(end); // b) replaced the selectedWord with the one clicked. googleTransliterationArea.val(htmlDecode(modifiedTexts)); removeSuggestionDropdown(); }); } ); } }); googleTransliterationArea.on('keyup', function (e) { /** * If Ctrl G then don't transliterate */ if (ctrlGPressed) { inputToolHelper.empty(); inputToolHelper.append('<em>English mode enabled. Press (CTRL + G) to switch back.</em>'); return; } if (e.which === 32 || e.which === 229 || e.which === 8) { //Is space or backspace let words = $(this).val().split(" "); let splitTextareaContent = false; let cursorEndToEndOfTextPart = ''; let totalLengthOfCurrentCursorEndPosition = 0; const currentCursorPosition = getInputSelection(e.currentTarget); const textareaText = e.target.value; const fullLengthOfTheString = textareaText.length; let lastWord = null; if (e.which === 8) { // If it is a backspace if (currentCursorPosition.end !== fullLengthOfTheString) { // console.log('to add logic to retrieve word'); } //Find the last word which is in locale language. For e.g. हाम्रो let lastLocaleWord = words[words.length - 1]; //Now find the pronunciation in english for e.g. hamro lastWord = transliteratedWordEnglishMapping[lastLocaleWord]; if (typeof lastWord === 'undefined') { return; } return; } else { //Need to take care of space, plus index 0 - so need to deduct 2 to get last word lastWord = words[words.length - 2]; } /** * Don't transliterate if last word is undefined. */ if (lastWord === null) { return; } if (currentCursorPosition.end !== fullLengthOfTheString) { totalLengthOfCurrentCursorEndPosition = currentCursorPosition.end; const endOfCursorTextPart = textareaText.substr(0, currentCursorPosition.end); cursorEndToEndOfTextPart = textareaText.substr(currentCursorPosition.end, fullLengthOfTheString); splitTextareaContent = true; words = endOfCursorTextPart.split(' '); lastWord = words[words.length - 2]; totalLengthOfCurrentCursorEndPosition -= lastWord.length; } googleInputTool(request, lastWord, googleInputKeyCode, maxResult) .then(function(response) { // makeTransliterationInventoryPostRequest( // 'https://www.api.phplab.info/v1/transliteration-inventory', // transliterationPostData(response, lastWord, googleInputKeyCode) // ) // .then(data => { // console.log('Success', data); // }) // .catch(error => { // console.error('Error:', errors) // }); /** * Add all the response word to a mapping */ response.forEach(function(responseWordAsKey) { transliteratedWordEnglishMapping[responseWordAsKey[0]] = lastWord; }); const lastWordWithSpecialCharacter = lastWord.match(/[A-Za-z0-9\u00C0-\u024F\u1E00-\u1EFF]+/)[0]; /** * Add english word on the list to be displayed on additional options. */ response.push([lastWord, lastWordWithSpecialCharacter]); /** * Clear the content of the div */ inputToolHelper.empty(); let i = 0; response.forEach(function(input) { i++; const inputHelperToolBtn = document.createElement('button'); inputHelperToolBtn.setAttribute('value', input[0]); inputHelperToolBtn.innerHTML = input[1]; inputHelperToolBtn.classList.add('btn'); if (i === 1) { inputHelperToolBtn.classList.add('btn-success'); } inputToolHelper.append(inputHelperToolBtn); }); if (suggestionPopupAfterSpace === true) { popUpSuggestionDropdown( response, googleTransliterationArea, suggestionDropdown, suggestionDropdownContent, additionalPopUpTopMargin ); } /** * On pressing enter on the selected work on suggestion box. */ googleTransliterationArea.on('keydown', function(keyUpEvent) { if ($('#InputSuggestions .selected') && keyUpEvent.key === "Enter") { const inputSuggestionDiv = document.getElementById('InputSuggestions'); if (inputSuggestionDiv !== null && inputSuggestionDiv.style.display === 'block' ) { keyUpEvent.preventDefault(); } const inputSuggestionsDropdownDiv = document.getElementById('InputSuggestions'); if (inputSuggestionsDropdownDiv === null) { return; } const selectedInputSuggestionButton = inputSuggestionsDropdownDiv.getElementsByClassName('selected'); if (typeof selectedInputSuggestionButton === 'undefined' || typeof selectedInputSuggestionButton[0] === 'undefined') { return; } const btnSelectedValue = selectedInputSuggestionButton[0]; enterSelectedWordOnTextarea( e, transliteratedWordEnglishMapping, lastWord, googleTransliterationArea, (typeof btnSelectedValue.value !== 'undefined') ? btnSelectedValue.value : btnSelectedValue.innerHTML, inputHelperToolId ); removeSuggestionDropdown(); } }); /** * On clicking the button, replace the word. */ $(inputHelperToolId + ' .btn, #InputSuggestions .suggestion-btn').on('click', function(btnElementEvent) { enterSelectedWordOnTextarea( e, transliteratedWordEnglishMapping, lastWord, googleTransliterationArea, (this.value !== '') ? this.value : this.innerHTML, inputHelperToolId ); }); if (e.which === 8) { /** * Replace the last word on the textarea with the first suggestion */ words[words.length - 1] = response[0][0]; } else { /** * Replace the last word on the textarea with the first suggestion */ words[words.length - 2] = response[0][0]; } // words[words.length - 2] = response[0]; words = words.join(' '); if (splitTextareaContent === true) { totalLengthOfCurrentCursorEndPosition += response[0][0].length; words += cursorEndToEndOfTextPart; } /** * Populate the mapping for later access. */ transliteratedWordEnglishMapping[response[0][0]] = lastWord; googleTransliterationArea.val(htmlDecode(words)); if (splitTextareaContent === true) { setCaretPosition(e.currentTarget, totalLengthOfCurrentCursorEndPosition); } }); } else { inputToolHelper.empty(); if (ctrlGPressed) { inputToolHelper.append('<em>English mode enabled. Press (CTRL + G) to switch back.</em>'); } else { inputToolHelper.append('<em>Additional options appears here!</em>'); } } }); } /** * For e.g. when button is clicked or press enter on selected word * @param mainEvent * @param transliteratedWordEnglishMapping * @param lastWord * @param googleTransliterationArea * @param buttonClickedValue * @param inputHelperToolId */ function enterSelectedWordOnTextarea( mainEvent, transliteratedWordEnglishMapping, lastWord, googleTransliterationArea, buttonClickedValue, inputHelperToolId ) { const currentCursorPositionOnBtnClick = getInputSelection(mainEvent.currentTarget); const textareaTextOnBtnClick = mainEvent.target.value; const fullLengthOfTheStringOnBtnClick = textareaTextOnBtnClick.length; let cursorEndToEndOfTextPartOnBtnClick = ''; let totalLengthOfCurrentCursorEndPositionOnBtnClicked = currentCursorPositionOnBtnClick.end; const endOfCursorTextPartOnBtnClick = textareaTextOnBtnClick.substr(0, currentCursorPositionOnBtnClick.end); cursorEndToEndOfTextPartOnBtnClick = textareaTextOnBtnClick.substr(currentCursorPositionOnBtnClick.end, fullLengthOfTheStringOnBtnClick); if (typeof buttonClickedValue !== 'undefined') { let wordsBeforeCursorEnd = endOfCursorTextPartOnBtnClick.split(' '); let lastWordBeforeCursorEnd = 0; if (wordsBeforeCursorEnd.includes("")) { lastWordBeforeCursorEnd = wordsBeforeCursorEnd[wordsBeforeCursorEnd.length - 2]; } else { lastWordBeforeCursorEnd = wordsBeforeCursorEnd[wordsBeforeCursorEnd.length - 1]; } /** * Populate the mapping for later access. */ transliteratedWordEnglishMapping[buttonClickedValue] = transliteratedWordEnglishMapping[lastWordBeforeCursorEnd]; if (typeof lastWordBeforeCursorEnd === 'undefined') { console.log('last word before cursor end is not defined'); return; } totalLengthOfCurrentCursorEndPositionOnBtnClicked -= lastWordBeforeCursorEnd.length; let toMoveCursorByOneCharacter = false; if (wordsBeforeCursorEnd.includes("")) { wordsBeforeCursorEnd[wordsBeforeCursorEnd.length - 2] = buttonClickedValue; } else { toMoveCursorByOneCharacter = true; wordsBeforeCursorEnd[wordsBeforeCursorEnd.length - 1] = buttonClickedValue; } wordsBeforeCursorEnd = wordsBeforeCursorEnd.join(' '); totalLengthOfCurrentCursorEndPositionOnBtnClicked += buttonClickedValue.length; wordsBeforeCursorEnd += cursorEndToEndOfTextPartOnBtnClick; if (toMoveCursorByOneCharacter) { totalLengthOfCurrentCursorEndPositionOnBtnClicked++; wordsBeforeCursorEnd += ' '; } googleTransliterationArea.val(htmlDecode(wordsBeforeCursorEnd)); setCaretPosition(mainEvent.currentTarget, totalLengthOfCurrentCursorEndPositionOnBtnClicked); $(inputHelperToolId + ' .btn, #InputSuggestions .suggestion-btn').removeClass('btn-success'); $(this).addClass('btn-success'); removeSuggestionDropdown(); } } /** * Detect if CTRL + g key combination is pressed. */ function isCtrlG(keyEvent) { if (keyEvent.which === 71 && keyEvent.ctrlKey) { keyEvent.preventDefault(); return true; } return false; } function removeSuggestionDropdown() { const inputSuggestions = document.getElementById('InputSuggestions'); if (inputSuggestions !== null) { document.getElementById('InputSuggestions').remove(); } } function removeClassFromElements(elements, classNameToRemove) { for (let index in elements) { const element = elements[index]; if (typeof element.classList !== 'undefined' && element.classList.contains(classNameToRemove)) { element.classList.remove(classNameToRemove); } } } function addClassOnElement(element, classNameToAdd) { if (typeof element !== 'undefined') { element.classList.add(classNameToAdd); } } function popUpSuggestionDropdown( response, googleTransliterationArea, suggestionDropdown, suggestionDropdownContent, additionalPopUpTopMargin ) { // Clear whatever it has suggestionDropdown.innerHTML = ''; let suggestionDropdownHeight = 0; let buttonHeight = 25; let numberOfButton = 0; // For each Input create a button and append on suggestion dropdown response.forEach(function (input) { // const alphaNumWithLatin = input.replace(/[^A-Za-z0-9\u00C0-\u024F\u1E00-\u1EFF]/, ''); numberOfButton++; suggestionDropdownHeight += buttonHeight; const button = document.createElement('button'); button.setAttribute('value', input[0]); button.innerHTML = input[1]; button.classList.add('suggestion-btn'); button.style.height = buttonHeight + 'px'; button.style.display = 'block'; button.style.backgroundColor = '#fefefe'; button.style.border = '0px'; button.style.width = '100%'; button.style.fontSize = '15px'; button.style.textAlign = 'left'; if (numberOfButton === 1) { button.classList.add('selected'); } suggestionDropdown.append(button); suggestionDropdown.style.height = suggestionDropdownHeight + 'px'; }); suggestionDropdown.remove(); const currentTextarea = googleTransliterationArea[0]; const point = getDropdownPosition(currentTextarea); addSuggestionDropdown(currentTextarea, suggestionDropdown, suggestionDropdownContent, point.x, point.y, additionalPopUpTopMargin); let suggestionBtnIndex = 0; $(document).keydown(function (keydownEvent) { const inputSuggestionsDiv = document.getElementById('InputSuggestions'); if (inputSuggestionsDiv === null) { return; } const inputSuggestionButtons = inputSuggestionsDiv.getElementsByTagName('button'); const numberOfInputSuggestionButtons = inputSuggestionButtons.length; const inputSuggestionDiv = document.getElementById('InputSuggestions'); if (inputSuggestionDiv !== null && inputSuggestionDiv.style.display === 'block' ) { switch (keydownEvent.which) { case 39: //right case 38: //up keydownEvent.preventDefault(); suggestionBtnIndex--; if (suggestionBtnIndex === -1) { suggestionBtnIndex = numberOfInputSuggestionButtons - 1; } removeClassFromElements(inputSuggestionButtons, 'selected'); addClassOnElement(inputSuggestionButtons[suggestionBtnIndex], 'selected'); break; case 37: //left case 40: //down keydownEvent.preventDefault(); suggestionBtnIndex++; if (suggestionBtnIndex === numberOfInputSuggestionButtons) { suggestionBtnIndex = 0; } removeClassFromElements(inputSuggestionButtons, 'selected'); addClassOnElement(inputSuggestionButtons[suggestionBtnIndex], 'selected'); break; } } }); } function addSuggestionDropdown( currentTextarea, suggestionDropdown, suggestionDropdownContent, leftPos, topPos, additionalPopUpTopMargin ) { suggestionDropdown.style.position = 'absolute'; suggestionDropdown.style.left = leftPos + 'px'; suggestionDropdown.style.top = additionalPopUpTopMargin + topPos + 'px'; suggestionDropdown.style.display = 'block'; suggestionDropdown.style.border = '1px solid #999'; const parentNodeOfCurrentTextarea = currentTextarea.parentNode; parentNodeOfCurrentTextarea.style.position = 'relative'; parentNodeOfCurrentTextarea.appendChild(suggestionDropdown); } function getAreaOfTextareaElement(textareaElement) { let area = {height: 0, width: 0}; area.height = textareaElement.clientHeight + 1; area.width = textareaElement.clientWidth + 1; return area; } function getDropdownPosition(currentTextarea) { let point = {x: 0, y: 0}; const cursorCoordinates = getCaretCoordinates(currentTextarea, currentTextarea.selectionEnd); const fontSize = currentTextarea.style.fontSize.replace('px', ''); const areaOfTextareaElement = getAreaOfTextareaElement(currentTextarea); const widthPercentOfCursor = cursorCoordinates.left * 100 / areaOfTextareaElement.width; if (widthPercentOfCursor < 70) { point.x = cursorCoordinates.left; } else { point.x = cursorCoordinates.left - 50; } point.y = cursorCoordinates.top + fontSize * 2; return point; } /** * Return an * @param {DOMElement} el A dom element of a textarea or input text. * @return {Object} reference Object with 2 properties (start and end) with the identifier of the location of the cursor and selected text. **/ function getInputSelection(el) { let start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange; if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { start = el.selectionStart; end = el.selectionEnd; } else { range = document.selection.createRange(); if (range && range.parentElement() == el) { len = el.value.length; normalizedValue = el.value.replace(/\r\n/g, "\n"); // Create a working TextRange that lives only in the input textInputRange = el.createTextRange(); textInputRange.moveToBookmark(range.getBookmark()); // Check if the start and end of the selection are at the very end // of the input, since moveStart/moveEnd doesn't return what we want // in those cases endRange = el.createTextRange(); endRange.collapse(false); if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { start = end = len; } else { start = -textInputRange.moveStart("character", -len); start += normalizedValue.slice(0, start).split("\n").length - 1; if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { end = len; } else { end = -textInputRange.moveEnd("character", -len); end += normalizedValue.slice(0, end).split("\n").length - 1; } } } } return { start: start, end: end }; } function setCaretPosition(elem, caretPos) { let range; if (elem.createTextRange) { range = elem.createTextRange(); range.move('character', caretPos); range.select(); } else { elem.focus(); if (elem.selectionStart !== undefined) { elem.setSelectionRange(caretPos, caretPos); } } } function htmlDecode(input){ let e = document.createElement('div'); e.innerHTML = input; return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue; } /** Get Caret Position **/ /* jshint browser: true */ // The properties that we copy into a mirrored div. // Note that some browsers, such as Firefox, // do not concatenate properties, i.e. padding-top, bottom etc. -> padding, // so we have to do every single property specifically. let properties = [ 'direction', // RTL support 'boxSizing', 'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does 'height', 'overflowX', 'overflowY', // copy the scrollbar for IE 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', // https://developer.mozilla.org/en-US/docs/Web/CSS/font 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust', 'lineHeight', 'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', // might not make a difference, but better be safe 'letterSpacing', 'wordSpacing' ]; let isFirefox = !(window.mozInnerScreenX == null); // module.exports = function (textarea, position, recalculate) { function getCaretCoordinates(element, position, recalculate) { // mirrored div let div = document.createElement('div'); div.id = 'input-textarea-caret-position-mirror-div'; document.body.appendChild(div); let style = div.style; let computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9 // default textarea styles style.whiteSpace = 'pre-wrap'; if (element.nodeName !== 'INPUT') style.wordWrap = 'break-word'; // only for textarea-s // position off-screen style.position = 'absolute'; // required to return coordinates properly style.visibility = 'hidden'; // not 'display: none' because we want rendering // transfer the element's properties to the div properties.forEach(function (prop) { style[prop] = computed[prop]; }); if (isFirefox) { style.width = parseInt(computed.width) - 2 + 'px' // Firefox adds 2 pixels to the padding - https://bugzilla.mozilla.org/show_bug.cgi?id=753662 // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275 if (element.scrollHeight > parseInt(computed.height)) style.overflowY = 'scroll'; } else { style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll' } div.textContent = element.value.substring(0, position); // the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037 if (element.nodeName === 'INPUT') { div.textContent = div.textContent.replace(/\s/g, "\u00a0"); } let span = document.createElement('span'); // Wrapping must be replicated *exactly*, including when a long word gets // onto the next line, with whitespace at the end of the line before (#7). // The *only* reliable way to do that is to copy the *entire* rest of the // textarea's content into the <span> created at the caret position. // for inputs, just '.' would be enough, but why bother? span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all div.appendChild(span); const coordinates = { top: span.offsetTop + parseInt(computed['borderTopWidth']), left: span.offsetLeft + parseInt(computed['borderLeftWidth']) }; document.body.removeChild(div); return coordinates; } /** * * @param url : http://www.api.phplab.info/v1/transliteration-inventory * @param postData * { * "origin-url" : "https://www.easynepalityping.com", * "two-digit-language" : "ne", * "selected-word" : "hamro", * "transliteration" : [ * ["हाम्रो", "हाम्रो"], * ["हम्रो", "हम्रो"], * ["हमरो", "हमरो"], * ["हामरो", "हामरो"], * ["हम्रों", "हम्रों"], * ["हमरों", "हमरों"], * ["hamro", "hamro"] * ] * } * * @returns {Promise<unknown> | Promise<unknown>} */ function makeTransliterationInventoryPostRequest(url, postData) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', url); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.onload = function () { if (xhr.status >= 200 && xhr.status < 300) { reject('pass', xhr.responseText); resolve(JSON.parse(xhr.responseText)); } else { reject(new Error('Request failed: ' + xhr.statusText)); } }; xhr.onerror = function () { reject(new Error('Network error occurred')); }; xhr.send(JSON.stringify(postData)); }); } function transliterationPostData(response, lastWord, googleInputKeyCode) { const transliterationData = response.slice(); // copy array value const selectedWord = lastWord.slice().toLowerCase().trim(); // copy array value transliterationData.push([selectedWord, selectedWord]); const postData = { "origin-url": window.location.hostname, "language-code": googleInputKeyCode.split('-')[0], "selected-word": selectedWord, "transliteration": transliterationData, "transliteration_source": "GOOGLE_TRANSLITERATION" }; return postData; }