Remplacer un mot spécifique dans contenteditable

J’ai un div contenteditable

Je dois obtenir le dernier mot de la position du curseur et, à certaines conditions, je dois tester et supprimer ce mot uniquement. Voici comment je vais

 $('#divTest').on('keyup focus', function (e) { if (e.keyCode == 32) { var lastWord = getWordPrecedingCaret(this), spanLastWord = $('#lastWord'); } }); function getWordPrecedingCaret(containerEl) { var preceding = "", sel, range, precedingRange; if (window.getSelection) { sel = window.getSelection(); if (sel.rangeCount > 0) { range = sel.getRangeAt(0).cloneRange(); range.collapse(true); range.setStart(containerEl, 0); preceding = range.toSsortingng(); } } else if ((sel = document.selection) && sel.type != "Control") { range = sel.createRange(); precedingRange = range.duplicate(); precedingRange.moveToElementText(containerEl); precedingRange.setEndPoint("EndToStart", range); preceding = precedingRange.text; } var words = range.toSsortingng().sortingm().split(' '), lastWord = words[words.length - 1]; if (lastWord) { var resultValue = 'some'; // this value is coming from some other function if (resultValue == lastWord) { alert('do nothing'); // do nothing } else { alert('replace word'); // delete That specific word and replace if with resultValue } return lastWord; } } 

Démo: http://codepen.io/anon/pen/ogzpXV

J’ai essayé range.deleteContents (); mais cela supprimera tout le contenu de la div. Comment puis-je remplacer un mot spécifique uniquement?

Pour travailler avec les Ranges nous devons garder à l’esprit que nous travaillons avec des nœuds, pas seulement le texte qui est rendu. La structure que vous souhaitez manipuler est la suivante:

 
<-- Element Node "some text" <-- TextNode

Mais cela pourrait aussi être:

 
<-- Element Node "some text" <-- TextNode "more text" <-- TextNode "" <-- TextNode

Pour résoudre votre problème, il est plus simple de gérer un seul TextNode . Je propose d’utiliser la fonction normalize() pour les associer à un seul.

Ensuite, il vous suffit de définir la Range sur les limites du mot avant deleteContents() . Une fois supprimé, vous pouvez insérer un nouveau TextNode avec la substitution en utilisant insertNode() .

 var wordStart = range.toSsortingng().lastIndexOf(lastWord); var wordEnd = wordStart + lastWord.length; /* containerEl.firstChild refers to the div's TextNode */ range.setStart(containerEl.firstChild, wordStart); range.setEnd(containerEl.firstChild, wordEnd); range.deleteContents(); range.insertNode(document.createTextNode(resultValue)); 

Pour que cela fonctionne, il faut que le texte soit dans un seul TextNode . Mais après ” ìnsertNode le div contiendra plusieurs nœuds de texte. Pour résoudre ce problème, appelez simplement normalize() pour joindre tous les éléments TextNode .

 containerEl.normalize(); 

Modifier:

Comme le souligne Basj, la solution d’origine échoue pour plusieurs lignes. En effet, lorsque vous appuyez sur ENTER, la structure change de:

 
<-- Element Node "some text" <-- TextNode

à quelque chose comme:

 
<-- Element Node
"some text"
"more text"

J’ai mis à jour cette réponse, mais il convient également de lire la réponse de Basj à cette question: Remplacez le mot avant le curseur lorsque plusieurs lignes sont disponibles dans contenteditable.

JSFiddle démo ou extrait de code exécutable:

 document.getElementById('divTest').onkeyup = function (e) { if (e.keyCode == 32) { getWordPrecedingCaret(this); } }; function getWordPrecedingCaret(containerEl) { var preceding = "", sel, range, precedingRange; if (window.getSelection) { sel = window.getSelection(); if (sel.rangeCount > 0) { range = sel.getRangeAt(0).cloneRange(); range.collapse(true); range.setStart(containerEl, 0); preceding = range.toSsortingng(); } } else if ((sel = document.selection) && sel.type != "Control") { range = sel.createRange(); precedingRange = range.duplicate(); precedingRange.moveToElementText(containerEl); precedingRange.setEndPoint("EndToStart", range); preceding = precedingRange.text; } var words = range.toSsortingng().sortingm().split(' '), lastWord = words[words.length - 1]; if (lastWord) { var resultValue = 'some'; // this value is coming from some other function if (resultValue == lastWord) { console.log('do nothing: ' + lastWord); // do nothing } else { console.log('replace word ' + lastWord); /* Find word start and end */ var wordStart = range.endContainer.data.lastIndexOf(lastWord); var wordEnd = wordStart + lastWord.length; console.log("pos: (" + wordStart + ", " + wordEnd + ")"); range.setStart(range.endContainer, wordStart); range.setEnd(range.endContainer, wordEnd); range.deleteContents(); range.insertNode(document.createTextNode(resultValue)); // delete That specific word and replace if with resultValue /* Merge multiple text nodes */ containerEl.normalize(); } return lastWord; } } 
 
Write words here and hit SPACE BAR
  words = ['oele', 'geel', 'politie', 'foo bar']; function markWords() { var html = div.html().replace(/<\/?strong>/gi, ''), text = html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' '), exp; $.each(words, function(i, word) { exp = new RegExp('\\b(' + word + ')\\b', 'gi'); html = html.replace(exp, function(m) { console.log('WORD MATCH:', m); return '' + m + ''; }); }); //html = html.replace(' ', ' ').replace(/\s+/g, ' '); console.log('HTML:', html); console.log('----'); div.html(html); } 

Appelez cette fonction sur setinterval

Violon

La solution de Tobías fonctionne bien pour les div. Mais si vous ajoutez plusieurs lignes, cela ne fonctionnera plus.

Voici une solution générale qui fonctionne à la fois pour les div à une ou plusieurs lignes.