简体   繁体   中英

Chrome Extension contenteditable get and set caret position

Working with the DOM in a google extension

I am creating an Google Chrome extension where I need to insert a link into the gmail window. I am able to create a simple extension button in the top of the window and execute code that does replaces elements in the email document.

Now, I want to do add links in the contenteditable section as I type. It seems like there should be a simple way to replace text with a link in the javascript document object model. Also this only needs to work for chrome because I don't intend to author a firefox or ie version any time soon.

Originally I was trying to do the following:

  1. Insert text from the current caret position
  2. Capture the caret position.
  3. Set the caret position by its offset.

getSelection, NOT so fast

I tried using window.getSelection within the gmail context(that is a body element that is set to contenteditable and embedded inside an iframe in the gmail tab) to get the current range and position. The selection was always empty and contained no ranges regardless of whether text was selected or not.

I have found that the reason the getSelection code didn't work was because document.getSelection was being selected on the wrong window. There are 5 iframes(windows) available when you are creating a new message in gmail. There is one called nested deep inside this frame is the iframe that needs to have getSelection called on to get correct text selection. Here is some code that will allow you to get the selected caret position within the contenteditable area for your email message.

This is one way to get the selection for the gmail document window

var mySelection = $("iframe")[4].contentDocument.
    querySelector("iframe").contentDocument.getSelection();

First text replacement attempt

I have now gotten hopefully a little closer to a solution. Sorry if it is sloppy I can't find any good documentation on how to write this code.

// Select the keyword
var range = s.getRangeAt(0);
var keyEl = range.startContainer.splitText(index);
var newRange = doc.createRange();
newRange.selectNode(keyEl);
s.removeAllRanges();
newRange.setEnd(keyEl, length);
s.addRange(newRange);

// Insert the link
var newLink = document.createElement("a");
newLink.setAttribute("href", value);
s.removeAllRanges();
newRange.surroundContents(newLink);

// Set the position
newRange.setStartAfter(newLink);
newRange.collapse(true);
s.addRange(newRange);

This works almost perfectly but the problem is that the text caret is inside the link so any text typed after just is inserted into the link. This is a big problem. And, trying some things I was able to make that problem go away partially. A new problem that was similar led to some weird links that extended in to the following words, but only for a few characters.

Hopefully, the final attempt at text replacement

// find position in text 
var index = s.focusNode.data.search(keyword);
var length = keyword.length;

var range = s.getRangeAt(0);
var keyEl = range.startContainer.splitText(index);
var newRange = doc.createRange();
newRange.selectNode(keyEl);
s.removeAllRanges();
newRange.setEnd(keyEl, length);
s.addRange(newRange);

var newLink = document.createElement("a");
newLink.setAttribute("href", value);
$(newLink).css("text-decoration", "none");
$(newLink).css("cursor", "pointer");
$(newLink).css("color", "color:#3366FF");
newRange.surroundContents(newLink);
newRange.collapse(false);
s.removeAllRanges();
s.addRange(newRange);

There was one problem that seemed to be pervasive in my many trials that has now gone away. This made some weird links that extended in to the following words.

Feel free to help me clean up the code to make it more succinct

If you exactly know the position where you want to insert the caret, this should do it:

var node = someTextNode;
var caret = 10; // insert caret after the 10th character
var range = document.createRange();
range.setStart(node,caret);
range.setEnd(node,caret);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

This however works only on DOM text nodes. So, if you want to manipulate the contents of a contenteditable div or similar, you will have to recursively walk through its child elements and check for text nodes.

I recently wrote a Chrome extension that does text replacement on websites including GMail - here's the source of the corresponding jQuery plugin, feel free do exploit it if it fits your use case: https://github.com/squidpeople/ASCIImoji/blob/master/asciimoji.jquery.js

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM