mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
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
This commit is contained in:
parent
2cd308fadf
commit
3df65a4e26
13 changed files with 437 additions and 4 deletions
|
@ -51,4 +51,4 @@ ssr = ["leptos/ssr", "leptos_meta/ssr"]
|
|||
hydrate = ["leptos/hydrate"]
|
||||
|
||||
[workspace]
|
||||
members = ["demo", "examples/*"]
|
||||
members = ["demo", "demo_markdown", "examples/*"]
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -5,7 +5,7 @@ public_url = "/thaw/"
|
|||
filehash = false
|
||||
|
||||
[watch]
|
||||
watch = ["../src", "./src"]
|
||||
watch = ["../src", "./src", "../demo_markdown"]
|
||||
|
||||
[serve]
|
||||
address = "127.0.0.1"
|
||||
|
|
|
@ -72,6 +72,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
|||
<Route path="/switch" view=SwitchPage/>
|
||||
<Route path="/tag" view=TagPage/>
|
||||
<Route path="/upload" view=UploadPage/>
|
||||
<Route path="/upload-md" view=UploadMdPage/>
|
||||
<Route path="/loading-bar" view=LoadingBarPage/>
|
||||
<Route path="/breadcrumb" view=BreadcrumbPage/>
|
||||
<Route path="/layout" view=LayoutPage/>
|
||||
|
|
|
@ -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! {
|
||||
<Style id="leptos-thaw-prism-css">{prisms::prism_css!()}</Style>
|
||||
<Style id="leptos-thaw-syntect-css">
|
||||
{include_str!("./syntect-css.css")}
|
||||
</Style>
|
||||
<Style id="leptos-thaw-prism-css-fix">
|
||||
".token.operator {
|
||||
background: hsla(0, 0%, 100%, 0) !important;
|
||||
}"
|
||||
</Style>
|
||||
<div style=move || style.get()>{children()}</div>
|
||||
<div style=move || code_style.get()>
|
||||
<div style=move || code_style.get() class=move || content_class.get()>
|
||||
<Code>
|
||||
{
|
||||
if is_highlight {
|
||||
view! {
|
||||
<pre style="margin: 0" inner_html=html></pre>
|
||||
}
|
||||
} else {
|
||||
view! {
|
||||
<pre style="margin: 0">
|
||||
{html}
|
||||
</pre>
|
||||
}
|
||||
}
|
||||
}
|
||||
</Code>
|
||||
</div>
|
||||
}
|
||||
|
|
60
demo/src/components/syntect-css.css
Normal file
60
demo/src/components/syntect-css.css
Normal file
|
@ -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%);
|
||||
}
|
5
demo/src/pages/markdown.rs
Normal file
5
demo/src/pages/markdown.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use crate::components::{Demo, DemoCode};
|
||||
use leptos::*;
|
||||
use thaw::*;
|
||||
|
||||
demo_markdown::include_md! {}
|
|
@ -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::*;
|
||||
|
|
17
demo_markdown/Cargo.toml
Normal file
17
demo_markdown/Cargo.toml
Normal file
|
@ -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"
|
54
demo_markdown/docs/upload/mod.md
Normal file
54
demo_markdown/docs/upload/mod.md
Normal file
|
@ -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!{
|
||||
<Upload>
|
||||
<Button>
|
||||
"upload"
|
||||
</Button>
|
||||
</Upload>
|
||||
}
|
||||
```
|
||||
|
||||
### 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! {
|
||||
<Upload custom_request>
|
||||
<UploadDragger>"Click or drag a file to this area to upload"</UploadDragger>
|
||||
</Upload>
|
||||
}
|
||||
```
|
||||
|
||||
### Upload Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------------- | -------------------------------- | -------------------- | ------------------------------------ |
|
||||
| accept | `MaybeSignal<String>` | `Default::default()` | The accept type of upload. |
|
||||
| multiple | `MaybeSignal<bool>` | `false` | Allow multiple files to be selected. |
|
||||
| custom_request | `Option<Callback<FileList, ()>>` | `Default::default()` | Customize upload request. |
|
||||
| children | `Children` | | Upload's content. |
|
||||
|
||||
### UploadDragger Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | ---------- | ------- | ------------------------ |
|
||||
| children | `Children` | | UploadDragger's content. |
|
55
demo_markdown/src/lib.rs
Normal file
55
demo_markdown/src/lib.rs
Normal file
|
@ -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<ItemFn> = demos
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, demo)| {
|
||||
format!(
|
||||
"#[component] fn Demo{}() -> impl IntoView {{ {} }}",
|
||||
index + 1,
|
||||
demo
|
||||
)
|
||||
})
|
||||
.map(|demo| syn::parse_str::<ItemFn>(&demo).unwrap())
|
||||
.collect();
|
||||
|
||||
fn_list.push(quote! {
|
||||
#[component]
|
||||
pub fn #fn_name() -> impl IntoView {
|
||||
#(#demos)*
|
||||
|
||||
view! {
|
||||
<div style="width: 896px; margin: 0 auto;">
|
||||
#body
|
||||
</div>
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
quote! {
|
||||
#(#fn_list)*
|
||||
}
|
||||
.into()
|
||||
}
|
78
demo_markdown/src/markdown/code_block.rs
Normal file
78
demo_markdown/src/markdown/code_block.rs
Normal file
|
@ -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<String>) -> 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>
|
||||
<#demo />
|
||||
<DemoCode slot is_highlight=#is_highlight>
|
||||
#literal
|
||||
</DemoCode>
|
||||
</Demo>
|
||||
}
|
||||
} 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! {
|
||||
<Demo>
|
||||
""
|
||||
<DemoCode slot is_highlight=#is_highlight>
|
||||
#literal
|
||||
</DemoCode>
|
||||
</Demo>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SYNTAX_SET: OnceLock<SyntaxSet> = OnceLock::new();
|
||||
|
||||
fn highlight_to_html(text: &str, syntax: &str) -> Option<String> {
|
||||
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())
|
||||
}
|
134
demo_markdown/src/markdown/mod.rs
Normal file
134
demo_markdown/src/markdown/mod.rs
Normal file
|
@ -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>), String> {
|
||||
let mut demos: Vec<String> = 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<String>) -> 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!(
|
||||
<p>
|
||||
#(#children)*
|
||||
</p >
|
||||
),
|
||||
NodeValue::Heading(node_h) => {
|
||||
let h = Ident::new(&format!("h{}", node_h.level), Span::call_site());
|
||||
quote!(
|
||||
<#h>
|
||||
#(#children)*
|
||||
</#h>
|
||||
)
|
||||
}
|
||||
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<TokenStream> = children.drain(0..header_index).collect();
|
||||
|
||||
quote!(
|
||||
<Table single_column=true>
|
||||
<thead>
|
||||
#(#header_children)*
|
||||
</thead>
|
||||
<tbody>
|
||||
#(#children)*
|
||||
</tbody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
NodeValue::TableRow(_) => {
|
||||
quote!(
|
||||
<tr>
|
||||
#(#children)*
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
NodeValue::TableCell => {
|
||||
let row = &node.parent().unwrap().data.borrow().value;
|
||||
let is_header = match *row {
|
||||
NodeValue::TableRow(header) => header,
|
||||
_ => panic!(),
|
||||
};
|
||||
if is_header {
|
||||
quote!(
|
||||
<th>
|
||||
#(#children)*
|
||||
</th>
|
||||
)
|
||||
} else {
|
||||
quote!(
|
||||
<td>
|
||||
#(#children)*
|
||||
</td>
|
||||
)
|
||||
}
|
||||
}
|
||||
NodeValue::Text(text) => {
|
||||
let text = text.clone();
|
||||
quote!(#text)
|
||||
}
|
||||
NodeValue::TaskItem(_) => quote!("TaskItem todo!!!"),
|
||||
NodeValue::SoftBreak => quote!("\n"),
|
||||
NodeValue::LineBreak => quote!(<br />),
|
||||
NodeValue::Code(node_code) => {
|
||||
let code = node_code.literal.clone();
|
||||
quote!(
|
||||
<Text code=true>
|
||||
#code
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
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!!!"),
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue