highlight the .replace() word in javascript - javascript

function handleTextNode(textNode) {
if(textNode.nodeName !== '#text'
|| textNode.parentNode.nodeName === 'SCRIPT'
|| textNode.parentNode.nodeName === 'STYLE'
) {
//Don't do anything except on text nodes, which are not children
// of or .
return;
}
var find = ["build", "add", "Fast"];
var replace = ["Develope", "add", "Fast"];
let origText = textNode.textContent;
let newHtml = origText.replace(new RegExp("(" + find.map(function(i){return i.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&")}).join("|") + ")", "g"),function(s){ return replace[ find.indexOf(s)]});
if( newHtml !== origText) {
let newSpan = document.createElement('span');
newSpan.innerHTML = newHtml;
textNode.parentNode.replaceChild(newSpan,textNode);
}
// <span title="Dyslexia is a learning disorder that involves difficulty reading due to problems identifying speech sounds. " style="background-color: yellow"></span>
}
let textNodes = [];
//Create a NodeIterator to get the text nodes in the body of the document
let nodeIter = document.createNodeIterator(document.body,NodeFilter.SHOW_TEXT);
let currentNode;
//Add the text nodes found to the list of text nodes to process.
while(currentNode = nodeIter.nextNode()) {
textNodes.push(currentNode);
}
//Process each text node
textNodes.forEach(function(el){
handleTextNode(el);
});
i want to make the replaced word highlighted. this code will run only for web pages. like i am building a adblocker with a functionality that help dyslexia Student. At the moment it only replace words correctly. can any body help?

Related

Replace CharacterData content with some HTML

First Time I am asking something here I hope I will be precise enough.
I am trying to make a simple extension for chrome that highlight the selected text when I press "H" on my keyboard, but I have some issue :
Use Case
The user select with his mouse a piece of text.
The user press H on his keyboard
Bibbidi-Bobbidi-Boo the selected text is highlighted.
Code so far
To detect when user press H and to get the piece of text he selected :
window.addEventListener('keydown', e => {
if(e.code == "KeyH")
{
var selected = window.getSelection()
//SimpleHighLight(selected);
ComplexHighLight(selected);
}
});
I have coded coded a simple way to do what I want like this :
function SimpleHighLight(selected){
var selectedText = selected.toString();
if(selectedText.length != 0)
{
var range = selected.getRangeAt(0);
var element = selected.anchorNode.parentNode;
var highlited = "<span style='background: rgb(255,255,0)'>" + selectedText + "</span>";
var reg = new RegExp(selectedText,"g");
var text = element.innerHTML.replace(reg, highlited);
element.innerHTML = text;
}
}
It work fine for piece of text in an unique DOM element and when there is no other occurrence of the selected text but I want it to always work, like in a case of my selected text comes from 2 different paragraphs.
So I did this :
function ComplexHighLight(selected){
var selectedText = selected.toString();
if(selectedText.length != 0)
{
console.log(" Selection : " + selectedText);
var range = selected.getRangeAt(0);
if(!range.collapsed)
{
var startNode = range.startContainer;
var startOffset = range.startOffset;
var endNode = range.endContainer;
var endOffset = range.endOffset;
if(startNode == endNode) //Means that its in the same node element
{
var highlited = "<span style='background: rgb(255,255,0)'>" + selectedText + "</span>";
startNode.replaceData(startOffset, endOffset-startOffset, highlited);
startNode.parentNode.innerHTML = startNode.nodeValue;
}
}
}
}
That's only a part of the problem where I handle when a piece of text is in the same element (I am already too much in trouble to handle when the selected text comes from multiples elements :( ).
Issue
On paper, it should work, but the main issue is that when I do :
startNode.parentNode.innerHTML = startNode.nodeValue;
the <span> division is given to innerHTML as a string and not some HTML stuff.
I have worked around this for about the whole evening but I can't fix it, does anyone have an idea of how I should do that ?

Get text from HTML with appropriate whitespace

It is easy to extract the text from HTML using the jQuery .text() method...
$("<p>This <b>That</b> Other</p>").text() == "This That Other"
But if there is no whitespace between the words/elements, then text becomes concatenated...
$("<p>This <b>That</b><br/>Other</p>").text() == "This ThatOther"
Desired: "This That Other"
$("<div><h1>Title</h1><p>Text</p></div>").text() == "TitleText"
Desired: "Title Text"
Is there any way to get all the text from the HTML (either using .text() or other methods) which would mean that the above examples would come out as desired?
You can traverse the DOM tree looking for a node with a nodeType of 3 (text node). When you find one, add it to an array. If you find a non-text node, you can pass it back into the function to keep looking.
function innerText(element) {
function getTextLoop(element) {
const texts = [];
Array.from(element.childNodes).forEach(node => {
if (node.nodeType === 3) {
texts.push(node.textContent.trim());
} else {
texts.push(...getTextLoop(node));
}
});
return texts;
}
return getTextLoop(element).join(' ');
}
/* EXAMPLES */
const div = document.createElement('div');
div.innerHTML = `<p>This <b>That</b><br/>Other</p>`;
console.log(innerText(div));
const div2 = document.createElement('div');
div2.innerHTML = `<div><h1>Title</h1><p>Text</p></div>`;
console.log(innerText(div2));
If you are just worried about br tags, you can replace them with a text node.
var elem = document.querySelector("#text")
var clone = elem.cloneNode(true)
clone.querySelectorAll("br").forEach( function (br) {
var space = document.createTextNode(' ')
br.replaceWith(space)
})
var cleanedText = clone.textContent.trim().replace(/\s+/,' ');
console.log(cleanedText)
<div id="text">
<p>This <b>That</br>Other</p>
</div>

Highlight Ajax Response with Javascript

I'm trying to highlight a query inside a text coming from an ajax response, before constructing HTML with it and pasting that into the DOM. Right now I'm using this code snippet:
function highlightWords(line, word, htmltag) {
var tag = htmltag || ["<b>", "</b>"];
var regex = new RegExp('(' + preg_quote(word) + ')', 'gi');
return line.replace(regex, tag[0] + "$1" + tag[1]);
}
function preg_quote(str) {
return (str + '').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
}
However, this is not capeable of highlighting different words if the query is something like sit behind. It will only highlight the complete phrase and not the single words. It also doesn't care about HTML tags and that produces unpretty results if the query is span for example...
I've found various libraries which handle highlighting way better, like https://markjs.io/ or https://www.the-art-of-web.com/javascript/search-highlight/
Those libraries though always want to highlight content which is already present in the DOM.
My search gets an ajax response, which I then turn into HTML with JS and paste the complete HTMLString into a parent container using DOM7 (which is similar to jQuery). Therfor I would prefer to highlight the text before creating the HTMLString and pasting it in the DOM.
Any ideas?
I just make the highlight in the response of ajax request. It's works for me:
$.ajax({
url : url,
type : 'POST',
success: function(response) {
// Highlight
let term = 'word';
$context = $("#selector");
$context.show().unmark();
if (term){
$context.mark(term, {
done: function() {
$context.not(":has(mark)").hide();
}
});
}
}
});
Snippet style: Warning: this uses DOM7 as per Question
Overview: Instead of appending the whole text as HTML string to your #container,
Append the portions of normal text, as text, and the highlighted elements as elements, so you can style them at will.
var text // your ajax text response
var strQuery = 'sit behind' // your query string
var queryWords = strQuery.split(' ')
var textWords = text.split(' ')
var bufferNormalWords = []
textWords.forEach(function (word) {
if (queryWords.indexOf(word) != -1) { // found
var normalWords = bufferNormalWords.splice(0, buffer.length) // empty buffer
// Your DOM7 commands
$$('#container').add('span').text(normalWords.join(' ')) // normal text
$$('#container').add('span').css('color', 'red').text(word + ' ') // why not red
}
else bufferNormalWords.push(word)
})
Do not mess up with text becoming HTMLStrings, just set text, and create the necesary elements to style them as you want with your DOM7.
If your ajax response contains html, I don't think there's an easy way to get around creating DOM elements first. Below gets the job done, even in the case where span is in the query and the ajax results contain <span>
function highlightWords(line, word, htmltag) {
var words = word.split(/\s+/);
var tag = htmltag || ["<b>", "</b>"];
var root = document.createElement("div");
root.innerHTML = line;
root = _highlightWords(words, tag, root);
return root.innerHTML;
}
// Recursively search the created DOM element
function _highlightWords(words, htmlTag, el) {
var children = [];
el.childNodes.forEach(function(el) {
if (el.nodeType != 3) { // anything other than Text Type
var highlighted = _highlightWords(words, htmlTag, el);
children.push(highlighted);
} else {
var line = _highlight(el.textContent, words, htmlTag);
var span = document.createElement("span");
span.innerHTML = line;
children.push(span);
}
});
// Clear the html of the element, so the new children can be added
el.innerHTML = "";
children.forEach(function (c) { el.appendChild(c)});
return el;
}
// Find and highlight any of the words
function _highlight(line, words, htmlTag) {
words.forEach(function(singleWord) {
if (!!singleWord) {
singleWord = htmlEscape(singleWord);
line = line.replace(singleWord, htmlTag[0] + singleWord + htmlTag[1]);
}
});
return line;
}
I think you were on the right track using a library for that.
I have been using for that a great library named mark.js.
It works without dependencies or with jQuery.
The way that you can make it work.
Make the AJAX call.
Load the string to the DOM.
Call the Mark.js API on the content you have loaded.
Here's a code snippet:
document.addEventListener('DOMContentLoaded', getText);
function getText() {
const headline = document.getElementsByTagName("h1")[0];
const p = document.getElementsByTagName("p")[0];
fetch('https://jsonplaceholder.typicode.com/posts/1').
then(response => response.json()).
then(json => {
console.log(json);
headline.innerHTML = json.title;
p.innerHTML = json.body;
addMark('aut facere');
});
}
function addMark(keyword) {
var markInstance = new Mark(document.querySelector('.context'));
var options = {
separateWordSearch: true
};
markInstance.unmark({
done: function() {
markInstance.mark(keyword, options);
},
});
}
<script src="https://cdn.jsdelivr.net/mark.js/8.6.0/mark.min.js"></script>
<div class="context">
<h1></h1>
<p></p>
</div>

Automatically Highlight Specific Word in Browser

In the content scripts of my chrome extension I am trying to inject code that highlights a specific word in the page.
In this instance, I am viewing espn.com and would like to have all instances of 'bryant' highlighted in the text immediately as the page is loaded.
This is the current code I have customized after viewing several questions similar to mine:
var all = document.getElementsByTagName("*");
highlight_words('Bryant', all);
function highlight_words(keywords, element) {
if(keywords) {
var textNodes;
keywords = keywords.replace(/\W/g, '');
var str = keywords.split(" ");
$(str).each(function() {
var term = this;
var textNodes = $(element).contents().filter(function() { return this.nodeType === 3 });
textNodes.each(function() {
var content = $(this).text();
var regex = new RegExp(term, "gi");
content = content.replace(regex, '<span class="highlight">' + term + '</span>');
$(this).replaceWith(content);
});
});
}
}
In my jquery-ui.css I have the following code. I understand it does not highlight at this moment but I am just trying to get a proof of concept:
.highlight {
font-weight: bold;
}
At this time everything loads properly but no iteration of 'bryant' is read in bold.
Thanks!
The fastest way to do this is to define the what are you want to search for highlight, for example:
You have 3 parts on site, the navbar on left, the title on the top and the content.
Lets attach .foo class to article.
var list = document.getElementsByClassName("foo")
var search_word = ""
var contents = []
for(var i = 0; i < list.length; i++){
var contents = list[i].textContext.split(search_word)
list[i].textContext = contents.join('<span class="heighlight\">'+search_word+'</span>')
}
Hope it will help.
(The highlight is bound to elements that have .foo class)
some example: https://jsfiddle.net/Danielduel/0842qntu/2/

Split SPAN when Inserting HTML Inside of it

I have a setup where I have a large amount of text that is formatted HTML (containing headings and paragraphs)–all inside of a contenteditable div. I've setup a function to insert a custom html entity: <newnote> into the editable div upon clicking a button. This works great except that when inserting the note inside of a span I need to split the span in two and place the <newnote> in between them.
I've looked at lots of functions around the web for inserting text into a DIV, and since it is a requirement that I be inserting HTML it seems like document.createTextNode() is my only choice.
So here is what I've setup thus far, it checks if the parent is a SPAN and then places different content based on that if statement.
function insertNodeAtRange() {
var newRange = rangy.getSelection().getRangeAt(0);
var parentElement = newRange.commonAncestorContainer;
if (parentElement.nodeType == 3) {
parentElement = parentElement.parentNode;
}
var el = document.createElement('newnote');
el.className = "bold";
if (parentElement.tagName == 'SPAN') {
el.appendChild(document.createTextNode(" </span>Test<span> "));
} else {
el.appendChild(document.createTextNode(" Test "));
}
newRange.insertNode(el);
}
Here is what I have sofar
Thanks in advance for any help...
The main problem with your code is that you were trying to make/modify html by creating a text node. Text nodes do not get parsed as dom.
This should work for you:
JSFiddle
function insertNodeAtRange() {
var newRange = rangy.getSelection().getRangeAt(0);
var parentElement = newRange.commonAncestorContainer;
if (parentElement.nodeType == 3) {
parentElement = parentElement.parentNode;
}
var el = document.createElement('newnote');
el.className = "bold";
el.appendChild(document.createTextNode(" Test "));
newRange.insertNode(el);
if (parentElement.tagName == 'SPAN') {
var $newnote = $(el);
var $span1 = $("<span>");
var $span2 = $("<span>");
var left = true;
$parentElement = $(parentElement);
$parentElement.contents().each(function (i, node) {
if (node.isSameNode(el)) {
left = false;
} else if (left) {
$span1.append(node);
} else {
$span2.append(node);
}
});
$parentElement.replaceWith($newnote);
$span1.insertBefore($newnote);
$span2.insertAfter($newnote);
}
}
I just went ahead and inserted the newnote element right away, then got the stuff before that and put it into span1, got the stuff after it and put it into span2, replaced the existing span with newnote and positioned the new spans around newnote.

Categories