From 2f28902202a3ea03b540dc9481c402a223e1e101 Mon Sep 17 00:00:00 2001
From: Daniil Karpenko <26330178+dankarization@users.noreply.github.com>
Date: Fri, 22 May 2026 18:24:00 +0400
Subject: [PATCH] Prototype Mermaid embeds for markdown preview
---
.../markdown/iv_markdown_prepare_blocks.cpp | 164 +++++++++++++++++-
1 file changed, 162 insertions(+), 2 deletions(-)
diff --git a/Telegram/SourceFiles/iv/markdown/iv_markdown_prepare_blocks.cpp b/Telegram/SourceFiles/iv/markdown/iv_markdown_prepare_blocks.cpp
index 883338883bcc8..977f0265e85ac 100644
--- a/Telegram/SourceFiles/iv/markdown/iv_markdown_prepare_blocks.cpp
+++ b/Telegram/SourceFiles/iv/markdown/iv_markdown_prepare_blocks.cpp
@@ -55,6 +55,161 @@ constexpr auto kMaxVisualQuoteDepth = 3;
return text;
}
+[[nodiscard]] PreparedPlaceholderBlockId GeneratePreparedPlaceholderBlockId(
+ PrepareState *state) {
+ return PreparedPlaceholderBlockId(++state->nextGeneratedId);
+}
+
+[[nodiscard]] QByteArray StoreMarkdownEmbedHtml(
+ QByteArray html,
+ PrepareState *state) {
+ const auto resourceId = QByteArray("markdown-embed/")
+ + QByteArray::number(++state->nextGeneratedId)
+ + ".html";
+ state->result.embedHtmlResources.emplace(resourceId, std::move(html));
+ return resourceId;
+}
+
+[[nodiscard]] QByteArray MermaidEmbedHtml(const QString &source) {
+ const auto escaped = source.toHtmlEscaped();
+ const auto body = uR"(
+
+
+
+
+
+
+
+
+
+
+
+
+
+)"_q;
+ return body.toUtf8();
+}
+
+[[nodiscard]] PreparedBlock PrepareMermaidBlock(
+ const MarkdownNode &node,
+ PrepareState *state) {
+ auto block = PreparedBlock();
+ block.kind = PreparedBlockKind::Placeholder;
+ block.supplementary = true;
+ block.placeholder.label = u"Render Mermaid diagram"_q;
+ block.placeholder.copyText = node.text;
+ block.placeholder.id = GeneratePreparedPlaceholderBlockId(state);
+ block.placeholder.embed = EmbedRequest{
+ .resourceId = StoreMarkdownEmbedHtml(
+ MermaidEmbedHtml(node.text),
+ state),
+ .width = 900,
+ .height = 520,
+ .fullWidth = true,
+ .fixedHeight = false,
+ .allowScrolling = true,
+ };
+ return block;
+}
+
[[nodiscard]] int FlowFormulaTextSize(
PreparedBlockKind kind,
int headingLevel,
@@ -325,7 +480,12 @@ void AppendRichBlock(
return block;
}
-[[nodiscard]] PreparedBlock PrepareCodeBlock(const MarkdownNode &node) {
+[[nodiscard]] PreparedBlock PrepareCodeBlock(
+ const MarkdownNode &node,
+ PrepareState *state) {
+ if (FirstInfoToken(node.info).compare(u"mermaid"_q, Qt::CaseInsensitive) == 0) {
+ return PrepareMermaidBlock(node, state);
+ }
auto block = PreparedBlock();
block.kind = PreparedBlockKind::CodeBlock;
block.text.text = StripOneTrailingNewline(node.text);
@@ -733,7 +893,7 @@ void PrepareFootnotes(PrepareState *state) {
case NodeKind::FootnoteDefinition:
return {};
case NodeKind::CodeBlock:
- return { PrepareCodeBlock(node) };
+ return { PrepareCodeBlock(node, state) };
case NodeKind::ThematicBreak:
return { PrepareRuleBlock() };
case NodeKind::List: {