I change the text color of matched words as the user type (He types in a contenteditable div). If he types the matching word, I create a span element and add a class name to it (this class has the styles via CSS).
The problem is that when there is a match, and the user keeps typing, he is inside that newly inserted span and he keeps writing with the new style (so if for example the new style is blue text color, the text will remain blue after the match).
But I want to wrap only that matched word with the span element.
Edit: My goal is to be able to toggle styles once there is a match, and if the user writes again inside that match then recheck it if it still matches. If not, completely remove that styling.
(Example: User writes "Let's find for the matching word", if the word is "word", then highlight this word and be able to click it. If he then edits the text to "Let's find for the matching wosdfsdfrd" then remove the styling from that word)
This is what I currently have. Sometimes it just leaves a <font> tag with color style that I never added: (You can change the searched word by editing the searchTerm variable. Originally I used regex, but for simplicity it's a string)
const processChange = debounce(() => replace());
function debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
}
function replaceText(textNode) {
let textDiv = document.getElementById("textarea");
let textContent = textNode.textContent;
let searchTerm = 'word';
let indexOfFirst = textContent.indexOf(searchTerm);
if (indexOfFirst >= 0) {
if (textNode.parentNode.nodeName.toLowerCase() != 'span') {
let subTextNode = textNode.splitText(indexOfFirst);
let span = document.createElement('span');
span.classList.add("match-found");
span.setAttribute("onclick", "toggleMatch(this)");
span.appendChild(document.createTextNode(searchTerm));
textDiv.insertBefore(span, subTextNode);
subTextNode.deleteData(0, searchTerm.length);
} else {
console.log("..");
}
}
}
// get all text nodes
function textNodesUnder(el) {
var n, a = [],
walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
while (n = walk.nextNode()) a.push(n);
return a;
}
function replace() {
let textDiv = document.getElementById("textarea");
let a = textNodesUnder(textDiv);
a.forEach(element => replaceText(element));
}
function matchFound(e) {
e.setAttribute("onclick", "toggleMatch(this)");
e.className = 'match-found';
}
function toggleMatch(e) {
e.setAttribute("onclick", "matchFound(this)");
e.className = 'toggle-match';
}
#textarea {
-moz-appearance: textfield-multiline;
-webkit-appearance: textarea;
border: 1px solid gray;
font: medium -moz-fixed;
font: -webkit-small-control;
height: 130px;
overflow: auto;
padding: 2px;
resize: both;
width: 400px;
}
.match-found {
color: red;
cursor: pointer;
}
.toggle-match {
color: green;
cursor: pointer;
}
<div id="textarea" contenteditable="true" onkeyup="processChange()"></div>