diff options
Diffstat (limited to 'tools/dochack')
-rw-r--r-- | tools/dochack/dochack.nim | 255 |
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("<") + of '>': s.add(">") + 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) |