Smart links

This commit is contained in:
2025-11-05 22:05:24 -08:00
parent 399fccb73b
commit dfffe824d1
3 changed files with 78 additions and 117 deletions

View File

@@ -1,4 +1,5 @@
use lazy_static::lazy_static;
use markdown_it::generics::inline::full_link;
use markdown_it::parser::block::{BlockRule, BlockState};
use markdown_it::parser::core::Root;
use markdown_it::parser::inline::{InlineRule, InlineState};
@@ -9,12 +10,47 @@ use std::str::FromStr;
use crate::components::fa::FAIcon;
use crate::components::mangle::{MangledBetaEmail, MangledGoogleEmail};
use crate::components::misc::{Backlinks, FarLink};
use crate::components::misc::Backlinks;
lazy_static! {
static ref MdParser: MarkdownIt = {
let mut md = markdown_it::MarkdownIt::new();
markdown_it::plugins::cmark::add(&mut md);
{
use markdown_it::plugins::cmark::*;
inline::newline::add(&mut md);
inline::escape::add(&mut md);
inline::backticks::add(&mut md);
inline::emphasis::add(&mut md);
// Replaced with smart links
//inline::link::add(&mut md);
full_link::add::<false>(&mut md, |href, title| {
Node::new(SmartLink {
url: href.unwrap_or_default(),
title,
})
});
inline::image::add(&mut md);
inline::autolink::add(&mut md);
inline::entity::add(&mut md);
block::code::add(&mut md);
block::fence::add(&mut md);
block::blockquote::add(&mut md);
block::hr::add(&mut md);
block::list::add(&mut md);
block::reference::add(&mut md);
block::heading::add(&mut md);
block::lheading::add(&mut md);
block::paragraph::add(&mut md);
}
markdown_it::plugins::html::add(&mut md);
md.block.add_rule::<YamlFrontMatter>().before_all();
@@ -100,6 +136,39 @@ pub fn page_from_markdown(md: impl Into<String>, default_image: Option<String>)
// MARK: extensions
//
//
// MARK: smart link
//
#[derive(Debug)]
pub struct SmartLink {
pub url: String,
pub title: Option<String>,
}
impl NodeValue for SmartLink {
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
let mut attrs = node.attrs.clone();
attrs.push(("href", self.url.clone()));
if let Some(title) = &self.title {
attrs.push(("title", title.clone()));
}
let external = !(self.url.starts_with(".") || self.url.starts_with("/"));
// Open external links in a new tab
if external {
attrs.push(("target", "_blank".into()));
attrs.push(("rel", "noopener noreferrer".into()));
}
fmt.open("a", &attrs);
fmt.contents(&node.children);
fmt.close("a");
}
}
//
// MARK: emote
//
@@ -159,10 +228,6 @@ impl NodeValue for InlineMdx {
return;
}
if mdx_external(&self.0, node, fmt) {
return;
}
fmt.open("code", &[]);
fmt.text(&self.0);
fmt.close("code");
@@ -340,105 +405,6 @@ fn mdx_include(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
return true;
}
fn mdx_external(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool {
// Parse inside of mdx: `external("text", "link")`
let args = {
let mdx = mdx
.trim()
.trim_start_matches('{')
.trim_end_matches('}')
.trim();
if !mdx.starts_with("external(") {
return false;
}
let skip = 9;
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 args = mdx[skip..end].trim();
let trail = mdx[end + 1..].trim();
if !trail.is_empty() {
return false;
}
args
};
// TODO: better parsing, handle , in string
let mut split = args.split(",");
let title = match split.next() {
Some(mut x) => {
x = x.trim();
if &x[0..1] == "\"" {
x = &x[1..]
} else {
return false;
}
if &x[x.len() - 1..] == "\"" {
x = &x[0..x.len() - 1]
} else {
return false;
}
x
}
None => return false,
};
let link = match split.next() {
Some(mut x) => {
x = x.trim();
if &x[0..1] == "\"" {
x = &x[1..]
} else {
return false;
}
if &x[x.len() - 1..] == "\"" {
x = &x[0..x.len() - 1]
} else {
return false;
}
x
}
None => return false,
};
if split.next().is_some() {
return false;
}
fmt.text_raw(&FarLink(link, title).render().0);
return true;
}
//
// MARK: yaml frontmatter
//

View File

@@ -31,4 +31,4 @@ A snippet of the [_Endless Sky_][es] map is below.
<br/>
<img alt="betalupi map" class="image" src="/assets/img/betalupi.png"></img>
<img alt="betalupi map" src="/assets/img/betalupi.png"></img>

View File

@@ -6,15 +6,12 @@ slug = "handouts"
# Mark's Handouts
[ORMC]: https://circles.math.ucla.edu/circles
This page lists all the handouts I've written for my classes at the
{{external("ORMC", "https://circles.math.ucla.edu/circles")}},
This page lists all the handouts I've written for my classes at the [ORMC],
arguably the best math circle in the western world. We teach students mathematics
far beyond the regular school curriculum, much like
{{external("AOPS", "https://artofproblemsolving.com")}}
and the
{{external("BMC", "https://mathcircle.berkeley.edu")}}
far beyond the regular school curriculum, much like [AOPS](https://artofproblemsolving.com)
and the [BMC](https://mathcircle.berkeley.edu).
<br></br>
@@ -36,10 +33,8 @@ are written with this in mind.\
I do not expect the average student to finish all problems during this two-hour session.
If the class finishes early, the lesson is either too short or too easy.
The sources for all these handouts are available
{{external("here", "https://git.betalupi.com/mark/handouts")}} \
Some are written in LaTeX, some are in
{{external("Typst", "https://typst.app")}}. \
The sources for all these handouts are available [here](https://git.betalupi.com/mark/handouts).\
Some are written in LaTeX, some are in [Typst](https://typst.app). \
The latter is vastly superior.
<br></br>