Set Cursor After an element - javascript

I have this code
elementSpan = document.createElement('span').className = 'mainSpan';
elementSpan.innerHTML = '<span class="childSpan">A</sapn>';
range.insertNode(elementSpan);
range.setStartAfter(elementSpan); //the problem am having
Now I set the cursor to be after the elementSpan after inserting it into a contenteditable div but instead of the cursor to be after the elementSpan, it goes after the text A which is inside the <span class="childSpan"> but I want it to be after the elementSpan. Please anyone with a clue on how to do this?

Went through your question again, this is the closest I could get to work. This creates a dummy a with at the end and moves cursor past it. Both a and are important as without these some browsers (Safari, Chrome) force the text typed to go inside last element due to some optimisations. The code moves cursor to next element if there is one already, it can be removed by removing the if.
I am not an expert in range/selection APIs. Looking forward to see other interesting answers.
elementSpan = document.createElement('span');
elementSpan.className = 'mainSpan';
document.getElementById('ce').appendChild(elementSpan);
elementSpan.innerHTML = '<span id="childSpan">Another text</span>'; //the problem am having
setCursorAfterElement(document.getElementById('childSpan'));
function setCursorAfterElement(ele) {
if (!ele.nextElementSibling) {
var dummyElement = document.createElement('a')
dummyElement.appendChild(document.createTextNode('\u00A0'))
ele.parentNode.appendChild(dummyElement)
}
var nextElement = ele.nextElementSibling
nextElement.tabIndex=0
nextElement.focus()
var r = document.createRange();
r.setStart(nextElement.childNodes[0], 1);
r.setEnd(nextElement.childNodes[0], 1);
var s = window.getSelection();
s.removeAllRanges();
s.addRange(r);
//document.execCommand("delete", false, null);
}
#childSpan {
border: 1px solid red;
margin: 0px;
}
<div id="ce" contenteditable="true">
</div>

Related

Get cursor position when a file is dropped in textarea in Chrome

When you drag a file from your OS filesystem over a textarea or text input, a cursor appears near the mouse pointer (this is different from positionStart), showing the user where the dragged content would be inserted.
UPDATE: here is an image, I'm dragging a file (test.sh) over the text input. You can see the drop cursor if the middle of the "text" word. The selection cursor is at the end of the string (not visible on this picture).
(Chrome's default behavior is to open the dropped file, but I'm overriding this behavior in the drop event. I want to insert the name of the file in the textarea.)
I'm trying to get this position (in terms of index in the textarea value string) when drop occurs. Any idea?
Phew, what you want to do isn't easy, since there is no way to reference this specific caret!
Off the top of my head, you could only implement this through heavy workarounds: What you can obtain upon drop occuring is the mouse cursor position.
You would have to make an invisible div-clone identical to the textarea in shape, text-size, margins, etc that automatically gets filled with the text from your textarea.
Next you'd have to create a span for each possible caret position (i.e. 1 span for every character of text) inside this div and get each span's x and y coordinates and save them into an array.
.txtarea {
width: 300px;
height: 150px;
font-size: 12px;
font-family: Arial;
padding: 5px;
text-align: left;
border: 1px solid red;
}
<textarea class="txtarea">Mytext</textarea>
<!--just an example, auto-fill this content through a JS oninput event -->
<div class="txtarea"><span>M</span><span>y</span><span>t</span><span>e</span><span>x</span><span>t</span></div>
Then, upon drop occuring, get the mouse coordinates of the event, compare them to the array of coordinates, approximate the closest x/y position and set a new selectionStart index in your textarea based on that, insert the file name, and then restore the previous selectionStart.
This is only a partial answer: The following works in IE11, but not in Chrome (didn't test in other browsers).
Just select some letters from the upper input and drag them into the bottom one:
let to = document.getElementById("to");
to.addEventListener("dragover", function (e) {
if (e.target.selectionStart) {
console.log(e.target.selectionStart);
}
});
<input type="text" id="from" value="select some letters from this sentence, and drag them into the other input element"/ style="width: 500px;"><br/>
<input type="text" id="to" value="drag over here"/>
Few notes for future research:
In Chrome, dragover event is fired only after focus is first set inside the input element.
In Chrome, the value of selectionStart is the value of the last selected text position within the target input (put differently: the last position of the cursor within the target input).
Setting type="number" fires with selectionStart assigned to null in Chrome, but not in IE11.
From Your picture, it looks like an input text (I admit, I was too lazy to code this for a textarea...) but - anyway - this is a nice question.
To get the text width, You need to clone the original element and loop over the text char by char. Just to mention an example, see some answers here on SO about the topic: "how to set the ellipsis text using JavaScript".
I also noticed that by moving the mouse pointer over the text, the caret is shifting left and right from the center of each char. At the end, the main problem here is: how to find the x of the middle of each char.
So, my code is splitted in two parts: text input clone and character position. After that, to position the caret, You can use an existing library like this: jQuery Caret Plugin or jQuery Caret - up to You - I would skip this part of the question.
I tested the code below in FF, Safari, IE & Chrome, there are obviously some small and annoying issues in the drag & drop behavior of these browser, but they seems to me irrelevant.
function getCaretPos(target, x) {
var txt = target.value, doc = target.ownerDocument,
clone = doc.createElement('span'), /* Make a clone */
style = doc.defaultView.getComputedStyle(target, null),
mL = style.getPropertyValue('margin-left'),
pL = style.getPropertyValue('padding-left'),
mouseX = x - parseFloat(mL) - parseFloat(pL);
clone.style.fontFamily = style.getPropertyValue('font-family');
clone.style.fontSize = style.getPropertyValue('font-size');
clone.style.fontWeight = style.getPropertyValue('font-weight');
clone.style.position = 'absolute'; /* Keep layout */
clone.style.left = -9999;
target.parentNode.appendChild(clone);
clone.style.width = 'auto';
clone.style.whiteSpace = 'pre'; /* Keep whitespaces */
clone.style.marginLeft = 0;
clone.style.paddingLeft = 0;
clone.innerText = txt; /* Start from full length */
var i = txt.length, pos = -1, xL = 0, xC = 0, xR = clone.clientWidth;
while (i--) { /* Find the caret pos */
clone.innerText = txt.slice(0, i);
xL = clone.clientWidth;
xC = (0.5 * (xR + xL)) | 0; /* We need the center */
if (xC < mouseX) { pos = i; break }
xR = xL;
}
pos++; /* Restore the correct index */
target.parentNode.removeChild(clone); /* Clean up */
clone = null;
return pos;
}
function onFileDragOver(e) {
if(!window.chrome) e.preventDefault(); /* Show the caret in Chromium */
}
function onFileDrop(e) {
e.preventDefault();
var target = e.currentTarget, txt = target.value,
pos = getCaretPos(target, e.offsetX),
tok1 = txt.substr(0, pos), tok2 = txt.substr(pos);
/* Get the drop-action result */
var result = '', files = e.dataTransfer.files,
data = e.dataTransfer.getData('text');
for (var i = 0, f; (f = files[i]); i++) { result += f.name }
target.value = tok1 + result + tok2;
target.focus();
/* Up to You how to position the caret */
if(target.setSelectionRange) target.setSelectionRange(pos, pos);
//$(target).caret(pos);
}
$(document).ready(function () {
var target = document.getElementById('search-input');
target.addEventListener('dragover', onFileDragOver);
target.addEventListener('drop', onFileDrop);
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.2/jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquerymobile/1.4.5/jquery.mobile.js"></script>
</head>
<body>
<div data-role="page">
<div data-role="header"><h1>Caret Position</h1></div>
<div data-role="content">
<div class="ui-field-contain">
<label for="search-input">Products</label>
<input type="search" name="search-input" id="search-input" value="target text box"
spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="none"/>
</div>
</div>
</div>
</body>
</html>
Try jQuery. What you have to do is get the text box with a query selector, then bind that to mousemove and get $(this).caret().start.
Example:
let cursorPosition = 0;
$("#textinput").bind("mousemove", function() {
cursorPosition = $(this).caret().start;
});
// Do what you want with cursorPosition when file is dropped
And I think that's all you need to do here.
As long as you're willing to use jQuery you should be good.

ContentEditable div - set cursor position after updating inner html

I have a spell check solution that uses a content editable div and inserts span tags around words that are misspelled. Every time the inner html of the div is updated, the cursor moves to the beginning of the div.
I know I can move the cursor to the end of the div if the user adds new words to the end of the sentence (code below).
Old Text: This is a spell checker|
New Text: This is a spell checker soluuution|
var range = document.createRange();
range.selectNodeContents(element[0]);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
However, I am unable to retain the cursor position if the user adds words in the middle of a sentence.
Old Text: This is a spell checker
New Text: This is a new spell checker|
In the above case, the cursor goes to the end of the div when it should be after "new".
How do I retain the cursor position? Since I am updating the html and adding nodes, saving the range before the update and adding it to the selection object isn't working.
Thanks in advance.
As far as I know, changing the content of the div will always have problem.
So here is the solution that I came with. Please type error word such as helloo, dudeee
This should ideally work for textarea as well.
Solution details:
Use a ghost div with same text content
Use transparent color for the ghost div
Use border-bottom for the ghost div span text
Change zIndex so that it does't appear infront
// some mock logic to identify spelling error
const errorWords = ["helloo", "dudeee"];
// Find words from string like ' Helloo world .. '
// Perhaps you could find a better library you that does this logic.
const getWords = (data) =>{
console.log("Input: ", data);
const allWords = data.split(/\b/);
console.log("Output: ", allWords)
return allWords;
}
// Simple mock logic to identify errors. Now works only for
// two words [ 'helloo', 'dudeee']
const containsSpellingError = word => {
const found = errorWords.indexOf(word) !== -1;
console.log("spell check:", word, found);
return found;
}
const processSpellCheck = text => {
const allWords = getWords(text);
console.log("Words in the string: ", allWords);
const newContent = allWords.map((word, index) => {
var text = word;
if(containsSpellingError(word.toLowerCase())) {
console.log("Error word found", word);
text = $("<span />")
.addClass("spell-error")
.text(word);
}
return text;
});
return newContent;
}
function initalizeSpellcheck(editorRef) {
var editorSize = editorRef.getBoundingClientRect();
var spellcheckContainer = $("<div />", {})
.addClass("spell-check")
.prop("spellcheck", "false");
var spellcheckSpan = $("<span />")
.addClass("spell-check-text-content")
.css({
width: editorSize.width,
height: editorSize.height,
position: "absolute",
zIndex: -1
});
var text = $(editorRef).text();
var newContent = processSpellCheck(text);
spellcheckSpan.append(newContent);
spellcheckContainer.append(spellcheckSpan);
spellcheckContainer.insertBefore(editorRef);
$(editorRef).on("input.spellcheck", function(event) {
var newText = $(event.target).text();
var newContent = processSpellCheck(newText);
$(".spell-check .spell-check-text-content").text("");
$(".spell-check .spell-check-text-content").append(newContent);
});
}
$(document).ready(function() {
var editor = document.querySelector("#editor");
initalizeSpellcheck(editor);
});
#editor {
border: 1px solid black;
height: 200px;
}
.spell-check {
color: transparent;
}
.spell-error {
border-bottom: 3px solid orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="editor" contenteditable="true" spellcheck="false">
dudeee
</div>
This answer might work from SitePoint:
Store the selection x, y:
cursorPos=document.selection.createRange().duplicate();
clickx = cursorPos.getBoundingClientRect().left;
clicky = cursorPos.getBoundingClientRect().top;
Restore the selection:
cursorPos = document.body.createTextRange();
cursorPos.moveToPoint(clickx, clicky);
cursorPos.select();
SitePoint Article: Saving/restoring caret position in a contentEditable div
Update 25.10.2019:
The solution mentioned above doesn't work anymore since functions are used that are deprecated. Does chrome supports document.selection?

Working with contenteditable

Okay, so I am able to edit the content of my DIV just fine. However, there are a couple things I'm trying to work around.
In my editable div, I have a div containing a site address (such as twitter.com), I don't want that to be deletable from the editable DIV; the reason it's in there is so a user could copy all of the text including the domain.
If you remove all of the text except the domain, then the caret doesn't show up - or it does for a second all the way at the right side of the DIV.
And if a user deletes the domain name, it reappears but the caret is now to the left of it which I also don't want.
Any solutions on making the caret always visible and keeping it to the right of the domain?
Oh, and for some reason I can't use onKeyDown on the editable DIV to activate the anylyeText function (at least not in JSfiddle) so I had to do a setTimeout loop.
Here is my fiddle: jsfiddle
CSS:
.url {
display: block;
border: 1px solid rgb(140,140,140);
padding: 5px;
box-sizing: border-box;
color: rgb(35,155,215);
cursor: text;
outline: none;
}
.url:focus {
border-color: rgb(35,155,215);
}
.noedit {
display: inline;
color: rgb(140,140,140);
}
HTML:
<div class="url" contenteditable="true" id="textbox"><div class="noedit" contenteditable="false">http://twitter.com/</div>username</div>
JS:
var analyzeText = function() {
var textbox = document.getElementById('textbox');
var textVal = textbox.innerHTML;
var urlString = '<div class="noedit" contenteditable="false">http://twitter.com/</div>';
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
textbox.focus();
}
setTimeout(function() { analyzeText(); }, 100);
};
(function(){analyzeText();})();
Here's a method using Selection and Range.
MDN Selection
MDN Range
(If you check out MDN, you'll notice that IE9 support isn't there - prior to IE9, you had to use proprietary IE script. Google can help you out there!)
First thing I do is add these two variables:
var range = document.createRange();
var sel = window.getSelection();
Then, I modified your script like so:
if (textVal.length < urlString.length) {
textbox.innerHTML = urlString;
// We create a text node and append to the textbox to select it
textbox.appendChild(document.createTextNode(''));
// Text node is selected
range.setStart(textbox.childNodes[1], 0);
// Collapse our range to single point (we're not selecting a word, after all!)
range.collapse(true);
// Let's clear any selections
sel.removeAllRanges();
// Alright, time to position our cursor caret!
sel.addRange(range);
textbox.focus();
}
And that's it! Here's an updated Fiddle to demonstrate:
Edit - Updated to include logic to prevent user from inserting text to the left of the domain
http://jsfiddle.net/Qycw2/6/

Find closing tag in HTML string

I need to select text from different paragraphs and make a span for showing this text. See this example:
<p> this is a text </p>
<p>hello ever one </p>
Now what I want is that if I select text from the web view in my iPhone app it highlights it in a different color. For this I am making a span and setting its style. It works fine for the same paragraph but not for different paragraphs. See this:
<p> this <span class="blue">is a </span> text </p>
Class blue declares its style and it works fine, but the following does not work:
<span class="blue">
<p> this is a text </p>
<p>hello ever </span> one </p>
For solving this problem I need two spans for both paragraphs. So how can I check where the new paragraph starts? The correct HTML code is:
<span class="blue">
<p> this is a text </p></span>
<p> <span class="blue"> hello ever </span> one </p>
I need to get this HTML string but I get the wrong one. I have written a JavaScript function that gets the selection and makes a span according to selection. But on selecting text from two paragraphs it does not work because it gives the wrong section of HTML code. See my JavaScript code:
function highlightsText()
{
var range = window.getSelection().getRangeAt(0);
var selectionContents = range.extractContents();
var div;
var newDate = new Date;
var randomnumber= newDate.getTime();
var imageTag = document.createElement("img");
imageTag.id=randomnumber;
imageTag.setAttribute("src","notes.png");
var linkTxt = document.createElement("a");
linkTxt.id=randomnumber;
linkTxt.setAttribute("href","highlight:"+randomnumber);
div = document.createElement("span");
div.style.backgroundColor = "yellow";
div.id=randomnumber;
linkTxt.appendChild(imageTag);
div.appendChild(selectionContents);
div.appendChild(linkTxt);
range.insertNode(div);
return document.body.innerHTML+"<noteseparator>"+randomnumber+"<noteseparator>"+range.toString();
}
Please provide a solution that can resolve this problem.
You could do something along the lines of:
Get highlighted section of text.
Insert span tag at the first point.
For every tag that you come accross within the highlighted text:
If it's an opening tag, check if it's corresponding closing tag is in the highlighted text.
If both opening and closing tags are within the text ignore them and move to the next point after the corresponding closing tag.
If only the opening tag or only the closing tag is present, then insert before the tag and after the tag.
Insert span closing tag at the end of the highlighted text.
Possible problem:
span is intented to group inline elements and not block elements so if your highlighted text includes block elements you could have problems. You could use div instead of span to solve this or you could add some checks to distinguish between inline and block tags.
To look at tag matching:
http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx
To find if the matching closing tag of an element is in the higlighted text (not tested):
function checkClosingTag(position)
{
//Find position of next opening or closing tag along the
//string of highlighted text.
//Return 0 if no more tags.
var nextTag = findNextTag(position);
if(nextTag == 0)
{
return 0;
}
if(!isOpeningTag(nextTag))
{
return nextTag;
}
var nextTagClose = checkClosingTag(nextTag);
if(nextTagClose == 0)
{
return 0;
}
return checkClosingTag(nextTagClose);
}
This looks like a fairly involved problem though - I don't have time to write the code for you but you should be able to work out a way of doing it from here.
some change in your code can work
see this line of codes
function highlightsText()
{
var range, sel;
if (window.getSelection)
{
sel = window.getSelection();
if (sel.getRangeAt) {
range = sel.getRangeAt(0);
}
document.designMode = "on";
if (range) {
sel.removeAllRanges();
sel.addRange(range);
}
if ( !document.execCommand("HiliteColor", false, "yellow") ) {
document.execCommand("BackColor", false, "yellow");
}
document.designMode = "off";
}
else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
range.execCommand("BackColor", false, "yellow");
}
var newDate = new Date;
var randomnumber= newDate.getTime();
var nodeList = document.querySelectorAll(".Apple-style-span");
for (var i = 0, length = nodeList.length; i < length; i++) {
nodeList[i].id = randomnumber;
}
var div = document.getElementById(randomnumber);
var imageTag = document.createElement("img");
imageTag.id=randomnumber;
imageTag.setAttribute("src","notes.png");
var linkTxt = document.createElement("a");
linkTxt.id=randomnumber;
linkTxt.setAttribute("href","highlight:"+randomnumber);
div.appendChild(linkTxt);
range.insertNode(div);
return document.body.innerHTML+"<noteseparator>"+randomnumber+"<noteseparator>"+range.toString();
}
You need make some adjustments in this code.
Since your goal (based on what you stated in your question) is to highlight selected text with a different color, here is a solution to that goal.
The HTML5BoilerPlate project includes styles to control the selection color (line 52 in the style.css file)
Here's the CSS for it:
/* Remove text-shadow in selection highlight: h5bp.com/i
*
* These selection declarations have to be separate
*
* Also: hot pink! (or customize the background color to match your design)
*/
::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; }
::selection { background: #fe57a1; color: #fff; text-shadow: none; }

Calculating the absolute start and finish of a range (highlighted text) within an editable div

I'm running an experiment to see if I can return that absolute start and end points of a highlighted block of test within a contentEditable (not actually important to the test) div. I'm not building a rich text editor or anything I just want to know how it's done! So all I want to return upon right click (not important, I'm just messing with that too) are two numbers, the absolute distance from the start of the wrapper div to the start of the selection and the absolute distance from the start of the wrapper div to the end of the selection.
I thought Mootools would make this easy but I could only get their implementation to work with forms (i.e. textarea, input etc). So I had a quick bash using Google, and it all worked fine when no tags were involved, e.g. He|llo Wor|ld (where the pipe, |, represents the highlighted range) would return [2, 9] which is correct. However, the moment I add tags to the div to allow colours / formatting these numbers do not make any sense as the range only gives position relative to text nodes and not an absolute value. Any ideas how to get this? I can only imagine it involves some form of horrendous DOM manipulation.
JS:
window.addEvent('domready', function()
{
document.body.addEvent('contextmenu',
function(e)
{
e.stop();
}
);
if(!window.SelectionHandler)
{
SelectionHandler = {};
}
SelectionHandler.Selector = {};
SelectionHandler.Selector.getSelected = function()
{
var userSelection = '';
if(window.getSelection)
{
userSelection = window.getSelection();
}
else if(document.getSelection)
{
userSelection = document.getSelection();
}
else if(document.selection)
{
userSelection = document.selection.createRange();
}
return userSelection;
}
SelectionHandler.Selector.getText = function(userSelection)
{
var selectedText = userSelection;
if(userSelection.text)
{
selectedText = userSelection.text;
}
return selectedText;
}
SelectionHandler.Selector.getRange = function(userSelection)
{
if(userSelection.getRangeAt && typeof(userSelection.getRangeAt) != 'undefined')
{
var selectedRange = userSelection.getRangeAt(0);
}
else
{
var selectedRange = document.createRange();
selectedRange.setStart(userSelection.anchorNode, userSelection.anchorOffset);
selectedRange.setEnd(userSelection.focusNode, userSelection.focusOffset);
}
return selectedRange;
}
$('mydiv').addEvent('mousedown',
function(event)
{
if(event.rightClick)
{
var userSelection = SelectionHandler.Selector.getSelected();
var selectedText = SelectionHandler.Selector.getText(userSelection);
var selectedRange = SelectionHandler.Selector.getRange(userSelection);
// New ranges to add identifiable nodes (must be in that order!?)
var endRange = document.createRange();
endRange.setStart(selectedRange.endContainer, selectedRange.endOffset);
endRange.insertNode(document.createTextNode('!~'));
var startRange = document.createRange();
startRange.setStart(selectedRange.startContainer, selectedRange.startOffset);
startRange.insertNode(document.createTextNode('~!'));
// Find the position of our new identifiable nodes (and account for their removal)
var div_content = $('mydiv').get('html');
var start = div_content.indexOf('~!');
var end = div_content.indexOf('!~') - 2;
// Remove our identifiable nodes (DOESN'T WORK)
//startRange.deleteContents();
//endRange.deleteContents();
// This does work, but obviously loses the selection
div_content = div_content.replace('~!', '').replace('!~', '');
$('mydiv').set('html', div_content);
console.log(start + ' vs ' + end);
}
}
);
}
);
HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Edit Range Test</title>
</head>
<script type="text/javascript" src="mootools.js"></script>
<script type="text/javascript" src="edit_range.js"></script>
<style>
#mydiv {
width: 400px;
height: 400px;
border: 1px solid #a2a2a2;
padding: 5px;
}
</style>
<body>
<h1>Edit Range Test</h1>
<div id="mydiv" contentEditable="true"><span style="color: red;">Hello</span> World! <span style="color: red;">Hello</span> World! </div>
</body>
</html>
So when I now select He|llo Wor|ld (where the pipe, |, again represents the highlighted range) it would return [2, 4] when I want [28, 42].
EDIT: I've updated the code to clarify what I am trying to do. It does most of what I wanted to test, but loses the selection and is very scruffy!
Thanks in advance!
First, the information you get from Range objects are about as useful as you can get: for each of the start and end boundaries of the Range you get a DOM node and an offset within that node (a character offset inside a text or comment node or a child node offset otherwise), which completely describes the boundary. What you mean by "absolute start and end points" I imagine is two character offsets within the whole editable element, but that is a slippery concept: it seems simple, but is tricky to pin down. For example, how many characters does a paragraph break count for? A <br>? An <a> element with display: block? Elements such as <script> elements that contain text but are not visible to the user? Elements hidden via display: none? Elements outside the normal document flow, such as those positioned via position: absolute? Multiple consecutive whitespace characters that are rendered as a single visible character by the browser?
Having said all that, I have recently had a go at writing code to do this, and yes, it does involve DOM manipulation (although not that horrendous). It's extremely rudimentary and doesn't deal satisfactorily with any of the above issues, partly because I knocked it up quite quickly for a question on SO, and partly because in general I don't think it's possible to deal nicely in general with all those issues. The following answer provides functions for saving and restoring the selection as character indices within an editable element: replace innerHTML in contenteditable div

Categories