mirror of https://github.com/LemmyNet/lemmy.git
Add markdown rule to add rel=nofollow for all links
parent
6d1a7c8ae0
commit
c2584d3e6e
|
@ -1,86 +0,0 @@
|
||||||
use markdown_it::MarkdownIt;
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
mod spoiler_rule;
|
|
||||||
|
|
||||||
static MARKDOWN_PARSER: Lazy<MarkdownIt> = Lazy::new(|| {
|
|
||||||
let mut parser = MarkdownIt::new();
|
|
||||||
markdown_it::plugins::cmark::add(&mut parser);
|
|
||||||
markdown_it::plugins::extra::add(&mut parser);
|
|
||||||
spoiler_rule::add(&mut parser);
|
|
||||||
|
|
||||||
parser
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn markdown_to_html(text: &str) -> String {
|
|
||||||
MARKDOWN_PARSER.parse(text).xrender()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#![allow(clippy::unwrap_used)]
|
|
||||||
#![allow(clippy::indexing_slicing)]
|
|
||||||
|
|
||||||
use crate::utils::markdown::markdown_to_html;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_basic_markdown() {
|
|
||||||
let tests: Vec<_> = vec![
|
|
||||||
(
|
|
||||||
"headings",
|
|
||||||
"# h1\n## h2\n### h3\n#### h4\n##### h5\n###### h6",
|
|
||||||
"<h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6>\n"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"line breaks",
|
|
||||||
"First\rSecond",
|
|
||||||
"<p>First\nSecond</p>\n"),
|
|
||||||
(
|
|
||||||
"emphasis",
|
|
||||||
"__bold__ **bold** *italic* ***bold+italic***",
|
|
||||||
"<p><strong>bold</strong> <strong>bold</strong> <em>italic</em> <em><strong>bold+italic</strong></em></p>\n"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"blockquotes",
|
|
||||||
"> #### Hello\n > \n > - Hola\n > - ์์ \n>> Goodbye\n",
|
|
||||||
"<blockquote>\n<h4>Hello</h4>\n<ul>\n<li>Hola</li>\n<li>์์</li>\n</ul>\n<blockquote>\n<p>Goodbye</p>\n</blockquote>\n</blockquote>\n"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"lists (ordered, unordered)",
|
|
||||||
"1. pen\n2. apple\n3. apple pen\n- pen\n- pineapple\n- pineapple pen",
|
|
||||||
"<ol>\n<li>pen</li>\n<li>apple</li>\n<li>apple pen</li>\n</ol>\n<ul>\n<li>pen</li>\n<li>pineapple</li>\n<li>pineapple pen</li>\n</ul>\n"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"code and code blocks",
|
|
||||||
"this is my amazing `code snippet` and my amazing ```code block```",
|
|
||||||
"<p>this is my amazing <code>code snippet</code> and my amazing <code>code block</code></p>\n"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"links",
|
|
||||||
"[Lemmy](https://join-lemmy.org/ \"Join Lemmy!\")",
|
|
||||||
"<p><a href=\"https://join-lemmy.org/\" title=\"Join Lemmy!\">Lemmy</a></p>\n"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"images",
|
|
||||||
"![My linked image](https://image.com \"image alt text\")",
|
|
||||||
"<p><img src=\"https://image.com\" alt=\"My linked image\" title=\"image alt text\" /></p>\n"
|
|
||||||
),
|
|
||||||
// Ensure any custom plugins are added to 'MARKDOWN_PARSER' implementation.
|
|
||||||
(
|
|
||||||
"basic spoiler",
|
|
||||||
"::: spoiler click to see more\nhow spicy!\n:::\n",
|
|
||||||
"<details><summary>click to see more</summary><p>how spicy!\n</p></details>\n"
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
tests.iter().for_each(|&(msg, input, expected)| {
|
|
||||||
let result = markdown_to_html(input);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
result, expected,
|
|
||||||
"Testing {}, with original input '{}'",
|
|
||||||
msg, input
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
use markdown_it::generics::inline::full_link;
|
||||||
|
use markdown_it::{MarkdownIt, Node, NodeValue, Renderer};
|
||||||
|
|
||||||
|
/// Renders markdown links. Copied directly from markdown-it source, unlike original code it also
|
||||||
|
/// sets `rel=nofollow` attribute.
|
||||||
|
///
|
||||||
|
/// TODO: We can set nofollow only if post was not made by mod/admin, but then we have to construct
|
||||||
|
/// new parser for every invocation which might have performance implications.
|
||||||
|
/// https://github.com/markdown-it-rust/markdown-it/blob/master/src/plugins/cmark/inline/link.rs
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Link {
|
||||||
|
pub url: String,
|
||||||
|
pub title: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeValue for Link {
|
||||||
|
fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
|
||||||
|
let mut attrs = node.attrs.clone();
|
||||||
|
attrs.push(("href", self.url.clone()));
|
||||||
|
attrs.push(("rel", "nofollow".to_string()));
|
||||||
|
|
||||||
|
if let Some(title) = &self.title {
|
||||||
|
attrs.push(("title", title.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.open("a", &attrs);
|
||||||
|
fmt.contents(&node.children);
|
||||||
|
fmt.close("a");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(md: &mut MarkdownIt) {
|
||||||
|
full_link::add::<false>(md, |href, title| Node::new(Link {
|
||||||
|
url: href.unwrap_or_default(),
|
||||||
|
title,
|
||||||
|
}));
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
use markdown_it::MarkdownIt;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
mod spoiler_rule;
|
||||||
|
mod link_rule;
|
||||||
|
|
||||||
|
static MARKDOWN_PARSER: Lazy<MarkdownIt> = Lazy::new(|| {
|
||||||
|
let mut parser = MarkdownIt::new();
|
||||||
|
markdown_it::plugins::cmark::add(&mut parser);
|
||||||
|
markdown_it::plugins::extra::add(&mut parser);
|
||||||
|
spoiler_rule::add(&mut parser);
|
||||||
|
link_rule::add(&mut parser);
|
||||||
|
|
||||||
|
parser
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn markdown_to_html(text: &str) -> String {
|
||||||
|
MARKDOWN_PARSER.parse(text).xrender()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
#![allow(clippy::indexing_slicing)]
|
||||||
|
|
||||||
|
use crate::utils::markdown::markdown_to_html;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_markdown() {
|
||||||
|
let tests: Vec<_> = vec![
|
||||||
|
(
|
||||||
|
"headings",
|
||||||
|
"# h1\n## h2\n### h3\n#### h4\n##### h5\n###### h6",
|
||||||
|
"<h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6>\n"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"line breaks",
|
||||||
|
"First\rSecond",
|
||||||
|
"<p>First\nSecond</p>\n"),
|
||||||
|
(
|
||||||
|
"emphasis",
|
||||||
|
"__bold__ **bold** *italic* ***bold+italic***",
|
||||||
|
"<p><strong>bold</strong> <strong>bold</strong> <em>italic</em> <em><strong>bold+italic</strong></em></p>\n"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"blockquotes",
|
||||||
|
"> #### Hello\n > \n > - Hola\n > - ์์ \n>> Goodbye\n",
|
||||||
|
"<blockquote>\n<h4>Hello</h4>\n<ul>\n<li>Hola</li>\n<li>์์</li>\n</ul>\n<blockquote>\n<p>Goodbye</p>\n</blockquote>\n</blockquote>\n"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"lists (ordered, unordered)",
|
||||||
|
"1. pen\n2. apple\n3. apple pen\n- pen\n- pineapple\n- pineapple pen",
|
||||||
|
"<ol>\n<li>pen</li>\n<li>apple</li>\n<li>apple pen</li>\n</ol>\n<ul>\n<li>pen</li>\n<li>pineapple</li>\n<li>pineapple pen</li>\n</ul>\n"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"code and code blocks",
|
||||||
|
"this is my amazing `code snippet` and my amazing ```code block```",
|
||||||
|
"<p>this is my amazing <code>code snippet</code> and my amazing <code>code block</code></p>\n"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"links",
|
||||||
|
"[Lemmy](https://join-lemmy.org/ \"Join Lemmy!\")",
|
||||||
|
"<p><a href=\"https://join-lemmy.org/\" rel=\"nofollow\" title=\"Join Lemmy!\">Lemmy</a></p>\n"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"images",
|
||||||
|
"![My linked image](https://image.com \"image alt text\")",
|
||||||
|
"<p><img src=\"https://image.com\" alt=\"My linked image\" title=\"image alt text\" /></p>\n"
|
||||||
|
),
|
||||||
|
// Ensure any custom plugins are added to 'MARKDOWN_PARSER' implementation.
|
||||||
|
(
|
||||||
|
"basic spoiler",
|
||||||
|
"::: spoiler click to see more\nhow spicy!\n:::\n",
|
||||||
|
"<details><summary>click to see more</summary><p>how spicy!\n</p></details>\n"
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
tests.iter().for_each(|&(msg, input, expected)| {
|
||||||
|
let result = markdown_to_html(input);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result, expected,
|
||||||
|
"Testing {}, with original input '{}'",
|
||||||
|
msg, input
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loadingโฆ
Reference in New Issue