diff --git a/experiments/trello/knete.nim b/experiments/trello/knete.nim new file mode 100644 index 0000000..2fd0d05 --- /dev/null +++ b/experiments/trello/knete.nim @@ -0,0 +1,752 @@ +## Raw DOM manipulation. No DOM diff'ing, no cry. + +import macros, tables +from strutils import startsWith, toLowerAscii, toUpperAscii + +when defined(js): + type kstring* = cstring +else: + type kstring* = string + +type + Tag* {.pure.} = enum + text = "#text", + + html, head, title, base, link, meta, style, + script, noscript, + body, section, nav, article, aside, + h1, h2, h3, h4, h5, h6, + header, footer, address, main + + p, hr, pre, blockquote, ol, ul, li, + dl, dt, dd, + figure, figcaption, + + tdiv = "div", + + a, em, strong, small, + strikethrough = "s", cite, quote, + dfn, abbr, data, time, code, `var` = "var", samp, + kdb, sub, sup, italic = "i", bold = "b", underlined = "u", + mark, ruby, rt, rp, bdi, dbo, span, br, wbr, + ins, del, img, iframe, embed, `object` = "object", + param, video, audio, source, track, canvas, map, + area, svg, math, + + table, caption, colgroup, col, tbody, thead, + tfoot, tr, td, th, + + form, fieldset, legend, label, input, button, + select, datalist, optgroup, option, textarea, + keygen, output, progress, meter, + details, summary, command, menu + +type + EventKind* {.pure.} = enum ## The events supported by the virtual DOM. + onclick, ## An element is clicked. + oncontextmenu, ## An element is right-clicked. + ondblclick, ## An element is double clicked. + onkeyup, ## A key was released. + onkeydown, ## A key is pressed. + onkeypressed, # A key was pressed. + onfocus, ## An element got the focus. + onblur, ## An element lost the focus. + onchange, ## The selected value of an element was changed. + onscroll, ## The user scrolled within an element. + + onmousedown, ## A pointing device button (usually a mouse) is pressed + ## on an element. + onmouseenter, ## A pointing device is moved onto the element that + ## has the listener attached. + onmouseleave, ## A pointing device is moved off the element that + ## has the listener attached. + onmousemove, ## A pointing device is moved over an element. + onmouseout, ## A pointing device is moved off the element that + ## has the listener attached or off one of its children. + onmouseover, ## A pointing device is moved onto the element that has + ## the listener attached or onto one of its children. + onmouseup, ## A pointing device button is released over an element. + + ondrag, ## An element or text selection is being dragged (every 350ms). + ondragend, ## A drag operation is being ended (by releasing a mouse button + ## or hitting the escape key). + ondragenter, ## A dragged element or text selection enters a valid drop target. + ondragleave, ## A dragged element or text selection leaves a valid drop target. + ondragover, ## An element or text selection is being dragged over a valid + ## drop target (every 350ms). + ondragstart, ## The user starts dragging an element or text selection. + ondrop, ## An element is dropped on a valid drop target. + + onsubmit, ## A form is submitted + oninput, ## An input value changes + + onkeyupenter, ## vdom extension: an input field received the ENTER key press + onkeyuplater ## vdom extension: a key was pressed and some time + ## passed (useful for on-the-fly text completions) + + +macro buildLookupTables(): untyped = + var a = newTree(nnkBracket) + for i in low(Tag)..high(Tag): + let x = $i + let y = if x[0] == '#': x else: toUpperAscii(x) + a.add(newCall("kstring", newLit(y))) + var e = newTree(nnkBracket) + for i in low(EventKind)..high(EventKind): + e.add(newCall("kstring", newLit(substr($i, 2)))) + + template tmpl(a, e) {.dirty.} = + const + toTagName*: array[Tag, kstring] = a + toEventName*: array[EventKind, kstring] = e + + result = getAst tmpl(a, e) + +buildLookupTables() + +# ---------------- CSS ---------------------------------------------- + +type + StyleAttr* {.pure.} = enum + ## The style attributes supported by the virtual DOM. + ## Reference: https://www.w3schools.com/jsref/dom_obj_style.asp + alignContent + alignItems + alignSelf + animation + animationDelay + animationDirection + animationDuration + animationFillMode + animationIterationCount + animationName + animationTimingFunction + animationPlayState + background + backgroundAttachment + backgroundColor + backgroundImage + backgroundPosition + backgroundRepeat + backgroundClip + backgroundOrigin + backgroundSize + backfaceVisibility + border + borderBottom + borderBottomColor + borderBottomLeftRadius + borderBottomRightRadius + borderBottomStyle + borderBottomWidth + borderCollapse + borderColor + borderImage + borderImageOutset + borderImageRepeat + borderImageSlice + borderImageSource + borderImageWidth + borderLeft + borderLeftColor + borderLeftStyle + borderLeftWidth + borderRadius + borderRight + borderRightColor + borderRightStyle + borderRightWidth + borderSpacing + borderStyle + borderTop + borderTopColor + borderTopLeftRadius + borderTopRightRadius + borderTopStyle + borderTopWidth + borderWidth + bottom + boxDecorationBreak + boxShadow + boxSizing + captionSide + clear + clip + color + columnCount + columnFill + columnGap + columnRule + columnRuleColor + columnRuleStyle + columnRuleWidth + columns + columnSpan + columnWidth + content + counterIncrement + counterReset + cursor + direction + display + emptyCells + filter + flex + flexBasis + flexDirection + flexFlow + flexGrow + flexShrink + flexWrap + cssFloat + font + fontFamily + fontSize + fontSizeAdjust + fontStretch + fontStyle + fontVariant + fontWeight + hangingPunctuation + height + hyphens + icon + imageOrientation + justifyContent + left + letterSpacing + lineHeight + listStyle + listStyleImage + listStylePosition + listStyleType + margin + marginBottom + marginLeft + marginRight + marginTop + maxHeight + maxWidth + minHeight + minWidth + navDown + navIndex + navLeft + navRight + navUp + opacity + order + orphans + outline + outlineColor + outlineOffset + outlineStyle + outlineWidth + overflow + overflowX + overflowY + padding + paddingBottom + paddingLeft + paddingRight + paddingTop + pageBreakAfter + pageBreakBefore + pageBreakInside + perspective + perspectiveOrigin + pointerEvents + position + quotes + resize + right + scrollbar3dLightColor # note: missing in w3schools reference + scrollbarArrowColor # note: missing in w3schools reference + scrollbarBaseColor # note: missing in w3schools reference + scrollbarDarkshadowColor # note: missing in w3schools reference + scrollbarFaceColor # note: missing in w3schools reference + scrollbarHighlightColor # note: missing in w3schools reference + scrollbarShadowColor # note: missing in w3schools reference + scrollbarTrackColor # note: missing in w3schools reference + tableLayout + tabSize + textAlign + textAlignLast + textDecoration + textDecorationColor + textDecorationLine + textDecorationStyle + textIndent + textJustify + textOverflow + textShadow + textTransform + top + transform + transformOrigin + transformStyle + transition + transitionDelay + transitionDuration + transitionProperty + transitionTimingFunction + unicodeBidi + userSelect + verticalAlign + visibility + whiteSpace + width + wordBreak + wordSpacing + wordWrap + widows + zIndex + +macro buildStyleLookupTable(): untyped = + var e = newTree(nnkBracket) + for i in low(StyleAttr)..high(StyleAttr): + e.add(newCall("kstring", newLit($i))) + template tmpl(e) {.dirty.} = + const + toStyleAttrName: array[StyleAttr, kstring] = e + result = getAst tmpl(e) + +buildStyleLookupTable() + +type + StyleObj {.importc.} = object + background*: cstring + backgroundAttachment*: cstring + backgroundColor*: cstring + backgroundImage*: cstring + backgroundPosition*: cstring + backgroundRepeat*: cstring + border*: cstring + borderBottom*: cstring + borderBottomColor*: cstring + borderBottomStyle*: cstring + borderBottomWidth*: cstring + borderColor*: cstring + borderLeft*: cstring + borderLeftColor*: cstring + borderLeftStyle*: cstring + borderLeftWidth*: cstring + borderRight*: cstring + borderRightColor*: cstring + borderRightStyle*: cstring + borderRightWidth*: cstring + borderStyle*: cstring + borderTop*: cstring + borderTopColor*: cstring + borderTopStyle*: cstring + borderTopWidth*: cstring + borderWidth*: cstring + bottom*: cstring + captionSide*: cstring + clear*: cstring + clip*: cstring + color*: cstring + cursor*: cstring + direction*: cstring + display*: cstring + emptyCells*: cstring + cssFloat*: cstring + font*: cstring + fontFamily*: cstring + fontSize*: cstring + fontStretch*: cstring + fontStyle*: cstring + fontVariant*: cstring + fontWeight*: cstring + height*: cstring + left*: cstring + letterSpacing*: cstring + lineHeight*: cstring + listStyle*: cstring + listStyleImage*: cstring + listStylePosition*: cstring + listStyleType*: cstring + margin*: cstring + marginBottom*: cstring + marginLeft*: cstring + marginRight*: cstring + marginTop*: cstring + maxHeight*: cstring + maxWidth*: cstring + minHeight*: cstring + minWidth*: cstring + overflow*: cstring + padding*: cstring + paddingBottom*: cstring + paddingLeft*: cstring + paddingRight*: cstring + paddingTop*: cstring + pageBreakAfter*: cstring + pageBreakBefore*: cstring + pointerEvents*: cstring + position*: cstring + right*: cstring + scrollbar3dLightColor*: cstring + scrollbarArrowColor*: cstring + scrollbarBaseColor*: cstring + scrollbarDarkshadowColor*: cstring + scrollbarFaceColor*: cstring + scrollbarHighlightColor*: cstring + scrollbarShadowColor*: cstring + scrollbarTrackColor*: cstring + tableLayout*: cstring + textAlign*: cstring + textDecoration*: cstring + textIndent*: cstring + textTransform*: cstring + transform*: cstring + top*: cstring + verticalAlign*: cstring + visibility*: cstring + width*: cstring + wordSpacing*: cstring + zIndex*: int + Style* = ref StyleObj + + ElementObj {.importc.} = object + id*, class*, value*: cstring + disabled*: bool + style*: Style + Element* = ref ElementObj + + DocumentObj {.importc.} = object + Document = ref DocumentObj + +proc setStyle(s: Style; key, val: cstring) {.importcpp: "#[#] = #", noSideEffect.} + +proc applyStyles*(e: Element; pairs: openArray[(StyleAttr, kstring)]) = + for x in pairs: + e.style.setStyle(toStyleAttrName[x[0]], x[1]) + +# ------- Tree manipulation --------------------------- + +var document {.importc.}: Document + +proc parent*(x: Element): Element {.importcpp: "#.parentNode".} + +proc up*(x: Element; className: cstring): Element = + result = x + while result != nil and result.class != className: + result = result.parent + +proc len*(x: Element): int {.importcpp: "#.childNodes.length".} +proc `[]`*(x: Element; idx: int): Element {.importcpp: "#.childNodes[#]".} +proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.} +proc add*(n, child: Element) {.importcpp: "appendChild".} + +proc removeChild(n, child: Element) {.importcpp.} +proc replaceChild(n, newNode, oldNode: Element) {.importcpp.} + +proc replace*(self, by: Element) = replaceChild(self.parent, by, self) +proc delete*(self: Element) = removeChild(self.parent, self) + +proc insertBefore(n, newNode, before: Element) {.importcpp.} +proc insert*(before, newNode: Element) = insertBefore(before.parent, newNode, before) + +proc createTextNode(d: Document, text: cstring): Element {.importcpp.} +proc text*(s: kstring): Element = createTextNode(document, s) + +iterator items*(n: Element): Element = + for i in 0.. 0: + result[L-1] = tcall2(result[L-1], tmpContext) + of nnkStmtList, nnkStmtListExpr, nnkWhenStmt, nnkIfStmt, nnkTryStmt, + nnkFinally: + # recurse for every child: + result = copyNimNode(n) + for x in n: + result.add tcall2(x, tmpContext) + of nnkCaseStmt: + # recurse for children, but don't add call for case ident + result = copyNimNode(n) + result.add n[0] + for i in 1 ..< n.len: + result.add tcall2(n[i], tmpContext) + of nnkProcDef: + let name = getName n[0] + if name.startsWith"on": + # turn it into an anon proc: + let anon = copyNimTree(n) + anon[0] = newEmptyNode() + if tmpContext == nil: + error "no Element to attach the event handler to" + else: + result = newCall(evHandler(), tmpContext, + newDotExpr(bindSym"EventKind", n[0]), anon, ident("kxi")) + else: + result = n + of nnkVarSection, nnkLetSection, nnkConstSection: + result = n + of nnkCallKinds - {nnkInfix}: + let op = getName(n[0]) + if isComponent(op) == ComponentKind.Tag: + let tmp = genSym(nskLet, "tmp") + let call = newCall(bindSym"tree", newDotExpr(bindSym"Tag", n[0])) + result = newTree( + if tmpContext == nil: nnkStmtListExpr else: nnkStmtList, + newLetStmt(tmp, call)) + for i in 1 ..< n.len: + # named parameters are transformed into attributes or events: + let x = n[i] + if x.kind == nnkExprEqExpr: + let key = getName x[0] + if key.startsWith("on"): + result.add newCall(evHandler(), + tmp, newDotExpr(bindSym"EventKind", x[0]), x[1]) + elif key in SpecialAttrs: + result.add newDotAsgn(tmp, key, x[1]) + elif eqIdent(key, "style"): + result.add newCall(bindSym"applyStyles", tmp, toKstring x[1]) + elif eqIdent(key, "setFocus"): + result.add newCall(bindSym"focus", tmp) + else: + result.add newCall(bindSym"setAttr", tmp, newLit(key), x[1]) + elif eqIdent(x, "setFocus"): + result.add newCall(bindSym"focus", tmp) + else: + result.add tcall2(x, tmp) + if tmpContext == nil: + result.add tmp + else: + result.add newCall(bindSym"add", tmpContext, tmp) + elif tmpContext != nil and op notin StmtContext: + var hasEventHandlers = false + for i in 1..