Skip to main content

yggdrasil/api/posts/
roundtrip.rs

1//! 富文本编辑器往返守卫:比较原文与编辑器往返后的 Markdown 渲染结果。
2
3use crate::api::error::ApiError;
4use leptos::prelude::*;
5
6#[cfg(feature = "server")]
7use super::helpers::get_current_admin_user;
8#[cfg(feature = "server")]
9use crate::api::error::AppError;
10
11/// 富文本编辑器载入正文后比较原文与往返 Markdown:两者渲染出的 HTML 完全相同才返回 true。
12#[server(CheckMarkdownRoundtrip, endpoint = "check_markdown_roundtrip", input = leptos::server_fn::codec::Json)]
13pub async fn check_markdown_roundtrip(
14    original: String,
15    roundtrip: String,
16) -> Result<bool, ApiError> {
17    let _user = get_current_admin_user().await?;
18    #[cfg(feature = "server")]
19    {
20        let same = tokio::task::spawn_blocking(move || renders_identically(&original, &roundtrip))
21            .await
22            .map_err(|_| AppError::Internal("Markdown 渲染任务失败"))?;
23        Ok(same)
24    }
25    #[cfg(not(feature = "server"))]
26    {
27        Ok(false)
28    }
29}
30
31#[cfg(feature = "server")]
32fn renders_identically(original: &str, roundtrip: &str) -> bool {
33    use crate::api::markdown::render_markdown_enhanced as render;
34    render(original).html == render(roundtrip).html
35}
36
37#[cfg(all(test, feature = "server"))]
38mod tests {
39    use super::renders_identically;
40
41    #[test]
42    fn alert_marker_escaped_by_editor_is_lossy() {
43        assert!(!renders_identically("> [!NOTE]\n> x", "> \\[!NOTE\\]\n> x"));
44    }
45
46    #[test]
47    fn table_column_padding_is_not_lossy() {
48        assert!(renders_identically(
49            "| a | b |\n| --- | --- |\n| 1 | 2 |",
50            "| a   | b   |\n| --- | --- |\n| 1   | 2   |"
51        ));
52    }
53
54    #[test]
55    fn escaped_intraword_delimiters_are_not_lossy() {
56        assert!(renders_identically("H~2~O 与 x^2^", "H\\~2\\~O 与 x^2\\^"));
57    }
58}