Skip to content

Commit d5e07e6

Browse files
committed
content: Handle <hr> horizontal lines
Fixes: zulip#353
1 parent 9044a9a commit d5e07e6

File tree

4 files changed

+54
-0
lines changed

4 files changed

+54
-0
lines changed

lib/model/content.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,19 @@ class LineBreakNode extends BlockContentNode {
178178
int get hashCode => 'LineBreakNode'.hashCode;
179179
}
180180

181+
/// A `hr` element
182+
class ThematicBreakNode extends BlockContentNode {
183+
const ThematicBreakNode({super.debugHtmlNode});
184+
185+
@override
186+
bool operator ==(Object other) {
187+
return other is ThematicBreakNode;
188+
}
189+
190+
@override
191+
int get hashCode => 'ThematicBreakNode'.hashCode;
192+
}
193+
181194
/// A `p` element, or a place where the DOM tree logically wanted one.
182195
///
183196
/// We synthesize these in the absence of an actual `p` element in cases where
@@ -962,6 +975,10 @@ class _ZulipContentParser {
962975
return LineBreakNode(debugHtmlNode: debugHtmlNode);
963976
}
964977

978+
if (localName == 'hr' && className.isEmpty) {
979+
return ThematicBreakNode(debugHtmlNode: debugHtmlNode);
980+
}
981+
965982
if (localName == 'p' && className.isEmpty) {
966983
// Oddly, the way a math block gets encoded in Zulip HTML is inside a <p>.
967984
if (element.nodes case [dom.Element(localName: 'span') && var child, ...]) {

lib/widgets/content.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ class InheritedMessage extends InheritedWidget {
5656
}
5757
}
5858

59+
class ThematicBreak extends StatelessWidget {
60+
const ThematicBreak({super.key});
61+
62+
static const htmlHeight = 2.0;
63+
static const htmlMarginY = 20.0;
64+
65+
@override
66+
Widget build(BuildContext context) {
67+
return Divider(
68+
color: const HSLColor.fromAHSL(1, 0, 0, .87).toColor(),
69+
thickness: htmlHeight,
70+
height: 2 * htmlMarginY + htmlHeight,
71+
);
72+
}
73+
}
74+
5975
//
6076
// Block layout.
6177
//
@@ -74,6 +90,8 @@ class BlockContentList extends StatelessWidget {
7490
// This goes in a Column. So to get the effect of a newline,
7591
// just use an empty Text.
7692
return const Text('');
93+
} else if (node is ThematicBreakNode) {
94+
return const ThematicBreak();
7795
} else if (node is ParagraphNode) {
7896
return Paragraph(node: node);
7997
} else if (node is HeadingNode) {

test/model/content_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,16 @@ class ContentExample {
521521
blockUnimplemented('more text'),
522522
]]),
523523
]);
524+
525+
static const thematicBreaks = ContentExample(
526+
'parse thematic break (<hr>) in block context',
527+
'a\n---\nb',
528+
'<p>a</p><hr><p>b</p>',
529+
[
530+
ParagraphNode(links: null, nodes: [TextNode('a')]),
531+
ThematicBreakNode(),
532+
ParagraphNode(links: null, nodes: [TextNode('b')]),
533+
]);
524534
}
525535

526536
UnimplementedBlockContentNode blockUnimplemented(String html) {
@@ -713,6 +723,8 @@ void main() {
713723
LineBreakNode(),
714724
]);
715725

726+
testParseExample(ContentExample.thematicBreaks);
727+
716728
testParse('parse two plain-text paragraphs',
717729
// "hello\n\nworld"
718730
'<p>hello</p>\n<p>world</p>', const [

test/widgets/content_test.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ void main() {
7979
return httpClient;
8080
}
8181

82+
group('ThematicBreak', () {
83+
testWidgets('smoke ThematicBreak', (tester) async {
84+
await prepareContentBare(tester, ContentExample.thematicBreaks.html);
85+
tester.widget(find.byType(ThematicBreak));
86+
});
87+
});
88+
8289
group('Heading', () {
8390
testWidgets('plain h6', (tester) async {
8491
await prepareContentBare(tester,

0 commit comments

Comments
 (0)