dochack.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import dom
  2. import fuzzysearch
  3. proc setTheme(theme: cstring) {.exportc.} =
  4. document.documentElement.setAttribute("data-theme", theme)
  5. window.localStorage.setItem("theme", theme)
  6. # set `data-theme` attribute early to prevent white flash
  7. setTheme:
  8. let t = window.localStorage.getItem("theme")
  9. if t.isNil: cstring"auto" else: t
  10. proc onDOMLoaded(e: Event) {.exportc.} =
  11. # set theme select value
  12. document.getElementById("theme-select").value = window.localStorage.getItem("theme")
  13. for pragmaDots in document.getElementsByClassName("pragmadots"):
  14. pragmaDots.onclick = proc (event: Event) =
  15. # Hide tease
  16. event.target.parentNode.style.display = "none"
  17. # Show actual
  18. event.target.parentNode.nextSibling.style.display = "inline"
  19. proc tree(tag: cstring; kids: varargs[Element]): Element =
  20. result = document.createElement tag
  21. for k in kids:
  22. result.appendChild k
  23. proc add(parent, kid: Element) =
  24. if parent.nodeName == "TR" and (kid.nodeName == "TD" or kid.nodeName == "TH"):
  25. let k = document.createElement("TD")
  26. appendChild(k, kid)
  27. appendChild(parent, k)
  28. else:
  29. appendChild(parent, kid)
  30. proc setClass(e: Element; value: cstring) =
  31. e.setAttribute("class", value)
  32. proc text(s: cstring): Element = cast[Element](document.createTextNode(s))
  33. proc replaceById(id: cstring; newTree: Node) =
  34. let x = document.getElementById(id)
  35. x.parentNode.replaceChild(newTree, x)
  36. newTree.id = id
  37. proc clone(e: Element): Element {.importcpp: "#.cloneNode(true)", nodecl.}
  38. proc markElement(x: Element) {.importcpp: "#.__karaxMarker__ = true", nodecl.}
  39. proc isMarked(x: Element): bool {.
  40. importcpp: "#.hasOwnProperty('__karaxMarker__')", nodecl.}
  41. proc title(x: Element): cstring {.importcpp: "#.title", nodecl.}
  42. proc sort[T](x: var openArray[T]; cmp: proc(a, b: T): int) {.importcpp:
  43. "#.sort(#)", nodecl.}
  44. proc extractItems(x: Element; items: var seq[Element]) =
  45. if x == nil: return
  46. if x.nodeName == "A":
  47. items.add x
  48. else:
  49. for i in 0..<x.len:
  50. extractItems(x[i], items)
  51. # HTML trees are so shitty we transform the TOC into a decent
  52. # data-structure instead and work on that.
  53. type
  54. TocEntry = ref object
  55. heading: Element
  56. kids: seq[TocEntry]
  57. sortId: int
  58. doSort: bool
  59. proc extractItems(x: TocEntry; heading: cstring; items: var seq[Element]) =
  60. if x == nil: return
  61. if x.heading != nil and x.heading.textContent == heading:
  62. for i in 0..<x.kids.len:
  63. items.add x.kids[i].heading
  64. else:
  65. for k in x.kids:
  66. extractItems(k, heading, items)
  67. proc toHtml(x: TocEntry; isRoot=false): Element =
  68. if x == nil: return nil
  69. if x.kids.len == 0:
  70. if x.heading == nil: return nil
  71. return x.heading.clone
  72. result = tree("DIV")
  73. if x.heading != nil and not isMarked(x.heading):
  74. result.add x.heading.clone
  75. let ul = tree("UL")
  76. if isRoot:
  77. ul.setClass("simple simple-toc")
  78. else:
  79. ul.setClass("simple")
  80. if x.dosort:
  81. x.kids.sort(proc(a, b: TocEntry): int =
  82. if a.heading != nil and b.heading != nil:
  83. let x = a.heading.textContent
  84. let y = b.heading.textContent
  85. if x < y: return -1
  86. if x > y: return 1
  87. return 0
  88. else:
  89. # ensure sorting is stable:
  90. return a.sortId - b.sortId
  91. )
  92. for k in x.kids:
  93. let y = toHtml(k)
  94. if y != nil:
  95. ul.add tree("LI", y)
  96. if ul.len != 0: result.add ul
  97. if result.len == 0: result = nil
  98. proc isWhitespace(text: cstring): bool {.importcpp: r"!/\S/.test(#)".}
  99. proc isWhitespace(x: Element): bool =
  100. x.nodeName == "#text" and x.textContent.isWhitespace or x.nodeName == "#comment"
  101. proc toToc(x: Element; father: TocEntry) =
  102. if x.nodeName == "UL":
  103. let f = TocEntry(heading: nil, kids: @[], sortId: father.kids.len)
  104. var i = 0
  105. while i < x.len:
  106. var nxt = i+1
  107. while nxt < x.len and x[nxt].isWhitespace:
  108. inc nxt
  109. if nxt < x.len and x[i].nodeName == "LI" and x[i].len == 1 and
  110. x[nxt].nodeName == "UL":
  111. let e = TocEntry(heading: x[i][0], kids: @[], sortId: f.kids.len)
  112. let it = x[nxt]
  113. for j in 0..<it.len:
  114. toToc(it[j], e)
  115. f.kids.add e
  116. i = nxt+1
  117. else:
  118. toToc(x[i], f)
  119. inc i
  120. father.kids.add f
  121. elif isWhitespace(x):
  122. discard
  123. elif x.nodeName == "LI":
  124. var idx: seq[int] = @[]
  125. for i in 0 ..< x.len:
  126. if not x[i].isWhitespace: idx.add i
  127. if idx.len == 2 and x[idx[1]].nodeName == "UL":
  128. let e = TocEntry(heading: x[idx[0]], kids: @[], sortId: father.kids.len)
  129. let it = x[idx[1]]
  130. for j in 0..<it.len:
  131. toToc(it[j], e)
  132. father.kids.add e
  133. else:
  134. for i in 0..<x.len:
  135. toToc(x[i], father)
  136. else:
  137. father.kids.add TocEntry(heading: x, kids: @[], sortId: father.kids.len)
  138. proc tocul(x: Element): Element =
  139. # x is a 'ul' element
  140. result = tree("UL")
  141. for i in 0..<x.len:
  142. let it = x[i]
  143. if it.nodeName == "LI":
  144. result.add it.clone
  145. elif it.nodeName == "UL":
  146. result.add tocul(it)
  147. proc uncovered(x: TocEntry): TocEntry =
  148. if x.kids.len == 0 and x.heading != nil:
  149. return if not isMarked(x.heading): x else: nil
  150. result = TocEntry(heading: x.heading, kids: @[], sortId: x.sortId,
  151. doSort: x.doSort)
  152. for k in x.kids:
  153. let y = uncovered(k)
  154. if y != nil: result.kids.add y
  155. if result.kids.len == 0: result = nil
  156. proc mergeTocs(orig, news: TocEntry): TocEntry =
  157. result = uncovered(orig)
  158. if result == nil:
  159. result = news
  160. else:
  161. for i in 0..<news.kids.len:
  162. result.kids.add news.kids[i]
  163. proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry =
  164. var newStuff = TocEntry(heading: nil, kids: @[], doSort: true)
  165. for t in types:
  166. let c = TocEntry(heading: t.clone, kids: @[], doSort: true)
  167. t.markElement()
  168. for p in procs:
  169. if not isMarked(p):
  170. let xx = getElementsByClass(p.parentNode, "attachedType")
  171. if xx.len == 1 and xx[0].textContent == t.textContent:
  172. let q = tree("A", text(p.title))
  173. q.setAttr("href", p.getAttribute("href"))
  174. c.kids.add TocEntry(heading: q, kids: @[])
  175. p.markElement()
  176. newStuff.kids.add c
  177. result = mergeTocs(orig, newStuff)
  178. var alternative: Element
  179. proc togglevis(d: Element) =
  180. if d.style.display == "none":
  181. d.style.display = "inline"
  182. else:
  183. d.style.display = "none"
  184. proc groupBy*(value: cstring) {.exportc.} =
  185. let toc = document.getElementById("toc-list")
  186. if alternative.isNil:
  187. var tt = TocEntry(heading: nil, kids: @[])
  188. toToc(toc, tt)
  189. tt = tt.kids[0]
  190. var types: seq[Element] = @[]
  191. var procs: seq[Element] = @[]
  192. extractItems(tt, "Types", types)
  193. extractItems(tt, "Procs", procs)
  194. extractItems(tt, "Converters", procs)
  195. extractItems(tt, "Methods", procs)
  196. extractItems(tt, "Templates", procs)
  197. extractItems(tt, "Macros", procs)
  198. extractItems(tt, "Iterators", procs)
  199. let ntoc = buildToc(tt, types, procs)
  200. let x = toHtml(ntoc, isRoot=true)
  201. alternative = tree("DIV", x)
  202. if value == "type":
  203. replaceById("tocRoot", alternative)
  204. else:
  205. replaceById("tocRoot", tree("DIV"))
  206. togglevis(document.getElementById"toc-list")
  207. var
  208. db: seq[Node]
  209. contents: seq[cstring]
  210. proc escapeCString(x: var cstring) =
  211. # Original strings are already escaped except HTML tags, so
  212. # we only escape `<` and `>`.
  213. var s = ""
  214. for c in x:
  215. case c
  216. of '<': s.add("&lt;")
  217. of '>': s.add("&gt;")
  218. else: s.add(c)
  219. x = s.cstring
  220. proc dosearch(value: cstring): Element =
  221. if db.len == 0:
  222. var stuff: Element
  223. {.emit: """
  224. var request = new XMLHttpRequest();
  225. request.open("GET", document.getElementById("indexLink").href, false);
  226. request.send(null);
  227. var doc = document.implementation.createHTMLDocument("theindex");
  228. doc.documentElement.innerHTML = request.responseText;
  229. `stuff` = doc.documentElement;
  230. """.}
  231. db = stuff.getElementsByClass"reference"
  232. contents = @[]
  233. for ahref in db:
  234. contents.add ahref.getAttribute("data-doc-search-tag")
  235. let ul = tree("UL")
  236. result = tree("DIV")
  237. result.setClass"search_results"
  238. var matches: seq[(Node, int)] = @[]
  239. for i in 0..<db.len:
  240. let c = contents[i]
  241. if c == "Examples" or c == "PEG construction":
  242. # Some manual exclusions.
  243. # Ideally these should be fixed in the index to be more
  244. # descriptive of what they are.
  245. continue
  246. let (score, matched) = fuzzymatch(value, c)
  247. if matched:
  248. matches.add((db[i], score))
  249. matches.sort(proc(a, b: auto): int = b[1] - a[1])
  250. for i in 0 ..< min(matches.len, 29):
  251. matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag")
  252. escapeCString(matches[i][0].innerHTML)
  253. ul.add(tree("LI", cast[Element](matches[i][0])))
  254. if ul.len == 0:
  255. result.add tree("B", text"no search results")
  256. else:
  257. result.add tree("B", text"search results")
  258. result.add ul
  259. var oldtoc: Element
  260. var timer: Timeout
  261. proc search*() {.exportc.} =
  262. proc wrapper() =
  263. let elem = document.getElementById("searchInput")
  264. let value = elem.value
  265. if value.len != 0:
  266. if oldtoc.isNil:
  267. oldtoc = document.getElementById("tocRoot")
  268. let results = dosearch(value)
  269. replaceById("tocRoot", results)
  270. elif not oldtoc.isNil:
  271. replaceById("tocRoot", oldtoc)
  272. if timer != nil: clearTimeout(timer)
  273. timer = setTimeout(wrapper, 400)
  274. proc copyToClipboard*() {.exportc.} =
  275. {.emit: """
  276. function updatePreTags() {
  277. const allPreTags = document.querySelectorAll("pre")
  278. allPreTags.forEach((e) => {
  279. const div = document.createElement("div")
  280. div.classList.add("copyToClipBoard")
  281. const preTag = document.createElement("pre")
  282. preTag.innerHTML = e.innerHTML
  283. const button = document.createElement("button")
  284. button.value = e.textContent.replace('...', '')
  285. button.classList.add("copyToClipBoardBtn")
  286. button.style.cursor = "pointer"
  287. div.appendChild(preTag)
  288. div.appendChild(button)
  289. e.outerHTML = div.outerHTML
  290. })
  291. }
  292. function copyTextToClipboard(e) {
  293. const clipBoardContent = e.target.value
  294. navigator.clipboard.writeText(clipBoardContent).then(function() {
  295. e.target.style.setProperty("--clipboard-image", "var(--clipboard-image-selected)")
  296. }, function(err) {
  297. console.error("Could not copy text: ", err);
  298. });
  299. }
  300. window.addEventListener("click", (e) => {
  301. if (e.target.classList.contains("copyToClipBoardBtn")) {
  302. copyTextToClipboard(e)
  303. }
  304. })
  305. window.addEventListener("mouseover", (e) => {
  306. if (e.target.nodeName === "PRE") {
  307. e.target.nextElementSibling.style.setProperty("--clipboard-image", "var(--clipboard-image-normal)")
  308. }
  309. })
  310. window.addEventListener("DOMContentLoaded", updatePreTags)
  311. """
  312. .}
  313. copyToClipboard()
  314. window.addEventListener("DOMContentLoaded", onDOMLoaded)