diff --git a/lib/model/content.dart b/lib/model/content.dart index 7f18689c0b..6fff325fba 100644 --- a/lib/model/content.dart +++ b/lib/model/content.dart @@ -289,6 +289,17 @@ class CodeBlockSpanNode extends InlineContentNode { } } +class ImageNodes extends BlockContentNode { + const ImageNodes(this.images, {super.debugHtmlNode}); + + final List images; + + @override + List debugDescribeChildren() { + return images.map((node) => node.toDiagnosticsNode()).toList(); + } +} + class ImageNode extends BlockContentNode { const ImageNode({super.debugHtmlNode, required this.srcUrl}); @@ -892,13 +903,24 @@ class _ZulipContentParser { List parseBlockContentList(dom.NodeList nodes) { assert(_debugParserContext == _ParserContext.block); - final acceptedNodes = nodes.where((node) { - // We get a bunch of newline Text nodes between paragraphs. - // A browser seems to ignore these; let's do the same. - if (node is dom.Text && (node.text == '\n')) return false; - return true; - }); - return acceptedNodes.map(parseBlockContent).toList(growable: false); + final List blocks = []; + final List imageNodes = []; + for (final node in nodes) { + if (node is dom.Text && (node.text == '\n')) continue; + + final block = parseBlockContent(node); + if (block is! ImageNode) { + if (imageNodes.isNotEmpty) { + blocks.add(ImageNodes(imageNodes)); + imageNodes.clear(); + } + blocks.add(block); + } else { + imageNodes.add(block); + } + } + if (imageNodes.isNotEmpty) blocks.add(ImageNodes(imageNodes)); + return blocks; } ZulipContent parse(String html) { diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index 999c7e1bbb..3b846d88ef 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -80,6 +80,8 @@ class BlockContentList extends StatelessWidget { return ListNodeWidget(node: node); } else if (node is CodeBlockNode) { return CodeBlock(node: node); + } else if (node is ImageNodes) { + return MessageImages(node: node); } else if (node is ImageNode) { return MessageImage(node: node); } else if (node is UnimplementedBlockContentNode) { @@ -249,6 +251,19 @@ class ListItemWidget extends StatelessWidget { } } +class MessageImages extends StatelessWidget { + const MessageImages({super.key, required this.node}); + + final ImageNodes node; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: node.images.map((imageNode) => MessageImage(node: imageNode)).toList()); + } +} + class MessageImage extends StatelessWidget { const MessageImage({super.key, required this.node});