Skip to content

Commit ec95eb3

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

File tree

4 files changed

+64
-0
lines changed

4 files changed

+64
-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
@@ -74,6 +74,8 @@ class BlockContentList extends StatelessWidget {
7474
// This goes in a Column. So to get the effect of a newline,
7575
// just use an empty Text.
7676
return const Text('');
77+
} else if (node is ThematicBreakNode) {
78+
return const ThematicBreak();
7779
} else if (node is ParagraphNode) {
7880
return Paragraph(node: node);
7981
} else if (node is HeadingNode) {
@@ -167,6 +169,22 @@ class Heading extends StatelessWidget {
167169
}
168170
}
169171

172+
class ThematicBreak extends StatelessWidget {
173+
const ThematicBreak({super.key});
174+
175+
static const htmlHeight = 2.0;
176+
static const htmlMarginY = 20.0;
177+
178+
@override
179+
Widget build(BuildContext context) {
180+
return Divider(
181+
color: const HSLColor.fromAHSL(1, 0, 0, .87).toColor(),
182+
thickness: htmlHeight,
183+
height: 2 * htmlMarginY + htmlHeight,
184+
);
185+
}
186+
}
187+
170188
class Quotation extends StatelessWidget {
171189
const Quotation({super.key, required this.node});
172190

test/model/content_test.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,24 @@ class ContentExample {
521521
blockUnimplemented('more text'),
522522
]]),
523523
]);
524+
525+
static const thematicBreaks = ContentExample(
526+
'parse thematic break (<hr>) in block context',
527+
'---'
528+
'---'
529+
'a'
530+
'---'
531+
'b'
532+
'---',
533+
'<hr><hr><p>a</p><hr><p>b</p><hr>',
534+
[
535+
ThematicBreakNode(),
536+
ThematicBreakNode(),
537+
ParagraphNode(links: null, nodes: [TextNode('a')]),
538+
ThematicBreakNode(),
539+
ParagraphNode(links: null, nodes: [TextNode('b')]),
540+
ThematicBreakNode(),
541+
]);
524542
}
525543

526544
UnimplementedBlockContentNode blockUnimplemented(String html) {
@@ -713,6 +731,8 @@ void main() {
713731
LineBreakNode(),
714732
]);
715733

734+
testParseExample(ContentExample.thematicBreaks);
735+
716736
testParse('parse two plain-text paragraphs',
717737
// "hello\n\nworld"
718738
'<p>hello</p>\n<p>world</p>', const [

test/widgets/content_test.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ void main() {
9595
});
9696
});
9797

98+
group('ThematicBreak', () {
99+
testWidgets('smoke ThematicBreak', (tester) async {
100+
await prepareContentBare(tester,
101+
// "---"
102+
'<hr>');
103+
tester.widget(find.byType(ThematicBreak));
104+
});
105+
});
106+
98107
group('Spoiler', () {
99108
testContentSmoke(ContentExample.spoilerDefaultHeader);
100109
testContentSmoke(ContentExample.spoilerPlainCustomHeader);

0 commit comments

Comments
 (0)