6

I have a string

const string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent tristique elit in volutpat iaculis. Proin a tincidunt turpis, et condimentum libero. Duis convallis nulla eu mattis porta. Nulla facilisi. Proin nec viverra orci. Nunc aliquam enim orci, ut dictum ipsum auctor ut. Quisque consectetur vestibulum tortor, mollis hendrerit velit hendrerit vel. In hac habitasse platea dictumst. Morbi volutpat lectus purus, eu sagittis odio viverra in. Phasellus vel volutpat felis. Proin a metus sit amet ipsum congue faucibus nec faucibus nisl. Aenean eu nisl ac velit dapibus vulputate non efficitur urna. Phasellus laoreet suscipit dictum. Curabitur sit amet justo at nisi dictum dapibus."

I want to be able to highlight some coherent sequence of words and show a tooltip when hovering the words.

What would the data structure look for this?

I guess it should be something like

id, wordIndexStart, wordIndexEnd, note
=======================================
1, 0, 5, Some note

in order to highlight the first 6 words, giving me something like

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent tristique elit in volutpat iaculis (...)

but how do I return this string with this highlighted text in React or something similar?

Normally, I would print my text with

<p>{string}</p>

but I guess it should be something like

<p>{string.split(" ").map(word => <span>{word}).join(" ")</p>

but how do I create a <span> around, for instance, the 6 first words?

Jamgreen
  • 10,329
  • 29
  • 113
  • 224
  • I think you were really close, I'd still create a simple component to wrap each word in a span: http://jsfiddle.net/48ydpzb5/ – Boris Lobanov Feb 20 '18 at 21:25

3 Answers3

1

How about this:

const rules = {
  start: 0,
  end: 5,
  tooltip: "Some note"
};
const Highlight = ({ words }) => {
  const portion = words.split(" ").slice(rules.start, rules.end).join(" ");
  const rest = words.split(" ").slice(rules.end).join(" ");

  return (
    <span>
      <span className="highlight">{portion}</span>
      <span className="standard">{rest}</span>
    </span>
  );
};
const App = () => {
  const sentence = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent tristique elit in volutpat iaculis.';
  
  return (
    <div>
      <Highlight words={sentence} />
     </div>
  )}
;

ReactDOM.render(<App />, document.getElementById("root"));
.highlight {
  font-weight: bold;
  color: red;
}

.standard {
  color: grey;
}
  <div id="root"></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
dashton
  • 2,684
  • 1
  • 18
  • 15
  • Why the sandbox link? Why not just make it a [snippet](https://stackoverflow.blog/2014/09/16/introducing-runnable-javascript-css-and-html-code-snippets/)? – Jordan Running Feb 20 '18 at 21:28
0

I would suggest including the phrase you intend to highlight. I am not sure why you want id's inside of your data structure, perhaps it makes sense in the database, but in the view they certainly don't need to be included.

{
    phrase: string,
    note: string
}

This would be all you need to parse the input sentence for the phrase, and where matched replace it with an element that when hovered would show the note.

As for the implementation, finding the phrase would be as easy as taking the index, sentence.indexOf(phrase) and then using that index sentence.substr(0,index) + note + sentence.substr(index+phrase.length) to reform the sentence with your created html supporting the note. As a majority of this implementation will depend on how the note is created, supported, or hooked in, I will leave that part open to you to decide.

Travis J
  • 81,153
  • 41
  • 202
  • 273
-2

When using React, you're going to want to move the highlighting logic outside the render function

stateShape:

{
    content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent tristique elit in volutpat iaculis. Proin a tincidunt turpis, et condimentum libero. Duis convallis nulla eu mattis porta. Nulla facilisi. Proin nec viverra orci. Nunc aliquam enim orci, ut dictum ipsum auctor ut. Quisque consectetur vestibulum tortor, mollis hendrerit velit hendrerit vel. In hac habitasse platea dictumst. Morbi volutpat lectus purus, eu sagittis odio viverra in. Phasellus vel volutpat felis. Proin a metus sit amet ipsum congue faucibus nec faucibus nisl. Aenean eu nisl ac velit dapibus vulputate non efficitur urna. Phasellus laoreet suscipit dictum. Curabitur sit amet justo at nisi dictum dapibus.",
    highlights: [{
        id: 1,
        wordIndexStart: 0,
        wordIndexEnd: 5,
        note: "Some note"
    }, {
        id: 2,
        wordIndexStart: 6,
        wordIndexEnd: 10,
        note: "Some other note"
    }]
}

connected component's mapStateToProps:

function mapStateToProps(state, ownProps) {
    const { content, highlights } = state;

    // Move this logic to ReSelect? https://github.com/reactjs/reselect
        const words = content.split(" ");

        const wordsWithHighlights = words.map((word, index) => {

            const highlightStart = highlights.find((highlightItem) => {
                return highlightItem.wordIndexStart == index;
            });
            if (highlightStart) {
                return `<span title=${highlightStart.note}>${word}`;
            }

            const highlightEnd = highlights.find((highlightItem) => {
                return highlightItem.wordIndexEnd == index;
            });
            if (highlightEnd) {
                return `${word}</span>`;
            }

            return word;
        });

    return {
        content: wordsWithHighlights.join(" ")
    };
}

and then the render function:

render() {
    const { content } = this.props;
    return (
        <p dangerouslySetInnerHTML={{ __html: content }} />
    )
}

as @jordan pointed out, you cannot add HTML as a string in React, and as per this answer you may want to use dangerouslySetInnerHTML attribute, like above

Varinder
  • 2,634
  • 1
  • 17
  • 20