parent
1823e5ecb9
commit
270e3b52e1
|
@ -10,7 +10,8 @@ use rocket::response::{Responder, Result};
|
||||||
use rocket::{get, routes, Request, Response};
|
use rocket::{get, routes, Request, Response};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use typed_html::types::LinkType;
|
use typed_html::types::LinkType;
|
||||||
use typed_html::{dom::DOMTree, html, text};
|
use typed_html::elements::FlowContent;
|
||||||
|
use typed_html::{dom::DOMTree, html, text, OutputType};
|
||||||
|
|
||||||
struct Html(DOMTree<String>);
|
struct Html(DOMTree<String>);
|
||||||
|
|
||||||
|
@ -24,17 +25,32 @@ impl<'r> Responder<'r> for Html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
// Function that wraps a DOM node in an HTML document, to demonstrate how you'd
|
||||||
fn index() -> Html {
|
// do this sort of templating.
|
||||||
let a = false;
|
//
|
||||||
Html(html!(
|
// It's a bit more complicated than you'd hope because you need to take an input
|
||||||
|
// argument of the type that the element that you're inserting it into expects,
|
||||||
|
// which in the case of `<body>` is `FlowContent`, not just `Node`, so you can't
|
||||||
|
// pass it a `DOMTree<T>` or you'll get a type error.
|
||||||
|
fn doc<T: OutputType + 'static>(tree: Box<dyn FlowContent<T>>) -> DOMTree<T> {
|
||||||
|
html!(
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>"Hello Kitty!"</title>
|
<title>"Hello Kitty!"</title>
|
||||||
<link rel=LinkType::StyleSheet href="lol.css"/>
|
<link rel=LinkType::StyleSheet href="lol.css"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div></div>
|
{ tree }
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> Html {
|
||||||
|
let a = false;
|
||||||
|
Html(doc(html!(
|
||||||
|
<div>
|
||||||
<h1 data-lol="omg">"Hello Kitty!"</h1>
|
<h1 data-lol="omg">"Hello Kitty!"</h1>
|
||||||
<p class="official-position-of-sanrio-ltd emphasis">
|
<p class="official-position-of-sanrio-ltd emphasis">
|
||||||
"She is not a "<em><a href="https://en.wikipedia.org/wiki/Cat">"cat"</a></em>". She is a "<em>"human girl"</em>"."
|
"She is not a "<em><a href="https://en.wikipedia.org/wiki/Cat">"cat"</a></em>". She is a "<em>"human girl"</em>"."
|
||||||
|
@ -47,9 +63,8 @@ fn index() -> Html {
|
||||||
}
|
}
|
||||||
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
|
<p>"<img src=\"javascript:alert('pwned lol')\">"</p>
|
||||||
<button disabled=a onclick="alert('She is not a cat.')">"Click me!"</button>
|
<button disabled=a onclick="alert('She is not a cat.')">"Click me!"</button>
|
||||||
</body>
|
</div>
|
||||||
</html>
|
)))
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -73,6 +73,15 @@ pub trait Node<T: OutputType>: Display {
|
||||||
fn vnode(&mut self) -> VNode<T>;
|
fn vnode(&mut self) -> VNode<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for Box<dyn Node<T>> where T: OutputType {
|
||||||
|
type Item = Box<dyn Node<T>>;
|
||||||
|
type IntoIter = std::vec::IntoIter<Box<dyn Node<T>>>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
vec![self].into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Trait for querying a typed HTML element.
|
/// Trait for querying a typed HTML element.
|
||||||
///
|
///
|
||||||
/// All [HTML elements][elements] implement this.
|
/// All [HTML elements][elements] implement this.
|
||||||
|
|
|
@ -8,24 +8,43 @@ use types::*;
|
||||||
|
|
||||||
// Marker traits for element content groups
|
// Marker traits for element content groups
|
||||||
|
|
||||||
pub trait MetadataContent<T: OutputType>: Node<T> {}
|
macro_rules! marker_trait {
|
||||||
pub trait FlowContent<T: OutputType>: Node<T> {}
|
($trait:ident) => {
|
||||||
pub trait SectioningContent<T: OutputType>: Node<T> {}
|
marker_trait!($trait, Node);
|
||||||
pub trait HeadingContent<T: OutputType>: Node<T> {}
|
};
|
||||||
|
|
||||||
|
($trait:ident, $parent:ident) => {
|
||||||
|
pub trait $trait<T: OutputType>: $parent<T> {}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for Box<dyn $trait<T>> where T: OutputType {
|
||||||
|
type Item = Box<dyn $trait<T>>;
|
||||||
|
type IntoIter = std::vec::IntoIter<Box<dyn $trait<T>>>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
vec![self].into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
marker_trait!(MetadataContent);
|
||||||
|
marker_trait!(FlowContent);
|
||||||
|
marker_trait!(SectioningContent);
|
||||||
|
marker_trait!(HeadingContent);
|
||||||
// Phrasing content seems to be entirely a subclass of FlowContent
|
// Phrasing content seems to be entirely a subclass of FlowContent
|
||||||
pub trait PhrasingContent<T: OutputType>: FlowContent<T> {}
|
marker_trait!(PhrasingContent, FlowContent);
|
||||||
pub trait EmbeddedContent<T: OutputType>: Node<T> {}
|
marker_trait!(EmbeddedContent);
|
||||||
pub trait InteractiveContent<T: OutputType>: Node<T> {}
|
marker_trait!(InteractiveContent);
|
||||||
pub trait FormContent<T: OutputType>: Node<T> {}
|
marker_trait!(FormContent);
|
||||||
|
|
||||||
// Traits for elements that are more picky about their children
|
// Traits for elements that are more picky about their children
|
||||||
pub trait DescriptionListContent<T: OutputType>: Node<T> {}
|
marker_trait!(DescriptionListContent);
|
||||||
pub trait HGroupContent<T: OutputType>: Node<T> {}
|
marker_trait!(HGroupContent);
|
||||||
pub trait MapContent<T: OutputType>: Node<T> {}
|
marker_trait!(MapContent);
|
||||||
pub trait MediaContent<T: OutputType>: Node<T> {} // <audio> and <video>
|
marker_trait!(MediaContent); // <audio> and <video>
|
||||||
pub trait SelectContent<T: OutputType>: Node<T> {}
|
marker_trait!(SelectContent);
|
||||||
pub trait TableContent<T: OutputType>: Node<T> {}
|
marker_trait!(TableContent);
|
||||||
pub trait TableColumnContent<T: OutputType>: Node<T> {}
|
marker_trait!(TableColumnContent);
|
||||||
|
|
||||||
declare_elements!{
|
declare_elements!{
|
||||||
html {
|
html {
|
||||||
|
@ -79,11 +98,11 @@ declare_elements!{
|
||||||
article in [FlowContent, SectioningContent] with FlowContent;
|
article in [FlowContent, SectioningContent] with FlowContent;
|
||||||
aside in [FlowContent, SectioningContent] with FlowContent;
|
aside in [FlowContent, SectioningContent] with FlowContent;
|
||||||
audio {
|
audio {
|
||||||
autoplay: Bool,
|
autoplay: bool,
|
||||||
controls: Bool,
|
controls: bool,
|
||||||
crossorigin: CrossOrigin,
|
crossorigin: CrossOrigin,
|
||||||
loop: Bool,
|
loop: bool,
|
||||||
muted: Bool,
|
muted: bool,
|
||||||
preload: Preload,
|
preload: Preload,
|
||||||
src: Uri,
|
src: Uri,
|
||||||
} in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent;
|
} in [FlowContent, PhrasingContent, EmbeddedContent] with MediaContent;
|
||||||
|
@ -95,13 +114,13 @@ declare_elements!{
|
||||||
} in [FlowContent] with FlowContent;
|
} in [FlowContent] with FlowContent;
|
||||||
br in [FlowContent, PhrasingContent];
|
br in [FlowContent, PhrasingContent];
|
||||||
button {
|
button {
|
||||||
autofocus: Bool,
|
autofocus: bool,
|
||||||
disabled: Bool,
|
disabled: bool,
|
||||||
form: Id,
|
form: Id,
|
||||||
formaction: Uri,
|
formaction: Uri,
|
||||||
formenctype: FormEncodingType,
|
formenctype: FormEncodingType,
|
||||||
formmethod: FormMethod,
|
formmethod: FormMethod,
|
||||||
formnovalidate: Bool,
|
formnovalidate: bool,
|
||||||
formtarget: Target,
|
formtarget: Target,
|
||||||
name: Id,
|
name: Id,
|
||||||
type: ButtonType,
|
type: ButtonType,
|
||||||
|
@ -122,7 +141,7 @@ declare_elements!{
|
||||||
datetime: Datetime,
|
datetime: Datetime,
|
||||||
} in [FlowContent, PhrasingContent] with FlowContent;
|
} in [FlowContent, PhrasingContent] with FlowContent;
|
||||||
details {
|
details {
|
||||||
open: Bool,
|
open: bool,
|
||||||
} in [FlowContent, SectioningContent, InteractiveContent] with [summary] FlowContent;
|
} in [FlowContent, SectioningContent, InteractiveContent] with [summary] FlowContent;
|
||||||
dfn in [FlowContent, PhrasingContent] with PhrasingContent;
|
dfn in [FlowContent, PhrasingContent] with PhrasingContent;
|
||||||
div in [FlowContent] with FlowContent;
|
div in [FlowContent] with FlowContent;
|
||||||
|
@ -146,7 +165,7 @@ declare_elements!{
|
||||||
enctype: FormEncodingType,
|
enctype: FormEncodingType,
|
||||||
method: FormMethod,
|
method: FormMethod,
|
||||||
name: Id,
|
name: Id,
|
||||||
novalidate: Bool,
|
novalidate: bool,
|
||||||
target: Target,
|
target: Target,
|
||||||
} in [FlowContent] with FlowContent;
|
} in [FlowContent] with FlowContent;
|
||||||
h1 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent;
|
h1 in [FlowContent, HeadingContent, HGroupContent] with PhrasingContent;
|
||||||
|
@ -161,8 +180,8 @@ declare_elements!{
|
||||||
i in [FlowContent, PhrasingContent] with PhrasingContent;
|
i in [FlowContent, PhrasingContent] with PhrasingContent;
|
||||||
iframe {
|
iframe {
|
||||||
allow: FeaturePolicy,
|
allow: FeaturePolicy,
|
||||||
allowfullscreen: Bool,
|
allowfullscreen: bool,
|
||||||
allowpaymentrequest: Bool,
|
allowpaymentrequest: bool,
|
||||||
height: usize,
|
height: usize,
|
||||||
name: Id,
|
name: Id,
|
||||||
referrerpolicy: ReferrerPolicy,
|
referrerpolicy: ReferrerPolicy,
|
||||||
|
@ -176,7 +195,7 @@ declare_elements!{
|
||||||
crossorigin: CrossOrigin,
|
crossorigin: CrossOrigin,
|
||||||
decoding: ImageDecoding,
|
decoding: ImageDecoding,
|
||||||
height: usize,
|
height: usize,
|
||||||
ismap: Bool,
|
ismap: bool,
|
||||||
sizes: SpacedList<String>, // FIXME it's not really just a string
|
sizes: SpacedList<String>, // FIXME it's not really just a string
|
||||||
src: Uri,
|
src: Uri,
|
||||||
srcset: String, // FIXME this is much more complicated
|
srcset: String, // FIXME this is much more complicated
|
||||||
|
@ -185,12 +204,12 @@ declare_elements!{
|
||||||
} in [FlowContent, PhrasingContent, EmbeddedContent];
|
} in [FlowContent, PhrasingContent, EmbeddedContent];
|
||||||
input {
|
input {
|
||||||
autocomplete: String,
|
autocomplete: String,
|
||||||
autofocus: Bool,
|
autofocus: bool,
|
||||||
disabled: Bool,
|
disabled: bool,
|
||||||
form: Id,
|
form: Id,
|
||||||
list: Id,
|
list: Id,
|
||||||
name: Id,
|
name: Id,
|
||||||
required: Bool,
|
required: bool,
|
||||||
tabindex: usize,
|
tabindex: usize,
|
||||||
type: InputType,
|
type: InputType,
|
||||||
value: String,
|
value: String,
|
||||||
|
@ -227,12 +246,12 @@ declare_elements!{
|
||||||
height: usize,
|
height: usize,
|
||||||
name: Id,
|
name: Id,
|
||||||
type: Mime,
|
type: Mime,
|
||||||
typemustmatch: Bool,
|
typemustmatch: bool,
|
||||||
usemap: String, // TODO should be a fragment starting with '#'
|
usemap: String, // TODO should be a fragment starting with '#'
|
||||||
width: usize,
|
width: usize,
|
||||||
} in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with param;
|
} in [FlowContent, PhrasingContent, EmbeddedContent, InteractiveContent, FormContent] with param;
|
||||||
ol {
|
ol {
|
||||||
reversed: Bool,
|
reversed: bool,
|
||||||
start: isize,
|
start: isize,
|
||||||
type: OrderedListType,
|
type: OrderedListType,
|
||||||
} in [FlowContent] with li;
|
} in [FlowContent] with li;
|
||||||
|
@ -254,11 +273,11 @@ declare_elements!{
|
||||||
s in [FlowContent, PhrasingContent] with PhrasingContent;
|
s in [FlowContent, PhrasingContent] with PhrasingContent;
|
||||||
samp in [FlowContent, PhrasingContent] with PhrasingContent;
|
samp in [FlowContent, PhrasingContent] with PhrasingContent;
|
||||||
script {
|
script {
|
||||||
async: Bool,
|
async: bool,
|
||||||
crossorigin: CrossOrigin,
|
crossorigin: CrossOrigin,
|
||||||
defer: Bool,
|
defer: bool,
|
||||||
integrity: Integrity,
|
integrity: Integrity,
|
||||||
nomodule: Bool,
|
nomodule: bool,
|
||||||
nonce: Nonce,
|
nonce: Nonce,
|
||||||
src: Uri,
|
src: Uri,
|
||||||
text: String,
|
text: String,
|
||||||
|
@ -267,12 +286,12 @@ declare_elements!{
|
||||||
section in [FlowContent, SectioningContent] with FlowContent;
|
section in [FlowContent, SectioningContent] with FlowContent;
|
||||||
select {
|
select {
|
||||||
autocomplete: String,
|
autocomplete: String,
|
||||||
autofocus: Bool,
|
autofocus: bool,
|
||||||
disabled: Bool,
|
disabled: bool,
|
||||||
form: Id,
|
form: Id,
|
||||||
multiple: Bool,
|
multiple: bool,
|
||||||
name: Id,
|
name: Id,
|
||||||
required: Bool,
|
required: bool,
|
||||||
size: usize,
|
size: usize,
|
||||||
} in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with SelectContent;
|
} in [FlowContent, PhrasingContent, InteractiveContent, FormContent] with SelectContent;
|
||||||
small in [FlowContent, PhrasingContent] with PhrasingContent;
|
small in [FlowContent, PhrasingContent] with PhrasingContent;
|
||||||
|
@ -284,16 +303,16 @@ declare_elements!{
|
||||||
template in [MetadataContent, FlowContent, PhrasingContent, TableColumnContent] with Node;
|
template in [MetadataContent, FlowContent, PhrasingContent, TableColumnContent] with Node;
|
||||||
textarea {
|
textarea {
|
||||||
autocomplete: OnOff,
|
autocomplete: OnOff,
|
||||||
autofocus: Bool,
|
autofocus: bool,
|
||||||
cols: usize,
|
cols: usize,
|
||||||
disabled: Bool,
|
disabled: bool,
|
||||||
form: Id,
|
form: Id,
|
||||||
maxlength: usize,
|
maxlength: usize,
|
||||||
minlength: usize,
|
minlength: usize,
|
||||||
name: Id,
|
name: Id,
|
||||||
placeholder: String,
|
placeholder: String,
|
||||||
readonly: Bool,
|
readonly: bool,
|
||||||
required: Bool,
|
required: bool,
|
||||||
rows: usize,
|
rows: usize,
|
||||||
spellcheck: BoolOrDefault,
|
spellcheck: BoolOrDefault,
|
||||||
wrap: Wrap,
|
wrap: Wrap,
|
||||||
|
@ -310,7 +329,7 @@ declare_elements!{
|
||||||
area {
|
area {
|
||||||
alt: String,
|
alt: String,
|
||||||
coords: String, // TODO could perhaps be validated
|
coords: String, // TODO could perhaps be validated
|
||||||
download: Bool,
|
download: bool,
|
||||||
href: Uri,
|
href: Uri,
|
||||||
hreflang: LanguageTag,
|
hreflang: LanguageTag,
|
||||||
ping: SpacedList<Uri>,
|
ping: SpacedList<Uri>,
|
||||||
|
@ -333,13 +352,13 @@ declare_elements!{
|
||||||
value: isize,
|
value: isize,
|
||||||
} with FlowContent;
|
} with FlowContent;
|
||||||
option {
|
option {
|
||||||
disabled: Bool,
|
disabled: bool,
|
||||||
label: String,
|
label: String,
|
||||||
selected: Bool,
|
selected: bool,
|
||||||
value: String,
|
value: String,
|
||||||
} in [SelectContent] with TextNode;
|
} in [SelectContent] with TextNode;
|
||||||
optgroup {
|
optgroup {
|
||||||
disabled: Bool,
|
disabled: bool,
|
||||||
label: String,
|
label: String,
|
||||||
} in [SelectContent] with option;
|
} in [SelectContent] with option;
|
||||||
param {
|
param {
|
||||||
|
@ -368,7 +387,7 @@ declare_elements!{
|
||||||
thead in [TableContent] with tr;
|
thead in [TableContent] with tr;
|
||||||
tr in [TableContent] with TableColumnContent;
|
tr in [TableContent] with TableColumnContent;
|
||||||
track {
|
track {
|
||||||
default: Bool,
|
default: bool,
|
||||||
kind: VideoKind,
|
kind: VideoKind,
|
||||||
label: String,
|
label: String,
|
||||||
src: Uri,
|
src: Uri,
|
||||||
|
@ -386,7 +405,7 @@ declare_elements!{
|
||||||
loop: isize,
|
loop: isize,
|
||||||
scrollamount: usize,
|
scrollamount: usize,
|
||||||
scrolldelay: usize,
|
scrolldelay: usize,
|
||||||
truespeed: Bool,
|
truespeed: bool,
|
||||||
vspace: String, // FIXME size
|
vspace: String, // FIXME size
|
||||||
width: String, // FIXME size
|
width: String, // FIXME size
|
||||||
} in [FlowContent, PhrasingContent] with PhrasingContent;
|
} in [FlowContent, PhrasingContent] with PhrasingContent;
|
||||||
|
|
Loading…
Reference in New Issue