2

Stuck for days with the following challenge:

The goal is to highlight several parts of a text that is rendered in a "dangerouslySetInnerHTML".

The text object that should be rendered includes the full html of the text content and an array of annotations, in which each annotation contains a string of the text part that should be highlighted and the X-Path (ranges).

{
 content: "<div><p>Hello world!</p><p>Another paragraph.</p></div>",
 annotations: [
   {ranges: [{start: '/p[1]', startOffset: 0, end: '/p[1]', endOffset: 12}]}
 ],
 quote: "Hello world!"
}

This is the simplified JSX:

render () {
    return (
      <div className="textcontainer">
       <p className="textcontent" dangerouslySetInnerHTML={{__html: this.state.text.content}} />    
      </div>            
   )
  }

Is it possible to do this without jquery?

I found this answer on that topic Highlighting when HTML and Xpath is given

Though I couldn't make it work. Sorry, React JS newbie here. Thanks so much for helping out. Any help really appreciated.

YvonC
  • 329
  • 2
  • 6
  • 22
  • `quote` will always contain the text that needs to be highlighted? – bamse Jul 30 '18 at 14:09
  • @bamse yes, that's correct. – YvonC Jul 30 '18 at 14:37
  • Would a solution that highlights the text using only the `quote` help you? I don't know much about Xpaths (and don't really like them) but you can highlight the text without using them. – bamse Jul 30 '18 at 14:50
  • If it works ;) - ... for several text parts in the same "content" text. -> It would be great though, if these text parts ("quotes") could additionally be triggered for example with a onHover or similar. – YvonC Jul 30 '18 at 15:44
  • What do you mean when you say _(quotes) be triggered for example with a onHover or similar_? Is the styling `:hover` enough? – bamse Jul 30 '18 at 16:53
  • @bamse First of all, thanks so much for looking into this. This looks already great. What I meant is to allow users potentially to interact with the highlight ... sneaking in a "onClick" etc. in order to see details of the annotation or similar. The CSS solution would at least allow to display stuff on :hover, though it would be even more awesome, if there would be away to be more flexible. Thanks again for taking the time. Really appreciated. – YvonC Jul 30 '18 at 17:29
  • You are welcomed! You could wrap your highlighted words in React components and do more elaborate things. Check out [react-jsx-parser](https://github.com/TroyAlford/react-jsx-parser) and [this example](https://stackoverflow.com/a/51540383/2890316). – bamse Jul 30 '18 at 17:34
  • @bamse One thing though that I just realised, reading the comment by Mosé Raguzzini below... what if a highlight is just a very generic one, like a "We do". In cases like this it might be very likely that the same "string" is showing up several times in the text. Though only in one very specific paragraph it is relevant. – YvonC Jul 30 '18 at 17:36
  • Yes, the solution isn't bulletproof. Using the given Xpath is a more precise way. – bamse Jul 30 '18 at 17:39

2 Answers2

3

One possible solution without using Xpath:

/** highlightXpath.js */
import React from "react";
import "./highlight.css";

class HighlightXpath extends React.Component {
  render = () => {
    /**
     * This creates the regex to find the wanted `quote`.
     * If you want to highlight all the occurrences of a `quote`, not
     * only the first occurrence, add 'g' as the second parameter:
     * ex: const regex = new RegExp(`(${this.props.quote})`);
     * If you want to highlight multiple quotes from an array
     * you could do
     * const regex = new RegExp(`(${this.props.quotes.join('|')})`);
     */
    const regex = new RegExp(`(${this.props.quote})`);
    /**
     * In `content` we wrap `quote`'s occurrence(s) in `em`s
     * with a class `highlighted`. Please note that this will
     * be rendered as html, not React, so `class` is used instead
     * of `className`.
     */
    const highlightedHtml = this.props.content.replace(
      regex,
      "<em class='highlighted'>$1</em>"
    );
    return (
      <div ref="test" dangerouslySetInnerHTML={{ __html: highlightedHtml }}     />
    );
  };
}

export default HighlightXpath;

/** highlight.css */
.highlighted {
  font-style: normal;
  background: yellow;
}

.highlighted:hover {
  background: lime;
}

Working example here Edit Highlight text

Hope it helps!

bamse
  • 4,243
  • 18
  • 25
  • Again @bamse thanks so much! I will go with this answer. I have though still a little issue with the "multiple quotes from an array" version. This one: const regex = new RegExp(`(${this.props.quotes.join('|')})`); Unfortunately it only highlights the text part from the quote in the array of quotes that comes first in the text. Meaning if in the array are three quotes, only the quote that comes first in the text is highlighted. Even if this quote was added to the array as the last one. – YvonC Jul 31 '18 at 15:12
  • `const regex = new RegExp(`(${this.props.quotes.join('|')})`, 'g'); ` This does the trick, that all text parts from the array are highlighted. Again, the problem stays that if the content of the quote is "generic", or the content is simply for some reason repeated several times in a text, text will be highlighted on wrong places. – YvonC Jul 31 '18 at 15:39
0

I've done an application like yours (an advanced version of Genius.com) and the trick was in avoiding reconciliation and working directly on html and nodes returning false on shouldcomponentupdate() method. I was also able to catch the exact word not only the first or all the occurrencies but EXACTLY the selected node containing the word.

Mosè Raguzzini
  • 15,399
  • 1
  • 31
  • 43
  • So, you would use xPath then? – YvonC Jul 30 '18 at 17:42
  • I used a mix of getSelection(), node manipulation and text structure versioning. When you highlight nodes the nodes structure will change so I always store the original version of my text/html – Mosè Raguzzini Jul 31 '18 at 06:50