Render markdown contained in bio and profile metadata values
This commit is contained in:
parent
43a70fb93f
commit
ee7a61833d
2 changed files with 113 additions and 15 deletions
|
@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::errors::ValidationError;
|
||||
use crate::errors::{HttpError, ValidationError};
|
||||
use crate::identity::did::Did;
|
||||
use crate::mastodon_api::pagination::PageSize;
|
||||
use crate::mastodon_api::uploads::{UploadError, save_validated_b64_file};
|
||||
|
@ -21,7 +21,10 @@ use crate::models::users::types::{
|
|||
validate_local_username,
|
||||
User,
|
||||
};
|
||||
use crate::utils::files::get_file_url;
|
||||
use crate::utils::{
|
||||
files::get_file_url,
|
||||
markdown::markdown_basic_to_html,
|
||||
};
|
||||
|
||||
/// https://docs.joinmastodon.org/entities/field/
|
||||
#[derive(Serialize)]
|
||||
|
@ -242,7 +245,14 @@ impl AccountUpdateData {
|
|||
current_identity_proofs: &[IdentityProof],
|
||||
current_payment_options: &[PaymentOption],
|
||||
media_dir: &Path,
|
||||
) -> Result<ProfileUpdateData, UploadError> {
|
||||
) -> Result<ProfileUpdateData, HttpError> {
|
||||
let maybe_bio = if let Some(ref note_source) = self.note_source {
|
||||
let bio = markdown_basic_to_html(note_source)
|
||||
.map_err(|_| ValidationError("invalid markdown"))?;
|
||||
Some(bio)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let avatar = process_b64_image_field_value(
|
||||
self.avatar, current_avatar.clone(), media_dir,
|
||||
)?;
|
||||
|
@ -251,10 +261,16 @@ impl AccountUpdateData {
|
|||
)?;
|
||||
let identity_proofs = current_identity_proofs.to_vec();
|
||||
let payment_options = current_payment_options.to_vec();
|
||||
let extra_fields = self.fields_attributes.unwrap_or(vec![]);
|
||||
let mut extra_fields = self.fields_attributes.unwrap_or(vec![]);
|
||||
for extra_field in extra_fields.iter_mut() {
|
||||
let value_source = extra_field.value_source.as_ref()
|
||||
.ok_or(ValidationError("missing value source"))?;
|
||||
extra_field.value = markdown_basic_to_html(value_source)
|
||||
.map_err(|_| ValidationError("invalid markdown"))?;
|
||||
};
|
||||
let profile_data = ProfileUpdateData {
|
||||
display_name: self.display_name,
|
||||
bio: self.note,
|
||||
bio: maybe_bio,
|
||||
bio_source: self.note_source,
|
||||
avatar,
|
||||
banner,
|
||||
|
|
|
@ -59,7 +59,29 @@ fn node_to_markdown<'a>(
|
|||
Ok(markdown)
|
||||
}
|
||||
|
||||
/// Supported markdown features:
|
||||
fn document_to_html<'a>(
|
||||
document: &'a AstNode<'a>,
|
||||
options: &ComrakOptions,
|
||||
) -> Result<String, MarkdownError> {
|
||||
let mut output = vec![];
|
||||
format_html(document, options, &mut output)?;
|
||||
let html = String::from_utf8(output)?;
|
||||
Ok(html)
|
||||
}
|
||||
|
||||
/// Removes extra soft breaks from a HTML document generated by comrak
|
||||
fn fix_linebreaks(html: &str) -> String {
|
||||
html
|
||||
// Fix hardbreaks
|
||||
.replace("<br />\n", "<br>")
|
||||
// Remove extra soft breaks
|
||||
.replace(">\n<", "><")
|
||||
.trim_end_matches('\n')
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Markdown Lite
|
||||
/// Supported features:
|
||||
/// - bold and italic
|
||||
/// - links and autolinks
|
||||
/// - inline code and code blocks
|
||||
|
@ -173,15 +195,61 @@ pub fn markdown_lite_to_html(text: &str) -> Result<String, MarkdownError> {
|
|||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut output = vec![];
|
||||
format_html(root, &options, &mut output)?;
|
||||
let html = String::from_utf8(output)?
|
||||
// Fix hardbreaks
|
||||
.replace("<br />\n", "<br>")
|
||||
// Remove extra soft breaks
|
||||
.replace(">\n<", "><")
|
||||
.trim_end_matches('\n')
|
||||
.to_string();
|
||||
let html = document_to_html(root, &options)?;
|
||||
let html = fix_linebreaks(&html);
|
||||
Ok(html)
|
||||
}
|
||||
|
||||
/// Markdown Basic
|
||||
/// Supported features: links, linebreaks
|
||||
pub fn markdown_basic_to_html(text: &str) -> Result<String, MarkdownError> {
|
||||
let options = build_comrak_options();
|
||||
let arena = Arena::new();
|
||||
let root = parse_document(
|
||||
&arena,
|
||||
text,
|
||||
&options,
|
||||
);
|
||||
|
||||
iter_nodes(root, &|node| {
|
||||
let node_value = node.data.borrow().value.clone();
|
||||
match node_value {
|
||||
NodeValue::Document |
|
||||
NodeValue::Text(_) |
|
||||
NodeValue::Link(_) |
|
||||
NodeValue::SoftBreak |
|
||||
NodeValue::LineBreak
|
||||
=> (),
|
||||
NodeValue::Paragraph => {
|
||||
if node.next_sibling().is_some() {
|
||||
// If this is not the last paragraph,
|
||||
// insert a line break, otherwise line break will not
|
||||
// be preserved during HTML cleaning.
|
||||
if let Some(last_child) = node.last_child() {
|
||||
let last_child_value = &last_child.data.borrow().value;
|
||||
if !matches!(last_child_value, NodeValue::LineBreak) {
|
||||
let line_break = AstNode::from(NodeValue::LineBreak);
|
||||
node.append(arena.alloc(line_break));
|
||||
};
|
||||
};
|
||||
};
|
||||
},
|
||||
_ => {
|
||||
// Replace node with text node containing markdown
|
||||
let markdown = node_to_markdown(node, &options)?;
|
||||
for child in node.children() {
|
||||
child.detach();
|
||||
};
|
||||
let text = NodeValue::Text(markdown.as_bytes().to_vec());
|
||||
let mut borrowed_node = node.data.borrow_mut();
|
||||
*borrowed_node = Ast::new(text);
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let html = document_to_html(root, &options)?;
|
||||
let html = fix_linebreaks(&html);
|
||||
Ok(html)
|
||||
}
|
||||
|
||||
|
@ -235,6 +303,20 @@ mod tests {
|
|||
assert_eq!(html, format!("<p>{}</p>", text));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_basic_to_html() {
|
||||
let text = "test **bold** test *italic* test ~~strike~~ with `code`, <span>html</span> and https://example.com\nnew line\n\nanother line";
|
||||
let html = markdown_basic_to_html(text).unwrap();
|
||||
let expected_html = concat!(
|
||||
"<p>",
|
||||
"test **bold** test *italic* test ~~strike~~ with `code`, <span>html</span>",
|
||||
r#" and <a href="https://example.com">https://example.com</a>"#,
|
||||
"<br>new line<br></p>",
|
||||
"<p>another line</p>",
|
||||
);
|
||||
assert_eq!(html, expected_html);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_to_html() {
|
||||
let text = "# heading\n\ntest";
|
||||
|
|
Loading…
Reference in a new issue