js contenteditable - prevent from writing into new

2019-04-12 14:15发布

问题:

I'm working on a simply syntax highlighter that replaces text with dom elements with classes.

Say, I have a

<div contenteditable="true">
  Some text. | And some other text.
</div>

and the cursor is at the | pipe

//if a user types foo

<div contenteditable="true">
  Some text. foo| And some other text.
</div>

// and I replace it, then set the selection after the inserted element

<div contenteditable="true">
  Some text. <span class="highlight-foo">foo</span>| And some other text.
</div>

but if you type, you type into the span..no matter what.

//type bar

<div contenteditable="true">
  Some text. <span class="highlight-foo">foobar|</span> And some other text.
</div>

and I don't want that, but I can't set the selection right after the newly inserted element.

<div contenteditable="true">
  Some text. <span class="highlight-foo">foo</span>bar| And some other text.
</div>

here's the js that does the highlighting and replacing..

...
// chunk is the variable that holds foo, if we stick the the above example..
// select it
range.setStart(range.startContainer, range.startOffset-chunk.length);

// add it to the range
sel.addRange(range);

// replace it..then set the range to the textNode after the span
// because after the injection selection.anchorNode is the textNode inside the span..
// in chrome, however, this below sets the range correctly.
range.setStart(sel.anchorNode.parentNode.nextSibling, 0);

// and it's all good until now, but adding the range to the selection does nothing..
sel.removeAllRanges();
sel.addRange(range)

// the selection is still inside the span..

How to solve that? oO I've read a lot on it even looked a fair amount of questions on here, but nothing regarding this particular problem.

回答1:

I assume you're testing this in WebKit. WebKit's caret handling prevents you from placing the caret at the start of a text node: https://bugs.webkit.org/show_bug.cgi?id=23189. In Firefox your code should be fine.

The workarounds are all horrible. One is to insert a zero-width space (such as \u200B) and select it, but then you have add special code to handle arrow keys and code to remove the zero-width spaces.

Update

Another workaround is to use the fact that WebKit has a special case for links where it forces the caret to go after the link:

http://jsfiddle.net/RfMju/

So what you could do, which is unpleasant but doable, is to use a <span> when you want the caret to go inside the highlighted element and change it into a link otherwise.