diff --git a/rslib/src/markdown.rs b/rslib/src/markdown.rs index 88bb211f3..3f1da808a 100644 --- a/rslib/src/markdown.rs +++ b/rslib/src/markdown.rs @@ -1,12 +1,134 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use std::borrow::Cow; + use pulldown_cmark::html; +use pulldown_cmark::Options; use pulldown_cmark::Parser; -pub(crate) fn render_markdown(markdown: &str) -> String { - let mut buf = String::with_capacity(markdown.len()); - let parser = Parser::new(markdown); +/// Render markdown to HTML with GitHub Flavored Markdown extensions. +/// +/// Enabled extensions: +/// - Tables (GFM) +/// - Strikethrough (~~text~~) +/// - Task lists (- [ ] and - [x]) +/// - Footnotes +/// +/// # Example +/// ``` +/// let html = render_markdown("| A | B |\n|---|---|\n| 1 | 2 |"); +/// // Returns:
tags. +/// Useful for inline content where paragraph tags would break layout. +pub(crate) fn render_markdown_inline(markdown: &str) -> Cow<'_, str> { + let rendered = render_markdown(markdown); + + // Strip outer
...
tags if present (for single-paragraph content) + if let Cow::Owned(s) = rendered { + let trimmed = s.trim(); + if trimmed.starts_with("") && trimmed.ends_with("
") && trimmed.matches("").count() == 1 {
+ let inner = &trimmed[3..trimmed.len() - 4];
+ return Cow::Owned(inner.to_string());
+ }
+ Cow::Owned(s)
+ } else {
+ rendered
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_basic_markdown() {
+ assert!(render_markdown("**bold**").contains("bold"));
+ assert!(render_markdown("*italic*").contains("italic"));
+ assert!(render_markdown("`code`").contains("code"));
+ }
+
+ #[test]
+ fn test_tables() {
+ let table = "| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | Cell 2 |";
+ let html = render_markdown(table);
+ assert!(html.contains("
| Header 1 | ")); + assert!(html.contains("Cell 1 | ")); + } + + #[test] + fn test_strikethrough() { + let result = render_markdown("~~deleted~~"); + assert!(result.contains("
|---|
| ")); + assert!(result.contains(" | "));
+ }
+
+ #[test]
+ fn markdown_strikethrough() {
+ let ctx = RenderContext {
+ fields: &Default::default(),
+ nonempty_fields: &Default::default(),
+ frontside: None,
+ card_ord: 0,
+ partial_for_python: false,
+ };
+
+ let (result, _) = apply_filters("~~deleted~~", &["markdown"], "Field", &ctx);
+ assert!(result.contains(" ")); + } + + #[test] + fn markdown_code_blocks() { + let ctx = RenderContext { + fields: &Default::default(), + nonempty_fields: &Default::default(), + frontside: None, + card_ord: 0, + partial_for_python: false, + }; + + let code = "```python\nprint('hello')\n```"; + let (result, _) = apply_filters(code, &["markdown"], "Field", &ctx); + assert!(result.contains(" "));
+ assert!(result.contains(" |
|---|