Reputation: 31
What's up guys, how's it going? I'm having trouble saving the cursor position and inserting dynamic tags.I'm using the Emojiarea plugin to create a div where I can write texts, insert emojis, templates and tags. https://github.com/mervick/emojionearea
I use the following function below to create a div on my textarea:
$("#email_campaign_description").emojioneArea({
search: false,
recentEmojis: false,
pickerPosition: "right",
events: {
blur: function (editor, event) {
$scope.lastPosition = getCaretCharacterOffsetWithin(editor[0])
},
}
});
The next function returns the last position of my cursor when I click somewhere in the text:
function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument;
var win = doc.defaultView;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
debugger
return caretOffset;
}
And the last function adds dynamic tags to the text:
$scope.chooseTag = function (label) {
var domElement = $('#email_campaign_description');
var emojiElement = domElement[0].emojioneArea;
if (document.selection) {
domElement.focus();
var sel = document.selection.createRange();
sel.text = $scope.tags.model;
domElement.focus();
} else if ($scope.lastPosition) {
var startPos = $scope.lastPosition;
var endPos = startPos + $scope.tags.model[0].length;
emojiElement.setText(emojiElement.getText().substring(0, startPos) + ' ' + $scope.tags.model + ' ' + emojiElement.getText().substring(endPos, emojiElement.getText().length));
domElement.focus();
} else {
emojiElement.setText($scope.tags.model);
domElement.focus();
}
if ($scope.tags.model === '[vendor_name]') {
emojiElement.setText(domElement.val().replace('[vendor_name]', $scope.vendor.name));
}
$scope.campaign.description = $('#email_campaign_description').val();
};
The problem happens that the function that stores the click position in the text does not read emojis. So, if I write a text like: "Hello [emoji], welcome!" and at the end of that text I try to add a tag, my function will not read the emoji, and will insert the tag over the last character of my sentence, in this case "!". Likewise if I add two emojis, my function will not read and insert the tag over the last two characters in this case "o!". The correct thing would be my function to read these two emojis, and add my tag exactly in the desired location, that is: "Hello [emoji], welcome! [Tag]"
What can I do for my function getCaretCharacterOffsetWithin(element)
to read emojis as a character, or a space occupied?
Upvotes: 2
Views: 1091
Reputation: 2032
A library-free (Typescript) solution:
correctUnicodeOffset(offset: number, str: string): number {
if (offset < 1) return offset;
return Array.from(str.substr(0, offset)).length;
}
Use it like this:
myOffset = this.correctUnicodeOffset(myOffset, myStr);
Upvotes: 0
Reputation: 141
The problem is that javascript isn't great at handling Unicode strings.
For example:
"hello".length === 5
"👩🏻🦰".length === 7
There are several libraries that can help accurately measure the length of unicode strings. Graphemer is one of them (full disclosure: I published this library).
To fix your getCaretCharacterOffsetWithin(element)
function do the following:
import Graphemer from 'graphemer';
const splitter = new Graphemer();
function getCaretCharacterOffsetWithin(element) {...}
caretOffset = preCaretRange.toString().length; // original
caretOffset = splitter.countGraphemes(preCaretRange.toString()); // updated
caretOffset = preCaretTextRange.text.length; // original
caretOffset = splitter.countGraphemes(preCaretTextRange.text); // updated
Upvotes: 2