aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHayleigh Thompson <me@hayleigh.dev>2023-11-30 23:31:33 +0000
committerHayleigh Thompson <me@hayleigh.dev>2023-11-30 23:31:33 +0000
commit6423cce9fab80619cca2c3bd215cec49ea06613a (patch)
treedf6be654b13f6dbf3fa618cb6379635b8a0b8d66
parent2067b58c301d92c1ecbd5045fd1da2b89a9fe6d7 (diff)
downloadlustre-6423cce9fab80619cca2c3bd215cec49ea06613a.tar.gz
lustre-6423cce9fab80619cca2c3bd215cec49ea06613a.zip
:sparkles: Add undocumented ability to directly set an element's inner html.
-rw-r--r--src/lustre/attribute.gleam2
-rw-r--r--src/lustre/element.gleam120
-rw-r--r--src/runtime.ffi.mjs16
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];