1

Based on this previous SO answer, I am trying to get text highlighting to work across multiple elements. For some reason, the middle element is not being highlighted and I cannot figure out why.

Here is the code:

function getSafeRanges(dangerous) {
  // a for ancestor
  const a = dangerous.commonAncestorContainer;
  // Starts -- Work inward from the start, selecting the largest safe range
  const s = [];
  const rs = [];
  // if highlight doesn't start and end in the same container…
  if (dangerous.startContainer != a) {
    for (let i = dangerous.startContainer; i != a; i = i.parentNode) {
      // put the starting text and all its parents (except
      // common ancestor of start and end) into array `s`
      /*       debugger */
      s.push(i);
    }
  }
  // if highlight didn't start and end in the same container,
  // then create a new range for each item in `s`
  if (s.length > 0) {
    for (let i = 0; i < s.length; i++) {
      const xs = document.createRange();
      console.log(i, xs)
      /* debugger */
      // if at the parents of starting node
      if (i) {
        xs.setStartAfter(s[i - 1]);
        xs.setEndAfter(s[i].lastChild);
      }
      // if at the starting node (text node)
      else {
        xs.setStart(s[i], dangerous.startOffset);
        /* if (s[i].nodeType != Node.TEXT_NODE) debugger */
        xs.setEndAfter(
          (s[i].nodeType == Node.TEXT_NODE) ?
          s[i] :
          s[i].lastChild
        );
      }
      console.log(i)
      rs.push(xs);
      console.log("rs", rs)
      /* debugger */
    }
  }

  // Ends -- basically the same code reversed
  const e = [];
  const re = [];
  // if highlight doesn't start and end in the same container…
  if (dangerous.endContainer != a) {
    for (let i = dangerous.endContainer; i != a; i = i.parentNode) {
      // put the ending text and all its parents (except
      // common ancestor of start and end) into array `e`
      e.push(i);
    }
  }
  // if highlight didn't start and end in the same container,
  // then create a new range for each item in `e`
  if (e.length > 0) {
    for (let i = 0; i < e.length; i++) {
      const xe = document.createRange();
      // if at the parents of ending node
      if (i) {
        xe.setStartBefore(e[i].firstChild);
        xe.setEndBefore(e[i - 1]);
      }
      // if at the ending node (text node)
      else {
        xe.setEnd(e[i], dangerous.endOffset);
        /* if (e[i].nodeType != Node.TEXT_NODE) debugger */
        xe.setStartBefore(
          (e[i].nodeType == Node.TEXT_NODE) ?
          e[i] :
          e[i].firstChild
        );
      }
      re.unshift(xe);
    }
  }

  // Middle -- the uncaptured middle
  if ((s.length > 0) && (e.length > 0)) {
    /* debugger */
    const xm = document.createRange();
    xm.setStartAfter(s[s.length - 1]);
    xm.setEndBefore(e[e.length - 1]);
    // Concat
    rs.push(xm);
  } else {
    console.log("no middle?")
    return [dangerous];
  }


  response = rs.concat(re);
  /* debugger */
  // Send to Console
  console.log(response)
  return response;
}


function highlightSelection() {
  const userSelection = window.getSelection().getRangeAt(0);
  if (userSelection.collapsed) return;
  const safeRanges = getSafeRanges(userSelection);
  for (let i = 0; i < safeRanges.length; i++) {
    highlightRange(safeRanges[i]);
  }
  document.getSelection().removeAllRanges();
}

function highlightRange(range) {
  const newNode = document.createElement("span");
  newNode.setAttribute(
    "style",
    "background-color: yellow; display: inline;"
  );
  newNode.addEventListener("click", function(el) {
    /* debugger */
    el.target.outerHTML = el.target.innerHTML;
  })
  range.surroundContents(newNode);
}


window.addEventListener("mouseup", highlightSelection);
<div>
  <p>
    line1
  </p>
  <p>
    line2
  </p>
  <span>line3</span>
  <p>
    line4
  </p>
  <span>line5</span>
  <p>
    line6
  </p>
  <span>line7</span>
  <table style="width:100%">
    <tr>
      <th>Firstname</th>
      <th>Lastname</th>
      <th>Age</th>
    </tr>
    <tr>
      <td>Jill</td>
      <td>Smith</td>
      <td>50</td>
    </tr>
    <tr>
      <td>Eve</td>
      <td>Jackson</td>
      <td>94</td>
    </tr>
  </table>
</div>
<span>awoifja woiefj awf</span>

If you select all the text starting from "line1" all the way till "line7", can someone please tell me why the middle element doesn't get highlighted? Also, here is a working fiddle since it seems that the SO snippet demo has a bunch of weird errors (nothing to do with my script).

Raj
  • 1,555
  • 18
  • 32
  • That's a bit to try to unravel. I have gotten this far: I simplified by reducing word count a bit. Then added several more elements above the table. Re-ran the fiddle. The first, the last, and every odd selected element got the treatment. That is, elements 1, 3, 5, ... were highlighted with yellow bg; elements 2, 4, ... were not. SUGGESTS a logic error in your code. WHICH MEANS, at the least, highlighting works just fine, and across multiple elements. It is simply skipping some. In the usual way, check boundary conditions and increments. And good luck. –  May 02 '20 at 18:46
  • @Yishmeray, thanks for pointing that out. Looks like the issue is with `

    ` vs `` in the HTML. `display:inline` + `background-color` apply to inline children, but not the block children. I updated the code and fiddle as per your suggestion.

    – Raj May 02 '20 at 22:12

0 Answers0