docgen: group by type feature
This commit is contained in:
parent
07fcce6e63
commit
083b31b473
5 changed files with 596 additions and 1 deletions
|
|
@ -27,6 +27,7 @@ type
|
||||||
seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
|
seenSymbols: StringTableRef # avoids duplicate symbol generation for HTML.
|
||||||
jArray: JsonNode
|
jArray: JsonNode
|
||||||
types: TStrTable
|
types: TStrTable
|
||||||
|
isPureRst: bool
|
||||||
|
|
||||||
PDoc* = ref TDocumentor ## Alias to type less.
|
PDoc* = ref TDocumentor ## Alias to type less.
|
||||||
|
|
||||||
|
|
@ -634,7 +635,9 @@ proc genOutFile(d: PDoc): Rope =
|
||||||
# Modules get an automatic title for the HTML, but no entry in the index.
|
# Modules get an automatic title for the HTML, but no entry in the index.
|
||||||
title = "Module " & extractFilename(changeFileExt(d.filename, ""))
|
title = "Module " & extractFilename(changeFileExt(d.filename, ""))
|
||||||
|
|
||||||
let bodyname = if d.hasToc: "doc.body_toc" else: "doc.body_no_toc"
|
let bodyname = if d.hasToc and not d.isPureRst: "doc.body_toc_group"
|
||||||
|
elif d.hasToc: "doc.body_toc"
|
||||||
|
else: "doc.body_no_toc"
|
||||||
content = ropeFormatNamedVars(getConfigVar(bodyname), ["title",
|
content = ropeFormatNamedVars(getConfigVar(bodyname), ["title",
|
||||||
"tableofcontents", "moduledesc", "date", "time", "content"],
|
"tableofcontents", "moduledesc", "date", "time", "content"],
|
||||||
[title.rope, toc, d.modDesc, rope(getDateStr()),
|
[title.rope, toc, d.modDesc, rope(getDateStr()),
|
||||||
|
|
@ -699,6 +702,7 @@ proc commandDoc*() =
|
||||||
proc commandRstAux(filename, outExt: string) =
|
proc commandRstAux(filename, outExt: string) =
|
||||||
var filen = addFileExt(filename, "txt")
|
var filen = addFileExt(filename, "txt")
|
||||||
var d = newDocumentor(filen, options.gConfigVars)
|
var d = newDocumentor(filen, options.gConfigVars)
|
||||||
|
d.isPureRst = true
|
||||||
var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc,
|
var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc,
|
||||||
{roSupportRawDirective})
|
{roSupportRawDirective})
|
||||||
var modDesc = newStringOfCap(30_000)
|
var modDesc = newStringOfCap(30_000)
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,26 @@ doc.body_toc = """
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
doc.body_toc_group = """
|
||||||
|
<div class="row">
|
||||||
|
<div class="three columns">
|
||||||
|
<div>
|
||||||
|
Group by:
|
||||||
|
<select onchange="groupBy(this.value)">
|
||||||
|
<option value="section">Section</option>
|
||||||
|
<option value="type">Type</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
$tableofcontents
|
||||||
|
</div>
|
||||||
|
<div class="nine columns" id="content">
|
||||||
|
<div id="tocRoot"></div>
|
||||||
|
<p class="module-desc">$moduledesc</p>
|
||||||
|
$content
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
doc.body_no_toc = """
|
doc.body_no_toc = """
|
||||||
$moduledesc
|
$moduledesc
|
||||||
$content
|
$content
|
||||||
|
|
@ -1248,6 +1268,7 @@ dt pre > span.Operator ~ span.Identifier, dt pre > span.Operator ~ span.Operator
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="../dochack.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function togglepragma(d) {
|
function togglepragma(d) {
|
||||||
|
|
|
||||||
1
koch.nim
1
koch.nim
|
|
@ -200,6 +200,7 @@ proc install(args: string) =
|
||||||
exec("sh ./install.sh $#" % args)
|
exec("sh ./install.sh $#" % args)
|
||||||
|
|
||||||
proc web(args: string) =
|
proc web(args: string) =
|
||||||
|
exec("$# js tools/dochack/dochack.nim" % findNim())
|
||||||
exec("$# cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
|
exec("$# cc -r tools/nimweb.nim $# web/website.ini --putenv:nimversion=$#" %
|
||||||
[findNim(), args, VersionAsString])
|
[findNim(), args, VersionAsString])
|
||||||
|
|
||||||
|
|
|
||||||
231
tools/dochack/dochack.nim
Normal file
231
tools/dochack/dochack.nim
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
|
||||||
|
|
||||||
|
import karax
|
||||||
|
|
||||||
|
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.}
|
||||||
|
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 == "A":
|
||||||
|
items.add x
|
||||||
|
else:
|
||||||
|
for i in 0..<x.len:
|
||||||
|
let it = x[i]
|
||||||
|
extractItems(it, 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 i in 0..<x.kids.len:
|
||||||
|
let it = x.kids[i]
|
||||||
|
extractItems(it, 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 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(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 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])
|
||||||
|
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 = karax.getElementsByClass(p.parent, cstring"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: @[])
|
||||||
|
p.markElement()
|
||||||
|
newStuff.kids.add c
|
||||||
|
result = mergeTocs(orig, newStuff)
|
||||||
|
|
||||||
|
var alternative: Element
|
||||||
|
|
||||||
|
proc togglevis(d: Element) =
|
||||||
|
asm """
|
||||||
|
if (`d`.style.display == 'none')
|
||||||
|
`d`.style.display = 'inline';
|
||||||
|
else
|
||||||
|
`d`.style.display = 'none';
|
||||||
|
"""
|
||||||
|
|
||||||
|
proc groupBy*(value: cstring) {.exportc.} =
|
||||||
|
let toc = 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(getElementById"toc-list")
|
||||||
338
tools/dochack/karax.nim
Normal file
338
tools/dochack/karax.nim
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
# Simple lib to write JS UIs
|
||||||
|
|
||||||
|
import dom
|
||||||
|
|
||||||
|
export dom.Element, dom.Event, dom.cloneNode, dom
|
||||||
|
|
||||||
|
proc kout*[T](x: T) {.importc: "console.log", varargs.}
|
||||||
|
## the preferred way of debugging karax applications.
|
||||||
|
|
||||||
|
proc id*(e: Node): cstring {.importcpp: "#.id", nodecl.}
|
||||||
|
proc `id=`*(e: Node; x: cstring) {.importcpp: "#.id = #", nodecl.}
|
||||||
|
proc className*(e: Node): cstring {.importcpp: "#.className", nodecl.}
|
||||||
|
proc `className=`*(e: Node; v: cstring) {.importcpp: "#.className = #", nodecl.}
|
||||||
|
|
||||||
|
proc value*(e: Element): cstring {.importcpp: "#.value", nodecl.}
|
||||||
|
proc `value=`*(e: Element; v: cstring) {.importcpp: "#.value = #", nodecl.}
|
||||||
|
|
||||||
|
proc getElementsByClass*(e: Element; name: cstring): seq[Element] {.importcpp: "#.getElementsByClassName(#)", nodecl.}
|
||||||
|
|
||||||
|
type
|
||||||
|
EventHandler* = proc(ev: Event)
|
||||||
|
EventHandlerId* = proc(ev: Event; id: int)
|
||||||
|
|
||||||
|
Timeout* = ref object
|
||||||
|
|
||||||
|
var document* {.importc.}: Document
|
||||||
|
|
||||||
|
var
|
||||||
|
dorender: proc (): Element {.closure.}
|
||||||
|
drawTimeout: Timeout
|
||||||
|
currentTree: Element
|
||||||
|
|
||||||
|
proc setRenderer*(renderer: proc (): Element) =
|
||||||
|
dorender = renderer
|
||||||
|
|
||||||
|
proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.}
|
||||||
|
proc clearTimeout*(t: Timeout) {.importc, nodecl.}
|
||||||
|
proc targetElem*(e: Event): Element = cast[Element](e.target)
|
||||||
|
|
||||||
|
proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.}
|
||||||
|
|
||||||
|
proc getElementsByClassName*(cls: cstring): seq[Element] {.importc:
|
||||||
|
"document.getElementsByClassName", nodecl.}
|
||||||
|
|
||||||
|
proc textContent*(e: Element): cstring {.
|
||||||
|
importcpp: "#.textContent", nodecl.}
|
||||||
|
|
||||||
|
proc replaceById*(id: cstring; newTree: Node) =
|
||||||
|
let x = getElementById(id)
|
||||||
|
x.parentNode.replaceChild(newTree, x)
|
||||||
|
newTree.id = id
|
||||||
|
|
||||||
|
proc equals(a, b: Node): bool =
|
||||||
|
if a.nodeType != b.nodeType: return false
|
||||||
|
if a.id != b.id: return false
|
||||||
|
if a.nodeName != b.nodeName: return false
|
||||||
|
if a.nodeType == TextNode:
|
||||||
|
if a.data != b.data: return false
|
||||||
|
elif a.childNodes.len != b.childNodes.len:
|
||||||
|
return false
|
||||||
|
if a.className != b.className:
|
||||||
|
# style differences are updated in place and we pretend
|
||||||
|
# it's still the same node
|
||||||
|
a.className = b.className
|
||||||
|
#return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc diffTree(parent, a, b: Node) =
|
||||||
|
if equals(a, b):
|
||||||
|
if a.nodeType != TextNode:
|
||||||
|
# we need to do this correctly in the presence of asyncronous
|
||||||
|
# DOM updates:
|
||||||
|
var i = 0
|
||||||
|
while i < a.childNodes.len and a.childNodes.len == b.childNodes.len:
|
||||||
|
diffTree(a, a.childNodes[i], b.childNodes[i])
|
||||||
|
inc i
|
||||||
|
elif parent == nil:
|
||||||
|
replaceById("ROOT", b)
|
||||||
|
else:
|
||||||
|
parent.replaceChild(b, a)
|
||||||
|
|
||||||
|
proc dodraw() =
|
||||||
|
let newtree = dorender()
|
||||||
|
newtree.id = "ROOT"
|
||||||
|
if currentTree == nil:
|
||||||
|
currentTree = newtree
|
||||||
|
replaceById("ROOT", currentTree)
|
||||||
|
else:
|
||||||
|
diffTree(nil, currentTree, newtree)
|
||||||
|
|
||||||
|
proc redraw*() =
|
||||||
|
# we buffer redraw requests:
|
||||||
|
if drawTimeout != nil:
|
||||||
|
clearTimeout(drawTimeout)
|
||||||
|
drawTimeout = setTimeout(dodraw, 30)
|
||||||
|
|
||||||
|
proc tree*(tag: string; kids: varargs[Element]): Element =
|
||||||
|
result = document.createElement tag
|
||||||
|
for k in kids:
|
||||||
|
result.appendChild k
|
||||||
|
|
||||||
|
proc tree*(tag: string; attrs: openarray[(string, string)];
|
||||||
|
kids: varargs[Element]): Element =
|
||||||
|
result = tree(tag, kids)
|
||||||
|
for a in attrs: result.setAttribute(a[0], a[1])
|
||||||
|
|
||||||
|
proc text*(s: string): Element = cast[Element](document.createTextNode(s))
|
||||||
|
proc text*(s: cstring): Element = cast[Element](document.createTextNode(s))
|
||||||
|
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 len*(x: Element): int {.importcpp: "#.childNodes.length".}
|
||||||
|
proc `[]`*(x: Element; idx: int): Element {.importcpp: "#.childNodes[#]".}
|
||||||
|
|
||||||
|
proc isInt*(s: cstring): bool {.asmNoStackFrame.} =
|
||||||
|
asm """
|
||||||
|
return s.match(/^[0-9]+$/);
|
||||||
|
"""
|
||||||
|
|
||||||
|
var
|
||||||
|
linkCounter: int
|
||||||
|
|
||||||
|
proc link*(id: int): Element =
|
||||||
|
result = document.createElement("a")
|
||||||
|
result.setAttribute("href", "#")
|
||||||
|
inc linkCounter
|
||||||
|
result.setAttribute("id", $linkCounter & ":" & $id)
|
||||||
|
|
||||||
|
proc link*(action: EventHandler): Element =
|
||||||
|
result = document.createElement("a")
|
||||||
|
result.setAttribute("href", "#")
|
||||||
|
addEventListener(result, "click", action)
|
||||||
|
|
||||||
|
proc parseInt*(s: cstring): int {.importc, nodecl.}
|
||||||
|
proc parseFloat*(s: cstring): float {.importc, nodecl.}
|
||||||
|
proc split*(s, sep: cstring): seq[cstring] {.importcpp, nodecl.}
|
||||||
|
|
||||||
|
proc startsWith*(a, b: cstring): bool {.importcpp: "startsWith", nodecl.}
|
||||||
|
proc contains*(a, b: cstring): bool {.importcpp: "(#.indexOf(#)>=0)", nodecl.}
|
||||||
|
proc substr*(s: cstring; start: int): cstring {.importcpp: "substr", nodecl.}
|
||||||
|
proc substr*(s: cstring; start, length: int): cstring {.importcpp: "substr", nodecl.}
|
||||||
|
|
||||||
|
#proc len*(s: cstring): int {.importcpp: "#.length", nodecl.}
|
||||||
|
proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)", nodecl.}
|
||||||
|
proc toCstr*(s: int): cstring {.importcpp: "((#)+'')", nodecl.}
|
||||||
|
|
||||||
|
proc suffix*(s, prefix: cstring): cstring =
|
||||||
|
if s.startsWith(prefix):
|
||||||
|
result = s.substr(prefix.len)
|
||||||
|
else:
|
||||||
|
kout(cstring"bug! " & s & cstring" does not start with " & prefix)
|
||||||
|
|
||||||
|
proc valueAsInt*(e: Element): int = parseInt(e.value)
|
||||||
|
proc suffixAsInt*(s, prefix: cstring): int = parseInt(suffix(s, prefix))
|
||||||
|
|
||||||
|
proc scrollTop*(e: Element): int {.importcpp: "#.scrollTop", nodecl.}
|
||||||
|
proc offsetHeight*(e: Element): int {.importcpp: "#.offsetHeight", nodecl.}
|
||||||
|
proc offsetTop*(e: Element): int {.importcpp: "#.offsetTop", nodecl.}
|
||||||
|
|
||||||
|
template onImpl(s) {.dirty} =
|
||||||
|
proc wrapper(ev: Event) =
|
||||||
|
action(ev)
|
||||||
|
redraw()
|
||||||
|
addEventListener(e, s, wrapper)
|
||||||
|
|
||||||
|
proc setOnclick*(e: Element; action: proc(ev: Event)) =
|
||||||
|
onImpl "click"
|
||||||
|
|
||||||
|
proc setOnclick*(e: Element; action: proc(ev: Event; id: int)) =
|
||||||
|
proc wrapper(ev: Event) =
|
||||||
|
let id = ev.target.id
|
||||||
|
let a = id.split(":")
|
||||||
|
if a.len == 2:
|
||||||
|
action(ev, parseInt(a[1]))
|
||||||
|
redraw()
|
||||||
|
else:
|
||||||
|
kout(cstring("cannot deal with id "), id)
|
||||||
|
addEventListener(e, "click", wrapper)
|
||||||
|
|
||||||
|
proc setOnfocuslost*(e: Element; action: EventHandler) =
|
||||||
|
onImpl "blur"
|
||||||
|
|
||||||
|
proc setOnchanged*(e: Element; action: EventHandler) =
|
||||||
|
onImpl "change"
|
||||||
|
|
||||||
|
proc setOnscroll*(e: Element; action: EventHandler) =
|
||||||
|
onImpl "scroll"
|
||||||
|
|
||||||
|
proc select*(choices: openarray[string]): Element =
|
||||||
|
result = document.createElement("select")
|
||||||
|
var i = 0
|
||||||
|
for c in choices:
|
||||||
|
result.add tree("option", [("value", $i)], text(c))
|
||||||
|
inc i
|
||||||
|
|
||||||
|
proc select*(choices: openarray[(int, string)]): Element =
|
||||||
|
result = document.createElement("select")
|
||||||
|
for c in choices:
|
||||||
|
result.add tree("option", [("value", $c[0])], text(c[1]))
|
||||||
|
|
||||||
|
var radioCounter: int
|
||||||
|
|
||||||
|
proc radio*(choices: openarray[(int, string)]): Element =
|
||||||
|
result = document.createElement("fieldset")
|
||||||
|
var i = 0
|
||||||
|
inc radioCounter
|
||||||
|
for c in choices:
|
||||||
|
let id = "radio_" & c[1] & $i
|
||||||
|
var kid = tree("input", [("type", "radio"),
|
||||||
|
("id", id), ("name", "radio" & $radioCounter),
|
||||||
|
("value", $c[0])])
|
||||||
|
if i == 0:
|
||||||
|
kid.setAttribute("checked", "checked")
|
||||||
|
var lab = tree("label", [("for", id)], text(c[1]))
|
||||||
|
kid.add lab
|
||||||
|
result.add kid
|
||||||
|
inc i
|
||||||
|
|
||||||
|
proc tag*(name: string; id="", class=""): Element =
|
||||||
|
result = document.createElement(name)
|
||||||
|
if id.len > 0:
|
||||||
|
result.setAttribute("id", id)
|
||||||
|
if class.len > 0:
|
||||||
|
result.setAttribute("class", class)
|
||||||
|
|
||||||
|
proc tdiv*(id="", class=""): Element = tag("div", id, class)
|
||||||
|
proc span*(id="", class=""): Element = tag("span", id, class)
|
||||||
|
|
||||||
|
proc th*(s: string): Element =
|
||||||
|
result = tag("th")
|
||||||
|
result.add text(s)
|
||||||
|
|
||||||
|
proc td*(s: string): Element =
|
||||||
|
result = tag("td")
|
||||||
|
result.add text(s)
|
||||||
|
|
||||||
|
proc td*(s: Element): Element =
|
||||||
|
result = tag("td")
|
||||||
|
result.add s
|
||||||
|
|
||||||
|
proc td*(class: string; s: Element): Element =
|
||||||
|
result = tag("td")
|
||||||
|
result.add s
|
||||||
|
result.setAttribute("class", class)
|
||||||
|
|
||||||
|
proc table*(class="", kids: varargs[Element]): Element =
|
||||||
|
result = tag("table", "", class)
|
||||||
|
for k in kids: result.add k
|
||||||
|
|
||||||
|
proc tr*(kids: varargs[Element]): Element =
|
||||||
|
result = tag("tr")
|
||||||
|
for k in kids:
|
||||||
|
if k.nodeName == "TD" or k.nodeName == "TH":
|
||||||
|
result.add k
|
||||||
|
else:
|
||||||
|
result.add td(k)
|
||||||
|
|
||||||
|
proc setClass*(e: Element; value: string) =
|
||||||
|
e.setAttribute("class", value)
|
||||||
|
|
||||||
|
proc setAttr*(e: Element; key, value: cstring) =
|
||||||
|
e.setAttribute(key, value)
|
||||||
|
|
||||||
|
proc getAttr*(e: Element; key: cstring): cstring {.
|
||||||
|
importcpp: "#.getAttribute(#)", nodecl.}
|
||||||
|
|
||||||
|
proc realtimeInput*(id, val: string; changed: proc(value: cstring)): Element =
|
||||||
|
let oldElem = getElementById(id)
|
||||||
|
#if oldElem != nil: return oldElem
|
||||||
|
let newVal = if oldElem.isNil: val else: $oldElem.value
|
||||||
|
var timer: Timeout
|
||||||
|
proc wrapper() =
|
||||||
|
changed(getElementById(id).value)
|
||||||
|
redraw()
|
||||||
|
proc onkeyup(ev: Event) =
|
||||||
|
if timer != nil: clearTimeout(timer)
|
||||||
|
timer = setTimeout(wrapper, 400)
|
||||||
|
result = tree("input", [("type", "text"),
|
||||||
|
("value", newVal),
|
||||||
|
("id", id)])
|
||||||
|
result.addEventListener("keyup", onkeyup)
|
||||||
|
|
||||||
|
proc ajax(meth, url: cstring; headers: openarray[(string, string)];
|
||||||
|
data: cstring;
|
||||||
|
cont: proc (httpStatus: int; response: cstring)) =
|
||||||
|
proc setRequestHeader(a, b: cstring) {.importc: "ajax.setRequestHeader".}
|
||||||
|
{.emit: """
|
||||||
|
var ajax = new XMLHttpRequest();
|
||||||
|
ajax.open(`meth`,`url`,true);""".}
|
||||||
|
for a, b in items(headers):
|
||||||
|
setRequestHeader(a, b)
|
||||||
|
{.emit: """
|
||||||
|
ajax.onreadystatechange = function(){
|
||||||
|
if(this.readyState == 4){
|
||||||
|
if(this.status == 200){
|
||||||
|
`cont`(this.status, this.responseText);
|
||||||
|
} else {
|
||||||
|
`cont`(this.status, this.statusText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ajax.send(`data`);
|
||||||
|
""".}
|
||||||
|
|
||||||
|
proc ajaxPut*(url: string; headers: openarray[(string, string)];
|
||||||
|
data: cstring;
|
||||||
|
cont: proc (httpStatus: int, response: cstring)) =
|
||||||
|
ajax("PUT", url, headers, data, cont)
|
||||||
|
|
||||||
|
proc ajaxGet*(url: string; headers: openarray[(string, string)];
|
||||||
|
cont: proc (httpStatus: int, response: cstring)) =
|
||||||
|
ajax("GET", url, headers, nil, cont)
|
||||||
|
|
||||||
|
{.push stackTrace:off.}
|
||||||
|
|
||||||
|
proc setupErrorHandler*(useAlert=false) =
|
||||||
|
## Installs an error handler that transforms native JS unhandled
|
||||||
|
## exceptions into Nim based stack traces. If `useAlert` is false,
|
||||||
|
## the error message it put into the console, otherwise `alert`
|
||||||
|
## is called.
|
||||||
|
proc stackTraceAsCstring(): cstring = cstring(getStackTrace())
|
||||||
|
{.emit: """
|
||||||
|
window.onerror = function(msg, url, line, col, error) {
|
||||||
|
var x = "Error: " + msg + "\n" + `stackTraceAsCstring`()
|
||||||
|
if (`useAlert`)
|
||||||
|
alert(x);
|
||||||
|
else
|
||||||
|
console.log(x);
|
||||||
|
var suppressErrorAlert = true;
|
||||||
|
return suppressErrorAlert;
|
||||||
|
};""".}
|
||||||
|
|
||||||
|
{.pop.}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue