DhammaCharts
2022-06-06 84c75d05460dd2974ff04a43f6a770fc31deca63
Merge branch 'hugo' into hugo
3 files modified
225 ■■■■ changed files
assets/js/router.js 30 ●●●●● patch | view | raw | blame | history
assets/js/search.js 171 ●●●● patch | view | raw | blame | history
layouts/partials/head.html 24 ●●●● patch | view | raw | blame | history
assets/js/router.js
@@ -1,16 +1,26 @@
import { router, navigate, reload, prefetch } from "https://unpkg.com/million@1.9.6/dist/router.mjs"
import {
  apply,
  navigate,
  prefetch,
  router,
} from "https://unpkg.com/million@1.9.8-0/dist/router.mjs"
export const attachSPARouting = (draw) => {
export const attachSPARouting = (init, rerender) => {
  // Attach SPA functions to the global Million namespace
  window.Million = {
    router,
    apply,
    navigate,
    reload,
    prefetch,
  };
  router(".singlePage")
  // We need on initial load, then subsequent redirs
  // requestAnimationFrame() delays graph draw until SPA routing is finished
  reload(draw)
  window.addEventListener("DOMContentLoaded", () => requestAnimationFrame(draw))
    router,
  }
  const render = () => requestAnimationFrame(rerender)
  window.addEventListener("DOMContentLoaded", () => {
    apply((doc) => init(doc))
    init()
    router(".singlePage")
    render()
  })
  window.addEventListener("million:navigate", render)
}
assets/js/search.js
@@ -7,47 +7,44 @@
    gfm: true,
    useImgAltText: false,
    preserveLinks: false,
  }
  },
) => {
  let output = markdown || ''
  output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, '')
  let output = markdown || ""
  output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, "")
  try {
    if (options.stripListLeaders) {
      if (options.listUnicodeChar)
        output = output.replace(
          /^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm,
          options.listUnicodeChar + ' $1'
        )
      else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
        output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, options.listUnicodeChar + " $1")
      else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, "$1")
    }
    if (options.gfm) {
      output = output
        .replace(/\n={2,}/g, '\n')
        .replace(/~{3}.*\n/g, '')
        .replace(/~~/g, '')
        .replace(/`{3}.*\n/g, '')
        .replace(/\n={2,}/g, "\n")
        .replace(/~{3}.*\n/g, "")
        .replace(/~~/g, "")
        .replace(/`{3}.*\n/g, "")
    }
    if (options.preserveLinks) {
      output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, '$1 ($2)')
      output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, "$1 ($2)")
    }
    output = output
      .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, '<a>$1</a>')
      .replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, '<a>$1</a>')
      .replace(/^\s{0,3}>\s?/g, '')
      .replace(/(^|\n)\s{0,3}>\s?/g, '\n\n')
      .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
      .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, '$2')
      .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, '$2')
      .replace(/(`{3,})(.*?)\1/gm, '$2')
      .replace(/`(.+?)`/g, '$1')
      .replace(/\n{2,}/g, '\n\n')
      .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, "<a>$1</a>")
      .replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, "<a>$1</a>")
      .replace(/^\s{0,3}>\s?/g, "")
      .replace(/(^|\n)\s{0,3}>\s?/g, "\n\n")
      .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, "")
      .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2")
      .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2")
      .replace(/(`{3,})(.*?)\1/gm, "$2")
      .replace(/`(.+?)`/g, "$1")
      .replace(/\n{2,}/g, "\n\n")
  } catch (e) {
    console.error(e)
    return markdown
@@ -64,27 +61,28 @@
  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 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 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())
    )
    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++
  ) {
  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) {
@@ -94,10 +92,7 @@
  }
  const startIndex = Math.max(bestIndex - highlightWindow, 0)
  const endIndex = Math.min(
    startIndex + 2 * highlightWindow,
    splitText.length
  )
  const endIndex = Math.min(startIndex + 2 * highlightWindow, splitText.length)
  const mappedText = splitText
    .slice(startIndex, endIndex)
    .map((token) => {
@@ -106,27 +101,28 @@
      }
      return token
    })
    .join(' ')
    .replaceAll('</span> <span class="search-highlight">', ' ')
  return `${startIndex === 0 ? '' : '...'}${mappedText}${endIndex === splitText.length ? '' : '...'
    }`
};
    .join(" ")
    .replaceAll('</span> <span class="search-highlight">', " ")
  return `${startIndex === 0 ? "" : "..."}${mappedText}${
    endIndex === splitText.length ? "" : "..."
  }`
}
(async function() {
;(async function () {
  const encoder = (str) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/)
  const contentIndex = new FlexSearch.Document({
    cache: true,
    charset: 'latin:extra',
    charset: "latin:extra",
    optimize: true,
    index: [
      {
        field: 'content',
        tokenize: 'reverse',
        field: "content",
        tokenize: "reverse",
        encode: encoder,
      },
      {
        field: 'title',
        tokenize: 'forward',
        field: "title",
        tokenize: "forward",
        encode: encoder,
      },
    ],
@@ -154,10 +150,8 @@
  const redir = (id, term) => {
    // SPA navigation
    window.Million.navigate(
      new URL(
        `${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/`
      ),
      '.singlePage'
      new URL(`${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/`),
      ".singlePage",
    )
    closeSearch()
  }
@@ -169,24 +163,24 @@
    content: content[id].content,
  })
  const source = document.getElementById('search-bar')
  const results = document.getElementById('results-container')
  const source = document.getElementById("search-bar")
  const results = document.getElementById("results-container")
  let term
  source.addEventListener('keyup', (e) => {
    if (e.key === 'Enter') {
      const anchor = document.getElementsByClassName('result-card')[0]
  source.addEventListener("keyup", (e) => {
    if (e.key === "Enter") {
      const anchor = document.getElementsByClassName("result-card")[0]
      redir(anchor.id, term)
    }
  })
  source.addEventListener('input', (e) => {
  source.addEventListener("input", (e) => {
    term = e.target.value
    const searchResults = contentIndex.search(term, [
      {
        field: 'content',
        field: "content",
        limit: 10,
      },
      {
        field: 'title',
        field: "title",
        limit: 5,
      },
    ])
@@ -198,7 +192,7 @@
        return [...results[0].result]
      }
    }
    const allIds = new Set([...getByField('title'), ...getByField('content')])
    const allIds = new Set([...getByField("title"), ...getByField("content")])
    const finalResults = [...allIds].map(formatForDisplay)
    // display
@@ -213,58 +207,55 @@
          resultToHTML({
            ...result,
            term,
          })
          }),
        )
        .join('\n')
      const anchors = [...document.getElementsByClassName('result-card')]
        .join("\n")
      const anchors = [...document.getElementsByClassName("result-card")]
      anchors.forEach((anchor) => {
        anchor.onclick = () => redir(anchor.id, term)
      })
    }
  })
  const searchContainer = document.getElementById('search-container')
  const searchContainer = document.getElementById("search-container")
  function openSearch() {
    if (
      searchContainer.style.display === 'none' ||
      searchContainer.style.display === ''
    ) {
      source.value = ''
      results.innerHTML = ''
      searchContainer.style.display = 'block'
    if (searchContainer.style.display === "none" || searchContainer.style.display === "") {
      source.value = ""
      results.innerHTML = ""
      searchContainer.style.display = "block"
      source.focus()
    } else {
      searchContainer.style.display = 'none'
      searchContainer.style.display = "none"
    }
  }
  function closeSearch() {
    searchContainer.style.display = 'none'
    searchContainer.style.display = "none"
  }
  document.addEventListener('keydown', (event) => {
    if (event.key === 'k' && (event.ctrlKey || event.metaKey)) {
  document.addEventListener("keydown", (event) => {
    if (event.key === "k" && (event.ctrlKey || event.metaKey)) {
      event.preventDefault()
      openSearch()
    }
    if (event.key === 'Escape') {
    if (event.key === "Escape") {
      event.preventDefault()
      closeSearch()
    }
  })
  const searchButton = document.getElementById('search-icon')
  searchButton.addEventListener('click', (evt) => {
  const searchButton = document.getElementById("search-icon")
  searchButton.addEventListener("click", (evt) => {
    openSearch()
  })
  searchButton.addEventListener('keydown', (evt) => {
  searchButton.addEventListener("keydown", (evt) => {
    openSearch()
  })
  searchContainer.addEventListener('click', (evt) => {
  searchContainer.addEventListener("click", (evt) => {
    closeSearch()
  })
  document.getElementById('search-space').addEventListener('click', (evt) => {
  document.getElementById("search-space").addEventListener("click", (evt) => {
    evt.stopPropagation()
  })
})()
layouts/partials/head.html
@@ -10,11 +10,7 @@
    end }}
  </title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link
    rel="shortcut icon"
    type="image/png"
    href="{{$.Site.BaseURL}}/icon.png"
  />
  <link rel="shortcut icon" type="image/png" href="{{$.Site.BaseURL}}/icon.png" />
  <!-- CSS Stylesheets and Fonts -->
  <link
@@ -61,18 +57,18 @@
          content,
        }))
    const draw = () => {
      const render = () => {
      // NOTE: everything within this callback will be executed for every page navigation. This is a good place to put JavaScript that loads or modifies data on the page, adds event listeners, etc. If you are only dealing with basic DOM replacement, use the init function
      const siteBaseURL = new URL({{$.Site.BaseURL}});
      const pathBase = siteBaseURL.pathname;
      const pathWindow = window.location.pathname;
      const isHome = pathBase == pathWindow;
      // NOTE: everything within this callback will be executed for every page navigation. This is a good place to put JavaScript that loads or modifies data on the page.
      {{if $.Site.Data.config.enableFooter}}
      const container = document.getElementById("graph-container")
      // retry if the graph is not ready
      if (!container) return requestAnimationFrame(draw)
      if (!container) return requestAnimationFrame(render)
      // clear the graph in case there is anything within it
      container.textContent = ""
@@ -93,6 +89,7 @@
      }
      {{end}}
      {{if $.Site.Data.config.enableLinkPreview}}
      initPopover(
        {{strings.TrimRight "/" .Site.BaseURL }},
@@ -100,8 +97,12 @@
        {{$.Site.Data.config.enableLatex}}
      )
      {{end}}
    }
    const init = (doc = document) => {
      // NOTE: everything within this callback will be executed for initial page navigation. This is a good place to put JavaScript that only replaces DOM nodes.
      {{if $.Site.Data.config.enableLatex}}
      renderMathInElement(document.body, {
      renderMathInElement(doc.body, {
        delimiters: [
          {left: '$$', right: '$$', display: true},
          {left: '$', right: '$', display: false},
@@ -116,7 +117,7 @@
  resources.Minify }}
  <script type="module">
    import { attachSPARouting } from "{{$router.Permalink}}"
    attachSPARouting(draw)
    attachSPARouting(init, render)
  </script>
  {{else}}
  <script>
@@ -124,7 +125,8 @@
      navigate: (url) => (window.location.href = url),
      prefetch: () => {},
    }
    draw()
    init()
    render()
  </script>
  {{end}}
</head>