markdown support for talk messages
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user