Skip to content

Commit abdec10

Browse files
wip
1 parent ffa41be commit abdec10

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

lib/model/content.dart

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,47 @@ class EmbedVideoNode extends BlockContentNode {
504504
}
505505
}
506506

507+
class LinkPreviewNode extends BlockContentNode {
508+
const LinkPreviewNode({
509+
super.debugHtmlNode,
510+
required this.imageHref,
511+
required this.imageSrc,
512+
required this.titleHref,
513+
required this.title,
514+
required this.description,
515+
});
516+
517+
final String imageHref;
518+
final String imageSrc;
519+
final String titleHref;
520+
final String title;
521+
final String description;
522+
523+
@override
524+
bool operator ==(Object other) {
525+
return other is LinkPreviewNode
526+
&& other.imageHref == imageHref
527+
&& other.imageSrc == imageSrc
528+
&& other.titleHref == titleHref
529+
&& other.title == title
530+
&& other.description == description;
531+
}
532+
533+
@override
534+
int get hashCode => Object.hash('LinkPreviewNode',
535+
imageHref, imageSrc, titleHref, title, description);
536+
537+
@override
538+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
539+
super.debugFillProperties(properties);
540+
properties.add(StringProperty('imageHref', imageHref));
541+
properties.add(StringProperty('imageSrc', imageSrc));
542+
properties.add(StringProperty('titleHref', titleHref));
543+
properties.add(StringProperty('title', title));
544+
properties.add(StringProperty('description', description));
545+
}
546+
}
547+
507548
/// A content node that expects an inline layout context from its parent.
508549
///
509550
/// When rendered into a Flutter widget tree, an inline content node
@@ -1220,6 +1261,57 @@ class _ZulipContentParser {
12201261
return EmbedVideoNode(hrefUrl: href, previewImageSrcUrl: imgSrc, debugHtmlNode: debugHtmlNode);
12211262
}
12221263

1264+
static final _linkPreviewImageSrcRegexp = RegExp(r'background-image: url\("(.+)"\)');
1265+
1266+
BlockContentNode parseLinkPreviewNode(dom.Element divElement) {
1267+
assert(_debugParserContext == _ParserContext.block);
1268+
assert(divElement.localName == 'div'
1269+
&& divElement.className == 'message_embed');
1270+
1271+
assert(divElement.nodes.length == 2);
1272+
final imageElm = divElement.nodes.first as dom.Element;
1273+
assert(imageElm.localName == 'a'
1274+
&& imageElm.className == 'message_embed_image');
1275+
1276+
final imageHrefUrl = imageElm.attributes['href']!;
1277+
1278+
final imageElmStyleAttr = imageElm.attributes['style']!;
1279+
final match = _linkPreviewImageSrcRegexp.firstMatch(imageElmStyleAttr)!;
1280+
final imageSrcUrl = match.group(1)!;
1281+
1282+
final dataContainer = divElement.nodes.last as dom.Element;
1283+
assert(dataContainer.localName == 'div'
1284+
&& dataContainer.className == 'data-container');
1285+
assert(dataContainer.nodes.length == 2);
1286+
1287+
final titleElm = dataContainer.nodes.first as dom.Element;
1288+
assert(titleElm.localName == 'div'
1289+
&& titleElm.className == 'message_embed_title');
1290+
assert(titleElm.nodes.length == 1);
1291+
1292+
final titleLinkElm = titleElm.nodes.single as dom.Element;
1293+
assert(titleLinkElm.localName == 'a');
1294+
assert(titleLinkElm.nodes.length == 1);
1295+
1296+
final titleLinkHrefUrl = titleLinkElm.attributes['href']!;
1297+
final titleLinkText = titleLinkElm.nodes.single as dom.Text;
1298+
1299+
final descriptionElm = dataContainer.nodes.last as dom.Element;
1300+
assert(descriptionElm.localName == 'div'
1301+
&& descriptionElm.className == 'message_embed_description');
1302+
assert(descriptionElm.nodes.length == 1);
1303+
1304+
final descriptionText = descriptionElm.nodes.single as dom.Text;
1305+
1306+
return LinkPreviewNode(
1307+
imageHref: imageHrefUrl,
1308+
imageSrc: imageSrcUrl,
1309+
titleHref: titleLinkHrefUrl,
1310+
title: titleLinkText.text,
1311+
description: descriptionText.text,
1312+
);
1313+
}
1314+
12231315
BlockContentNode parseBlockContent(dom.Node node) {
12241316
assert(_debugParserContext == _ParserContext.block);
12251317
final debugHtmlNode = kDebugMode ? node : null;
@@ -1313,6 +1405,10 @@ class _ZulipContentParser {
13131405
}
13141406
}
13151407

1408+
if (localName == 'div' && className == 'message_embed') {
1409+
return parseLinkPreviewNode(element);
1410+
}
1411+
13161412
// TODO more types of node
13171413
return UnimplementedBlockContentNode(htmlNode: node);
13181414
}

lib/widgets/content.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ class BlockContentList extends StatelessWidget {
326326
EmbedVideoNode() => MessageEmbedVideo(node: node),
327327
UnimplementedBlockContentNode() =>
328328
Text.rich(_errorUnimplemented(node, context: context)),
329+
LinkPreviewNode() => MessageLinkPreview(node: node),
329330
};
330331

331332
}),
@@ -799,6 +800,42 @@ class MathBlock extends StatelessWidget {
799800
}
800801
}
801802

803+
class MessageLinkPreview extends StatelessWidget {
804+
const MessageLinkPreview({super.key, required this.node});
805+
806+
final LinkPreviewNode node;
807+
808+
@override
809+
Widget build(BuildContext context) {
810+
return DecoratedBox(
811+
decoration: const BoxDecoration(
812+
border: Border(left: BorderSide(color: Colors.white, width: 3))
813+
),
814+
child: Padding(
815+
padding: const EdgeInsets.fromLTRB(5 + 3, 5, 5, 5),
816+
child: Row(
817+
children: [
818+
GestureDetector(
819+
onTap: () => _launchUrl(context, node.imageHref),
820+
child: RealmContentNetworkImage(Uri.parse(node.imageSrc), fit: BoxFit.cover, width: 70, height: 70)),
821+
Padding(
822+
padding: const EdgeInsets.symmetric(horizontal: 5),
823+
child: Column(
824+
mainAxisAlignment: MainAxisAlignment.start,
825+
crossAxisAlignment: CrossAxisAlignment.start,
826+
children: [
827+
GestureDetector(
828+
onTap: () => _launchUrl(context, node.titleHref),
829+
child: Text(node.title)),
830+
Text(node.description),
831+
]),
832+
),
833+
]),
834+
),
835+
);
836+
}
837+
}
838+
802839
//
803840
// Inline layout.
804841
//

0 commit comments

Comments
 (0)