Jacky Zhao
2022-05-06 e302f6c423136d1dbdfda48c2b241e62bb5654e7
assets/js/search.js
@@ -35,9 +35,11 @@
      .replace(/<[^>]*>/g, '')
      .replace(/^[=\-]{2,}\s*$/g, '')
      .replace(/\[\^.+?\](\: .*?$)?/g, '')
      .replace(/(#{1,6})\s+(.+)\1?/g, '<b>$2</b>')
      .replace(/\s{0,2}\[.*?\]: .*?$/g, '')
      .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? '$1' : '')
      .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
      .replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, '$1')
      .replace(/^\s{0,3}>\s?/g, '')
      .replace(/(^|\n)\s{0,3}>\s?/g, '\n\n')
      .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
@@ -51,9 +53,65 @@
    return markdown
  }
  return output
};
}
// -----
const highlight = (content, term) => {
  const highlightWindow = 20
  // try to find direct match first
  const directMatchIdx = content.indexOf(term)
  if (directMatchIdx !== -1) {
    const h = highlightWindow / 2
    const before = content.substring(0, directMatchIdx).split(" ").slice(-h)
    const after = content.substring(directMatchIdx + term.length, content.length - 1).split(" ").slice(0, h)
    return (before.length == h ? `...${before.join(" ")}` : before.join(" ")) + `<span class="search-highlight">${term}</span>` + after.join(" ")
  }
  const tokenizedTerm = term.split(/\s+/).filter((t) => t !== '')
  const splitText = content.split(/\s+/).filter((t) => t !== '')
  const includesCheck = (token) =>
    tokenizedTerm.some((term) =>
      token.toLowerCase().startsWith(term.toLowerCase())
    )
  const occurrencesIndices = splitText.map(includesCheck)
  // calculate best index
  let bestSum = 0
  let bestIndex = 0
  for (
    let i = 0;
    i < Math.max(occurrencesIndices.length - highlightWindow, 0);
    i++
  ) {
    const window = occurrencesIndices.slice(i, i + highlightWindow)
    const windowSum = window.reduce((total, cur) => total + cur, 0)
    if (windowSum >= bestSum) {
      bestSum = windowSum
      bestIndex = i
    }
  }
  const startIndex = Math.max(bestIndex - highlightWindow, 0)
  const endIndex = Math.min(
    startIndex + 2 * highlightWindow,
    splitText.length
  )
  const mappedText = splitText
    .slice(startIndex, endIndex)
    .map((token) => {
      if (includesCheck(token)) {
        return `<span class="search-highlight">${token}</span>`
      }
      return token
    })
    .join(' ')
    .replaceAll('</span> <span class="search-highlight">', ' ')
  return `${startIndex === 0 ? '' : '...'}${mappedText}${endIndex === splitText.length ? '' : '...'
    }`
};
(async function() {
  const encoder = (str) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/)
  const contentIndex = new FlexSearch.Document({
@@ -83,52 +141,6 @@
    })
  }
  const highlight = (content, term) => {
    const highlightWindow = 20
    const tokenizedTerm = term.split(/\s+/).filter((t) => t !== '')
    const splitText = content.split(/\s+/).filter((t) => t !== '')
    const includesCheck = (token) =>
      tokenizedTerm.some((term) =>
        token.toLowerCase().startsWith(term.toLowerCase())
      )
    const occurrencesIndices = splitText.map(includesCheck)
    // calculate best index
    let bestSum = 0
    let bestIndex = 0
    for (
      let i = 0;
      i < Math.max(occurrencesIndices.length - highlightWindow, 0);
      i++
    ) {
      const window = occurrencesIndices.slice(i, i + highlightWindow)
      const windowSum = window.reduce((total, cur) => total + cur, 0)
      if (windowSum >= bestSum) {
        bestSum = windowSum
        bestIndex = i
      }
    }
    const startIndex = Math.max(bestIndex - highlightWindow, 0)
    const endIndex = Math.min(
      startIndex + 2 * highlightWindow,
      splitText.length
    )
    const mappedText = splitText
      .slice(startIndex, endIndex)
      .map((token) => {
        if (includesCheck(token)) {
          return `<span class="search-highlight">${token}</span>`
        }
        return token
      })
      .join(' ')
      .replaceAll('</span> <span class="search-highlight">', ' ')
    return `${startIndex === 0 ? '' : '...'}${mappedText}${endIndex === splitText.length ? '' : '...'
      }`
  }
  const resultToHTML = ({ url, title, content, term }) => {
    const text = removeMarkdown(content)
    const resultTitle = highlight(title, term)
@@ -143,7 +155,7 @@
    // SPA navigation
    window.navigate(
      new URL(
        `${BASE_URL.slice(0, -1)}${id}#:~:text=${encodeURIComponent(term)}/`
        `${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/`
      ),
      '.singlePage'
    )