From 3df65a4e266a2fa1705d1f8aae668122313255c5 Mon Sep 17 00:00:00 2001 From: luoxiaozero <48741584+luoxiaozero@users.noreply.github.com> Date: Sat, 30 Dec 2023 14:45:16 +0800 Subject: [PATCH] Demo/markdown (#61) * demo: init demo-markdown * demo: add include_md macro * demo: improve include_md macro * demo: improve include_md macro * demo: improve include_md macro * demo: improve code block * demo: add syntect css * demo: improve include_md macro * demo: include_md handle table --- Cargo.toml | 2 +- demo/Cargo.toml | 1 + demo/Trunk.toml | 2 +- demo/src/app.rs | 1 + demo/src/components/demo.rs | 30 ++++- demo/src/components/syntect-css.css | 60 ++++++++++ demo/src/pages/markdown.rs | 5 + demo/src/pages/mod.rs | 2 + demo_markdown/Cargo.toml | 17 +++ demo_markdown/docs/upload/mod.md | 54 +++++++++ demo_markdown/src/lib.rs | 55 ++++++++++ demo_markdown/src/markdown/code_block.rs | 78 +++++++++++++ demo_markdown/src/markdown/mod.rs | 134 +++++++++++++++++++++++ 13 files changed, 437 insertions(+), 4 deletions(-) create mode 100644 demo/src/components/syntect-css.css create mode 100644 demo/src/pages/markdown.rs create mode 100644 demo_markdown/Cargo.toml create mode 100644 demo_markdown/docs/upload/mod.md create mode 100644 demo_markdown/src/lib.rs create mode 100644 demo_markdown/src/markdown/code_block.rs create mode 100644 demo_markdown/src/markdown/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 30843ed..062ff8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,4 +51,4 @@ ssr = ["leptos/ssr", "leptos_meta/ssr"] hydrate = ["leptos/hydrate"] [workspace] -members = ["demo", "examples/*"] +members = ["demo", "demo_markdown", "examples/*"] diff --git a/demo/Cargo.toml b/demo/Cargo.toml index 622fca4..5db3a1b 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -20,6 +20,7 @@ icondata = { version = "0.1.0", features = [ "AiSearchOutlined", ] } prisms = { git = "https://github.com/luoxiaozero/prisms", rev = "16d4d34b93fc20578ebf03137d54ecc7eafa4d4b" } +demo_markdown = { path = "../demo_markdown" } [features] default = ["csr"] diff --git a/demo/Trunk.toml b/demo/Trunk.toml index 328d218..2227c00 100644 --- a/demo/Trunk.toml +++ b/demo/Trunk.toml @@ -5,7 +5,7 @@ public_url = "/thaw/" filehash = false [watch] -watch = ["../src", "./src"] +watch = ["../src", "./src", "../demo_markdown"] [serve] address = "127.0.0.1" diff --git a/demo/src/app.rs b/demo/src/app.rs index 19a8842..4d6063c 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -72,6 +72,7 @@ fn TheRouter(is_routing: RwSignal) -> impl IntoView { + diff --git a/demo/src/components/demo.rs b/demo/src/components/demo.rs index f28f067..7b58782 100644 --- a/demo/src/components/demo.rs +++ b/demo/src/components/demo.rs @@ -4,6 +4,8 @@ use thaw::*; #[slot] pub struct DemoCode { + #[prop(default = true)] + is_highlight: bool, children: Children, } @@ -37,7 +39,16 @@ pub fn Demo(demo_code: DemoCode, children: Children) -> impl IntoView { }); style }); + let content_class = create_memo(move |_| { + theme.with(|theme| { + format!( + "thaw-demo__content color-scheme--{}", + theme.common.color_scheme + ) + }) + }); + let is_highlight = demo_code.is_highlight; let frag = (demo_code.children)(); let mut html = String::new(); for node in frag.nodes { @@ -51,15 +62,30 @@ pub fn Demo(demo_code: DemoCode, children: Children) -> impl IntoView { view! { +
{children()}
-
+
-

+                {
+                    if is_highlight {
+                        view! {
+                            

+                        }
+                    } else {
+                        view! {
+                            
+                                {html}
+                            
+ } + } + }
} diff --git a/demo/src/components/syntect-css.css b/demo/src/components/syntect-css.css new file mode 100644 index 0000000..847cc9d --- /dev/null +++ b/demo/src/components/syntect-css.css @@ -0,0 +1,60 @@ +.thaw-demo__content pre { + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, + Liberation Mono, monospace; +} + +/** https://github.com/AmjadHD/sublime_one_theme */ + +/** light */ + +.color-scheme--light .syntect-storage { + color: hsl(301, 63%, 40%); +} + +.color-scheme--light .syntect-keyword.syntect-operator { + color: hsl(335, 95%, 62%); +} + +.color-scheme--light .syntect-function, +.color-scheme--light .syntect-macro { + color: hsl(221, 87%, 60%); +} + +.color-scheme--light .syntect-support.syntect-type { + color: hsl(198, 99%, 37%); +} + +.color-scheme--light .syntect-string { + color: hsl(119, 34%, 47%); +} + +.color-scheme--light .syntect-placeholder { + color: hsl(41, 99%, 30%); +} + +/** dark */ + +.color-scheme--dark .syntect-storage { + color: hsl(286, 60%, 67%); +} + +.color-scheme--dark .syntect-keyword.syntect-operator { + color: hsl(335, 95%, 62%); +} + +.color-scheme--dark .syntect-function, +.color-scheme--dark .syntect-macro { + color: hsl(207, 82%, 66%); +} + +.color-scheme--dark .syntect-support.syntect-type { + color: hsl(187, 47%, 55%); +} + +.color-scheme--dark .syntect-string { + color: hsl(95, 38%, 62%); +} + +.color-scheme--dark .syntect-placeholder { + color: hsl(29, 54%, 61%); +} diff --git a/demo/src/pages/markdown.rs b/demo/src/pages/markdown.rs new file mode 100644 index 0000000..3818021 --- /dev/null +++ b/demo/src/pages/markdown.rs @@ -0,0 +1,5 @@ +use crate::components::{Demo, DemoCode}; +use leptos::*; +use thaw::*; + +demo_markdown::include_md! {} diff --git a/demo/src/pages/mod.rs b/demo/src/pages/mod.rs index 5932a1f..f935e25 100644 --- a/demo/src/pages/mod.rs +++ b/demo/src/pages/mod.rs @@ -20,6 +20,7 @@ mod input; mod input_number; mod layout; mod loading_bar; +mod markdown; mod menu; mod message; mod mobile; @@ -66,6 +67,7 @@ pub use input::*; pub use input_number::*; pub use layout::*; pub use loading_bar::*; +pub use markdown::*; pub use menu::*; pub use message::*; pub use mobile::*; diff --git a/demo_markdown/Cargo.toml b/demo_markdown/Cargo.toml new file mode 100644 index 0000000..b9e5a59 --- /dev/null +++ b/demo_markdown/Cargo.toml @@ -0,0 +1,17 @@ +[package] +publish = false +name = "demo_markdown" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.33" +comrak = "0.20.0" +proc-macro2 = "1.0.71" +syn = "2.0.43" +syntect = "5.1.0" \ No newline at end of file diff --git a/demo_markdown/docs/upload/mod.md b/demo_markdown/docs/upload/mod.md new file mode 100644 index 0000000..5c66ae7 --- /dev/null +++ b/demo_markdown/docs/upload/mod.md @@ -0,0 +1,54 @@ +# Upload + +```rust demo +let message = use_message(); +let custom_request = move |file_list: FileList| { + message.create( + format!("Number of uploaded files: {}", file_list.length()), + MessageVariant::Success, + Default::default(), + ); +}; + +view!{ + + + +} +``` + +### Drag to upload + +```rust demo +let message = use_message(); +let custom_request = move |file_list: FileList| { + message.create( + format!("Number of uploaded files: {}", file_list.length()), + MessageVariant::Success, + Default::default(), + ); +}; + +view! { + + "Click or drag a file to this area to upload" + +} +``` + +### Upload Props + +| Name | Type | Default | Description | +| -------------- | -------------------------------- | -------------------- | ------------------------------------ | +| accept | `MaybeSignal` | `Default::default()` | The accept type of upload. | +| multiple | `MaybeSignal` | `false` | Allow multiple files to be selected. | +| custom_request | `Option>` | `Default::default()` | Customize upload request. | +| children | `Children` | | Upload's content. | + +### UploadDragger Props + +| Name | Type | Default | Description | +| -------- | ---------- | ------- | ------------------------ | +| children | `Children` | | UploadDragger's content. | diff --git a/demo_markdown/src/lib.rs b/demo_markdown/src/lib.rs new file mode 100644 index 0000000..0c51626 --- /dev/null +++ b/demo_markdown/src/lib.rs @@ -0,0 +1,55 @@ +mod markdown; + +use crate::markdown::parse_markdown; +use proc_macro2::{Ident, Span}; +use quote::quote; +use syn::ItemFn; + +#[proc_macro] +pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenStream { + let file_list = vec![("UploadMdPage", include_str!("../docs/upload/mod.md"))]; + + let mut fn_list = vec![]; + + for (fn_name, file_str) in file_list { + let fn_name = Ident::new(fn_name, Span::call_site()); + + let (body, demos) = match parse_markdown(file_str) { + Ok(body) => body, + Err(err) => { + return quote!(compile_error!(#err)).into(); + } + }; + + let demos: Vec = demos + .into_iter() + .enumerate() + .map(|(index, demo)| { + format!( + "#[component] fn Demo{}() -> impl IntoView {{ {} }}", + index + 1, + demo + ) + }) + .map(|demo| syn::parse_str::(&demo).unwrap()) + .collect(); + + fn_list.push(quote! { + #[component] + pub fn #fn_name() -> impl IntoView { + #(#demos)* + + view! { +
+ #body +
+ } + } + }); + } + + quote! { + #(#fn_list)* + } + .into() +} diff --git a/demo_markdown/src/markdown/code_block.rs b/demo_markdown/src/markdown/code_block.rs new file mode 100644 index 0000000..ee5f348 --- /dev/null +++ b/demo_markdown/src/markdown/code_block.rs @@ -0,0 +1,78 @@ +use comrak::nodes::NodeCodeBlock; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use std::sync::OnceLock; +use syntect::{ + html::{ClassStyle, ClassedHTMLGenerator}, + parsing::SyntaxSet, + util::LinesWithEndings, +}; + +pub fn to_tokens(code_block: &NodeCodeBlock, demos: &mut Vec) -> TokenStream { + let langs: Vec<&str> = code_block.info.split_ascii_whitespace().collect(); + if langs.iter().any(|lang| lang == &"demo") { + demos.push(code_block.literal.clone()); + + let demo = Ident::new(&format!("Demo{}", demos.len()), Span::call_site()); + let mut is_highlight = true; + let literal = langs + .iter() + .find(|lang| lang != &&"demo") + .map(|lang| highlight_to_html(&code_block.literal, lang)) + .flatten() + .unwrap_or_else(|| { + is_highlight = false; + code_block.literal.clone() + }); + + quote! { + + <#demo /> + + #literal + + + } + } else { + let mut is_highlight = true; + let literal = langs + .first() + .map(|lang| highlight_to_html(&code_block.literal, lang)) + .flatten() + .unwrap_or_else(|| { + is_highlight = false; + code_block.literal.clone() + }); + quote! { + + "" + + #literal + + + } + } +} + +static SYNTAX_SET: OnceLock = OnceLock::new(); + +fn highlight_to_html(text: &str, syntax: &str) -> Option { + let syntax_set = SYNTAX_SET.get_or_init(|| SyntaxSet::load_defaults_newlines()); + let Some(syntax) = syntax_set.find_syntax_by_token(syntax) else { + return None; + }; + + let mut html_generator = ClassedHTMLGenerator::new_with_class_style( + syntax, + syntax_set, + ClassStyle::SpacedPrefixed { prefix: "syntect-" }, + ); + + for line in LinesWithEndings::from(text) { + html_generator + .parse_html_for_line_which_includes_newline(line) + .expect(line); + } + + Some(html_generator.finalize()) +} diff --git a/demo_markdown/src/markdown/mod.rs b/demo_markdown/src/markdown/mod.rs new file mode 100644 index 0000000..6fed5b9 --- /dev/null +++ b/demo_markdown/src/markdown/mod.rs @@ -0,0 +1,134 @@ +mod code_block; + +use comrak::{ + nodes::{AstNode, NodeValue}, + parse_document, Arena, +}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; + +pub fn parse_markdown(md_text: &str) -> Result<(TokenStream, Vec), String> { + let mut demos: Vec = vec![]; + + let arena = Arena::new(); + let mut options = comrak::Options::default(); + options.extension.table = true; + + let root = parse_document(&arena, &md_text, &options); + let body = iter_nodes(root, &mut demos); + Ok((body, demos)) +} + +fn iter_nodes<'a>(node: &'a AstNode<'a>, demos: &mut Vec) -> TokenStream { + let mut children = vec![]; + for c in node.children() { + children.push(iter_nodes(c, demos)); + } + match &node.data.borrow().value { + NodeValue::Document => quote!(#(#children)*), + NodeValue::FrontMatter(_) => quote!("FrontMatter todo!!!"), + NodeValue::BlockQuote => quote!("BlockQuote todo!!!"), + NodeValue::List(_) => quote!("List todo!!!"), + NodeValue::Item(_) => quote!("Item todo!!!"), + NodeValue::DescriptionList => quote!("DescriptionList todo!!!"), + NodeValue::DescriptionItem(_) => quote!("DescriptionItem todo!!!"), + NodeValue::DescriptionTerm => quote!("DescriptionTerm todo!!!"), + NodeValue::DescriptionDetails => quote!("DescriptionDetails todo!!!"), + NodeValue::CodeBlock(node_code_block) => code_block::to_tokens(node_code_block, demos), + NodeValue::HtmlBlock(_) => quote!("HtmlBlock todo!!!"), + NodeValue::Paragraph => quote!( +

+ #(#children)* +

+ ), + NodeValue::Heading(node_h) => { + let h = Ident::new(&format!("h{}", node_h.level), Span::call_site()); + quote!( + <#h> + #(#children)* + + ) + } + NodeValue::ThematicBreak => quote!("ThematicBreak todo!!!"), + NodeValue::FootnoteDefinition(_) => quote!("FootnoteDefinition todo!!!"), + NodeValue::Table(_) => { + let header_index = { + let mut header_index = 0; + for (index, c) in node.children().enumerate() { + let row = &c.data.borrow().value; + let is_header = match *row { + NodeValue::TableRow(header) => header, + _ => panic!(), + }; + if !is_header { + header_index = index; + break; + } + } + header_index + }; + let header_children: Vec = children.drain(0..header_index).collect(); + + quote!( + + + #(#header_children)* + + + #(#children)* + +
+ ) + } + NodeValue::TableRow(_) => { + quote!( + + #(#children)* + + ) + } + NodeValue::TableCell => { + let row = &node.parent().unwrap().data.borrow().value; + let is_header = match *row { + NodeValue::TableRow(header) => header, + _ => panic!(), + }; + if is_header { + quote!( + + #(#children)* + + ) + } else { + quote!( + + #(#children)* + + ) + } + } + NodeValue::Text(text) => { + let text = text.clone(); + quote!(#text) + } + NodeValue::TaskItem(_) => quote!("TaskItem todo!!!"), + NodeValue::SoftBreak => quote!("\n"), + NodeValue::LineBreak => quote!(
), + NodeValue::Code(node_code) => { + let code = node_code.literal.clone(); + quote!( + + #code + + ) + } + NodeValue::HtmlInline(_) => quote!("HtmlInline todo!!!"), + NodeValue::Emph => quote!("Emph todo!!!"), + NodeValue::Strong => quote!("Strong todo!!!"), + NodeValue::Strikethrough => quote!("Strikethrough todo!!!"), + NodeValue::Superscript => quote!("Superscript todo!!!"), + NodeValue::Link(_) => quote!("Link todo!!!"), + NodeValue::Image(_) => quote!("Image todo!!!"), + NodeValue::FootnoteReference(_) => quote!("FootnoteReference todo!!!"), + } +}