saminsohag

fixed issues 34 and 72

  1 +## 1.1.1
  2 +
  3 +* 🖼️ Fixed issue where images wrapped in links (e.g. `[![](img)](url)`) were not rendering properly (#72)
  4 +* 🔗 Resolved parsing errors for consecutive inline links without spacing (e.g. `[a](url)[b](url)`) (#34)
  5 +
1 ## 1.1.0 6 ## 1.1.0
2 7
3 * Changed `onLinkTab` to `onLinkTap` fixed issues of newLine issues. 8 * Changed `onLinkTab` to `onLinkTap` fixed issues of newLine issues.
@@ -75,21 +75,15 @@ class MyHomePage extends StatefulWidget { @@ -75,21 +75,15 @@ class MyHomePage extends StatefulWidget {
75 class _MyHomePageState extends State<MyHomePage> { 75 class _MyHomePageState extends State<MyHomePage> {
76 TextDirection _direction = TextDirection.ltr; 76 TextDirection _direction = TextDirection.ltr;
77 final TextEditingController _controller = TextEditingController( 77 final TextEditingController _controller = TextEditingController(
78 - text: r'''  
79 -decsiob (*) is on the set PQ = {91, 905} jjjzjsx * jjdbhsjsjmamajmsghdhhi msnnsjnskaksjjshahsh  
80 -  
81 -(*)  
82 -  
83 -This is a sample markdown document.  
84 - 78 + text: r'''This is a sample markdown document.
85 * **bold** 79 * **bold**
86 * *italic* 80 * *italic*
87 * **_bold and italic_** 81 * **_bold and italic_**
88 * ~~strikethrough~~ 82 * ~~strikethrough~~
89 * `code` 83 * `code`
90 -* [link](https://www.google.com) ![image](https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png)  
91 - 84 +* [link](https://www.google.com)
92 85
  86 +[![alt text](https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png)](link_url)
93 ```markdown 87 ```markdown
94 # Complex Markdown Document for Testing 88 # Complex Markdown Document for Testing
95 89
@@ -457,7 +451,8 @@ This document was created to test the robustness of Markdown parsers and to ensu @@ -457,7 +451,8 @@ This document was created to test the robustness of Markdown parsers and to ensu
457 textAlign: TextAlign.justify, 451 textAlign: TextAlign.justify,
458 textScaler: const TextScaler.linear(1), 452 textScaler: const TextScaler.linear(1),
459 style: const TextStyle( 453 style: const TextStyle(
460 - fontSize: 15, 454 + fontFamily: 'monospace',
  455 + fontWeight: FontWeight.bold,
461 ), 456 ),
462 highlightBuilder: (context, text, style) { 457 highlightBuilder: (context, text, style) {
463 return Container( 458 return Container(
@@ -612,7 +607,7 @@ This document was created to test the robustness of Markdown parsers and to ensu @@ -612,7 +607,7 @@ This document was created to test the robustness of Markdown parsers and to ensu
612 }, 607 },
613 linkBuilder: 608 linkBuilder:
614 (context, label, path, style) { 609 (context, label, path, style) {
615 - return Text( 610 + return Text.rich(
616 label, 611 label,
617 style: style.copyWith( 612 style: style.copyWith(
618 color: Colors.blue, 613 color: Colors.blue,
@@ -13,6 +13,9 @@ class LinkButton extends StatefulWidget { @@ -13,6 +13,9 @@ class LinkButton extends StatefulWidget {
13 /// The text of the link. 13 /// The text of the link.
14 final String text; 14 final String text;
15 15
  16 + /// The child of the link.
  17 + final Widget? child;
  18 +
16 /// The callback function to be called when the link is pressed. 19 /// The callback function to be called when the link is pressed.
17 final VoidCallback? onPressed; 20 final VoidCallback? onPressed;
18 21
@@ -40,6 +43,7 @@ class LinkButton extends StatefulWidget { @@ -40,6 +43,7 @@ class LinkButton extends StatefulWidget {
40 this.onPressed, 43 this.onPressed,
41 this.textStyle, 44 this.textStyle,
42 this.url, 45 this.url,
  46 + this.child,
43 }); 47 });
44 48
45 @override 49 @override
@@ -65,7 +69,9 @@ class _LinkButtonState extends State<LinkButton> { @@ -65,7 +69,9 @@ class _LinkButtonState extends State<LinkButton> {
65 onTapUp: (_) => _handlePress(false), 69 onTapUp: (_) => _handlePress(false),
66 onTapCancel: () => _handlePress(false), 70 onTapCancel: () => _handlePress(false),
67 onTap: widget.onPressed, 71 onTap: widget.onPressed,
68 - child: widget.config.getRich(TextSpan(text: widget.text, style: style)), 72 + child:
  73 + widget.child ??
  74 + widget.config.getRich(TextSpan(text: widget.text, style: style)),
69 ), 75 ),
70 ); 76 );
71 } 77 }
@@ -44,7 +44,7 @@ typedef LatexBuilder = @@ -44,7 +44,7 @@ typedef LatexBuilder =
44 typedef LinkBuilder = 44 typedef LinkBuilder =
45 Widget Function( 45 Widget Function(
46 BuildContext context, 46 BuildContext context,
47 - String text, 47 + InlineSpan text,
48 String url, 48 String url,
49 TextStyle style, 49 TextStyle style,
50 ); 50 );
@@ -18,8 +18,8 @@ abstract class MarkdownComponent { @@ -18,8 +18,8 @@ abstract class MarkdownComponent {
18 ]; 18 ];
19 19
20 static final List<MarkdownComponent> inlineComponents = [ 20 static final List<MarkdownComponent> inlineComponents = [
21 - ImageMd(),  
22 ATagMd(), 21 ATagMd(),
  22 + ImageMd(),
23 TableMd(), 23 TableMd(),
24 StrikeMd(), 24 StrikeMd(),
25 BoldMd(), 25 BoldMd(),
@@ -780,7 +780,7 @@ class SourceTag extends InlineMd { @@ -780,7 +780,7 @@ class SourceTag extends InlineMd {
780 /// Link text component 780 /// Link text component
781 class ATagMd extends InlineMd { 781 class ATagMd extends InlineMd {
782 @override 782 @override
783 - RegExp get exp => RegExp(r"\[[^\[\]]*\]\([^\s]*\)"); 783 + RegExp get exp => RegExp(r"(?<!\!)\[.*\]\([^\s]*\)");
784 784
785 @override 785 @override
786 InlineSpan span( 786 InlineSpan span(
@@ -788,14 +788,33 @@ class ATagMd extends InlineMd { @@ -788,14 +788,33 @@ class ATagMd extends InlineMd {
788 String text, 788 String text,
789 final GptMarkdownConfig config, 789 final GptMarkdownConfig config,
790 ) { 790 ) {
791 - // First try to find the basic pattern  
792 - final basicMatch = RegExp(r'\[([^\[\]]*)\]\(').firstMatch(text.trim());  
793 - if (basicMatch == null) { 791 + var bracketCount = 0;
  792 + var start = 1;
  793 + var end = 0;
  794 + for (var i = 0; i < text.length; i++) {
  795 + if (text[i] == '[') {
  796 + bracketCount++;
  797 + } else if (text[i] == ']') {
  798 + bracketCount--;
  799 + if (bracketCount == 0) {
  800 + end = i;
  801 + break;
  802 + }
  803 + }
  804 + }
  805 +
  806 + if (text[end + 1] != '(') {
794 return const TextSpan(); 807 return const TextSpan();
795 } 808 }
796 809
797 - final linkText = basicMatch.group(1) ?? '';  
798 - final urlStart = basicMatch.end; 810 + // First try to find the basic pattern
  811 + // final basicMatch = RegExp(r'(?<!\!)\[(.*)\]\(').firstMatch(text.trim());
  812 + // if (basicMatch == null) {
  813 + // return const TextSpan();
  814 + // }
  815 +
  816 + final linkText = text.substring(start, end);
  817 + final urlStart = end + 2;
799 818
800 // Now find the balanced closing parenthesis 819 // Now find the balanced closing parenthesis
801 int parenCount = 0; 820 int parenCount = 0;
@@ -826,14 +845,29 @@ class ATagMd extends InlineMd { @@ -826,14 +845,29 @@ class ATagMd extends InlineMd {
826 845
827 var builder = config.linkBuilder; 846 var builder = config.linkBuilder;
828 847
  848 + var ending = text.substring(urlEnd + 1);
  849 +
  850 + var endingSpans = MarkdownComponent.generate(
  851 + context,
  852 + ending,
  853 + config,
  854 + false,
  855 + );
  856 + var theme = GptMarkdownTheme.of(context);
  857 + var linkTextSpan = TextSpan(
  858 + children: MarkdownComponent.generate(context, linkText, config, false),
  859 + style: config.style?.copyWith(color: theme.linkColor),
  860 + );
  861 +
829 // Use custom builder if provided 862 // Use custom builder if provided
  863 + WidgetSpan? child;
830 if (builder != null) { 864 if (builder != null) {
831 - return WidgetSpan( 865 + child = WidgetSpan(
832 child: GestureDetector( 866 child: GestureDetector(
833 onTap: () => config.onLinkTap?.call(url, linkText), 867 onTap: () => config.onLinkTap?.call(url, linkText),
834 child: builder( 868 child: builder(
835 context, 869 context,
836 - linkText, 870 + linkTextSpan,
837 url, 871 url,
838 config.style ?? const TextStyle(), 872 config.style ?? const TextStyle(),
839 ), 873 ),
@@ -842,8 +876,9 @@ class ATagMd extends InlineMd { @@ -842,8 +876,9 @@ class ATagMd extends InlineMd {
842 } 876 }
843 877
844 // Default rendering 878 // Default rendering
845 - var theme = GptMarkdownTheme.of(context);  
846 - return WidgetSpan( 879 + child ??= WidgetSpan(
  880 + alignment: PlaceholderAlignment.baseline,
  881 + baseline: TextBaseline.alphabetic,
847 child: LinkButton( 882 child: LinkButton(
848 hoverColor: theme.linkHoverColor, 883 hoverColor: theme.linkHoverColor,
849 color: theme.linkColor, 884 color: theme.linkColor,
@@ -852,8 +887,11 @@ class ATagMd extends InlineMd { @@ -852,8 +887,11 @@ class ATagMd extends InlineMd {
852 }, 887 },
853 text: linkText, 888 text: linkText,
854 config: config, 889 config: config,
  890 + child: config.getRich(linkTextSpan),
855 ), 891 ),
856 ); 892 );
  893 + var textSpan = TextSpan(children: [child, ...endingSpans]);
  894 + return textSpan;
857 } 895 }
858 } 896 }
859 897
1 name: gpt_markdown 1 name: gpt_markdown
2 description: "Powerful Flutter Markdown & LaTeX Renderer: Rich Text, Math, Tables, Links, and Text Selection. Ideal for ChatGPT, Gemini, and more." 2 description: "Powerful Flutter Markdown & LaTeX Renderer: Rich Text, Math, Tables, Links, and Text Selection. Ideal for ChatGPT, Gemini, and more."
3 -version: 1.1.0 3 +version: 1.1.1
4 homepage: https://github.com/Infinitix-LLC/gpt_markdown 4 homepage: https://github.com/Infinitix-LLC/gpt_markdown
5 5
6 environment: 6 environment: