at main 124 lines 4.4 kB view raw
1//! WASM bindings for weaver-renderer. 2//! 3//! Exposes sync rendering functions to JS/TS apps via wasm-bindgen. 4 5use jacquard::types::string::AtUri; 6use jacquard::types::value::Data; 7use serde::Deserialize; 8use serde_wasm_bindgen::Deserializer; 9use wasm_bindgen::prelude::*; 10 11mod types; 12 13pub use types::*; 14 15/// Initialize panic hook for better error messages in console. 16#[wasm_bindgen(start)] 17pub fn init() { 18 console_error_panic_hook::set_once(); 19} 20 21/// Render an AT Protocol record as HTML. 22/// 23/// Takes a record URI and the record data (typically fetched from an appview). 24/// Returns the rendered HTML string. 25/// 26/// # Arguments 27/// * `at_uri` - The AT Protocol URI (e.g., "at://did:plc:.../app.bsky.feed.post/...") 28/// * `record_json` - The record data as JSON 29/// * `fallback_author` - Optional author profile for records that don't include author info 30/// * `resolved_content` - Optional pre-rendered embed content 31#[wasm_bindgen] 32pub fn render_record( 33 at_uri: &str, 34 record_json: JsValue, 35 fallback_author: Option<JsValue>, 36 resolved_content: Option<JsResolvedContent>, 37) -> Result<String, JsError> { 38 let uri = AtUri::new(at_uri).map_err(|e| JsError::new(&format!("Invalid AT URI: {}", e)))?; 39 40 // Use Deserializer directly to avoid DeserializeOwned bounds (breaks Jacquard types). 41 let deserializer = Deserializer::from(record_json); 42 let data = Data::deserialize(deserializer) 43 .map_err(|e| JsError::new(&format!("Invalid record JSON: {}", e)))?; 44 45 let author: Option<weaver_api::sh_weaver::actor::ProfileDataView<'_>> = fallback_author 46 .map(|v| { 47 let deserializer = Deserializer::from(v); 48 weaver_api::sh_weaver::actor::ProfileDataView::deserialize(deserializer) 49 }) 50 .transpose() 51 .map_err(|e| JsError::new(&format!("Invalid author JSON: {}", e)))?; 52 53 let resolved = resolved_content.map(|r| r.into_inner()); 54 55 weaver_renderer::atproto::render_record(&uri, &data, author.as_ref(), resolved.as_ref()) 56 .map_err(|e| JsError::new(&e.to_string())) 57} 58 59/// Render markdown to HTML. 60/// 61/// # Arguments 62/// * `markdown` - The markdown source text 63/// * `resolved_content` - Optional pre-rendered embed content 64#[wasm_bindgen] 65pub fn render_markdown( 66 markdown: &str, 67 resolved_content: Option<JsResolvedContent>, 68) -> Result<String, JsError> { 69 use weaver_renderer::atproto::ClientWriter; 70 71 let resolved = resolved_content.map(|r| r.into_inner()).unwrap_or_default(); 72 73 let parser = markdown_weaver::Parser::new_ext(markdown, weaver_renderer::default_md_options()) 74 .into_offset_iter(); 75 let events: Vec<_> = parser.collect(); 76 77 let mut html_buf = String::new(); 78 let writer = ClientWriter::new(events.into_iter(), &mut html_buf, markdown) 79 .with_embed_provider(&resolved); 80 81 writer.run().map_err(|_| JsError::new("Render error"))?; 82 83 Ok(html_buf) 84} 85 86/// Render LaTeX math to MathML. 87/// 88/// # Arguments 89/// * `latex` - The LaTeX math expression 90/// * `display_mode` - true for display math (block), false for inline math 91#[wasm_bindgen] 92pub fn render_math(latex: &str, display_mode: bool) -> JsMathResult { 93 match weaver_renderer::math::render_math(latex, display_mode) { 94 weaver_renderer::math::MathResult::Success(html) => JsMathResult { 95 success: true, 96 html, 97 error: None, 98 }, 99 weaver_renderer::math::MathResult::Error { html, message } => JsMathResult { 100 success: false, 101 html, 102 error: Some(message), 103 }, 104 } 105} 106 107/// Render faceted text (rich text with mentions, links, etc.) to HTML. 108/// 109/// Accepts facets from several AT Protocol lexicons (app.bsky, pub.leaflet, blog.pckt). 110/// 111/// # Arguments 112/// * `text` - The plain text content 113/// * `facets_json` - Array of facets with `index` (byteStart/byteEnd) and `features` array 114#[wasm_bindgen] 115pub fn render_faceted_text(text: &str, facets_json: JsValue) -> Result<String, JsError> { 116 use weaver_renderer::facet::NormalizedFacet; 117 118 let deserializer = Deserializer::from(facets_json); 119 let facets = Vec::<NormalizedFacet<'_>>::deserialize(deserializer) 120 .map_err(|e| JsError::new(&format!("Invalid facets JSON: {}", e)))?; 121 122 weaver_renderer::facet::render_faceted_html(text, &facets) 123 .map_err(|e| JsError::new(&e.to_string())) 124}