123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- import dom
- import fuzzysearch
- import std/[jsfetch, asyncjs]
- proc setTheme(theme: cstring) {.exportc.} =
- document.documentElement.setAttribute("data-theme", theme)
- window.localStorage.setItem("theme", theme)
- # set `data-theme` attribute early to prevent white flash
- setTheme:
- let t = window.localStorage.getItem("theme")
- if t.isNil: cstring"auto" else: t
- proc onDOMLoaded(e: Event) {.exportc.} =
- # set theme select value
- document.getElementById("theme-select").value = window.localStorage.getItem("theme")
- for pragmaDots in document.getElementsByClassName("pragmadots"):
- pragmaDots.onclick = proc (event: Event) =
- # Hide tease
- event.target.parentNode.style.display = "none"
- # Show actual
- event.target.parentNode.nextSibling.style.display = "inline"
- proc tree(tag: cstring; kids: varargs[Element]): Element =
- result = document.createElement tag
- for k in kids:
- result.appendChild k
- proc add(parent, kid: Element) =
- if parent.nodeName == "TR" and (kid.nodeName == "TD" or kid.nodeName == "TH"):
- let k = document.createElement("TD")
- appendChild(k, kid)
- appendChild(parent, k)
- else:
- appendChild(parent, kid)
- proc setClass(e: Element; value: cstring) =
- e.setAttribute("class", value)
- proc text(s: cstring): Element = cast[Element](document.createTextNode(s))
- proc replaceById(id: cstring; newTree: Node) =
- let x = document.getElementById(id)
- x.parentNode.replaceChild(newTree, x)
- newTree.id = id
- proc clone(e: Element): Element {.importcpp: "#.cloneNode(true)", nodecl.}
- proc markElement(x: Element) {.importcpp: "#.__karaxMarker__ = true", nodecl.}
- proc isMarked(x: Element): bool {.
- importcpp: "#.hasOwnProperty('__karaxMarker__')", nodecl.}
- proc title(x: Element): cstring {.importcpp: "#.title", nodecl.}
- proc sort[T](x: var openArray[T]; cmp: proc(a, b: T): int) {.importcpp:
- "#.sort(#)", nodecl.}
- proc extractItems(x: Element; items: var seq[Element]) =
- if x == nil: return
- if x.nodeName == "A":
- items.add x
- else:
- for i in 0..<x.len:
- extractItems(x[i], items)
- # HTML trees are so shitty we transform the TOC into a decent
- # data-structure instead and work on that.
- type
- TocEntry = ref object
- heading: Element
- kids: seq[TocEntry]
- sortId: int
- doSort: bool
- proc extractItems(x: TocEntry; heading: cstring; items: var seq[Element]) =
- if x == nil: return
- if x.heading != nil and x.heading.textContent == heading:
- for i in 0..<x.kids.len:
- items.add x.kids[i].heading
- else:
- for k in x.kids:
- extractItems(k, heading, items)
- proc toHtml(x: TocEntry; isRoot=false): Element =
- if x == nil: return nil
- if x.kids.len == 0:
- if x.heading == nil: return nil
- return x.heading.clone
- result = tree("DIV")
- if x.heading != nil and not isMarked(x.heading):
- result.add x.heading.clone
- let ul = tree("UL")
- if isRoot:
- ul.setClass("simple simple-toc")
- else:
- ul.setClass("simple")
- if x.dosort:
- x.kids.sort(proc(a, b: TocEntry): int =
- if a.heading != nil and b.heading != nil:
- let x = a.heading.textContent
- let y = b.heading.textContent
- if x < y: return -1
- if x > y: return 1
- return 0
- else:
- # ensure sorting is stable:
- return a.sortId - b.sortId
- )
- for k in x.kids:
- let y = toHtml(k)
- if y != nil:
- ul.add tree("LI", y)
- if ul.len != 0: result.add ul
- if result.len == 0: result = nil
- proc isWhitespace(text: cstring): bool {.importcpp: r"!/\S/.test(#)".}
- proc isWhitespace(x: Element): bool =
- x.nodeName == "#text" and x.textContent.isWhitespace or x.nodeName == "#comment"
- proc toToc(x: Element; father: TocEntry) =
- if x.nodeName == "UL":
- let f = TocEntry(heading: nil, kids: @[], sortId: father.kids.len)
- var i = 0
- while i < x.len:
- var nxt = i+1
- while nxt < x.len and x[nxt].isWhitespace:
- inc nxt
- if nxt < x.len and x[i].nodeName == "LI" and x[i].len == 1 and
- x[nxt].nodeName == "UL":
- let e = TocEntry(heading: x[i][0], kids: @[], sortId: f.kids.len)
- let it = x[nxt]
- for j in 0..<it.len:
- toToc(it[j], e)
- f.kids.add e
- i = nxt+1
- else:
- toToc(x[i], f)
- inc i
- father.kids.add f
- elif isWhitespace(x):
- discard
- elif x.nodeName == "LI":
- var idx: seq[int] = @[]
- for i in 0 ..< x.len:
- if not x[i].isWhitespace: idx.add i
- if idx.len == 2 and x[idx[1]].nodeName == "UL":
- let e = TocEntry(heading: x[idx[0]], kids: @[], sortId: father.kids.len)
- let it = x[idx[1]]
- for j in 0..<it.len:
- toToc(it[j], e)
- father.kids.add e
- else:
- for i in 0..<x.len:
- toToc(x[i], father)
- else:
- father.kids.add TocEntry(heading: x, kids: @[], sortId: father.kids.len)
- proc tocul(x: Element): Element =
- # x is a 'ul' element
- result = tree("UL")
- for i in 0..<x.len:
- let it = x[i]
- if it.nodeName == "LI":
- result.add it.clone
- elif it.nodeName == "UL":
- result.add tocul(it)
- proc uncovered(x: TocEntry): TocEntry =
- if x.kids.len == 0 and x.heading != nil:
- return if not isMarked(x.heading): x else: nil
- result = TocEntry(heading: x.heading, kids: @[], sortId: x.sortId,
- doSort: x.doSort)
- for k in x.kids:
- let y = uncovered(k)
- if y != nil: result.kids.add y
- if result.kids.len == 0: result = nil
- proc mergeTocs(orig, news: TocEntry): TocEntry =
- result = uncovered(orig)
- if result == nil:
- result = news
- else:
- for i in 0..<news.kids.len:
- result.kids.add news.kids[i]
- proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry =
- var newStuff = TocEntry(heading: nil, kids: @[], doSort: true)
- for t in types:
- let c = TocEntry(heading: t.clone, kids: @[], doSort: true)
- t.markElement()
- for p in procs:
- if not isMarked(p):
- let xx = getElementsByClass(p.parentNode, "attachedType")
- if xx.len == 1 and xx[0].textContent == t.textContent:
- let q = tree("A", text(p.title))
- q.setAttr("href", p.getAttribute("href"))
- c.kids.add TocEntry(heading: q, kids: @[])
- p.markElement()
- newStuff.kids.add c
- result = mergeTocs(orig, newStuff)
- var alternative: Element
- proc togglevis(d: Element) =
- if d.style.display == "none":
- d.style.display = "inline"
- else:
- d.style.display = "none"
- proc groupBy*(value: cstring) {.exportc.} =
- let toc = document.getElementById("toc-list")
- if alternative.isNil:
- var tt = TocEntry(heading: nil, kids: @[])
- toToc(toc, tt)
- tt = tt.kids[0]
- var types: seq[Element] = @[]
- var procs: seq[Element] = @[]
- extractItems(tt, "Types", types)
- extractItems(tt, "Procs", procs)
- extractItems(tt, "Converters", procs)
- extractItems(tt, "Methods", procs)
- extractItems(tt, "Templates", procs)
- extractItems(tt, "Macros", procs)
- extractItems(tt, "Iterators", procs)
- let ntoc = buildToc(tt, types, procs)
- let x = toHtml(ntoc, isRoot=true)
- alternative = tree("DIV", x)
- if value == "type":
- replaceById("tocRoot", alternative)
- else:
- replaceById("tocRoot", tree("DIV"))
- togglevis(document.getElementById"toc-list")
- var
- db: seq[Node]
- contents: seq[cstring]
- proc escapeCString(x: var cstring) =
- # Original strings are already escaped except HTML tags, so
- # we only escape `<` and `>`.
- var s = ""
- for c in x:
- case c
- of '<': s.add("<")
- of '>': s.add(">")
- else: s.add(c)
- x = s.cstring
- proc dosearch(value: cstring): Element =
- if db.len == 0:
- return
- let ul = tree("UL")
- result = tree("DIV")
- result.setClass"search_results"
- var matches: seq[(Node, int)] = @[]
- for i in 0..<db.len:
- let c = contents[i]
- if c == "Examples" or c == "PEG construction":
- # Some manual exclusions.
- # Ideally these should be fixed in the index to be more
- # descriptive of what they are.
- continue
- let (score, matched) = fuzzymatch(value, c)
- if matched:
- matches.add((db[i], score))
- matches.sort(proc(a, b: auto): int = b[1] - a[1])
- for i in 0 ..< min(matches.len, 29):
- matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag")
- escapeCString(matches[i][0].innerHTML)
- ul.add(tree("LI", cast[Element](matches[i][0])))
- if ul.len == 0:
- result.add tree("B", text"no search results")
- else:
- result.add tree("B", text"search results")
- result.add ul
- proc loadIndex() {.async.} =
- ## Loads theindex.html to enable searching
- let
- indexURL = document.getElementById("indexLink").getAttribute("href")
- # Get root of project documentation by cutting off theindex.html from index href
- rootURL = ($indexURL)[0 ..< ^"theindex.html".len]
- var resp = fetch(indexURL).await().text().await()
- # Convert into element so we can use DOM functions to parse the html
- var indexElem = document.createElement("div")
- indexElem.innerHtml = resp
- # Add items into the DB/contents
- for href in indexElem.getElementsByClass("reference"):
- # Make links be relative to project root instead of current page
- href.setAttr("href", cstring(rootURL & $href.getAttribute("href")))
- db &= href
- contents &= href.getAttribute("data-doc-search-tag")
- var
- oldtoc: Element
- timer: Timeout
- loadIndexFut: Future[void] = nil
- proc search*() {.exportc.} =
- proc wrapper() =
- let elem = document.getElementById("searchInput")
- let value = elem.value
- if value.len != 0:
- if oldtoc.isNil:
- oldtoc = document.getElementById("tocRoot")
- let results = dosearch(value)
- replaceById("tocRoot", results)
- elif not oldtoc.isNil:
- replaceById("tocRoot", oldtoc)
- # Start loading index as soon as user starts typing.
- # Will only be loaded the once anyways
- if loadIndexFut == nil:
- loadIndexFut = loadIndex()
- # Run wrapper once loaded so we don't miss the users query
- discard loadIndexFut.then(wrapper)
- if timer != nil: clearTimeout(timer)
- timer = setTimeout(wrapper, 400)
- proc copyToClipboard*() {.exportc.} =
- {.emit: """
- function updatePreTags() {
- const allPreTags = document.querySelectorAll("pre:not(.line-nums)")
- allPreTags.forEach((e) => {
-
- const div = document.createElement("div")
- div.classList.add("copyToClipBoard")
-
- const preTag = document.createElement("pre")
- preTag.innerHTML = e.innerHTML
-
- const button = document.createElement("button")
- button.value = e.textContent.replace('...', '')
- button.classList.add("copyToClipBoardBtn")
- button.style.cursor = "pointer"
-
- div.appendChild(preTag)
- div.appendChild(button)
-
- e.outerHTML = div.outerHTML
-
- })
- }
- function copyTextToClipboard(e) {
- const clipBoardContent = e.target.value
- navigator.clipboard.writeText(clipBoardContent).then(function() {
- e.target.style.setProperty("--clipboard-image", "var(--clipboard-image-selected)")
- }, function(err) {
- console.error("Could not copy text: ", err);
- });
- }
- window.addEventListener("click", (e) => {
- if (e.target.classList.contains("copyToClipBoardBtn")) {
- copyTextToClipboard(e)
- }
- })
- window.addEventListener("mouseover", (e) => {
- if (e.target.nodeName === "PRE") {
- e.target.nextElementSibling.style.setProperty("--clipboard-image", "var(--clipboard-image-normal)")
- }
- })
-
- window.addEventListener("DOMContentLoaded", updatePreTags)
- """
- .}
- copyToClipboard()
- window.addEventListener("DOMContentLoaded", onDOMLoaded)
|