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;
|
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 {
|
impl TextComponent {
|
||||||
/// Get the text from the component
|
/// Get the text from the component
|
||||||
pub fn get_text(&self) -> &str {
|
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 {
|
impl ToPlaintext for RichText {
|
||||||
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
|
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
|
||||||
self.0
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -170,7 +206,7 @@ mod tests {
|
||||||
});
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn t_to_plaintext() {
|
fn to_plaintext() {
|
||||||
let richtext = RichText::from(TEXT_SOURCE.clone());
|
let richtext = RichText::from(TEXT_SOURCE.clone());
|
||||||
let plaintext = richtext.to_plaintext_yt_host("https://piped.kavin.rocks");
|
let plaintext = richtext.to_plaintext_yt_host("https://piped.kavin.rocks");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -197,7 +233,7 @@ aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"#
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn t_to_html() {
|
fn to_html() {
|
||||||
let richtext = RichText::from(TEXT_SOURCE.clone());
|
let richtext = RichText::from(TEXT_SOURCE.clone());
|
||||||
let html = richtext.to_html_yt_host("https://piped.kavin.rocks");
|
let html = richtext.to_html_yt_host("https://piped.kavin.rocks");
|
||||||
assert_eq!(
|
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"
|
"🎧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
|
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> {
|
pub fn video_id_from_thumbnail_url(url: &str) -> Option<String> {
|
||||||
static URL_REGEX: Lazy<Regex> =
|
static URL_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^https://i.ytimg.com/vi/([A-Za-z0-9_-]{11})/").unwrap());
|
Lazy::new(|| Regex::new(r"^https://i.ytimg.com/vi/([A-Za-z0-9_-]{11})/").unwrap());
|
||||||
|
|
|
||||||
Reference in a new issue