Rangy: indexes returned by range.selectCharacters() change depend on HTML structure

245 Views Asked by At

I need to highlight some commas in a text. To do so, I first get the indexes of all the commas:

for (let i = 0; i < text.length; i++) {
      if (text[i] == ',')
          commaIndexes.push(i);
}

In Well, I guess there's a few commas missing, right?, these indexes are 4 and 42.

Then this text is wrapped in spans I get from a diff algorithm:

<span>Well</span>
<span>,</span>
<span> I guess there's a few commas missing</span>
<span>,</span>
<span> right?</span>

I want to select the commas by their index using Rangy:

// Create a Rangy range at the index that should contain a comma
let range = rangy.createRange();

// I'd expect the range to include the comma and only the comma.
range.selectCharacters(fragment, index, index + 1);

This works for the first comma in the text only. Wrapping the text in spans seems to shift the index, and I could figure out a system: For every previous comma, I need to add 2 to every other comma's index:

for (let i = 0; i < text.length; i++) {
    if (text[i] == ',')
        commaIndexes.push(i + 2 * commaIndexes.length);
}

My code is working now, but I'd like to understand what's going on. As I understand it, Rangy's range.selectCharacters method should select characters based on the visible text's indexes, it shouldn't matter if the text is wrapped in spans.

Edit: There are line breaks in my second code sample. I copied this from Chrome's developer tools. The line breaks might cause my problem, however, I'm not sure if are actually part of the DOM or just displayed in dev tools. This is all I do to wrap the text in spans:

// Create new span to host the segment
let span = document.createElement('span');

// Set text and append span
span.innerText = 'some text';
fragment.appendChild(span); 

Second edit: Code snippet

rangy.init();

// A sample text. Commas should be at (zero-based) indexes 4 and 42.
let text = "Well, I guess there's a few commas missing, right?";

// Store zero-based comma indexes
let commaIndexes = [];

for (let i = 0; i < text.length; i++) {
      if (text[i] == ',')
          commaIndexes.push(i);
}

// Simulate diff output
let substrings = [
  'Well',
  ',',
  " I guess there's a few commas missing",
  ',',
  ' right?'
];

// Create a DOM fragment to hold all spans created in the next step
let fragment = document.createDocumentFragment();

// Create new span for each substring
substrings.forEach(substring => {
    let span = document.createElement('span');

    // Set text and append span
    span.innerText = substring;
    fragment.appendChild(span);
});

commaIndexes.forEach(index => {
    // Create a Rangy range
    let range = rangy.createRange();

    // I guess selectCharacters is one-based, left index including, right index excluding?
    range.selectCharacters(fragment, index + 1, index + 2);

    // I get the first comma and the 'g' preceding the second one
    console.log(range.text());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-selectionsaverestore.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-textrange.js"></script>

0

There are 0 best solutions below