~eanyanwu/toph

7d8034aee77a68d1b42e08ac45c5234128c81f17 — Eze 2 months ago d9f5212
Add css_reset component & Modify Node impls
8 files changed, 455 insertions(+), 169 deletions(-)

A examples/posthaven/main.rs
A posthaven.html
D src/bin/el.rs
R src/{layout.rs => component.rs}
A src/css/reset.css
M src/lib.rs
M src/node.rs
M src/node/tag.rs
A examples/posthaven/main.rs => examples/posthaven/main.rs +39 -0
@@ 0,0 1,39 @@
use std::error::Error;
use std::fs;
use toph::{component::*, tag::*, Node};

fn button(text: &str) -> Node {
    let css = r#"
        button {
            padding: 0.5rem 1.25rem;
            background-color: #ffffff;
            border-radius: 0.25rem;
        }
    "#;
    button_.set(t_(text)).stylesheet(css)
}
fn header() -> Node {
    let nav_elements = [
        "Features",
        "Screenshots",
        "Our pledge",
        "Pricing",
        "Questions?",
    ];

    let li_items = nav_elements.into_iter().map(|e| li_.set(a_.set(t_(e))));
    let nav = ul_.set(li_items);
    let login = button("Login");
    let cta = button("Get Started");
    header_.set([nav, div_.set([login, cta])])
}
fn main() -> Result<(), Box<dyn Error>> {
    let mut html: Node = [
        doctype_,
        html_.set([head_, body_.set([css_reset(), header()])]),
    ]
    .into();

    fs::write("posthaven.html", html.write_to_string(true))?;
    Ok(())
}

A posthaven.html => posthaven.html +192 -0
@@ 0,0 1,192 @@
<!DOCTYPE html>
<html>
  <head>
    <style>
      /* http://meyerweb.com/eric/tools/css/reset/ 
      *
         v2.0 | 20110126
         License: none (public domain)
      */
      
      html,
      body,
      div,
      span,
      applet,
      object,
      iframe,
      h1,
      h2,
      h3,
      h4,
      h5,
      h6,
      p,
      blockquote,
      pre,
      a,
      abbr,
      acronym,
      address,
      big,
      cite,
      code,
      del,
      dfn,
      em,
      img,
      ins,
      kbd,
      q,
      s,
      samp,
      small,
      strike,
      strong,
      sub,
      sup,
      tt,
      var,
      b,
      u,
      i,
      center,
      dl,
      dt,
      dd,
      ol,
      ul,
      li,
      fieldset,
      form,
      label,
      legend,
      table,
      caption,
      tbody,
      tfoot,
      thead,
      tr,
      th,
      td,
      article,
      aside,
      canvas,
      details,
      embed,
      figure,
      figcaption,
      footer,
      header,
      hgroup,
      menu,
      nav,
      output,
      ruby,
      section,
      summary,
      time,
      mark,
      audio,
      video,
      input {
        margin: 0;
        padding: 0;
        border: 0;
        vertical-align: baseline;
        box-sizing: border-box;
      }
      
      /* HTML5 display-role reset for older browsers */
      article,
      aside,
      details,
      figcaption,
      figure,
      footer,
      header,
      hgroup,
      menu,
      nav,
      section {
        display: block;
      }
      
      ol,
      ul {
        list-style: none;
      }
      
      blockquote,
      q {
        quotes: none;
      }
      
      blockquote:before,
      blockquote:after,
      q:before,
      q:after {
        content: "";
        content: none;
      }
      
      table {
        border-collapse: collapse;
        border-spacing: 0;
      }
      
      a {
        text-decoration: none;
      }
    </style>
    <style>
      
              button {
                  padding: 0.5rem 1.25rem;
                  background-color: #ffffff;
                  border-radius: 0.25rem;
              }
    </style>
  </head>
  <body>
    <span>
    </span>
    <header>
      <ul>
        <li>
          <a>
            Features
          </a>
        </li>
        <li>
          <a>
            Screenshots
          </a>
        </li>
        <li>
          <a>
            Our pledge
          </a>
        </li>
        <li>
          <a>
            Pricing
          </a>
        </li>
        <li>
          <a>
            Questions?
          </a>
        </li>
      </ul>
      <div>
        <button>
          Login
        </button>
        <button>
          Get Started
        </button>
      </div>
    </header>
  </body>
</html>

D src/bin/el.rs => src/bin/el.rs +0 -88
@@ 1,88 0,0 @@
use std::fs;
use toph::{attr, layout::*, tag::*, Node};

fn stub() -> Node {
    let css = ".stub { width: 50px; height: 50px; background-color: black }";
    div_.stylesheet(css).with(attr![class = "stub"])
}

fn main() {
    let mut html: Node = [
        doctype_,
        html_.set([
            head_.set(title_.set("Every Layout")),
            body_.set([
                h1_.set("Stack"),
                stack(
                    5,
                    [
                        padded(1, stack(1, [stub(), stub(), stub()])),
                        padded(1, stack(4, [stub(), stub(), stub()])),
                        padded(1, stack(6, [stub(), stub(), stub()])),
                    ],
                ),
                h1_.set("Center"),
                center([stub()]),
                h1_.set("Cluster"),
                cluster(
                    5,
                    [
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                    ],
                ),
                h1_.set("Switcher"),
                switcher(4, 60, [stub(), stub(), stub(), stub()]),
                h1_.set("Cover"),
                cover(stub(), None, None, Some(50)),
                cover(stub(), Some(stub()), None, None),
                cover(stub(), Some(stub()), Some(stub()), None),
                h1_.set("Fluid Grid"),
                fluid_grid(
                    10,
                    1,
                    [
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                        stub(),
                    ],
                ),
                h1_.set("Frame"),
                frame(
                    (3, 4),
                    img_.with(
                        attr![src="https://img.freepik.com/free-photo/painting-mountain-lake-with-mountain-background_188544-9126.jpg"]
                        )
                    ).with(attr![style="width: 400px;"]),
                h1_.set("Manual SVG"),
                svg_.with(attr![width="32", height="32", viewBox="0 0 32 32"])
                    .set(custom_("path")
                         .with(attr![
                               fill="none",
                               stroke="currentColor",
                               stroke_linecap="round",
                               stroke_linejoin="round",
                               stroke_width="2",
                               d="M2 6v24h28V6Zm0 9h28M7 3v6m6-6v6m6-6v6m6-6v6"
                         ]))


            ]),
        ]),
    ]
    .into();

    fs::write("every-layout.html", html.write_to_string(true)).unwrap();
}

R src/layout.rs => src/component.rs +8 -2
@@ 1,4 1,4 @@
//! Composable CSS Layout primitives
//! Composable CSS Layout primitives and components
//!
//! Sources: <https://every-layout.dev>



@@ 185,7 185,7 @@ pub fn center(child: impl Into<Node>) -> Node {
/// So for example, this won't give you what you expect because when a list of Nodes is converted
/// into a single one, it is actually an HTML [fragment](crate::Fragment).
/// ```
/// use toph::{tag::*, layout::cover};
/// use toph::{tag::*, component::cover};
///
/// let nope = cover([
///     span_,


@@ 297,3 297,9 @@ pub fn frame(ratio: impl Into<Ratio>, child: impl Into<Node>) -> Node {
        .var("t-frame-ratio", &ratio)
        .stylesheet(include_str!("css/frame.css"))
}

/// Applies a slightly modified [Meyer CSS reset](https://meyerweb.com/eric/tools/css/reset/) to
/// the page
pub fn css_reset() -> Node {
    span_.stylesheet(include_str!("css/reset.css"))
}

A src/css/reset.css => src/css/reset.css +136 -0
@@ 0,0 1,136 @@
/* http://meyerweb.com/eric/tools/css/reset/ 
*
   v2.0 | 20110126
   License: none (public domain)
*/

html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video,
input {
  margin: 0;
  padding: 0;
  border: 0;
  vertical-align: baseline;
  box-sizing: border-box;
}

/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
  display: block;
}

ol,
ul {
  list-style: none;
}

blockquote,
q {
  quotes: none;
}

blockquote:before,
blockquote:after,
q:before,
q:after {
  content: "";
  content: none;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

a {
  text-decoration: none;
}

M src/lib.rs => src/lib.rs +7 -7
@@ 6,7 6,7 @@
//! - Link [css](crate::Node::stylesheet) & [javascript](crate::Node::js) snippets to HTML
//! elements, such that those snippets appear when the linked element is displayed.
//!
//! The crate also implements [a set of layout primitives](crate::layout) so you don't have to
//! The crate also implements [a set of core css components](crate::component) so you don't have to
//!
//! ## Example
//!


@@ 18,16 18,16 @@
//!     doctype_,
//!     html_.with(attr![lang="en"])
//!         .set([
//!             head_.set(title_.set("My Webpage")),
//!             head_.set(title_.set(t_("My Webpage"))),
//!             body_.set([
//!                 ul_.with(attr![id="navigation"])
//!                     .set(
//!                         navigation.into_iter().map(|(caption, url)| {
//!                             li_.set(a_.with(attr![href=url]).set(caption))
//!                             li_.set(a_.with(attr![href=url]).set(t_(caption)))
//!                         }).collect::<Vec<_>>()
//!                     ),
//!                 h1_.stylesheet("h1 { text-decoration: underline; }")
//!                     .set("My Webpage")
//!                     .set(t_("My Webpage"))
//!             ])
//!         ])
//! ]);


@@ 75,7 75,7 @@
//! use toph::{attr, tag::*};
//!
//! let xss_attr_attempt = r#"" onclick="alert(1)""#;
//! let xss_attempt = r#"<script>alert(1)"#;
//! let xss_attempt = t_(r#"<script>alert(1)"#);
//! let url = "/path with space";
//!
//! let mut span = span_


@@ 84,7 84,7 @@
//!
//! let mut anchor = a_
//!     .with(attr![href=url])
//!     .set("A link");
//!     .set(t_("A link"));
//!
//! assert_eq!(
//!     span.write_to_string(false),


@@ 143,8 143,8 @@
#![warn(missing_docs)]
#![forbid(unsafe_code)]

pub mod component;
mod encode;
pub mod layout;
mod node;

pub use node::{tag, Element, Fragment, Node, Text};

M src/node.rs => src/node.rs +67 -72
@@ 281,36 281,24 @@ impl Node {

    /// Sets this Element's children
    ///
    /// You can pass in anything that can be converted to a [`Node`](crate::Node):
    ///
    /// ```
    /// use toph::tag::*;
    /// You can pass in another `Node` as an argument, as well as anything that can be [converted into
    /// an iterator of `Nodes`](std::iter::IntoIterator)
    ///
    /// // A string slice
    /// span_.set("hello");
    /// This includes things such as `Option<Node>`, `Result<Node>` and arrays & `Vec`s of `Node`s
    ///
    /// // An owned string
    /// span_.set(String::from("hello"));
    /// ```
    /// use toph::tag::*;
    ///
    /// // A span with nothing in it.
    /// span_;
    /// // Another node
    /// span_.set(t_("hello"));
    ///
    /// // An array of nodes
    /// span_.set([div_, span_]);
    ///
    /// // string slices and owned strings can be
    /// // converted to nodes, so this also works
    /// span_.set([
    ///     div_,
    ///     "bare string".into(),
    ///     span_,
    /// ]);
    ///
    /// // An option of anything that can be converted into a node
    /// span_.set(Some("hello"));
    /// // A node wrapped in an  option or result
    /// span_.set(Some(div_));
    /// ```
    ///
    /// Refer to the [trait implementations](#trait-implementations) for a comprehensive list
    pub fn set(mut self, child: impl Into<Node>) -> Node {
        if let Self::Element(ref mut el) = self {
            el.child = Some(Box::new(child.into()));


@@ 340,60 328,64 @@ impl Node {
    }
}

impl From<&str> for Node {
    fn from(value: &str) -> Self {
        Node::from(value.to_string())
    }
}

impl From<String> for Node {
    fn from(value: String) -> Self {
        let encoded = encode::html(&value);
        Node::Text(Text(encoded))
    }
}

impl<I: Into<Node>> From<Option<I>> for Node {
    fn from(value: Option<I>) -> Self {
        value.map(|v| v.into()).unwrap_or_default()
    }
}

impl<I> From<Vec<I>> for Node
// impl From<&str> for Node {
//     fn from(value: &str) -> Self {
//         Node::from(value.to_string())
//     }
// }

// impl From<String> for Node {
//     fn from(value: String) -> Self {
//         let encoded = encode::html(&value);
//         Node::Text(Text(encoded))
//     }
// }

//impl<I: Into<Node>> From<Option<I>> for Node {
//    fn from(value: Option<I>) -> Self {
//        value.map(|v| v.into()).unwrap_or_default()
//    }
//}

// impl<I> From<Vec<I>> for Node
// where
//     I: Into<Node>,
// {
//     fn from(value: Vec<I>) -> Self {
//         let nodes = value.into_iter().map(|v| v.into()).collect::<Vec<_>>();
//         Self::Fragment(Fragment(nodes))
//     }
// }

impl<I> From<I> for Node
where
    I: Into<Node>,
    I: IntoIterator<Item = Node>,
{
    fn from(value: Vec<I>) -> Self {
        let nodes = value.into_iter().map(|v| v.into()).collect::<Vec<_>>();
        Self::Fragment(Fragment(nodes))
    }
}

macro_rules! impl_node_for_array_of_nodes {
    ($($n:expr),+) => {
        $(
            impl<I: Into<Node>> From<[I; $n]> for Node {
                fn from(value: [I; $n]) -> Self {
                    let nodes = value.into_iter().map(|v| v.into()).collect::<Vec<_>>();
                    Node::Fragment(Fragment(nodes))
                }
            }
        )+
    };
}

impl<I: Into<Node>> From<[I; 0]> for Node {
    fn from(_value: [I; 0]) -> Self {
        Self::Text(Text("".into()))
    fn from(value: I) -> Self {
        let vec = value.into_iter().collect::<Vec<_>>();
        Self::Fragment(Fragment(vec))
    }
}

#[rustfmt::skip]
impl_node_for_array_of_nodes!(
    1, 2, 3, 4, 5, 6, 7, 8, 9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    20
);
// macro_rules! impl_node_for_array_of_nodes {
//     ($($n:expr),+) => {
//         $(
//             impl<I: Into<Node>> From<[I; $n]> for Node {
//                 fn from(value: [I; $n]) -> Self {
//                     let nodes = value.into_iter().map(|v| v.into()).collect::<Vec<_>>();
//                     Node::Fragment(Fragment(nodes))
//                 }
//             }
//         )+
//     };
// }
//
// #[rustfmt::skip]
// impl_node_for_array_of_nodes!(
//     1, 2, 3, 4, 5, 6, 7, 8, 9,
//     10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
//     20
// );

#[cfg(test)]
mod tests {


@@ 410,12 402,15 @@ mod tests {
    fn html_fragments() {
        // including strings
        assert_html(
            [span_.set("literal"), span_.set(String::from("string"))],
            [
                span_.set(t_("literal")),
                span_.set(t_(String::from("string"))),
            ],
            "<span>literal</span><span>string</span>",
        );

        // strings are html encoded
        assert_html(span_.set("<script>"), "<span>&lt;script&gt;</span>");
        assert_html(span_.set(t_("<script>")), "<span>&lt;script&gt;</span>");

        // nesting nodes
        assert_html(

M src/node/tag.rs => src/node/tag.rs +6 -0
@@ 11,6 11,7 @@

use super::*;
use attribute::AttributeMap;
use encode::html;
use variable::CSSVariableMap;

/// Creates an HTML Node with a custom tag name.


@@ 24,6 25,11 @@ pub fn custom_(tag: &'static str) -> Node {
    })
}

/// Creates a plain HTML text element
pub fn t_<I: AsRef<str>>(text: I) -> Node {
    Node::Text(Text(html(text.as_ref())))
}

macro_rules! impl_tag {
    ($($tag:ident),+) => {
        $(