@@ -6,6 +6,7 @@ import 'package:html/parser.dart';
66import '../api/model/model.dart' ;
77import '../api/model/submessage.dart' ;
88import 'code_block.dart' ;
9+ import 'katex.dart' ;
910
1011/// A node in a parse tree for Zulip message-style content.
1112///
@@ -341,22 +342,59 @@ class CodeBlockSpanNode extends ContentNode {
341342}
342343
343344class 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+ });
345350
346351 final String texSource;
347352
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+
348360 @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));
351364 }
352365
353366 @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;
355388
356389 @override
357390 void debugFillProperties (DiagnosticPropertiesBuilder properties) {
358391 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 [];
360398 }
361399}
362400
@@ -822,23 +860,25 @@ class ImageEmojiNode extends EmojiNode {
822860}
823861
824862class 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+ });
826868
827869 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;
836871
837872 @override
838873 void debugFillProperties (DiagnosticPropertiesBuilder properties) {
839874 super .debugFillProperties (properties);
840875 properties.add (StringProperty ('texSource' , texSource));
841876 }
877+
878+ @override
879+ List <DiagnosticsNode > debugDescribeChildren () {
880+ return nodes? .map ((node) => node.toDiagnosticsNode ()).toList () ?? const [];
881+ }
842882}
843883
844884class GlobalTimeNode extends InlineContentNode {
@@ -864,59 +904,6 @@ class GlobalTimeNode extends InlineContentNode {
864904
865905////////////////////////////////////////////////////////////////
866906
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-
920907/// Parser for the inline-content subtrees within Zulip content HTML.
921908///
922909/// The only entry point to this class is [parseBlockInline] .
@@ -927,9 +914,12 @@ String? _parseMath(dom.Element element, {required bool block}) {
927914class _ZulipInlineContentParser {
928915 InlineContentNode ? parseInlineMath (dom.Element element) {
929916 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);
933923 }
934924
935925 UserMentionNode ? parseUserMention (dom.Element element) {
@@ -1631,10 +1621,11 @@ class _ZulipContentParser {
16311621 })());
16321622
16331623 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 ) {
16361626 result.add (MathBlockNode (
1637- texSource: texSource,
1627+ texSource: parsed.texSource,
1628+ nodes: parsed.nodes,
16381629 debugHtmlNode: kDebugMode ? firstChild : null ));
16391630 } else {
16401631 result.add (UnimplementedBlockContentNode (htmlNode: firstChild));
@@ -1666,10 +1657,11 @@ class _ZulipContentParser {
16661657 if (child case dom.Text (text: '\n\n ' )) continue ;
16671658
16681659 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 ) {
16711662 result.add (MathBlockNode (
1672- texSource: texSource,
1663+ texSource: parsed.texSource,
1664+ nodes: parsed.nodes,
16731665 debugHtmlNode: debugHtmlNode));
16741666 continue ;
16751667 }
0 commit comments