@@ -6,6 +6,7 @@ import 'package:html/parser.dart';
6
6
import '../api/model/model.dart' ;
7
7
import '../api/model/submessage.dart' ;
8
8
import 'code_block.dart' ;
9
+ import 'katex.dart' ;
9
10
10
11
/// A node in a parse tree for Zulip message-style content.
11
12
///
@@ -341,22 +342,59 @@ class CodeBlockSpanNode extends ContentNode {
341
342
}
342
343
343
344
class MathBlockNode extends BlockContentNode {
344
- const MathBlockNode ({super .debugHtmlNode, required this .texSource});
345
+ const MathBlockNode ({
346
+ super .debugHtmlNode,
347
+ required this .texSource,
348
+ required this .nodes,
349
+ });
345
350
346
351
final String texSource;
347
352
353
+ /// Parsed KaTeX node tree to be used for rendering the KaTeX content.
354
+ ///
355
+ /// It will be null if the parser encounters an unsupported HTML element or
356
+ /// CSS style, indicating that the widget should render the [texSource] as a
357
+ /// fallback instead.
358
+ final List <KatexNode >? nodes;
359
+
348
360
@override
349
- bool operator == (Object other) {
350
- return other is MathBlockNode && other.texSource == texSource;
361
+ void debugFillProperties (DiagnosticPropertiesBuilder properties) {
362
+ super .debugFillProperties (properties);
363
+ properties.add (StringProperty ('texSource' , texSource));
351
364
}
352
365
353
366
@override
354
- int get hashCode => Object .hash ('MathBlockNode' , texSource);
367
+ List <DiagnosticsNode > debugDescribeChildren () {
368
+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
369
+ }
370
+ }
371
+
372
+ class KatexNode extends ContentNode {
373
+ const KatexNode ({
374
+ required this .text,
375
+ required this .nodes,
376
+ super .debugHtmlNode,
377
+ }) : assert ((text != null ) ^ (nodes != null ));
378
+
379
+ /// The text this KaTeX node contains.
380
+ ///
381
+ /// It will be null if [nodes] is non-null.
382
+ final String ? text;
383
+
384
+ /// The child nodes of this node in the KaTeX HTML tree.
385
+ ///
386
+ /// It will be null if [text] is non-null.
387
+ final List <KatexNode >? nodes;
355
388
356
389
@override
357
390
void debugFillProperties (DiagnosticPropertiesBuilder properties) {
358
391
super .debugFillProperties (properties);
359
- properties.add (StringProperty ('texSource' , texSource));
392
+ properties.add (StringProperty ('text' , text));
393
+ }
394
+
395
+ @override
396
+ List <DiagnosticsNode > debugDescribeChildren () {
397
+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
360
398
}
361
399
}
362
400
@@ -822,23 +860,25 @@ class ImageEmojiNode extends EmojiNode {
822
860
}
823
861
824
862
class MathInlineNode extends InlineContentNode {
825
- const MathInlineNode ({super .debugHtmlNode, required this .texSource});
863
+ const MathInlineNode ({
864
+ super .debugHtmlNode,
865
+ required this .texSource,
866
+ required this .nodes,
867
+ });
826
868
827
869
final String texSource;
828
-
829
- @override
830
- bool operator == (Object other) {
831
- return other is MathInlineNode && other.texSource == texSource;
832
- }
833
-
834
- @override
835
- int get hashCode => Object .hash ('MathInlineNode' , texSource);
870
+ final List <KatexNode >? nodes;
836
871
837
872
@override
838
873
void debugFillProperties (DiagnosticPropertiesBuilder properties) {
839
874
super .debugFillProperties (properties);
840
875
properties.add (StringProperty ('texSource' , texSource));
841
876
}
877
+
878
+ @override
879
+ List <DiagnosticsNode > debugDescribeChildren () {
880
+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
881
+ }
842
882
}
843
883
844
884
class GlobalTimeNode extends InlineContentNode {
@@ -864,59 +904,6 @@ class GlobalTimeNode extends InlineContentNode {
864
904
865
905
////////////////////////////////////////////////////////////////
866
906
867
- String ? _parseMath (dom.Element element, {required bool block}) {
868
- final dom.Element katexElement;
869
- if (! block) {
870
- assert (element.localName == 'span' && element.className == 'katex' );
871
-
872
- katexElement = element;
873
- } else {
874
- assert (element.localName == 'span' && element.className == 'katex-display' );
875
-
876
- if (element.nodes case [
877
- dom.Element (localName: 'span' , className: 'katex' ) && final child,
878
- ]) {
879
- katexElement = child;
880
- } else {
881
- return null ;
882
- }
883
- }
884
-
885
- // Expect two children span.katex-mathml, span.katex-html .
886
- // For now we only care about the .katex-mathml .
887
- if (katexElement.nodes case [
888
- dom.Element (localName: 'span' , className: 'katex-mathml' , nodes: [
889
- dom.Element (
890
- localName: 'math' ,
891
- namespaceUri: 'http://www.w3.org/1998/Math/MathML' )
892
- && final mathElement,
893
- ]),
894
- ...
895
- ]) {
896
- if (mathElement.attributes['display' ] != (block ? 'block' : null )) {
897
- return null ;
898
- }
899
-
900
- final String texSource;
901
- if (mathElement.nodes case [
902
- dom.Element (localName: 'semantics' , nodes: [
903
- ...,
904
- dom.Element (
905
- localName: 'annotation' ,
906
- attributes: {'encoding' : 'application/x-tex' },
907
- : final text),
908
- ]),
909
- ]) {
910
- texSource = text.trim ();
911
- } else {
912
- return null ;
913
- }
914
- return texSource;
915
- } else {
916
- return null ;
917
- }
918
- }
919
-
920
907
/// Parser for the inline-content subtrees within Zulip content HTML.
921
908
///
922
909
/// The only entry point to this class is [parseBlockInline] .
@@ -927,9 +914,12 @@ String? _parseMath(dom.Element element, {required bool block}) {
927
914
class _ZulipInlineContentParser {
928
915
InlineContentNode ? parseInlineMath (dom.Element element) {
929
916
final debugHtmlNode = kDebugMode ? element : null ;
930
- final texSource = _parseMath (element, block: false );
931
- if (texSource == null ) return null ;
932
- return MathInlineNode (texSource: texSource, debugHtmlNode: debugHtmlNode);
917
+ final parsed = parseMath (element, block: false );
918
+ if (parsed == null ) return null ;
919
+ return MathInlineNode (
920
+ texSource: parsed.texSource,
921
+ nodes: parsed.nodes,
922
+ debugHtmlNode: debugHtmlNode);
933
923
}
934
924
935
925
UserMentionNode ? parseUserMention (dom.Element element) {
@@ -1631,10 +1621,11 @@ class _ZulipContentParser {
1631
1621
})());
1632
1622
1633
1623
final firstChild = nodes.first as dom.Element ;
1634
- final texSource = _parseMath (firstChild, block: true );
1635
- if (texSource != null ) {
1624
+ final parsed = parseMath (firstChild, block: true );
1625
+ if (parsed != null ) {
1636
1626
result.add (MathBlockNode (
1637
- texSource: texSource,
1627
+ texSource: parsed.texSource,
1628
+ nodes: parsed.nodes,
1638
1629
debugHtmlNode: kDebugMode ? firstChild : null ));
1639
1630
} else {
1640
1631
result.add (UnimplementedBlockContentNode (htmlNode: firstChild));
@@ -1666,10 +1657,11 @@ class _ZulipContentParser {
1666
1657
if (child case dom.Text (text: '\n\n ' )) continue ;
1667
1658
1668
1659
if (child case dom.Element (localName: 'span' , className: 'katex-display' )) {
1669
- final texSource = _parseMath (child, block: true );
1670
- if (texSource != null ) {
1660
+ final parsed = parseMath (child, block: true );
1661
+ if (parsed != null ) {
1671
1662
result.add (MathBlockNode (
1672
- texSource: texSource,
1663
+ texSource: parsed.texSource,
1664
+ nodes: parsed.nodes,
1673
1665
debugHtmlNode: debugHtmlNode));
1674
1666
continue ;
1675
1667
}
0 commit comments