summary refs log tree commit diff stats
path: root/tools/dochack/dochack.nim
diff options
context:
space:
mode:
Diffstat (limited to 'tools/dochack/dochack.nim')
-rw-r--r--tools/dochack/dochack.nim255
1 files changed, 153 insertions, 102 deletions
diff --git a/tools/dochack/dochack.nim b/tools/dochack/dochack.nim
index 4c4db4638..946945346 100644
--- a/tools/dochack/dochack.nim
+++ b/tools/dochack/dochack.nim
@@ -1,49 +1,52 @@
 import dom
 import fuzzysearch
+import std/[jsfetch, asyncjs]
 
-proc textContent(e: Element): cstring {.
-  importcpp: "#.textContent", nodecl.}
 
-proc textContent(e: Node): cstring {.
-  importcpp: "#.textContent", nodecl.}
+proc setTheme(theme: cstring) {.exportc.} =
+  document.documentElement.setAttribute("data-theme", theme)
+  window.localStorage.setItem("theme", theme)
 
-proc tree(tag: string; kids: varargs[Element]): Element =
+# 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 == cstring"TR" and (
-      kid.nodeName == cstring"TD" or kid.nodeName == cstring"TH"):
+  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: string) =
+proc setClass(e: Element; value: cstring) =
   e.setAttribute("class", value)
-proc text(s: string): Element = cast[Element](document.createTextNode(s))
 proc text(s: cstring): Element = cast[Element](document.createTextNode(s))
 
-proc getElementById(id: cstring): Element {.importc: "document.getElementById", nodecl.}
-
 proc replaceById(id: cstring; newTree: Node) =
-  let x = getElementById(id)
+  let x = document.getElementById(id)
   x.parentNode.replaceChild(newTree, x)
   newTree.id = id
 
-proc findNodeWith(x: Element; tag, content: cstring): Element =
-  if x.nodeName == tag and x.textContent == content:
-    return x
-  for i in 0..<x.len:
-    let it = x[i]
-    let y = findNodeWith(it, tag, content)
-    if y != nil: return y
-  return nil
-
 proc clone(e: Element): Element {.importcpp: "#.cloneNode(true)", nodecl.}
-proc parent(e: Element): Element {.importcpp: "#.parentNode", nodecl.}
 proc markElement(x: Element) {.importcpp: "#.__karaxMarker__ = true", nodecl.}
 proc isMarked(x: Element): bool {.
   importcpp: "#.hasOwnProperty('__karaxMarker__')", nodecl.}
@@ -52,20 +55,13 @@ 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 parentWith(x: Element; tag: cstring): Element =
-  result = x.parent
-  while result.nodeName != tag:
-    result = result.parent
-    if result == nil: return nil
-
 proc extractItems(x: Element; items: var seq[Element]) =
   if x == nil: return
-  if x.nodeName == cstring"A":
+  if x.nodeName == "A":
     items.add x
   else:
     for i in 0..<x.len:
-      let it = x[i]
-      extractItems(it, items)
+      extractItems(x[i], items)
 
 # HTML trees are so shitty we transform the TOC into a decent
 # data-structure instead and work on that.
@@ -76,16 +72,14 @@ type
     sortId: int
     doSort: bool
 
-proc extractItems(x: TocEntry; heading: cstring;
-                  items: var seq[Element]) =
+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 i in 0..<x.kids.len:
-      let it = x.kids[i]
-      extractItems(it, heading, items)
+    for k in x.kids:
+      extractItems(k, heading, items)
 
 proc toHtml(x: TocEntry; isRoot=false): Element =
   if x == nil: return nil
@@ -119,31 +113,21 @@ proc toHtml(x: TocEntry; isRoot=false): Element =
   if ul.len != 0: result.add ul
   if result.len == 0: result = nil
 
-#proc containsWord(a, b: cstring): bool {.asmNoStackFrame.} =
-  #{.emit: """
-     #var escaped = `b`.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
-     #return new RegExp("\\b" + escaped + "\\b").test(`a`);
-  #""".}
-
-proc isWhitespace(text: cstring): bool {.asmNoStackFrame.} =
-  {.emit: """
-     return !/[^\s]/.test(`text`);
-  """.}
+proc isWhitespace(text: cstring): bool {.importcpp: r"!/\S/.test(#)".}
 
 proc isWhitespace(x: Element): bool =
-  x.nodeName == cstring"#text" and x.textContent.isWhitespace or
-    x.nodeName == cstring"#comment"
+  x.nodeName == "#text" and x.textContent.isWhitespace or x.nodeName == "#comment"
 
 proc toToc(x: Element; father: TocEntry) =
-  if x.nodeName == cstring"UL":
+  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 == cstring"LI" and x[i].len == 1 and
-          x[nxt].nodeName == cstring"UL":
+      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:
@@ -156,13 +140,12 @@ proc toToc(x: Element; father: TocEntry) =
     father.kids.add f
   elif isWhitespace(x):
     discard
-  elif x.nodeName == cstring"LI":
+  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 == cstring"UL":
-      let e = TocEntry(heading: x[idx[0]], kids: @[],
-                       sortId: father.kids.len)
+    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)
@@ -171,31 +154,25 @@ proc toToc(x: Element; father: TocEntry) =
       for i in 0..<x.len:
         toToc(x[i], father)
   else:
-    father.kids.add TocEntry(heading: x, kids: @[],
-                             sortId: father.kids.len)
+    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 == cstring"LI":
+    if it.nodeName == "LI":
       result.add it.clone
-    elif it.nodeName == cstring"UL":
+    elif it.nodeName == "UL":
       result.add tocul(it)
 
-proc getSection(toc: Element; name: cstring): Element =
-  let sec = findNodeWith(toc, "A", name)
-  if sec != nil:
-    result = sec.parentWith("LI")
-
 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 i in 0..<x.kids.len:
-    let y = uncovered(x.kids[i])
+  for k in x.kids:
+    let y = uncovered(k)
     if y != nil: result.kids.add y
   if result.kids.len == 0: result = nil
 
@@ -214,9 +191,8 @@ proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry =
     t.markElement()
     for p in procs:
       if not isMarked(p):
-        let xx = getElementsByClass(p.parent, cstring"attachedType")
+        let xx = getElementsByClass(p.parentNode, "attachedType")
         if xx.len == 1 and xx[0].textContent == t.textContent:
-          #kout(cstring"found ", p.nodeName)
           let q = tree("A", text(p.title))
           q.setAttr("href", p.getAttribute("href"))
           c.kids.add TocEntry(heading: q, kids: @[])
@@ -227,15 +203,13 @@ proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry =
 var alternative: Element
 
 proc togglevis(d: Element) =
-  asm """
-    if (`d`.style.display == 'none')
-      `d`.style.display = 'inline';
-    else
-      `d`.style.display = 'none';
-  """
+  if d.style.display == "none":
+    d.style.display = "inline"
+  else:
+    d.style.display = "none"
 
 proc groupBy*(value: cstring) {.exportc.} =
-  let toc = getElementById("toc-list")
+  let toc = document.getElementById("toc-list")
   if alternative.isNil:
     var tt = TocEntry(heading: nil, kids: @[])
     toToc(toc, tt)
@@ -255,45 +229,38 @@ proc groupBy*(value: cstring) {.exportc.} =
     let ntoc = buildToc(tt, types, procs)
     let x = toHtml(ntoc, isRoot=true)
     alternative = tree("DIV", x)
-  if value == cstring"type":
+  if value == "type":
     replaceById("tocRoot", alternative)
   else:
     replaceById("tocRoot", tree("DIV"))
-  togglevis(getElementById"toc-list")
+  togglevis(document.getElementById"toc-list")
 
 var
   db: seq[Node]
   contents: seq[cstring]
 
-template normalize(x: cstring): cstring = x.toLower.replace("_", "")
+
+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("&lt;")
+    of '>': s.add("&gt;")
+    else: s.add(c)
+  x = s.cstring
 
 proc dosearch(value: cstring): Element =
   if db.len == 0:
-    var stuff: Element
-    {.emit: """
-    var request = new XMLHttpRequest();
-    request.open("GET", "theindex.html", false);
-    request.send(null);
-
-    var doc = document.implementation.createHTMLDocument("theindex");
-    doc.documentElement.innerHTML = request.responseText;
-
-    //parser=new DOMParser();
-    //doc=parser.parseFromString("<html></html>", "text/html");
-
-    `stuff` = doc.documentElement;
-    """.}
-    db = stuff.getElementsByClass"reference"
-    contents = @[]
-    for ahref in db:
-      contents.add ahref.getAttribute("data-doc-search-tag")
+    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 == cstring"Examples" or c == cstring"PEG construction":
+    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.
@@ -305,6 +272,7 @@ proc dosearch(value: cstring): Element =
   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")
@@ -312,20 +280,103 @@ proc dosearch(value: cstring): Element =
     result.add tree("B", text"search results")
     result.add ul
 
-var oldtoc: Element
-var timer: Timeout
+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 = getElementById("searchInput")
+    let elem = document.getElementById("searchInput")
     let value = elem.value
     if value.len != 0:
       if oldtoc.isNil:
-        oldtoc = getElementById("tocRoot")
+        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)