import 'package:markdown/markdown.dart' as md; /// The Markdown feature set the app renders for Talk messages — CommonMark plus /// the GFM extensions the official Talk clients enable. /// /// It deliberately omits `InlineHtmlSyntax` (raw HTML stays literal), footnotes, /// alert blocks, color swatches and `:shortcode:` emoji — matching Talk's /// restrictions. Shared by the renderer ([MessageMarkdown]) and the plain-text /// stripper below so both agree on what counts as Markdown. final md.ExtensionSet talkMarkdownExtensionSet = md.ExtensionSet( [ const md.FencedCodeBlockSyntax(), const md.TableSyntax(), const md.UnorderedListWithCheckboxSyntax(), const md.OrderedListWithCheckboxSyntax(), ], [ md.StrikethroughSyntax(), md.AutolinkExtensionSyntax(), ], ); /// Block-level tags after which running text would otherwise glue together; /// their boundaries become a single space in the flattened preview. const Set _blockTags = { 'p', 'li', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'br', 'hr', 'tr', 'th', 'td', }; /// Flattens a Markdown [source] into a single line of readable plain text — /// markers (`**`, `#`, `` ` ``, list bullets, link/image syntax …) removed, /// links reduced to their label and images to their alt text. Used for the /// one-line previews (reply quote, conversation list) where Talk also strips /// formatting rather than rendering it. String markdownToPlainText(String source) { final nodes = md.Document( extensionSet: talkMarkdownExtensionSet, encodeHtml: false, ).parse(source); final buffer = StringBuffer(); _collect(nodes, buffer); return buffer.toString().replaceAll(RegExp(r'\s+'), ' ').trim(); } void _collect(List nodes, StringBuffer buffer) { for (final node in nodes) { if (node is md.Text) { buffer.write(node.text); } else if (node is md.Element) { // Images carry their text in the `alt` attribute, not in child nodes. if (node.tag == 'img') { buffer.write(node.attributes['alt'] ?? ''); continue; } final isBlock = _blockTags.contains(node.tag); if (isBlock) buffer.write(' '); final children = node.children; if (children != null) _collect(children, buffer); if (isBlock) buffer.write(' '); } } }