add: use pulldown-cmark instead

This commit is contained in:
trisua 2025-07-20 15:28:44 -04:00
parent 3f70a8f465
commit fe2e61118a
5 changed files with 68 additions and 56 deletions

53
Cargo.lock generated
View file

@ -955,6 +955,15 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "getopts"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.16" version = "0.1.16"
@ -1743,15 +1752,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "markdown"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb"
dependencies = [
"unicode-id",
]
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.35.0" version = "0.35.0"
@ -2355,6 +2355,25 @@ dependencies = [
"syn 2.0.101", "syn 2.0.101",
] ]
[[package]]
name = "pulldown-cmark"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0"
dependencies = [
"bitflags 2.9.1",
"getopts",
"memchr",
"pulldown-cmark-escape",
"unicase",
]
[[package]]
name = "pulldown-cmark-escape"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]] [[package]]
name = "qoi" name = "qoi"
version = "0.4.1" version = "0.4.1"
@ -3334,12 +3353,12 @@ dependencies = [
[[package]] [[package]]
name = "tetratto-shared" name = "tetratto-shared"
version = "12.0.1" version = "12.0.2"
dependencies = [ dependencies = [
"ammonia", "ammonia",
"chrono", "chrono",
"hex_fmt", "hex_fmt",
"markdown", "pulldown-cmark",
"rand 0.9.1", "rand 0.9.1",
"serde", "serde",
"sha2", "sha2",
@ -3871,12 +3890,6 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-id"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.18"
@ -3898,6 +3911,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unicode-width"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"

View file

@ -36,12 +36,13 @@ pub(crate) type InnerState = (DataManager, Tera, Client, Option<StripeClient>);
pub(crate) type State = Arc<RwLock<InnerState>>; pub(crate) type State = Arc<RwLock<InnerState>>;
fn render_markdown(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> { fn render_markdown(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
Ok( Ok(tetratto_shared::markdown::render_markdown(
tetratto_shared::markdown::render_markdown(&CustomEmoji::replace(value.as_str().unwrap())) &CustomEmoji::replace(value.as_str().unwrap()),
.replace("\\@", "@") true,
.replace("%5C@", "@")
.into(),
) )
.replace("\\@", "@")
.replace("%5C@", "@")
.into())
} }
fn render_emojis(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> { fn render_emojis(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {

View file

@ -267,7 +267,7 @@ pub async fn delete_by_dir_request(
} }
pub async fn render_markdown_request(Json(req): Json<RenderMarkdown>) -> impl IntoResponse { pub async fn render_markdown_request(Json(req): Json<RenderMarkdown>) -> impl IntoResponse {
tetratto_shared::markdown::render_markdown(&CustomEmoji::replace(&req.content)) tetratto_shared::markdown::render_markdown(&CustomEmoji::replace(&req.content), true)
.replace("\\@", "@") .replace("\\@", "@")
.replace("%5C@", "@") .replace("%5C@", "@")
} }

View file

@ -1,7 +1,7 @@
[package] [package]
name = "tetratto-shared" name = "tetratto-shared"
description = "Shared stuff for Tetratto" description = "Shared stuff for Tetratto"
version = "12.0.1" version = "12.0.2"
edition = "2024" edition = "2024"
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
@ -10,8 +10,8 @@ license.workspace = true
[dependencies] [dependencies]
ammonia = "4.1.1" ammonia = "4.1.1"
chrono = "0.4.41" chrono = "0.4.41"
markdown = "1.0.0"
hex_fmt = "0.3.0" hex_fmt = "0.3.0"
pulldown-cmark = "0.13.0"
rand = "0.9.1" rand = "0.9.1"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
sha2 = "0.10.9" sha2 = "0.10.9"

View file

@ -1,37 +1,24 @@
use ammonia::Builder; use ammonia::Builder;
use markdown::{to_html_with_options, Options, CompileOptions, ParseOptions, Constructs}; use pulldown_cmark::{Parser, Options, html::push_html};
use std::collections::HashSet; use std::collections::HashSet;
/// Render markdown input into HTML /// Render markdown input into HTML
pub fn render_markdown(input: &str) -> String { pub fn render_markdown(input: &str, proxy_images: bool) -> String {
let input = &parse_alignment(input); let input = &parse_alignment(input);
let options = Options {
compile: CompileOptions {
allow_any_img_src: false,
allow_dangerous_html: true,
allow_dangerous_protocol: true,
gfm_task_list_item_checkable: false,
gfm_tagfilter: false,
..Default::default()
},
parse: ParseOptions {
constructs: Constructs {
math_flow: true,
math_text: true,
..Constructs::gfm()
},
gfm_strikethrough_single_tilde: false,
math_text_single_dollar: false,
mdx_expression_parse: None,
mdx_esm_parse: None,
..Default::default()
},
};
let html = match to_html_with_options(input, &options) { let mut options = Options::empty();
Ok(h) => h, options.insert(Options::ENABLE_STRIKETHROUGH);
Err(e) => e.to_string(), options.insert(Options::ENABLE_GFM);
}; options.insert(Options::ENABLE_FOOTNOTES);
options.insert(Options::ENABLE_TABLES);
options.insert(Options::ENABLE_HEADING_ATTRIBUTES);
options.insert(Options::ENABLE_SUBSCRIPT);
options.insert(Options::ENABLE_SUPERSCRIPT);
let parser = Parser::new_ext(input, options);
let mut html = String::new();
push_html(&mut html, parser);
let mut allowed_attributes = HashSet::new(); let mut allowed_attributes = HashSet::new();
allowed_attributes.insert("id"); allowed_attributes.insert("id");
@ -43,7 +30,7 @@ pub fn render_markdown(input: &str) -> String {
allowed_attributes.insert("align"); allowed_attributes.insert("align");
allowed_attributes.insert("src"); allowed_attributes.insert("src");
Builder::default() let output = Builder::default()
.generic_attributes(allowed_attributes) .generic_attributes(allowed_attributes)
.add_tags(&[ .add_tags(&[
"video", "source", "img", "b", "span", "p", "i", "strong", "em", "a", "align", "video", "source", "img", "b", "span", "p", "i", "strong", "em", "a", "align",
@ -53,11 +40,16 @@ pub fn render_markdown(input: &str) -> String {
.add_url_schemes(&["atto"]) .add_url_schemes(&["atto"])
.clean(&html) .clean(&html)
.to_string() .to_string()
.replace( .replace("<video loading=", "<video controls loading=");
if proxy_images {
output.replace(
"src=\"http", "src=\"http",
"loading=\"lazy\" src=\"/api/v1/util/proxy?url=http", "loading=\"lazy\" src=\"/api/v1/util/proxy?url=http",
) )
.replace("<video loading=", "<video controls loading=") } else {
output
}
} }
fn parse_alignment_line(line: &str, output: &mut String, buffer: &mut String, is_in_pre: bool) { fn parse_alignment_line(line: &str, output: &mut String, buffer: &mut String, is_in_pre: bool) {