替换contenteditable div中的单词并正确设置插入位置

时间:2017-07-03 10:52:22

标签: javascript range selection contenteditable

我一直在搜索很多范围和选择相关的问题(大部分是由@tim-down回答的),但我无法得到我需要的东西,尽管我接近了。

我想在当前关注的文本节点中搜索单词foo。如果我找到它 - 用bar替换它,并在替换字的末尾设置插入位置。例如:

"Lorem ipsum dolor foo amet, consectetur adipiscing elit."

变成:

"Lorem ipsum dolor bar amet, consectetur adipiscing elit."
// -------------------^--- caret position

我的尝试

我目前只有一半工作 - 它删除了文本,但没有添加任何内容。不过,我不确定它是最好的方法:



function replacer(search, replace) {
  var sel = window.getSelection();
  if (!sel.focusNode) {
    return;
  }

  var startIndex = sel.focusNode.nodeValue.indexOf(search);
  var endIndex = startIndex + search.length;

  if (startIndex === -1) {
    return;
  }

  var range = document.createRange();
  range.setStart(sel.focusNode, startIndex);
  range.setEnd(sel.focusNode, endIndex);
  range.insertNode(document.createTextNode("bar"));

  sel.removeAllRanges();
  sel.addRange(range);
}

document.addEventListener("keypress", function() {
  replacer("foo", "bar");
});

<div contenteditable="true" style="width: 600px; height: 300px;">Lorem ipsum dolor foo amet, consectetur adipiscing elit.</div>
&#13;
&#13;
&#13;

注意:我只关心与Chrome的兼容性。

3 个答案:

答案 0 :(得分:1)

解释代码注释和控制台日志。
请参阅selection

function replacer(search, replace) {
  var sel = window.getSelection();
  if (!sel.focusNode) {
    return;
  }

  var startIndex = sel.focusNode.nodeValue.indexOf(search);
  var endIndex = startIndex + search.length;
  if (startIndex === -1) {
    return;
  }
  console.log("first focus node: ", sel.focusNode.nodeValue);
  var range = document.createRange();
  //Set the range to contain search text
  range.setStart(sel.focusNode, startIndex);
  range.setEnd(sel.focusNode, endIndex);
  //Delete search text
  range.deleteContents();
  console.log("focus node after delete: ", sel.focusNode.nodeValue);
  //Insert replace text
  range.insertNode(document.createTextNode(replace));
  console.log("focus node after insert: ", sel.focusNode.nodeValue);
  //Move the caret to end of replace text
  sel.collapse(sel.focusNode, 0);
}

document.addEventListener("keypress", function() {
  replacer("foo", "bar");
});
<div contenteditable="true" style="width: 600px; height: 300px;" id='content'>Lorem ipsum dolor foo amet, consectetur adipiscing elit.</div>

答案 1 :(得分:0)

sidoshi,这修复了替换后的插入符号位置

function replacer(search, replace) {
  var sel = window.getSelection();
  if (!sel.focusNode) {
    return;
  }

  var startIndex = sel.focusNode.nodeValue.indexOf(search);
  var endIndex = startIndex + search.length;
  if (startIndex === -1) {
    return;
  }
  console.log("first focus node: ", sel.focusNode.nodeValue);
  var range = document.createRange();
  //Set the range to contain search text
  range.setStart(sel.focusNode, startIndex);
  range.setEnd(sel.focusNode, endIndex);
  //Delete search text
  range.deleteContents();
  console.log("focus node after delete: ", sel.focusNode.nodeValue);
  //Insert replace text
  range.insertNode(document.createTextNode(replace));
  console.log("focus node after insert: ", sel.focusNode.nodeValue);
  //Move the caret to end of replace text
  var replacement = document.createTextNode(replace); 
  range.insertNode(replacement);
  range.setStartAfter(replacement);                       
  // Chrome fix
  sel.removeAllRanges();
  sel.addRange(range); 
}

document.addEventListener("keypress", function() {
  replacer("foo", "bar");
});
<div contenteditable="true" style="width: 600px; height: 300px;" id='content'>Lorem ipsum dolor foo amet, consectetur adipiscing elit.</div>

这不是所要求的,但循环多次替换并处理上述DIV和textareas同样好。

var replaceValues = {
    'foo' : 'bar',
    'aaa': 'bbbb'
}

$('.macroenabled').keyup(function(e) {
  // change to keypress if you want it to happen after space
  for (var search in replaceValues) {
    if ($(document.activeElement).is(":input")) {
      var newText = $(e.target).val();
      var temp = new RegExp(search, 'gim');
      newText = newText.replace(temp, replaceValues[search]);
      $(e.target).val(newText);
    } else { // element contenteditable DIV
      var sel = window.getSelection();
      if (!sel.focusNode || ! sel.focusNode.nodeValue) { // Nothing focused
        continue;
      }
      var startIndex = sel.focusNode.nodeValue.indexOf(search);
      var endIndex = startIndex + search.length;
      if (startIndex === -1) { // search not found
        continue;
      }
      var range = document.createRange();
      range.setStart(sel.focusNode, startIndex);
      range.setEnd(sel.focusNode, endIndex);
      range.deleteContents(); //Delete search text
      var replacement = document.createTextNode(replaceValues[search]); 
      range.insertNode(replacement);
      range.setStartAfter(replacement);                       
      sel.removeAllRanges(); // Chrome fix
      sel.addRange(range);
    }
  }
});
#div1 {
  border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>I am a textarea, try 'aaa' or 'foo'</div>
<textarea class="macroenabled"></textarea>
<div>I am a content editable DIV, try 'aaa' or 'foo'</div>
<div id="div1" contenteditable="true" class="macroenabled"></div>

答案 2 :(得分:0)

使用createNodeIteratorNodeFilter.SHOW_TEXT迭代所有textNode,很容易找出所有出现的字符串foo并用所需的任何内容bar textNode替换情况。

第二个阶段,应该与查找和替换功能分离,是插入符放置请求,并由函数setRangeAtEnd实现,该函数接收单个参数node,可以从第一个函数replaceTextWithNode的输出中获得该函数,该函数返回 contentEditable 中替换的所有节点的数组。

const elm = document.querySelector('[contenteditable]');

function replaceTextWithNode( elm, text, replacerNode ){
  var iter = document.createNodeIterator(elm, NodeFilter.SHOW_TEXT),
      textnode, replacedNode, idx, newNode,
      maxIterations = 100,
      addedNodes = [];

  while( textnode = iter.nextNode() ){
      if( !maxIterations-- ) break;
      // get the index of which the text is within the textNode (if at all)
      idx = textnode.nodeValue.indexOf(text)
      if( idx == -1 ) continue
      
      replacedNode = textnode.splitText(idx)
      newNode = replacerNode.cloneNode()

      // clean up the tag's string and put tag element instead
      replacedNode.nodeValue = replacedNode.nodeValue.replace(text, '')
      textnode.parentNode.insertBefore(newNode, replacedNode)
      addedNodes.push(newNode)
  }

  return addedNodes
}


function setRangeAtEnd( node ){
  node = node.firstChild || node;
  const sel = document.getSelection()

  if( sel.rangeCount )
      ['Start', 'End'].forEach(pos =>
          sel.getRangeAt(0)["set" + pos](node, node.length)
      )
}

// Replace all 'foo' with 'bar' textNodes
var addedNodes = replaceTextWithNode(elm, 'foo', document.createTextNode('bar'))

// Place caret at last occurrence of 'bar'
elm.focus()
setRangeAtEnd(addedNodes[addedNodes.length-1])
<div contenteditable>Lorem ipsum foo dolor foo amet foo, consectetur adipiscing elit.</div>