feat: add richtext to markdown conversion
This commit is contained in:
parent
cbeb14f3fd
commit
a0819ac72c
2 changed files with 93 additions and 2 deletions
|
|
@ -57,6 +57,18 @@ pub trait ToHtml {
|
|||
fn to_html_yt_host(&self, yt_host: &str) -> String;
|
||||
}
|
||||
|
||||
/// Trait for converting rich text to markdown.
|
||||
pub trait ToMarkdown {
|
||||
/// Convert rich text to markdown.
|
||||
fn to_markdown(&self) -> String {
|
||||
self.to_markdown_yt_host("https://www.youtube.com")
|
||||
}
|
||||
/// Convert rich text to markdown while changing YouTube links to a custom site.
|
||||
///
|
||||
/// expected yt_host format (no trailing slash): `https://example.com`
|
||||
fn to_markdown_yt_host(&self, yt_host: &str) -> String;
|
||||
}
|
||||
|
||||
impl TextComponent {
|
||||
/// Get the text from the component
|
||||
pub fn get_text(&self) -> &str {
|
||||
|
|
@ -110,6 +122,21 @@ impl ToHtml for TextComponent {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToMarkdown for TextComponent {
|
||||
fn to_markdown_yt_host(&self, yt_host: &str) -> String {
|
||||
match self {
|
||||
TextComponent::Text(text) => util::escape_markdown(text),
|
||||
TextComponent::Web { text, .. } | TextComponent::YouTube { text, .. } => {
|
||||
format!(
|
||||
"[{}]({})",
|
||||
util::escape_markdown(text),
|
||||
self.get_url(yt_host)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPlaintext for RichText {
|
||||
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
|
||||
self.0
|
||||
|
|
@ -125,6 +152,15 @@ impl ToHtml for RichText {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToMarkdown for RichText {
|
||||
fn to_markdown_yt_host(&self, yt_host: &str) -> String {
|
||||
self.0
|
||||
.iter()
|
||||
.map(|c| c.to_markdown_yt_host(yt_host))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -170,7 +206,7 @@ mod tests {
|
|||
});
|
||||
|
||||
#[test]
|
||||
fn t_to_plaintext() {
|
||||
fn to_plaintext() {
|
||||
let richtext = RichText::from(TEXT_SOURCE.clone());
|
||||
let plaintext = richtext.to_plaintext_yt_host("https://piped.kavin.rocks");
|
||||
assert_eq!(
|
||||
|
|
@ -197,7 +233,7 @@ aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"#
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn t_to_html() {
|
||||
fn to_html() {
|
||||
let richtext = RichText::from(TEXT_SOURCE.clone());
|
||||
let html = richtext.to_html_yt_host("https://piped.kavin.rocks");
|
||||
assert_eq!(
|
||||
|
|
@ -205,4 +241,31 @@ aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"#
|
|||
"🎧Listen and download aespa's debut single "Black Mamba": <a href=\"https://smarturl.it/aespa_BlackMamba\" target=\"_blank\" rel=\"noreferrer\">https://smarturl.it/aespa_BlackMamba</a><br>🐍The Debut Stage <a href=\"https://piped.kavin.rocks/watch?v=Ky5RT5oGg0w\">https://youtu.be/Ky5RT5oGg0w</a><br><br>🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: <a href=\"https://www.ticketmaster.com/event/0A005CCD9E871F6E\" target=\"_blank\" rel=\"noreferrer\">https://www.ticketmaster.com/event/0A...</a><br><br>Subscribe to aespa Official YouTube Channel!<br><a href=\"https://www.youtube.com/aespa?sub_confirmation=1\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/aespa?sub_con...</a><br><br>aespa official<br><a href=\"https://www.youtube.com/c/aespa\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/c/aespa</a><br><a href=\"https://www.instagram.com/aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.instagram.com/aespa_official</a><br><a href=\"https://www.tiktok.com/@aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.tiktok.com/@aespa_official</a><br><a href=\"https://twitter.com/aespa_Official\" target=\"_blank\" rel=\"noreferrer\">https://twitter.com/aespa_Official</a><br><a href=\"https://www.facebook.com/aespa.official\" target=\"_blank\" rel=\"noreferrer\">https://www.facebook.com/aespa.official</a><br><a href=\"https://weibo.com/aespa\" target=\"_blank\" rel=\"noreferrer\">https://weibo.com/aespa</a><br><br>#aespa #æspa #BlackMamba #블랙맘바 #에스파<br>aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_markdown() {
|
||||
let richtext = RichText::from(TEXT_SOURCE.clone());
|
||||
let markdown = richtext.to_markdown_yt_host("https://piped.kavin.rocks");
|
||||
assert_eq!(
|
||||
markdown,
|
||||
r#"🎧Listen and download aespa's debut single "Black Mamba": [https://smarturl.it/aespa\_BlackMamba](https://smarturl.it/aespa_BlackMamba)<br>
|
||||
🐍The Debut Stage [https://youtu.be/Ky5RT5oGg0w](https://piped.kavin.rocks/watch?v=Ky5RT5oGg0w)
|
||||
|
||||
🎟️ aespa Showcase SYNK in LA! Tickets now on sale: [https://www.ticketmaster.com/event/0A...](https://www.ticketmaster.com/event/0A005CCD9E871F6E)
|
||||
|
||||
Subscribe to aespa Official YouTube Channel!<br>
|
||||
[https://www.youtube.com/aespa?sub\_con...](https://www.youtube.com/aespa?sub_confirmation=1)
|
||||
|
||||
aespa official<br>
|
||||
[https://www.youtube.com/c/aespa](https://www.youtube.com/c/aespa)<br>
|
||||
[https://www.instagram.com/aespa\_official](https://www.instagram.com/aespa_official)<br>
|
||||
[https://www.tiktok.com/@aespa\_official](https://www.tiktok.com/@aespa_official)<br>
|
||||
[https://twitter.com/aespa\_Official](https://twitter.com/aespa_Official)<br>
|
||||
[https://www.facebook.com/aespa.official](https://www.facebook.com/aespa.official)<br>
|
||||
[https://weibo.com/aespa](https://weibo.com/aespa)
|
||||
|
||||
\#aespa \#æspa \#BlackMamba \#블랙맘바 \#에스파<br>
|
||||
aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"#
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,6 +390,34 @@ pub fn escape_html(input: &str) -> String {
|
|||
buf
|
||||
}
|
||||
|
||||
/// Replace all markdown control characters to make a string safe for
|
||||
/// inserting into Markdown.
|
||||
pub fn escape_markdown(input: &str) -> String {
|
||||
let mut buf = String::with_capacity(input.len());
|
||||
let mut chars = input.chars().peekable();
|
||||
|
||||
while let Some(c) = chars.next() {
|
||||
match c {
|
||||
'<' => buf.push_str("<"),
|
||||
'>' => buf.push_str(">"),
|
||||
'\n' => {
|
||||
if chars.peek() == Some(&'\n') {
|
||||
chars.next();
|
||||
buf.push_str("\n\n");
|
||||
} else {
|
||||
buf.push_str("<br>\n");
|
||||
}
|
||||
}
|
||||
'*' | '#' | '(' | ')' | '[' | ']' | '_' | '`' => {
|
||||
buf.push('\\');
|
||||
buf.push(c);
|
||||
}
|
||||
_ => buf.push(c),
|
||||
};
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn video_id_from_thumbnail_url(url: &str) -> Option<String> {
|
||||
static URL_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^https://i.ytimg.com/vi/([A-Za-z0-9_-]{11})/").unwrap());
|
||||
|
|
|
|||
Reference in a new issue