diff options
author | Hayleigh Thompson <me@hayleigh.dev> | 2023-11-30 23:31:33 +0000 |
---|---|---|
committer | Hayleigh Thompson <me@hayleigh.dev> | 2023-11-30 23:31:33 +0000 |
commit | 6423cce9fab80619cca2c3bd215cec49ea06613a (patch) | |
tree | df6be654b13f6dbf3fa618cb6379635b8a0b8d66 | |
parent | 2067b58c301d92c1ecbd5045fd1da2b89a9fe6d7 (diff) | |
download | lustre-6423cce9fab80619cca2c3bd215cec49ea06613a.tar.gz lustre-6423cce9fab80619cca2c3bd215cec49ea06613a.zip |
:sparkles: Add undocumented ability to directly set an element's inner html.
-rw-r--r-- | src/lustre/attribute.gleam | 2 | ||||
-rw-r--r-- | src/lustre/element.gleam | 120 | ||||
-rw-r--r-- | src/runtime.ffi.mjs | 16 |
3 files changed, 98 insertions, 40 deletions
diff --git a/src/lustre/attribute.gleam b/src/lustre/attribute.gleam index 65cbb16..9531938 100644 --- a/src/lustre/attribute.gleam +++ b/src/lustre/attribute.gleam @@ -64,6 +64,7 @@ pub fn to_string(attr: Attribute(msg)) -> String { /// pub fn to_string_parts(attr: Attribute(msg)) -> Result(#(String, String), Nil) { case attr { + Attribute("dangerous-unescaped-html", _, _) -> Error(Nil) Attribute(name, value, as_property) -> { case dynamic.classify(value) { "String" -> Ok(#(name, dynamic.unsafe_coerce(value))) @@ -91,6 +92,7 @@ pub fn to_string_parts(attr: Attribute(msg)) -> Result(#(String, String), Nil) { /// pub fn to_string_builder(attr: Attribute(msg)) -> StringBuilder { case attr { + Attribute("dangerous-unescaped-html", _, _) -> string_builder.new() Attribute(name, value, as_property) -> { case dynamic.classify(value) { "String" -> diff --git a/src/lustre/element.gleam b/src/lustre/element.gleam index 928c526..25b84b4 100644 --- a/src/lustre/element.gleam +++ b/src/lustre/element.gleam @@ -92,8 +92,7 @@ pub fn to_string_builder(element: Element(msg)) -> StringBuilder { to_string_builder_helper(element, False) } -/// -pub fn to_string_builder_helper( +fn to_string_builder_helper( element: Element(msg), raw_text: Bool, ) -> StringBuilder { @@ -114,67 +113,108 @@ pub fn to_string_builder_helper( | Element("param" as tag, attrs, _) | Element("source" as tag, attrs, _) | Element("track" as tag, attrs, _) - | Element("wbr" as tag, attrs, _) -> - string_builder.from_string("<" <> tag) - |> attrs_to_string_builder(attrs) + | Element("wbr" as tag, attrs, _) -> { + let html = string_builder.from_string("<" <> tag) + let #(attrs, _) = attrs_to_string_builder(attrs) + + html + |> string_builder.append_builder(attrs) |> string_builder.append(">") + } Element("style" as tag, attrs, children) - | Element("script" as tag, attrs, children) -> - string_builder.from_string("<" <> tag) - |> attrs_to_string_builder(attrs) + | Element("script" as tag, attrs, children) -> { + let html = string_builder.from_string("<" <> tag) + let #(attrs, _) = attrs_to_string_builder(attrs) + + html + |> string_builder.append_builder(attrs) |> string_builder.append(">") |> children_to_string_builder(children, True) |> string_builder.append("</" <> tag <> ">") + } - Element(tag, attrs, children) -> - string_builder.from_string("<" <> tag) - |> attrs_to_string_builder(attrs) - |> string_builder.append(">") - |> children_to_string_builder(children, raw_text) - |> string_builder.append("</" <> tag <> ">") + Element(tag, attrs, children) -> { + let html = string_builder.from_string("<" <> tag) + let #(attrs, inner_html) = attrs_to_string_builder(attrs) + + case inner_html { + "" -> + html + |> string_builder.append_builder(attrs) + |> string_builder.append(">") + |> children_to_string_builder(children, raw_text) + |> string_builder.append("</" <> tag <> ">") + _ -> + html + |> string_builder.append_builder(attrs) + |> string_builder.append(">" <> inner_html <> "</" <> tag <> ">") + } + } - ElementNs(tag, attrs, children, namespace) -> - string_builder.from_string("<" <> tag) - |> attrs_to_string_builder(attrs) - |> string_builder.append(" xmlns=\"" <> namespace <> "\"") - |> string_builder.append(">") - |> children_to_string_builder(children, raw_text) - |> string_builder.append("</" <> tag <> ">") + ElementNs(tag, attrs, children, namespace) -> { + let html = string_builder.from_string("<" <> tag) + let #(attrs, inner_html) = attrs_to_string_builder(attrs) + + case inner_html { + "" -> + html + |> string_builder.append_builder(attrs) + |> string_builder.append(" xmlns=\"" <> namespace <> "\"") + |> string_builder.append(">") + |> children_to_string_builder(children, raw_text) + |> string_builder.append("</" <> tag <> ">") + _ -> + html + |> string_builder.append_builder(attrs) + |> string_builder.append(" xmlns=\"" <> namespace <> "\"") + |> string_builder.append(">" <> inner_html <> "</" <> tag <> ">") + } + } } } fn attrs_to_string_builder( - html: StringBuilder, attrs: List(Attribute(msg)), -) -> StringBuilder { - let #(html, class, style) = { - use #(html, class, style), attr <- list.fold(attrs, #(html, "", "")) +) -> #(StringBuilder, String) { + let #(html, class, style, inner_html) = { + let init = #(string_builder.new(), "", "", "") + use #(html, class, style, inner_html), attr <- list.fold(attrs, init) case attribute.to_string_parts(attr) { - Ok(#("class", val)) if class == "" -> #(html, val, style) - Ok(#("class", val)) -> #(html, class <> " " <> val, style) - Ok(#("style", val)) if style == "" -> #(html, class, val) - Ok(#("style", val)) -> #(html, class, style <> " " <> val) + Ok(#("dangerous-unescaped-html", val)) -> #( + html, + class, + style, + inner_html <> val, + ) + Ok(#("class", val)) if class == "" -> #(html, val, style, inner_html) + Ok(#("class", val)) -> #(html, class <> " " <> val, style, inner_html) + Ok(#("style", val)) if style == "" -> #(html, class, val, inner_html) + Ok(#("style", val)) -> #(html, class, style <> " " <> val, inner_html) Ok(#(key, val)) -> #( string_builder.append(html, " " <> key <> "=\"" <> val <> "\""), class, style, + inner_html, ) - Error(_) -> #(html, class, style) + Error(_) -> #(html, class, style, inner_html) } } - case class, style { - "", "" -> html - _, "" -> string_builder.append(html, " class=\"" <> class <> "\"") - "", _ -> string_builder.append(html, " style=\"" <> style <> "\"") - _, _ -> - string_builder.append( - html, - " class=\"" <> class <> "\" style=\"" <> style <> "\"", - ) - } + #( + case class, style { + "", "" -> html + _, "" -> string_builder.append(html, " class=\"" <> class <> "\"") + "", _ -> string_builder.append(html, " style=\"" <> style <> "\"") + _, _ -> + string_builder.append( + html, + " class=\"" <> class <> "\" style=\"" <> style <> "\"", + ) + }, + inner_html, + ) } fn children_to_string_builder( diff --git a/src/runtime.ffi.mjs b/src/runtime.ffi.mjs index 03e741a..45b05bf 100644 --- a/src/runtime.ffi.mjs +++ b/src/runtime.ffi.mjs @@ -42,11 +42,15 @@ function createElement(prev, curr, ns, dispatch, parent = null) { el.$lustre = {}; let attr = curr[1]; + let dangerousUnescapedHtml = ""; + while (attr.head) { if (attr.head[0] === "class") { morphAttr(el, attr.head[0], `${el.className} ${attr.head[1]}`); } else if (attr.head[0] === "style") { morphAttr(el, attr.head[0], `${el.style.cssText} ${attr.head[1]}`); + } else if (attr.head[0] === "dangerous-unescaped-html") { + dangerousUnescapedHtml += attr.head[1]; } else { morphAttr(el, attr.head[0], attr.head[1], dispatch); } @@ -73,6 +77,8 @@ function createElement(prev, curr, ns, dispatch, parent = null) { el.appendChild(morph(null, child.head, dispatch, el)); child = child.tail; } + } else if (dangerousUnescapedHtml) { + el.innerHTML = dangerousUnescapedHtml; } else { let child = curr[2]; while (child.head) { @@ -106,6 +112,14 @@ function morphElement(prev, curr, ns, dispatch, parent) { currAttr.head[0], `${currAttrs.get("style")} ${currAttr.head[1]}` ); + } else if ( + currAttr.head[0] === "dangerous-unescaped-html" && + currAttrs.has("dangerous-unescaped-html") + ) { + currAttrs.set( + currAttr.head[0], + `${currAttrs.get("dangerous-unescaped-html")} ${currAttr.head[1]}` + ); } else { currAttrs.set(currAttr.head[0], currAttr.head[1]); } @@ -159,6 +173,8 @@ function morphElement(prev, curr, ns, dispatch, parent) { prev.appendChild(morph(null, currChild.head, dispatch, prev)); currChild = currChild.tail; } + } else if (currAttrs.has("dangerous-unescaped-html")) { + prev.innerHTML = currAttrs.get("dangerous-unescaped-html"); } else { let prevChild = prev.firstChild; let currChild = curr[2]; |