@@ -73,6 +73,8 @@ public class MarkdownParser {
7373
7474 private static final int MAX_RICH_TEXT_LEN = 8192 ;
7575 private static final int MAX_FILE_SIZE = 64 * 1024 ;
76+ private static final int MERMAID_EMBED_WIDTH = 720 ;
77+ private static final int MERMAID_EMBED_HEIGHT = 240 ;
7678
7779 public static boolean isMarkdown (MessageObject msg ) {
7880 if (msg == null ) return false ;
@@ -900,6 +902,10 @@ public void visit(ThematicBreak thematicBreak) {
900902
901903 @ Override
902904 public void visit (FencedCodeBlock fencedCodeBlock ) {
905+ if (isMermaidFence (fencedCodeBlock .getInfo ())) {
906+ emit (buildMermaidBlock (fencedCodeBlock .getLiteral ()));
907+ return ;
908+ }
903909 final TLRPC .TL_pageBlockPreformatted b = new TLRPC .TL_pageBlockPreformatted ();
904910 b .text = first (plain (fencedCodeBlock .getLiteral ()));
905911 b .language = fencedCodeBlock .getInfo () == null ? "" : fencedCodeBlock .getInfo ();
@@ -1051,6 +1057,88 @@ private TLRPC.TL_pageTableCell buildTableCell(TableCell cell, boolean header) {
10511057 }
10521058 }
10531059
1060+ private static boolean isMermaidFence (String info ) {
1061+ if (TextUtils .isEmpty (info )) {
1062+ return false ;
1063+ }
1064+ final String trimmed = info .trim ();
1065+ if (trimmed .isEmpty ()) {
1066+ return false ;
1067+ }
1068+ final int space = trimmed .indexOf (' ' );
1069+ final String language = (space >= 0 ? trimmed .substring (0 , space ) : trimmed ).trim ();
1070+ return "mermaid" .equalsIgnoreCase (language );
1071+ }
1072+
1073+ private static TLRPC .TL_pageBlockEmbed buildMermaidBlock (String literal ) {
1074+ final TLRPC .TL_pageBlockEmbed block = new TLRPC .TL_pageBlockEmbed ();
1075+ block .html = buildMermaidHtml (literal == null ? "" : literal );
1076+ block .flags |= TLObject .FLAG_2 ;
1077+ block .w = MERMAID_EMBED_WIDTH ;
1078+ block .h = MERMAID_EMBED_HEIGHT ;
1079+ block .flags |= TLObject .FLAG_5 ;
1080+ block .allow_scrolling = false ;
1081+ block .full_width = false ;
1082+ block .caption = emptyCaption ();
1083+ return block ;
1084+ }
1085+
1086+ private static TLRPC .TL_pageCaption emptyCaption () {
1087+ final TLRPC .TL_pageCaption caption = new TLRPC .TL_pageCaption ();
1088+ caption .text = new TLRPC .TL_textEmpty ();
1089+ caption .credit = new TLRPC .TL_textEmpty ();
1090+ return caption ;
1091+ }
1092+
1093+ private static String buildMermaidHtml (String source ) {
1094+ final StringBuilder html = new StringBuilder (source .length () + 2048 );
1095+ html .append ("<!doctype html><html><head><meta charset=\" utf-8\" >" );
1096+ html .append ("<meta name=\" viewport\" content=\" width=device-width,initial-scale=1,maximum-scale=1\" >" );
1097+ html .append ("<style>" );
1098+ html .append ("html,body{margin:0;padding:0;background:#fff;color:#222;font:14px/1.45 sans-serif;overflow:hidden;}" );
1099+ html .append (".wrap{padding:12px;box-sizing:border-box;}" );
1100+ html .append (".mermaid{display:flex;justify-content:center;}" );
1101+ html .append ("#error{display:none;white-space:pre-wrap;color:#b00020;padding:12px;font:12px/1.5 monospace;}" );
1102+ html .append ("</style>" );
1103+ html .append ("</head><body><div class=\" wrap\" >" );
1104+ html .append ("<pre class=\" mermaid\" >" );
1105+ appendEscapedHtml (html , source );
1106+ html .append ("</pre><div id=\" error\" ></div></div>" );
1107+ html .append ("<script src=\" https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js\" ></script>" );
1108+ html .append ("<script>" );
1109+ html .append ("function tgResize(){var h=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,120);" );
1110+ html .append ("if(window.TelegramWebviewProxy&&window.TelegramWebviewProxy.postEvent){" );
1111+ html .append ("window.TelegramWebviewProxy.postEvent('resize_frame',JSON.stringify({height:Math.ceil(h)}));}}" );
1112+ html .append ("window.addEventListener('resize',function(){setTimeout(tgResize,0);});" );
1113+ html .append ("(async function(){try{" );
1114+ html .append ("mermaid.initialize({startOnLoad:false,securityLevel:'loose',theme:(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'default'});" );
1115+ html .append ("await mermaid.run({nodes:document.querySelectorAll('.mermaid')});" );
1116+ html .append ("}catch(e){var err=document.getElementById('error');if(err){err.style.display='block';err.textContent=String(e);}}" );
1117+ html .append ("setTimeout(tgResize,50);})();" );
1118+ html .append ("</script></body></html>" );
1119+ return html .toString ();
1120+ }
1121+
1122+ private static void appendEscapedHtml (StringBuilder out , String source ) {
1123+ for (int i = 0 ; i < source .length (); i ++) {
1124+ final char ch = source .charAt (i );
1125+ switch (ch ) {
1126+ case '&' :
1127+ out .append ("&" );
1128+ break ;
1129+ case '<' :
1130+ out .append ("<" );
1131+ break ;
1132+ case '>' :
1133+ out .append (">" );
1134+ break ;
1135+ default :
1136+ out .append (ch );
1137+ break ;
1138+ }
1139+ }
1140+ }
1141+
10541142 public static class RichTextParser extends AbstractVisitor {
10551143
10561144 private static final int MAX_BLOCK_DEPTH = 64 ;
0 commit comments