0

The following code is recording which keys are being pressed. If the two first keys match the two first characters in a link, those characters will be highlighted. (In this case, "li" from "link.")

let keys = []

document.addEventListener('keypress', event => {
  keys.push(event.key)

  console.log(keys)

  const selector = 'a'
  let text = keys.join('')
  let element = [...document.querySelectorAll(selector)]
    .find(elements => elements.textContent.includes(text))

  if (keys.length === 2) {
    if (!element) {
      keys = []
      return
    }

    element.textContent = element.textContent.replace(text, `<strong>$1</strong>`)
  }
})
<a href="#">link</a>

I want to highlight the characters with <strong> tags.

I was expecting this:

link

But I got this instead:

<strong>$1</strong>nk

Steps:

  1. Press "Run code snippet"
  2. Type l then i.
alexchenco
  • 53,565
  • 76
  • 241
  • 413
  • 2
    You’re replacing the `textContent` which is for the text, not for the HTML. – Sebastian Simon Aug 08 '21 at 14:43
  • 2
    Also, `$1` is for regexes as search criteria, not for strings, because strings cannot have capture groups. You can use `$&` or you can use a function instead: ```.replace(text, s => `${s}`)``` – CherryDT Aug 08 '21 at 14:46
  • 1
    This approach has other shortcomings as well though, such as either not considering matches across tags or alternatively destroying event listeners and tag references. You might consider using a library for that purpose, for example https://markjs.io/ – CherryDT Aug 08 '21 at 14:52
  • @CherryDT `$&` references works even for string pattern search replacements: `'abc'.replace('b',':$&$&:') // "a:bb:c"`. (I've did not know that, just learnt.) – myf Aug 08 '21 at 14:56
  • @CherryDT I'm working on this. My plan is to keep matching characters until there's only element. (And only match elements that are in the view port.) – alexchenco Aug 08 '21 at 14:56
  • @myf: yes, I also realized that and edited it in a few minutes ago :) – CherryDT Aug 08 '21 at 14:59
  • 1
    There’s no need for a library. The [DOM API](//developer.mozilla.org/docs/Web/API/Document_Object_Model) has various methods for Text nodes. I’ve written a few answers about [Finding and replacing parts of a document](/a/41886794/4642212) and about [highlighting text in a document](/a/32167527/4642212). – Sebastian Simon Aug 08 '21 at 14:59
  • 1
    just concat the key press string searchText =''; searchText += event.key; instead of pushing to array and calling join; –  Aug 08 '21 at 15:52

1 Answers1

3

There are at least 2 issues: first you are inserting html into textContent which being inserted as text not html. second, since you have no capturing groups in your search you can't use $1 instead you either can use ${text} or $&:

let keys = []

document.addEventListener('keypress', event => {
  keys.push(event.key)

  console.log(keys)

  const selector = 'a'
  let text = keys.join('')
  let element = [...document.querySelectorAll(selector)]
    .find(elements => elements.textContent.includes(text))

  if (keys.length === 2) {
    if (!element) {
      keys = []
      return
    }
    element.innerHTML = element.innerHTML.replace(text, `<strong>$&</strong>`)
  }
})
<a href="#">link</a>

Here is a little "safer" version that doesn't affect html:

let keys = []

document.addEventListener('keypress', event => {
  keys.push(event.key)

  console.log(keys)

  const selector = 'a'
  let text = keys.join('')
  let element = [...document.querySelectorAll(selector)]
    .find(elements => elements.textContent.includes(text))

  if (keys.length === 2) {
    if (!element) {
      keys = []
      return
    }


    //make it html friendly
    text = text.replace(/&/g, "&amp;")
               .replace(/</g, "&lt;")
               .replace(/>/g, "&gt;")
                //make it regex friendly
               .replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');

    element.innerHTML = element.innerHTML
                  //remove old highlightings
                  .replace(/<mark>([^<]*)?<\/mark>/g, "$1")
                  // convert selected text into regex,
                  // search and enclose found strings with <mark></mark>
                  .replace(new RegExp("(" + text + ")", "gi"), '<mark>$1</mark>'); //add new highlighting
  }
})
mark
{
  background-color: inherit;
  font-weight: bold;
}
<a href="#">link</a>
<a href="#">href</a>
vanowm
  • 9,466
  • 2
  • 21
  • 37
  • Note this won't work correctly if part of a match is an HTML entity. Try matching `Mac & Cheese` - it won't be replaced because in HTML it's `Mac & Cheese`. Simply replacing textContent by innerHTML in the match part won't be enough either though because it could then also match the middle of tags and attributes. – CherryDT Aug 08 '21 at 14:54
  • this approach only works for the first 2 keys pressed. you need to remove what you inject ... on every keypress if mark exists remove all –  Aug 08 '21 at 15:46
  • @dMd That's something OP will have to decide, it's not part of the original issue. – vanowm Aug 08 '21 at 17:57
  • Very throughout answer. Thanks. – alexchenco Aug 08 '21 at 18:04