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
//