about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/html/dom.nim78
-rw-r--r--src/js/javascript.nim20
-rw-r--r--src/utils/twtstr.nim30
3 files changed, 96 insertions, 32 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 941d3ce7..bd8d735a 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -145,6 +145,9 @@ type
     element: Element
     localName: string
 
+  DOMStringMap = object
+    target {.cursor.}: HTMLElement
+
   Node* = ref object of EventTarget
     nodeType*: NodeType
     childList*: seq[Node]
@@ -242,6 +245,7 @@ type
     element: Element
 
   HTMLElement* = ref object of Element
+    dataset {.jsget.}: DOMStringMap
 
   FormAssociatedElement* = ref object of HTMLElement
     parserInserted*: bool
@@ -417,6 +421,7 @@ jsDestructor(Location)
 jsDestructor(Document)
 jsDestructor(DOMImplementation)
 jsDestructor(DOMTokenList)
+jsDestructor(DOMStringMap)
 jsDestructor(Comment)
 jsDestructor(CDATASection)
 jsDestructor(DocumentFragment)
@@ -1152,6 +1157,56 @@ func value(tokenList: DOMTokenList): string {.jsfget.} =
 func getter(tokenList: DOMTokenList, i: int): Option[string] {.jsgetprop.} =
   return tokenList.item(i)
 
+# DOMStringMap
+func validateAttributeName(name: string, isq: static bool = false):
+    Err[DOMException] =
+  when isq:
+    if name.matchNameProduction():
+      return ok()
+  else:
+    if name.matchQNameProduction():
+      return ok()
+  return errDOMException("Invalid character in attribute name",
+    "InvalidCharacterError")
+
+func hasprop(map: ptr DOMStringMap, name: string): bool {.jshasprop.} =
+  return "data-" & name in map[].target.attrs
+
+proc delete(map: ptr DOMStringMap, name: string): bool {.jsfunc.} =
+  let name = "data-" & name.camelToKebabCase()
+  let res = name in map[].target.attrs
+  map[].target.attrs.del(name)
+  return res
+
+func getter(map: ptr DOMStringMap, name: string): Option[string]
+    {.jsgetprop.} =
+  let name = "data-" & name.camelToKebabCase()
+  map[].target.attrs.withValue(name, p):
+    return some(p[])
+  return none(string)
+
+proc setter(map: ptr DOMStringMap, name, value: string): Err[DOMException]
+    {.jssetprop.} =
+  var washy = false
+  for c in name:
+    if not washy or c notin AsciiLowerAlpha:
+      washy = c == '-'
+      continue
+    return errDOMException("Lower case after hyphen is not allowed in dataset",
+      "InvalidCharacterError")
+  let name = "data-" & name.camelToKebabCase()
+  ?name.validateAttributeName()
+  map.target.attr(name, value)
+  return ok()
+
+func names(ctx: JSContext, map: ptr DOMStringMap): JSPropertyEnumList
+    {.jspropnames.} =
+  var list = newJSPropertyEnumList(ctx, uint32(map[].target.attrs.len))
+  for k, v in map[].target.attrs:
+    if k.startsWith("data-") and AsciiUpperAlpha notin k:
+      list.add(k["data-".len .. ^1].kebabToCamelCase())
+  return list
+
 # NodeList
 func length(nodeList: NodeList): uint32 {.jsfget.} =
   return uint32(nodeList.len)
@@ -1712,15 +1767,17 @@ func getElementsByClassName0(node: Node, classNames: string): HTMLCollection =
   var classes = classNames.split(AsciiWhitespace)
   let isquirks = node.document.mode == QUIRKS
   if isquirks:
-    for i in 0 .. classes.high:
-      classes[i].mtoLowerAscii()
+    for class in classes.mitems:
+      for c in class.mitems:
+        c = c.toLowerAscii()
   return newCollection[HTMLCollection](node,
     func(node: Node): bool =
       if node.nodeType == ELEMENT_NODE:
         if isquirks:
           var cl = Element(node).classList
-          for i in 0 .. cl.toks.high:
-            cl.toks[i].mtoLowerAscii()
+          for tok in cl.toks.mitems:
+            for c in tok.mitems:
+              c = c.toLowerAscii()
           for class in classes:
             if class notin cl:
               return false
@@ -2157,6 +2214,7 @@ func newHTMLElement*(document: Document, tagType: TagType,
   result.attributes = NamedNodeMap(element: result)
   result.classList = DOMTokenList(element: result, localName: "classList")
   result.index = -1
+  result.dataset = DOMStringMap(target: result)
   {.cast(noSideEffect).}:
     for k, v in attrs:
       result.attr(k, v)
@@ -2413,17 +2471,6 @@ proc attrulgz(element: Element, name: string, value: uint32) =
   if value > 0:
     element.attrul(name, value)
 
-func validateAttributeName(name: string, isq: static bool = false):
-    Err[DOMException] =
-  when isq:
-    if name.matchNameProduction():
-      return ok()
-  else:
-    if name.matchQNameProduction():
-      return ok()
-  return errDOMException("Invalid character in attribute name",
-    "InvalidCharacterError")
-
 proc setAttribute(element: Element, qualifiedName, value: string):
     Err[DOMException] {.jsfunc.} =
   ?validateAttributeName(qualifiedName)
@@ -3483,6 +3530,7 @@ proc addDOMModule*(ctx: JSContext) =
   ctx.registerType(Document, parent = nodeCID)
   ctx.registerType(DOMImplementation)
   ctx.registerType(DOMTokenList)
+  ctx.registerType(DOMStringMap)
   let characterDataCID = ctx.registerType(CharacterData, parent = nodeCID)
   ctx.registerType(Comment, parent = characterDataCID)
   ctx.registerType(CDATASection, parent = characterDataCID)
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index d3de6508..124eab5d 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -873,7 +873,7 @@ proc setupGenerator(fun: NimNode, t: BoundFunctionType,
 proc makeJSCallAndRet(gen: var JSFuncGenerator, okstmt, errstmt: NimNode) =
   let jfcl = gen.jsFunCallList
   let dl = gen.dielabel
-  gen.jsCallAndRet = if gen.returnType.issome:
+  gen.jsCallAndRet = if gen.returnType.isSome:
     quote do:
       block `dl`:
         return ctx.toJS(`jfcl`)
@@ -962,11 +962,19 @@ macro jssetprop*(fun: typed) =
   gen.finishFunCallList()
   let jfcl = gen.jsFunCallList
   let dl = gen.dielabel
-  gen.jsCallAndRet = quote do:
-    block `dl`:
-      `jfcl`
-      return cint(1)
-    return cint(-1)
+  gen.jsCallAndRet = if gen.returnType.isSome:
+    quote do:
+      block `dl`:
+        let v = toJS(ctx, `jfcl`)
+        if not JS_IsException(v):
+          return cint(1)
+      return cint(-1)
+  else:
+    quote do:
+      block `dl`:
+        `jfcl`
+        return cint(1)
+      return cint(-1)
   let jsProc = gen.newJSProc(getJSSetPropParams(), false)
   gen.registerFunction()
   return newStmtList(fun, jsProc)
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index 2756f2de..8372e588 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -67,17 +67,13 @@ const controlLetterMap = genControlLetterMap()
 func getControlLetter*(c: char): char =
   return controlLetterMap[int(c)]
 
-proc mtoLowerAscii*(str: var string) =
-  for i in 0 ..< str.len:
-    str[i] = str[i].toLowerAscii()
-
 func toHeaderCase*(str: string): string =
   result = str
   var flip = true
-  for i in 0..str.high:
+  for c in result.mitems:
     if flip:
-      result[i] = result[i].toUpperAscii()
-    flip = result[i] == '-'
+      c = c.toUpperAscii()
+    flip = c == '-'
 
 func toScreamingSnakeCase*(str: string): string = # input is camel case
   if str.len >= 1: result &= str[0].toUpperAscii()
@@ -94,10 +90,22 @@ func snakeToKebabCase*(str: string): string =
     if c == '_':
       c = '-'
 
-func normalizeLocale*(s: string): string =
-  for i in 0 ..< s.len:
-    if cast[uint8](s[i]) > 0x20 and s[i] != '_' and s[i] != '-':
-      result &= s[i].toLowerAscii()
+func kebabToCamelCase*(s: string): string =
+  result = s
+  var flip = false
+  for c in result.mitems:
+    if flip:
+      c = c.toUpperAscii()
+    flip = c == '-'
+
+func camelToKebabCase*(s: string): string =
+  result = ""
+  for c in s:
+    if c in AsciiUpperAlpha:
+      result &= '-'
+      result &= c.toLowerAscii()
+    else:
+      result &= c
 
 func isAscii*(r: Rune): bool =
   return cast[uint32](r) < 128