77 lines
2.3 KiB
Dart
77 lines
2.3 KiB
Dart
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(
|
|
<md.BlockSyntax>[
|
|
const md.FencedCodeBlockSyntax(),
|
|
const md.TableSyntax(),
|
|
const md.UnorderedListWithCheckboxSyntax(),
|
|
const md.OrderedListWithCheckboxSyntax(),
|
|
],
|
|
<md.InlineSyntax>[
|
|
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<String> _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<md.Node> 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(' ');
|
|
}
|
|
}
|
|
}
|