MD upgrades
This commit is contained in:
@@ -1,106 +1,170 @@
|
||||
use markdown::{CompileOptions, Constructs, LineEnding, Options, ParseOptions};
|
||||
use emojis::Emoji;
|
||||
use markdown_it::parser::inline::{InlineRule, InlineState};
|
||||
use markdown_it::{Node, NodeValue, Renderer};
|
||||
use maud::{Markup, PreEscaped, Render};
|
||||
|
||||
pub struct Markdown<'a>(pub &'a str);
|
||||
|
||||
const OPTS: Options = Options {
|
||||
parse: ParseOptions {
|
||||
constructs: Constructs {
|
||||
attention: true,
|
||||
autolink: false,
|
||||
block_quote: true,
|
||||
character_escape: true,
|
||||
character_reference: true,
|
||||
code_indented: false,
|
||||
code_fenced: true,
|
||||
code_text: true,
|
||||
definition: true,
|
||||
frontmatter: false,
|
||||
gfm_autolink_literal: true,
|
||||
gfm_footnote_definition: false,
|
||||
gfm_label_start_footnote: false,
|
||||
gfm_strikethrough: false,
|
||||
gfm_table: false,
|
||||
gfm_task_list_item: false,
|
||||
hard_break_escape: false,
|
||||
hard_break_trailing: false,
|
||||
heading_atx: true,
|
||||
heading_setext: false,
|
||||
label_start_image: false,
|
||||
label_start_link: true,
|
||||
label_end: true,
|
||||
list_item: true,
|
||||
math_flow: false,
|
||||
math_text: false,
|
||||
mdx_esm: false,
|
||||
thematic_break: false,
|
||||
|
||||
// INLINE HTML
|
||||
html_flow: false,
|
||||
html_text: false,
|
||||
|
||||
// INLINE {}
|
||||
mdx_expression_flow: true,
|
||||
mdx_expression_text: true,
|
||||
|
||||
// INLINE HTML (alternative)
|
||||
mdx_jsx_flow: false,
|
||||
mdx_jsx_text: false,
|
||||
},
|
||||
|
||||
gfm_strikethrough_single_tilde: false,
|
||||
math_text_single_dollar: false,
|
||||
mdx_expression_parse: None,
|
||||
mdx_esm_parse: None,
|
||||
},
|
||||
|
||||
compile: CompileOptions {
|
||||
allow_any_img_src: false,
|
||||
allow_dangerous_html: false,
|
||||
allow_dangerous_protocol: false,
|
||||
default_line_ending: LineEnding::LineFeed,
|
||||
gfm_footnote_back_label: None,
|
||||
gfm_footnote_clobber_prefix: None,
|
||||
gfm_footnote_label_attributes: None,
|
||||
gfm_footnote_label_tag_name: None,
|
||||
gfm_footnote_label: None,
|
||||
gfm_task_list_item_checkable: false,
|
||||
gfm_tagfilter: false,
|
||||
},
|
||||
};
|
||||
|
||||
impl Render for Markdown<'_> {
|
||||
fn render(&self) -> Markup {
|
||||
/*
|
||||
let mut ast = markdown::to_mdast(MD_A, &opts.parse).unwrap();
|
||||
let walk = AstWalkMut::new(&mut ast);
|
||||
// TODO: init once
|
||||
let md = &mut markdown_it::MarkdownIt::new();
|
||||
markdown_it::plugins::cmark::add(md);
|
||||
markdown_it::plugins::html::add(md);
|
||||
md.inline.add_rule::<InlineEmote>();
|
||||
md.inline.add_rule::<InlineMdx>();
|
||||
|
||||
for i in walk {
|
||||
match i {
|
||||
AstWalkMutStep::Exit(node) => {
|
||||
match node {
|
||||
Node::MdxTextExpression(x) => {
|
||||
println!("{x:?}");
|
||||
}
|
||||
Node::MdxFlowExpression(x) => {
|
||||
println!("{x:?}");
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
let md = md.parse(&self.0);
|
||||
let html = md.render();
|
||||
|
||||
*node = Node::Text(Text {
|
||||
value: "LOL".to_owned(),
|
||||
position: node.position().cloned(),
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
println!("{ast:?}");
|
||||
*/
|
||||
|
||||
let md = markdown::to_html_with_options(self.0, &OPTS).unwrap();
|
||||
return PreEscaped(md);
|
||||
return PreEscaped(html);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MARK: extensions
|
||||
//
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InlineEmote(&'static Emoji);
|
||||
|
||||
impl NodeValue for InlineEmote {
|
||||
fn render(&self, _node: &Node, fmt: &mut dyn Renderer) {
|
||||
fmt.text(self.0.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl InlineRule for InlineEmote {
|
||||
const MARKER: char = ':';
|
||||
|
||||
fn run(state: &mut InlineState<'_, '_>) -> Option<(Node, usize)> {
|
||||
let input = &state.src[state.pos..state.pos_max];
|
||||
|
||||
if !input.starts_with(':') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let end_idx = input[1..].find(':')? + 1;
|
||||
let emote = emojis::get_by_shortcode(&input[1..end_idx])?;
|
||||
|
||||
Some((Node::new(InlineEmote(emote)), end_idx + 1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InlineMdx(String);
|
||||
|
||||
impl NodeValue for InlineMdx {
|
||||
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
|
||||
if mdx_style(&self.0, node, fmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
fmt.open("code", &[]);
|
||||
fmt.text(&self.0);
|
||||
fmt.close("code");
|
||||
}
|
||||
}
|
||||
|
||||
impl InlineRule for InlineMdx {
|
||||
const MARKER: char = '{';
|
||||
|
||||
fn run(state: &mut InlineState<'_, '_>) -> Option<(Node, usize)> {
|
||||
let input = &state.src[state.pos..state.pos_max];
|
||||
if !input.starts_with('{') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut balance = 1;
|
||||
let mut end = 1;
|
||||
for i in input[1..].bytes() {
|
||||
match i {
|
||||
b'}' => balance -= 1,
|
||||
b'{' => balance += 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if balance == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
end += 1;
|
||||
}
|
||||
|
||||
if balance != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let content = &input[1..end];
|
||||
Some((Node::new(InlineMdx(content.to_owned())), content.len() + 2))
|
||||
}
|
||||
}
|
||||
|
||||
fn mdx_style(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
|
||||
let mdx = mdx.trim();
|
||||
|
||||
if !mdx.starts_with("style(") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let skip = 6;
|
||||
let mut balance = 1;
|
||||
let mut end = skip;
|
||||
for i in mdx[skip..].bytes() {
|
||||
match i {
|
||||
b')' => balance -= 1,
|
||||
b'(' => balance += 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if balance == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
end += 1;
|
||||
}
|
||||
|
||||
if balance != 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let style = &mdx[skip..end].trim();
|
||||
let content = &mdx[end + 1..].trim();
|
||||
|
||||
let mut style_str = String::new();
|
||||
|
||||
for kv in style.split(";") {
|
||||
let mut s = kv.split(":");
|
||||
let k = s.next();
|
||||
let v = s.next();
|
||||
|
||||
if k.is_none() || v.is_none() || s.next().is_some() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let k = k.unwrap().trim();
|
||||
let v = v.unwrap().trim();
|
||||
|
||||
match k {
|
||||
"color" => {
|
||||
style_str.push_str("color:");
|
||||
style_str.push_str(v);
|
||||
style_str.push_str(";");
|
||||
}
|
||||
|
||||
"color_var" => {
|
||||
style_str.push_str("color:var(--");
|
||||
style_str.push_str(v);
|
||||
style_str.push_str(");");
|
||||
}
|
||||
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
fmt.open("span", &[("style", style_str)]);
|
||||
fmt.text(&content);
|
||||
fmt.close("span");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,7 @@ pub async fn betalupi() -> Markup {
|
||||
html!(
|
||||
(Backlinks(&[("/", "home")], "whats-a-betalupi"))
|
||||
|
||||
(Markdown(MD_A))
|
||||
|
||||
br {}
|
||||
|
||||
(Markdown(MD_B))
|
||||
|
||||
br {}
|
||||
(Markdown(MD))
|
||||
|
||||
img alt="betalupi map" class="image" src=(Image_Betalupi::URL) {}
|
||||
)
|
||||
@@ -38,7 +32,7 @@ pub async fn betalupi() -> Markup {
|
||||
}
|
||||
}
|
||||
|
||||
const MD_A: &str = r#"[es]: https://github.com/endless-sky/endless-sky
|
||||
const MD: &str = r#"[es]: https://github.com/endless-sky/endless-sky
|
||||
[*Stellaris*]: https://www.paradoxinteractive.com/games/stellaris/about
|
||||
[Arabic]: https://en.wikipedia.org/wiki/List_of_Arabic_star_names
|
||||
[wiki-betalupi]: https://en.wikipedia.org/wiki/Beta_Lupi
|
||||
@@ -52,10 +46,16 @@ Stellar names (especially those of [Arabic] origin) make pretty good hostnames:
|
||||
|
||||
Beta Lupi also happens to be a real star in the southern constellation of Lupus ([wiki][wiki-betalupi]), but that's not particularly important.
|
||||
|
||||
A snippet of the [_Endless Sky_][es] map is below."#;
|
||||
A snippet of the [_Endless Sky_][es] map is below.
|
||||
|
||||
const MD_B: &str = r#"**In other words:** Try finding a `.com` domain that...
|
||||
<br/>
|
||||
|
||||
|
||||
**In other words:** Try finding a `.com` domain that...
|
||||
|
||||
- Isn't already taken
|
||||
- Doesn't sound awful
|
||||
- Isn't owned by a scalper that's selling it for $300"#;
|
||||
- Isn't owned by a scalper that's selling it for $300"
|
||||
|
||||
<br/>
|
||||
"#;
|
||||
|
||||
@@ -24,19 +24,7 @@ pub async fn links() -> Markup {
|
||||
html!(
|
||||
(Backlinks(&[("/", "home")], "links"))
|
||||
|
||||
(Markdown(MD_A))
|
||||
|
||||
hr style="margin-top: 8rem; margin-bottom: 8rem" {}
|
||||
|
||||
(Markdown(MD_B))
|
||||
|
||||
hr style="margin-top: 8rem; margin-bottom: 8rem" {}
|
||||
|
||||
(Markdown(MD_C))
|
||||
|
||||
hr style="margin-top: 8rem; margin-bottom: 8rem" {}
|
||||
|
||||
(Markdown(MD_D))
|
||||
(Markdown(MD))
|
||||
)
|
||||
))
|
||||
}
|
||||
@@ -49,11 +37,13 @@ https://www.commitstrip.com/en/
|
||||
http://www.3dprintmath.com/
|
||||
*/
|
||||
|
||||
const MD_A: &str = r#"# Bookmarks
|
||||
const MD: &str = r#"# Bookmarks
|
||||
|
||||
This is a heavily opinionated bookmarks toolbar."#;
|
||||
This is a heavily opinionated bookmarks toolbar.
|
||||
|
||||
const MD_B: &str = r#"## Podcasts
|
||||
<hr style="margin-top: 8rem; margin-bottom: 8rem"></hr>
|
||||
|
||||
## Podcasts
|
||||
|
||||
- :star: [Darknet Diaries](https://darknetdiaries.com/): A perennial classic.
|
||||
- [Art of Manliness](https://www.artofmanliness.com/podcast/): Philosophy, literaure, psychology.
|
||||
@@ -88,9 +78,11 @@ const MD_B: &str = r#"## Podcasts
|
||||
- [User Friendly](https://en.wikipedia.org/wiki/User_Friendly): Old-school. Offline and archived.
|
||||
- [PhD Comics](https://phdcomics.com/): Quality academic humor. Discontinued.
|
||||
- :star: [Spintronics](https://store.upperstory.com/collections/spintronics/products/spintronics-act-one): Mechanical circuits. Very clever toy.
|
||||
- :star: [Turing Tumble](https://store.upperstory.com/collections/turing-tumble-game/products/turing-tumble): Modern Dr. Nim"#;
|
||||
- :star: [Turing Tumble](https://store.upperstory.com/collections/turing-tumble-game/products/turing-tumble): Modern Dr. Nim
|
||||
|
||||
const MD_C: &str = r#"## Tools
|
||||
<hr style="margin-top: 8rem; margin-bottom: 8rem"></hr>
|
||||
|
||||
## Tools
|
||||
|
||||
- :star: [Numbat](https://numbat.dev/)
|
||||
- [MxToolbox](https://mxtoolbox.com/)
|
||||
@@ -157,9 +149,12 @@ const MD_C: &str = r#"## Tools
|
||||
|
||||
## Rust
|
||||
- [Understanding Memory Ordering in Rust](https://emschwartz.me/understanding-memory-ordering-in-rust/)
|
||||
- [Unfair Rust Quiz](https://this.quiz.is.fckn.gay/): wtfjs, but in Rust."#;
|
||||
- [Unfair Rust Quiz](https://this.quiz.is.fckn.gay/): wtfjs, but in Rust.
|
||||
|
||||
const MD_D: &str = r#"## Misc
|
||||
|
||||
<hr style="margin-top: 8rem; margin-bottom: 8rem"></hr>
|
||||
|
||||
## Misc
|
||||
|
||||
- [Slide Rule Collection](https://www.followingtherules.info/)
|
||||
- [MK-61 Command Reference](http://www.thimet.de/CalcCollection/Calculators/Elektronika-MK-61/CmdRef.html)
|
||||
|
||||
Reference in New Issue
Block a user