Merge remote-tracking branch 'github/main'
# Conflicts: # example/pubspec.lock # lib/gpt_markdown.dart # lib/markdown_component.dart # pubspec.yaml
Showing
18 changed files
with
668 additions
and
188 deletions
| 1 | +## 1.1.4 | ||
| 2 | + | ||
| 3 | +* 🔗 Fixed vertical alignment issue with link text rendering ([#92](https://github.com/Infinitix-LLC/gpt_markdown/issues/92)) | ||
| 4 | +* 📝 Resolved "null" rendering issue in ordered lists with multiple spaces and line breaks ([#89](https://github.com/Infinitix-LLC/gpt_markdown/issues/89)) | ||
| 5 | +* 🧹 Removed erroneous `trim()` from `CodeBlockMd` to preserve necessary whitespace in code blocks ([#99](https://github.com/Infinitix-LLC/gpt_markdown/issues/99)) | ||
| 6 | +* 🎨 Fixed heading style customization issue where custom colors in heading styles were not being applied ([#95](https://github.com/Infinitix-LLC/gpt_markdown/issues/95)) | ||
| 7 | + | ||
| 8 | +## 1.1.3 | ||
| 9 | + | ||
| 10 | +* Added `RadioGroup` widget for managing radio buttons. | ||
| 11 | +* Updated to align with Flutter 3.35 by resolving the deprecations of `Radio.groupValue` and `Radio.onChanged`. | ||
| 12 | + | ||
| 13 | +## 1.1.2 | ||
| 14 | + | ||
| 15 | +* 📊 Fixed table column alignment support ([#65](https://github.com/Infinitix-LLC/gpt_markdown/issues/65)) | ||
| 16 | +* 🎨 Added `tableBuilder` parameter to customize table rendering | ||
| 17 | +* 🔗 Fixed text decoration color of link markdown component | ||
| 18 | + | ||
| 19 | +## 1.1.1 | ||
| 20 | + | ||
| 21 | +* 🖼️ Fixed issue where images wrapped in links (e.g. `[](url)`) were not rendering properly (#72) | ||
| 22 | +* 🔗 Resolved parsing errors for consecutive inline links without spacing (e.g. `[a](url)[b](url)`) (#34) | ||
| 23 | + | ||
| 24 | +## 1.1.0 | ||
| 25 | + | ||
| 26 | +* Changed `onLinkTab` to `onLinkTap` fixed issues of newLine issues. | ||
| 27 | + | ||
| 28 | +## 1.0.20 | ||
| 29 | + | ||
| 30 | +* Fix: support balanced parentheses in image and link URLs. [#68](https://github.com/Infinitix-LLC/gpt_markdown/pull/68) | ||
| 31 | + | ||
| 32 | +## 1.0.19 | ||
| 33 | + | ||
| 34 | +* Performance improvements. | ||
| 35 | + | ||
| 36 | +## 1.0.18 | ||
| 37 | + | ||
| 38 | +* dollarSignForLatex is added and by default it is false. | ||
| 39 | + | ||
| 40 | +## 1.0.17 | ||
| 41 | + | ||
| 42 | +* Bloc components rendering inside table. | ||
| 43 | + | ||
| 1 | ## 1.0.16 | 44 | ## 1.0.16 |
| 2 | 45 | ||
| 3 | * `IndentMd` and `BlockQuote` fixed. | 46 | * `IndentMd` and `BlockQuote` fixed. |
| @@ -4,6 +4,8 @@ | @@ -4,6 +4,8 @@ | ||
| 4 | 4 | ||
| 5 | A comprehensive Flutter package for rendering rich Markdown and LaTeX content in your apps, designed for seamless integration with AI outputs like ChatGPT and Gemini. | 5 | A comprehensive Flutter package for rendering rich Markdown and LaTeX content in your apps, designed for seamless integration with AI outputs like ChatGPT and Gemini. |
| 6 | 6 | ||
| 7 | +gpt_markdown is a drop-in replacement for flutter_markdown, offering extended support for LaTeX, custom builders, and better AI integration for Flutter apps. | ||
| 8 | + | ||
| 7 | ⭐ If you find this package helpful, please give it a like on [pub.dev](https://pub.dev/packages/gpt_markdown)! Your support means a lot! ⭐ | 9 | ⭐ If you find this package helpful, please give it a like on [pub.dev](https://pub.dev/packages/gpt_markdown)! Your support means a lot! ⭐ |
| 8 | 10 | ||
| 9 | --- | 11 | --- |
| @@ -30,7 +32,7 @@ A comprehensive Flutter package for rendering rich Markdown and LaTeX content in | @@ -30,7 +32,7 @@ A comprehensive Flutter package for rendering rich Markdown and LaTeX content in | ||
| 30 | | 🔗 Links | ✅ | | 32 | | 🔗 Links | ✅ | |
| 31 | | 📱 Selectable | ✅ | | 33 | | 📱 Selectable | ✅ | |
| 32 | | 🧩 Custom components | ✅ | | | 34 | | 🧩 Custom components | ✅ | | |
| 33 | -| 📎 Underline | | 🔜 | | 35 | +| 📎 Underline | ✅ | | |
| 34 | 36 | ||
| 35 | ## ✨ Key Features | 37 | ## ✨ Key Features |
| 36 | 38 | ||
| @@ -84,6 +86,11 @@ Render a wide variety of content with full Markdown and LaTeX support, including | @@ -84,6 +86,11 @@ Render a wide variety of content with full Markdown and LaTeX support, including | ||
| 84 | *Italic text* | 86 | *Italic text* |
| 85 | ``` | 87 | ``` |
| 86 | 88 | ||
| 89 | +- <u>Underline text</u> | ||
| 90 | +``` | ||
| 91 | +<u>Underline text</u> | ||
| 92 | +``` | ||
| 93 | + | ||
| 87 | - heading texts | 94 | - heading texts |
| 88 | 95 | ||
| 89 | ``` | 96 | ``` |
| @@ -138,7 +145,8 @@ return GptMarkdown( | @@ -138,7 +145,8 @@ return GptMarkdown( | ||
| 138 | * This is a unordered list. | 145 | * This is a unordered list. |
| 139 | ''', | 146 | ''', |
| 140 | style: const TextStyle( | 147 | style: const TextStyle( |
| 141 | - color: Colors.red, | 148 | + color: Colors.red, |
| 149 | + ), | ||
| 142 | ), | 150 | ), |
| 143 | 151 | ||
| 144 | ``` | 152 | ``` |
| @@ -186,6 +194,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -186,6 +194,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
| 186 | 194 | ||
| 187 | <img width="614" alt="Screenshot 2024-02-15 at 4 13 59 AM" src="https://github.com/saminsohag/flutter_packages/assets/59507062/8f4a4068-a12c-45d1-a954-ebaf3822e754"> | 195 | <img width="614" alt="Screenshot 2024-02-15 at 4 13 59 AM" src="https://github.com/saminsohag/flutter_packages/assets/59507062/8f4a4068-a12c-45d1-a954-ebaf3822e754"> |
| 188 | 196 | ||
| 197 | +If you're using flutter_markdown and need more customization or LaTeX support, gpt_markdown is a great alternative. | ||
| 189 | 198 | ||
| 190 | ## 🔗 Additional Information | 199 | ## 🔗 Additional Information |
| 191 | 200 |
| @@ -123,5 +123,28 @@ darkTheme: ThemeData( | @@ -123,5 +123,28 @@ darkTheme: ThemeData( | ||
| 123 | ), | 123 | ), |
| 124 | ``` | 124 | ``` |
| 125 | 125 | ||
| 126 | +Use `tableBuilder` to customize table rendering: | ||
| 127 | + | ||
| 128 | +```dart | ||
| 129 | +GptMarkdown( | ||
| 130 | + markdownText, | ||
| 131 | + tableBuilder: (context, tableRows, textStyle, config) { | ||
| 132 | + return Table( | ||
| 133 | + border: TableBorder.all( | ||
| 134 | + width: 1, | ||
| 135 | + color: Colors.red, | ||
| 136 | + ), | ||
| 137 | + children: tableRows.map((e) { | ||
| 138 | + return TableRow( | ||
| 139 | + children: e.fields.map((e) { | ||
| 140 | + return Text(e.data); | ||
| 141 | + }).toList(), | ||
| 142 | + ); | ||
| 143 | + }).toList(), | ||
| 144 | + ); | ||
| 145 | + }, | ||
| 146 | +); | ||
| 147 | +``` | ||
| 148 | + | ||
| 126 | Please see the [README.md](https://github.com/Infinitix-LLC/gpt_markdown) and also [example](https://github.com/Infinitix-LLC/gpt_markdown/tree/main/example/lib/main.dart) app for more details. | 149 | Please see the [README.md](https://github.com/Infinitix-LLC/gpt_markdown) and also [example](https://github.com/Infinitix-LLC/gpt_markdown/tree/main/example/lib/main.dart) app for more details. |
| 127 | 150 |
| @@ -75,7 +75,15 @@ class MyHomePage extends StatefulWidget { | @@ -75,7 +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''' | 78 | + text: r'''This is a sample markdown document. |
| 79 | +* **bold** | ||
| 80 | +* *italic* | ||
| 81 | +* **_bold and italic_** | ||
| 82 | +* ~~strikethrough~~ | ||
| 83 | +* `code` | ||
| 84 | +* [link](https://www.google.com) | ||
| 85 | + | ||
| 86 | +[](link_url) | ||
| 79 | ```markdown | 87 | ```markdown |
| 80 | # Complex Markdown Document for Testing | 88 | # Complex Markdown Document for Testing |
| 81 | 89 | ||
| @@ -317,9 +325,16 @@ This document was created to test the robustness of Markdown parsers and to ensu | @@ -317,9 +325,16 @@ This document was created to test the robustness of Markdown parsers and to ensu | ||
| 317 | 325 | ||
| 318 | bool writingMod = true; | 326 | bool writingMod = true; |
| 319 | bool selectable = false; | 327 | bool selectable = false; |
| 328 | + bool useDollarSignsForLatex = false; | ||
| 320 | 329 | ||
| 321 | @override | 330 | @override |
| 322 | Widget build(BuildContext context) { | 331 | Widget build(BuildContext context) { |
| 332 | +// var data = '''|asdfasfd|asdfasf| | ||
| 333 | +// |---|---| | ||
| 334 | +// |sohag|asdfasf| | ||
| 335 | +// |asdfasf|asdfasf| | ||
| 336 | +// '''; | ||
| 337 | + | ||
| 323 | return GptMarkdownTheme( | 338 | return GptMarkdownTheme( |
| 324 | gptThemeData: GptMarkdownTheme.of(context).copyWith( | 339 | gptThemeData: GptMarkdownTheme.of(context).copyWith( |
| 325 | highlightColor: Colors.purple, | 340 | highlightColor: Colors.purple, |
| @@ -331,6 +346,22 @@ This document was created to test the robustness of Markdown parsers and to ensu | @@ -331,6 +346,22 @@ This document was created to test the robustness of Markdown parsers and to ensu | ||
| 331 | IconButton( | 346 | IconButton( |
| 332 | onPressed: () { | 347 | onPressed: () { |
| 333 | setState(() { | 348 | setState(() { |
| 349 | + useDollarSignsForLatex = !useDollarSignsForLatex; | ||
| 350 | + }); | ||
| 351 | + }, | ||
| 352 | + icon: Icon( | ||
| 353 | + Icons.monetization_on_outlined, | ||
| 354 | + color: useDollarSignsForLatex | ||
| 355 | + ? Theme.of(context).colorScheme.onSurfaceVariant | ||
| 356 | + : Theme.of(context) | ||
| 357 | + .colorScheme | ||
| 358 | + .onSurface | ||
| 359 | + .withValues(alpha: 0.38), | ||
| 360 | + ), | ||
| 361 | + ), | ||
| 362 | + IconButton( | ||
| 363 | + onPressed: () { | ||
| 364 | + setState(() { | ||
| 334 | selectable = !selectable; | 365 | selectable = !selectable; |
| 335 | }); | 366 | }); |
| 336 | }, | 367 | }, |
| @@ -383,9 +414,10 @@ This document was created to test the robustness of Markdown parsers and to ensu | @@ -383,9 +414,10 @@ This document was created to test the robustness of Markdown parsers and to ensu | ||
| 383 | Expanded( | 414 | Expanded( |
| 384 | child: ListView( | 415 | child: ListView( |
| 385 | children: [ | 416 | children: [ |
| 386 | - AnimatedBuilder( | ||
| 387 | - animation: _controller, | 417 | + ListenableBuilder( |
| 418 | + listenable: _controller, | ||
| 388 | builder: (context, _) { | 419 | builder: (context, _) { |
| 420 | + var data = _controller.text; | ||
| 389 | return Container( | 421 | return Container( |
| 390 | padding: const EdgeInsets.all(8), | 422 | padding: const EdgeInsets.all(8), |
| 391 | decoration: BoxDecoration( | 423 | decoration: BoxDecoration( |
| @@ -415,17 +447,20 @@ This document was created to test the robustness of Markdown parsers and to ensu | @@ -415,17 +447,20 @@ This document was created to test the robustness of Markdown parsers and to ensu | ||
| 415 | child: Builder( | 447 | child: Builder( |
| 416 | builder: (context) { | 448 | builder: (context) { |
| 417 | Widget child = GptMarkdown( | 449 | Widget child = GptMarkdown( |
| 418 | - _controller.text, | 450 | + data, |
| 419 | textDirection: _direction, | 451 | textDirection: _direction, |
| 420 | - onLinkTab: (url, title) { | 452 | + onLinkTap: (url, title) { |
| 421 | debugPrint(url); | 453 | debugPrint(url); |
| 422 | debugPrint(title); | 454 | debugPrint(title); |
| 423 | }, | 455 | }, |
| 456 | + useDollarSignsForLatex: | ||
| 457 | + useDollarSignsForLatex, | ||
| 424 | textAlign: TextAlign.justify, | 458 | textAlign: TextAlign.justify, |
| 425 | textScaler: const TextScaler.linear(1), | 459 | textScaler: const TextScaler.linear(1), |
| 426 | style: const TextStyle( | 460 | style: const TextStyle( |
| 427 | - fontSize: 15, | ||
| 428 | - ), | 461 | + // fontFamily: 'monospace', |
| 462 | + // fontWeight: FontWeight.bold, | ||
| 463 | + ), | ||
| 429 | highlightBuilder: (context, text, style) { | 464 | highlightBuilder: (context, text, style) { |
| 430 | return Container( | 465 | return Container( |
| 431 | padding: const EdgeInsets.symmetric( | 466 | padding: const EdgeInsets.symmetric( |
| @@ -487,13 +522,13 @@ This document was created to test the robustness of Markdown parsers and to ensu | @@ -487,13 +522,13 @@ This document was created to test the robustness of Markdown parsers and to ensu | ||
| 487 | RegExp(r"align\*"), | 522 | RegExp(r"align\*"), |
| 488 | (match) => "aligned"); | 523 | (match) => "aligned"); |
| 489 | }, | 524 | }, |
| 490 | - imageBuilder: (context, url) { | ||
| 491 | - return Image.network( | ||
| 492 | - url, | ||
| 493 | - width: 100, | ||
| 494 | - height: 100, | ||
| 495 | - ); | ||
| 496 | - }, | 525 | + // imageBuilder: (context, url) { |
| 526 | + // return Image.network( | ||
| 527 | + // url, | ||
| 528 | + // width: 100, | ||
| 529 | + // height: 100, | ||
| 530 | + // ); | ||
| 531 | + // }, | ||
| 497 | latexBuilder: | 532 | latexBuilder: |
| 498 | (context, tex, textStyle, inline) { | 533 | (context, tex, textStyle, inline) { |
| 499 | if (tex.contains(r"\begin{tabular}")) { | 534 | if (tex.contains(r"\begin{tabular}")) { |
| @@ -579,47 +614,65 @@ This document was created to test the robustness of Markdown parsers and to ensu | @@ -579,47 +614,65 @@ This document was created to test the robustness of Markdown parsers and to ensu | ||
| 579 | }, | 614 | }, |
| 580 | linkBuilder: | 615 | linkBuilder: |
| 581 | (context, label, path, style) { | 616 | (context, label, path, style) { |
| 582 | - return Text( | 617 | + return Text.rich( |
| 583 | label, | 618 | label, |
| 584 | style: style.copyWith( | 619 | style: style.copyWith( |
| 585 | color: Colors.blue, | 620 | color: Colors.blue, |
| 586 | ), | 621 | ), |
| 587 | ); | 622 | ); |
| 588 | }, | 623 | }, |
| 589 | - components: [ | ||
| 590 | - CodeBlockMd(), | ||
| 591 | - NewLines(), | ||
| 592 | - BlockQuote(), | ||
| 593 | - ImageMd(), | ||
| 594 | - ATagMd(), | ||
| 595 | - TableMd(), | ||
| 596 | - HTag(), | ||
| 597 | - UnOrderedList(), | ||
| 598 | - OrderedList(), | ||
| 599 | - RadioButtonMd(), | ||
| 600 | - CheckBoxMd(), | ||
| 601 | - HrLine(), | ||
| 602 | - StrikeMd(), | ||
| 603 | - BoldMd(), | ||
| 604 | - ItalicMd(), | ||
| 605 | - LatexMath(), | ||
| 606 | - LatexMathMultiLine(), | ||
| 607 | - HighlightedText(), | ||
| 608 | - SourceTag(), | ||
| 609 | - IndentMd(), | ||
| 610 | - ], | ||
| 611 | - inlineComponents: [ | ||
| 612 | - ImageMd(), | ||
| 613 | - ATagMd(), | ||
| 614 | - TableMd(), | ||
| 615 | - StrikeMd(), | ||
| 616 | - BoldMd(), | ||
| 617 | - ItalicMd(), | ||
| 618 | - LatexMath(), | ||
| 619 | - LatexMathMultiLine(), | ||
| 620 | - HighlightedText(), | ||
| 621 | - SourceTag(), | ||
| 622 | - ], | 624 | + |
| 625 | + // tableBuilder: (context, tableRows, | ||
| 626 | + // textStyle, config) { | ||
| 627 | + // return Table( | ||
| 628 | + // border: TableBorder.all( | ||
| 629 | + // width: 1, | ||
| 630 | + // color: Colors.red, | ||
| 631 | + // ), | ||
| 632 | + // children: tableRows.map((e) { | ||
| 633 | + // return TableRow( | ||
| 634 | + // children: e.fields.map((e) { | ||
| 635 | + // return Text(e.data); | ||
| 636 | + // }).toList(), | ||
| 637 | + // ); | ||
| 638 | + // }).toList(), | ||
| 639 | + // ); | ||
| 640 | + // }, | ||
| 641 | + | ||
| 642 | + // components: [ | ||
| 643 | + // CodeBlockMd(), | ||
| 644 | + // NewLines(), | ||
| 645 | + // BlockQuote(), | ||
| 646 | + // ImageMd(), | ||
| 647 | + // ATagMd(), | ||
| 648 | + // TableMd(), | ||
| 649 | + // HTag(), | ||
| 650 | + // UnOrderedList(), | ||
| 651 | + // OrderedList(), | ||
| 652 | + // RadioButtonMd(), | ||
| 653 | + // CheckBoxMd(), | ||
| 654 | + // HrLine(), | ||
| 655 | + // StrikeMd(), | ||
| 656 | + // BoldMd(), | ||
| 657 | + // ItalicMd(), | ||
| 658 | + // LatexMath(), | ||
| 659 | + // LatexMathMultiLine(), | ||
| 660 | + // HighlightedText(), | ||
| 661 | + // SourceTag(), | ||
| 662 | + // IndentMd(), | ||
| 663 | + // ], | ||
| 664 | + // inlineComponents: [ | ||
| 665 | + // ImageMd(), | ||
| 666 | + // ATagMd(), | ||
| 667 | + // TableMd(), | ||
| 668 | + // StrikeMd(), | ||
| 669 | + // BoldMd(), | ||
| 670 | + // ItalicMd(), | ||
| 671 | + // LatexMath(), | ||
| 672 | + // LatexMathMultiLine(), | ||
| 673 | + // HighlightedText(), | ||
| 674 | + // SourceTag(), | ||
| 675 | + // ], | ||
| 623 | // codeBuilder: (context, name, code, closed) { | 676 | // codeBuilder: (context, name, code, closed) { |
| 624 | // return Padding( | 677 | // return Padding( |
| 625 | // padding: const EdgeInsets.symmetric( | 678 | // padding: const EdgeInsets.symmetric( |
| 1 | -platform :osx, '10.14' | 1 | +platform :osx, '10.15' |
| 2 | 2 | ||
| 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. |
| 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' | 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' |
| @@ -15,8 +15,8 @@ EXTERNAL SOURCES: | @@ -15,8 +15,8 @@ EXTERNAL SOURCES: | ||
| 15 | 15 | ||
| 16 | SPEC CHECKSUMS: | 16 | SPEC CHECKSUMS: |
| 17 | desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 | 17 | desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 |
| 18 | - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 | 18 | + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 |
| 19 | 19 | ||
| 20 | -PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 | 20 | +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 |
| 21 | 21 | ||
| 22 | COCOAPODS: 1.16.2 | 22 | COCOAPODS: 1.16.2 |
| @@ -553,7 +553,7 @@ | @@ -553,7 +553,7 @@ | ||
| 553 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 553 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
| 554 | GCC_WARN_UNUSED_FUNCTION = YES; | 554 | GCC_WARN_UNUSED_FUNCTION = YES; |
| 555 | GCC_WARN_UNUSED_VARIABLE = YES; | 555 | GCC_WARN_UNUSED_VARIABLE = YES; |
| 556 | - MACOSX_DEPLOYMENT_TARGET = 10.14; | 556 | + MACOSX_DEPLOYMENT_TARGET = 10.15; |
| 557 | MTL_ENABLE_DEBUG_INFO = NO; | 557 | MTL_ENABLE_DEBUG_INFO = NO; |
| 558 | SDKROOT = macosx; | 558 | SDKROOT = macosx; |
| 559 | SWIFT_COMPILATION_MODE = wholemodule; | 559 | SWIFT_COMPILATION_MODE = wholemodule; |
| @@ -632,7 +632,7 @@ | @@ -632,7 +632,7 @@ | ||
| 632 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 632 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
| 633 | GCC_WARN_UNUSED_FUNCTION = YES; | 633 | GCC_WARN_UNUSED_FUNCTION = YES; |
| 634 | GCC_WARN_UNUSED_VARIABLE = YES; | 634 | GCC_WARN_UNUSED_VARIABLE = YES; |
| 635 | - MACOSX_DEPLOYMENT_TARGET = 10.14; | 635 | + MACOSX_DEPLOYMENT_TARGET = 10.15; |
| 636 | MTL_ENABLE_DEBUG_INFO = YES; | 636 | MTL_ENABLE_DEBUG_INFO = YES; |
| 637 | ONLY_ACTIVE_ARCH = YES; | 637 | ONLY_ACTIVE_ARCH = YES; |
| 638 | SDKROOT = macosx; | 638 | SDKROOT = macosx; |
| @@ -679,7 +679,7 @@ | @@ -679,7 +679,7 @@ | ||
| 679 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; | 679 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; |
| 680 | GCC_WARN_UNUSED_FUNCTION = YES; | 680 | GCC_WARN_UNUSED_FUNCTION = YES; |
| 681 | GCC_WARN_UNUSED_VARIABLE = YES; | 681 | GCC_WARN_UNUSED_VARIABLE = YES; |
| 682 | - MACOSX_DEPLOYMENT_TARGET = 10.14; | 682 | + MACOSX_DEPLOYMENT_TARGET = 10.15; |
| 683 | MTL_ENABLE_DEBUG_INFO = NO; | 683 | MTL_ENABLE_DEBUG_INFO = NO; |
| 684 | SDKROOT = macosx; | 684 | SDKROOT = macosx; |
| 685 | SWIFT_COMPILATION_MODE = wholemodule; | 685 | SWIFT_COMPILATION_MODE = wholemodule; |
| @@ -72,7 +72,13 @@ class _CodeFieldState extends State<CodeField> { | @@ -72,7 +72,13 @@ class _CodeFieldState extends State<CodeField> { | ||
| 72 | SingleChildScrollView( | 72 | SingleChildScrollView( |
| 73 | scrollDirection: Axis.horizontal, | 73 | scrollDirection: Axis.horizontal, |
| 74 | padding: const EdgeInsets.all(16), | 74 | padding: const EdgeInsets.all(16), |
| 75 | - child: Text(widget.codes), | 75 | + child: Text( |
| 76 | + widget.codes, | ||
| 77 | + style: TextStyle( | ||
| 78 | + fontFamily: 'JetBrainsMono', | ||
| 79 | + package: "gpt_markdown", | ||
| 80 | + ), | ||
| 81 | + ), | ||
| 76 | ), | 82 | ), |
| 77 | ], | 83 | ], |
| 78 | ), | 84 | ), |
| @@ -24,6 +24,7 @@ class CustomRb extends StatelessWidget { | @@ -24,6 +24,7 @@ class CustomRb extends StatelessWidget { | ||
| 24 | return Directionality( | 24 | return Directionality( |
| 25 | textDirection: textDirection, | 25 | textDirection: textDirection, |
| 26 | child: Row( | 26 | child: Row( |
| 27 | + mainAxisSize: MainAxisSize.min, | ||
| 27 | textBaseline: TextBaseline.alphabetic, | 28 | textBaseline: TextBaseline.alphabetic, |
| 28 | crossAxisAlignment: CrossAxisAlignment.baseline, | 29 | crossAxisAlignment: CrossAxisAlignment.baseline, |
| 29 | children: [ | 30 | children: [ |
| @@ -35,16 +36,18 @@ class CustomRb extends StatelessWidget { | @@ -35,16 +36,18 @@ class CustomRb extends StatelessWidget { | ||
| 35 | start: spacing, | 36 | start: spacing, |
| 36 | end: spacing, | 37 | end: spacing, |
| 37 | ), | 38 | ), |
| 38 | - child: Radio( | ||
| 39 | - value: value, | ||
| 40 | - groupValue: true, | ||
| 41 | - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, | 39 | + child: RadioGroup( |
| 42 | onChanged: (value) {}, | 40 | onChanged: (value) {}, |
| 41 | + groupValue: true, | ||
| 42 | + child: Radio( | ||
| 43 | + value: value, | ||
| 44 | + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, | ||
| 45 | + ), | ||
| 43 | ), | 46 | ), |
| 44 | ), | 47 | ), |
| 45 | ), | 48 | ), |
| 46 | ), | 49 | ), |
| 47 | - Expanded(child: child), | 50 | + Flexible(child: child), |
| 48 | ], | 51 | ], |
| 49 | ), | 52 | ), |
| 50 | ); | 53 | ); |
| @@ -75,6 +78,7 @@ class CustomCb extends StatelessWidget { | @@ -75,6 +78,7 @@ class CustomCb extends StatelessWidget { | ||
| 75 | return Directionality( | 78 | return Directionality( |
| 76 | textDirection: textDirection, | 79 | textDirection: textDirection, |
| 77 | child: Row( | 80 | child: Row( |
| 81 | + mainAxisSize: MainAxisSize.min, | ||
| 78 | textBaseline: TextBaseline.alphabetic, | 82 | textBaseline: TextBaseline.alphabetic, |
| 79 | crossAxisAlignment: CrossAxisAlignment.baseline, | 83 | crossAxisAlignment: CrossAxisAlignment.baseline, |
| 80 | children: [ | 84 | children: [ |
| @@ -90,7 +94,7 @@ class CustomCb extends StatelessWidget { | @@ -90,7 +94,7 @@ class CustomCb extends StatelessWidget { | ||
| 90 | ), | 94 | ), |
| 91 | ), | 95 | ), |
| 92 | ), | 96 | ), |
| 93 | - Expanded(child: child), | 97 | + Flexible(child: child), |
| 94 | ], | 98 | ], |
| 95 | ), | 99 | ), |
| 96 | ); | 100 | ); |
| @@ -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,11 +44,20 @@ typedef LatexBuilder = | @@ -44,11 +44,20 @@ 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 | ); |
| 51 | 51 | ||
| 52 | +/// A builder function for the table. | ||
| 53 | +typedef TableBuilder = | ||
| 54 | + Widget Function( | ||
| 55 | + BuildContext context, | ||
| 56 | + List<CustomTableRow> tableRows, | ||
| 57 | + TextStyle textStyle, | ||
| 58 | + GptMarkdownConfig config, | ||
| 59 | + ); | ||
| 60 | + | ||
| 52 | /// A builder function for the highlight. | 61 | /// A builder function for the highlight. |
| 53 | typedef HighlightBuilder = | 62 | typedef HighlightBuilder = |
| 54 | Widget Function(BuildContext context, String text, TextStyle style); | 63 | Widget Function(BuildContext context, String text, TextStyle style); |
| @@ -61,12 +70,12 @@ typedef ImageBuilder = Widget Function(BuildContext context, String imageUrl); | @@ -61,12 +70,12 @@ typedef ImageBuilder = Widget Function(BuildContext context, String imageUrl); | ||
| 61 | /// The [GptMarkdownConfig] class is used to configure the GPT Markdown component. | 70 | /// The [GptMarkdownConfig] class is used to configure the GPT Markdown component. |
| 62 | /// It takes a [style] parameter to set the style of the text, | 71 | /// It takes a [style] parameter to set the style of the text, |
| 63 | /// a [textDirection] parameter to set the direction of the text, | 72 | /// a [textDirection] parameter to set the direction of the text, |
| 64 | -/// and an optional [onLinkTab] parameter to handle link clicks. | 73 | +/// and an optional [onLinkTap] parameter to handle link clicks. |
| 65 | class GptMarkdownConfig { | 74 | class GptMarkdownConfig { |
| 66 | const GptMarkdownConfig({ | 75 | const GptMarkdownConfig({ |
| 67 | this.style, | 76 | this.style, |
| 68 | this.textDirection = TextDirection.ltr, | 77 | this.textDirection = TextDirection.ltr, |
| 69 | - this.onLinkTab, | 78 | + this.onLinkTap, |
| 70 | this.textAlign, | 79 | this.textAlign, |
| 71 | this.textScaler, | 80 | this.textScaler, |
| 72 | this.latexWorkaround, | 81 | this.latexWorkaround, |
| @@ -83,6 +92,7 @@ class GptMarkdownConfig { | @@ -83,6 +92,7 @@ class GptMarkdownConfig { | ||
| 83 | this.overflow, | 92 | this.overflow, |
| 84 | this.components, | 93 | this.components, |
| 85 | this.inlineComponents, | 94 | this.inlineComponents, |
| 95 | + this.tableBuilder, | ||
| 86 | }); | 96 | }); |
| 87 | 97 | ||
| 88 | /// The direction of the text. | 98 | /// The direction of the text. |
| @@ -98,7 +108,7 @@ class GptMarkdownConfig { | @@ -98,7 +108,7 @@ class GptMarkdownConfig { | ||
| 98 | final TextScaler? textScaler; | 108 | final TextScaler? textScaler; |
| 99 | 109 | ||
| 100 | /// The callback function to handle link clicks. | 110 | /// The callback function to handle link clicks. |
| 101 | - final void Function(String url, String title)? onLinkTab; | 111 | + final void Function(String url, String title)? onLinkTap; |
| 102 | 112 | ||
| 103 | /// The LaTeX workaround. | 113 | /// The LaTeX workaround. |
| 104 | final String Function(String tex)? latexWorkaround; | 114 | final String Function(String tex)? latexWorkaround; |
| @@ -142,11 +152,14 @@ class GptMarkdownConfig { | @@ -142,11 +152,14 @@ class GptMarkdownConfig { | ||
| 142 | /// The list of inline components. | 152 | /// The list of inline components. |
| 143 | final List<MarkdownComponent>? inlineComponents; | 153 | final List<MarkdownComponent>? inlineComponents; |
| 144 | 154 | ||
| 155 | + /// The table builder. | ||
| 156 | + final TableBuilder? tableBuilder; | ||
| 157 | + | ||
| 145 | /// A copy of the configuration with the specified parameters. | 158 | /// A copy of the configuration with the specified parameters. |
| 146 | GptMarkdownConfig copyWith({ | 159 | GptMarkdownConfig copyWith({ |
| 147 | TextStyle? style, | 160 | TextStyle? style, |
| 148 | TextDirection? textDirection, | 161 | TextDirection? textDirection, |
| 149 | - final void Function(String url, String title)? onLinkTab, | 162 | + final void Function(String url, String title)? onLinkTap, |
| 150 | final TextAlign? textAlign, | 163 | final TextAlign? textAlign, |
| 151 | final TextScaler? textScaler, | 164 | final TextScaler? textScaler, |
| 152 | final String Function(String tex)? latexWorkaround, | 165 | final String Function(String tex)? latexWorkaround, |
| @@ -163,11 +176,12 @@ class GptMarkdownConfig { | @@ -163,11 +176,12 @@ class GptMarkdownConfig { | ||
| 163 | final UnOrderedListBuilder? unOrderedListBuilder, | 176 | final UnOrderedListBuilder? unOrderedListBuilder, |
| 164 | final List<MarkdownComponent>? components, | 177 | final List<MarkdownComponent>? components, |
| 165 | final List<MarkdownComponent>? inlineComponents, | 178 | final List<MarkdownComponent>? inlineComponents, |
| 179 | + final TableBuilder? tableBuilder, | ||
| 166 | }) { | 180 | }) { |
| 167 | return GptMarkdownConfig( | 181 | return GptMarkdownConfig( |
| 168 | style: style ?? this.style, | 182 | style: style ?? this.style, |
| 169 | textDirection: textDirection ?? this.textDirection, | 183 | textDirection: textDirection ?? this.textDirection, |
| 170 | - onLinkTab: onLinkTab ?? this.onLinkTab, | 184 | + onLinkTap: onLinkTap ?? this.onLinkTap, |
| 171 | textAlign: textAlign ?? this.textAlign, | 185 | textAlign: textAlign ?? this.textAlign, |
| 172 | textScaler: textScaler ?? this.textScaler, | 186 | textScaler: textScaler ?? this.textScaler, |
| 173 | latexWorkaround: latexWorkaround ?? this.latexWorkaround, | 187 | latexWorkaround: latexWorkaround ?? this.latexWorkaround, |
| @@ -184,6 +198,7 @@ class GptMarkdownConfig { | @@ -184,6 +198,7 @@ class GptMarkdownConfig { | ||
| 184 | unOrderedListBuilder: unOrderedListBuilder ?? this.unOrderedListBuilder, | 198 | unOrderedListBuilder: unOrderedListBuilder ?? this.unOrderedListBuilder, |
| 185 | components: components ?? this.components, | 199 | components: components ?? this.components, |
| 186 | inlineComponents: inlineComponents ?? this.inlineComponents, | 200 | inlineComponents: inlineComponents ?? this.inlineComponents, |
| 201 | + tableBuilder: tableBuilder ?? this.tableBuilder, | ||
| 187 | ); | 202 | ); |
| 188 | } | 203 | } |
| 189 | 204 | ||
| @@ -198,4 +213,27 @@ class GptMarkdownConfig { | @@ -198,4 +213,27 @@ class GptMarkdownConfig { | ||
| 198 | overflow: overflow, | 213 | overflow: overflow, |
| 199 | ); | 214 | ); |
| 200 | } | 215 | } |
| 216 | + | ||
| 217 | + /// A method to check if the configuration is the same. | ||
| 218 | + bool isSame(GptMarkdownConfig other) { | ||
| 219 | + return style == other.style && | ||
| 220 | + textAlign == other.textAlign && | ||
| 221 | + textScaler == other.textScaler && | ||
| 222 | + maxLines == other.maxLines && | ||
| 223 | + overflow == other.overflow && | ||
| 224 | + followLinkColor == other.followLinkColor && | ||
| 225 | + // latexWorkaround == other.latexWorkaround && | ||
| 226 | + // components == other.components && | ||
| 227 | + // inlineComponents == other.inlineComponents && | ||
| 228 | + // latexBuilder == other.latexBuilder && | ||
| 229 | + // sourceTagBuilder == other.sourceTagBuilder && | ||
| 230 | + // codeBuilder == other.codeBuilder && | ||
| 231 | + // orderedListBuilder == other.orderedListBuilder && | ||
| 232 | + // unOrderedListBuilder == other.unOrderedListBuilder && | ||
| 233 | + // linkBuilder == other.linkBuilder && | ||
| 234 | + // imageBuilder == other.imageBuilder && | ||
| 235 | + // highlightBuilder == other.highlightBuilder && | ||
| 236 | + // onLinkTap == other.onLinkTap && | ||
| 237 | + textDirection == other.textDirection; | ||
| 238 | + } | ||
| 201 | } | 239 | } |
| @@ -38,6 +38,7 @@ class UnorderedListView extends StatelessWidget { | @@ -38,6 +38,7 @@ class UnorderedListView extends StatelessWidget { | ||
| 38 | return Directionality( | 38 | return Directionality( |
| 39 | textDirection: textDirection, | 39 | textDirection: textDirection, |
| 40 | child: Row( | 40 | child: Row( |
| 41 | + mainAxisSize: MainAxisSize.min, | ||
| 41 | textBaseline: TextBaseline.alphabetic, | 42 | textBaseline: TextBaseline.alphabetic, |
| 42 | crossAxisAlignment: CrossAxisAlignment.baseline, | 43 | crossAxisAlignment: CrossAxisAlignment.baseline, |
| 43 | children: [ | 44 | children: [ |
| @@ -63,7 +64,7 @@ class UnorderedListView extends StatelessWidget { | @@ -63,7 +64,7 @@ class UnorderedListView extends StatelessWidget { | ||
| 63 | ), | 64 | ), |
| 64 | ), | 65 | ), |
| 65 | ), | 66 | ), |
| 66 | - Expanded(child: child), | 67 | + Flexible(child: child), |
| 67 | ], | 68 | ], |
| 68 | ), | 69 | ), |
| 69 | ); | 70 | ); |
| @@ -104,6 +105,7 @@ class OrderedListView extends StatelessWidget { | @@ -104,6 +105,7 @@ class OrderedListView extends StatelessWidget { | ||
| 104 | return Directionality( | 105 | return Directionality( |
| 105 | textDirection: textDirection, | 106 | textDirection: textDirection, |
| 106 | child: Row( | 107 | child: Row( |
| 108 | + mainAxisSize: MainAxisSize.min, | ||
| 107 | textBaseline: TextBaseline.alphabetic, | 109 | textBaseline: TextBaseline.alphabetic, |
| 108 | crossAxisAlignment: CrossAxisAlignment.baseline, | 110 | crossAxisAlignment: CrossAxisAlignment.baseline, |
| 109 | children: [ | 111 | children: [ |
| @@ -111,7 +113,7 @@ class OrderedListView extends StatelessWidget { | @@ -111,7 +113,7 @@ class OrderedListView extends StatelessWidget { | ||
| 111 | padding: EdgeInsetsDirectional.only(start: padding, end: spacing), | 113 | padding: EdgeInsetsDirectional.only(start: padding, end: spacing), |
| 112 | child: Text.rich(TextSpan(text: no), style: _style), | 114 | child: Text.rich(TextSpan(text: no), style: _style), |
| 113 | ), | 115 | ), |
| 114 | - Expanded(child: child), | 116 | + Flexible(child: child), |
| 115 | ], | 117 | ], |
| 116 | ), | 118 | ), |
| 117 | ); | 119 | ); |
lib/fonts/JetBrainsMono-Regular.ttf
0 → 100644
No preview for this file type
| @@ -30,7 +30,7 @@ class GptMarkdown extends StatelessWidget { | @@ -30,7 +30,7 @@ class GptMarkdown extends StatelessWidget { | ||
| 30 | this.textAlign, | 30 | this.textAlign, |
| 31 | this.imageBuilder, | 31 | this.imageBuilder, |
| 32 | this.textScaler, | 32 | this.textScaler, |
| 33 | - this.onLinkTab, | 33 | + this.onLinkTap, |
| 34 | this.latexBuilder, | 34 | this.latexBuilder, |
| 35 | this.codeBuilder, | 35 | this.codeBuilder, |
| 36 | this.sourceTagBuilder, | 36 | this.sourceTagBuilder, |
| @@ -40,8 +40,10 @@ class GptMarkdown extends StatelessWidget { | @@ -40,8 +40,10 @@ class GptMarkdown extends StatelessWidget { | ||
| 40 | this.overflow, | 40 | this.overflow, |
| 41 | this.orderedListBuilder, | 41 | this.orderedListBuilder, |
| 42 | this.unOrderedListBuilder, | 42 | this.unOrderedListBuilder, |
| 43 | + this.tableBuilder, | ||
| 43 | this.components, | 44 | this.components, |
| 44 | this.inlineComponents, | 45 | this.inlineComponents, |
| 46 | + this.useDollarSignsForLatex = false, | ||
| 45 | }); | 47 | }); |
| 46 | 48 | ||
| 47 | /// The direction of the text. | 49 | /// The direction of the text. |
| @@ -60,7 +62,7 @@ class GptMarkdown extends StatelessWidget { | @@ -60,7 +62,7 @@ class GptMarkdown extends StatelessWidget { | ||
| 60 | final TextScaler? textScaler; | 62 | final TextScaler? textScaler; |
| 61 | 63 | ||
| 62 | /// The callback function to handle link clicks. | 64 | /// The callback function to handle link clicks. |
| 63 | - final void Function(String url, String title)? onLinkTab; | 65 | + final void Function(String url, String title)? onLinkTap; |
| 64 | 66 | ||
| 65 | /// The LaTeX workaround. | 67 | /// The LaTeX workaround. |
| 66 | final String Function(String tex)? latexWorkaround; | 68 | final String Function(String tex)? latexWorkaround; |
| @@ -96,6 +98,12 @@ class GptMarkdown extends StatelessWidget { | @@ -96,6 +98,12 @@ class GptMarkdown extends StatelessWidget { | ||
| 96 | /// The unordered list builder. | 98 | /// The unordered list builder. |
| 97 | final UnOrderedListBuilder? unOrderedListBuilder; | 99 | final UnOrderedListBuilder? unOrderedListBuilder; |
| 98 | 100 | ||
| 101 | + /// Whether to use dollar signs for LaTeX. | ||
| 102 | + final bool useDollarSignsForLatex; | ||
| 103 | + | ||
| 104 | + /// The table builder. | ||
| 105 | + final TableBuilder? tableBuilder; | ||
| 106 | + | ||
| 99 | /// The list of components. | 107 | /// The list of components. |
| 100 | /// ```dart | 108 | /// ```dart |
| 101 | /// List<MarkdownComponent> components = [ | 109 | /// List<MarkdownComponent> components = [ |
| @@ -154,37 +162,40 @@ class GptMarkdown extends StatelessWidget { | @@ -154,37 +162,40 @@ class GptMarkdown extends StatelessWidget { | ||
| 154 | @override | 162 | @override |
| 155 | Widget build(BuildContext context) { | 163 | Widget build(BuildContext context) { |
| 156 | String tex = data.trim(); | 164 | String tex = data.trim(); |
| 157 | - // print("texBefore:\n:$tex"); | ||
| 158 | - | ||
| 159 | - //去除 $$前面的空格 会导致渲染出错 | ||
| 160 | - tex = tex.replaceAllMapped( | ||
| 161 | - RegExp(r"(?<!\\)\s*\$\$(.*?)(?<!\\)\$\$", dotAll: true), | ||
| 162 | - (match) => "\n\\[${match[1] ?? ""}\\]", | ||
| 163 | - ); | 165 | + if (useDollarSignsForLatex) { |
| 166 | + // print("texBefore:\n:$tex"); | ||
| 164 | 167 | ||
| 165 | - // print("texAfter:\n:$tex"); | ||
| 166 | - | ||
| 167 | - if (!tex.contains(r"\(")) { | 168 | + //去除 $$前面的空格 会导致渲染出错 |
| 168 | tex = tex.replaceAllMapped( | 169 | tex = tex.replaceAllMapped( |
| 169 | - RegExp(r"(?<!\\)\$(.*?)(?<!\\)\$"), | ||
| 170 | - (match) => "\\(${match[1] ?? ""}\\)", | ||
| 171 | - ); | ||
| 172 | - tex = tex.splitMapJoin( | ||
| 173 | - RegExp(r"\[.*?\]|\(.*?\)"), | ||
| 174 | - onNonMatch: (p0) { | ||
| 175 | - return p0.replaceAll("\\\$", "\$"); | ||
| 176 | - }, | 170 | + RegExp(r"(?<!\\)\s*\$\$(.*?)(?<!\\)\$\$", dotAll: true), |
| 171 | + (match) => "\n\\[${match[1] ?? ""}\\]", | ||
| 177 | ); | 172 | ); |
| 173 | + | ||
| 174 | + // print("texAfter:\n:$tex"); | ||
| 175 | + | ||
| 176 | + if (!tex.contains(r"\(")) { | ||
| 177 | + tex = tex.replaceAllMapped( | ||
| 178 | + RegExp(r"(?<!\\)\$(.*?)(?<!\\)\$"), | ||
| 179 | + (match) => "\\(${match[1] ?? ""}\\)", | ||
| 180 | + ); | ||
| 181 | + tex = tex.splitMapJoin( | ||
| 182 | + RegExp(r"\[.*?\]|\(.*?\)"), | ||
| 183 | + onNonMatch: (p0) { | ||
| 184 | + return p0.replaceAll("\\\$", "\$"); | ||
| 185 | + }, | ||
| 186 | + ); | ||
| 187 | + } | ||
| 178 | } | 188 | } |
| 179 | // tex = _removeExtraLinesInsideBlockLatex(tex); | 189 | // tex = _removeExtraLinesInsideBlockLatex(tex); |
| 180 | return ClipRRect( | 190 | return ClipRRect( |
| 181 | child: MdWidget( | 191 | child: MdWidget( |
| 192 | + context, | ||
| 182 | tex, | 193 | tex, |
| 183 | true, | 194 | true, |
| 184 | config: GptMarkdownConfig( | 195 | config: GptMarkdownConfig( |
| 185 | textDirection: textDirection, | 196 | textDirection: textDirection, |
| 186 | style: style, | 197 | style: style, |
| 187 | - onLinkTab: onLinkTab, | 198 | + onLinkTap: onLinkTap, |
| 188 | textAlign: textAlign, | 199 | textAlign: textAlign, |
| 189 | textScaler: textScaler, | 200 | textScaler: textScaler, |
| 190 | followLinkColor: followLinkColor, | 201 | followLinkColor: followLinkColor, |
| @@ -201,6 +212,7 @@ class GptMarkdown extends StatelessWidget { | @@ -201,6 +212,7 @@ class GptMarkdown extends StatelessWidget { | ||
| 201 | unOrderedListBuilder: unOrderedListBuilder, | 212 | unOrderedListBuilder: unOrderedListBuilder, |
| 202 | components: components, | 213 | components: components, |
| 203 | inlineComponents: inlineComponents, | 214 | inlineComponents: inlineComponents, |
| 215 | + tableBuilder: tableBuilder, | ||
| 204 | ), | 216 | ), |
| 205 | ), | 217 | ), |
| 206 | ); | 218 | ); |
| @@ -2,12 +2,11 @@ part of 'gpt_markdown.dart'; | @@ -2,12 +2,11 @@ part of 'gpt_markdown.dart'; | ||
| 2 | 2 | ||
| 3 | /// Markdown components | 3 | /// Markdown components |
| 4 | abstract class MarkdownComponent { | 4 | abstract class MarkdownComponent { |
| 5 | - static final List<MarkdownComponent> components = [ | 5 | + static List<MarkdownComponent> get globalComponents => [ |
| 6 | CodeBlockMd(), | 6 | CodeBlockMd(), |
| 7 | + LatexMathMultiLine(), | ||
| 7 | NewLines(), | 8 | NewLines(), |
| 8 | BlockQuote(), | 9 | BlockQuote(), |
| 9 | - ImageMd(), | ||
| 10 | - ATagMd(), | ||
| 11 | TableMd(), | 10 | TableMd(), |
| 12 | HTag(), | 11 | HTag(), |
| 13 | UnOrderedList(), | 12 | UnOrderedList(), |
| @@ -15,23 +14,17 @@ abstract class MarkdownComponent { | @@ -15,23 +14,17 @@ abstract class MarkdownComponent { | ||
| 15 | RadioButtonMd(), | 14 | RadioButtonMd(), |
| 16 | CheckBoxMd(), | 15 | CheckBoxMd(), |
| 17 | HrLine(), | 16 | HrLine(), |
| 18 | - StrikeMd(), | ||
| 19 | - BoldMd(), | ||
| 20 | - ItalicMd(), | ||
| 21 | - LatexMath(), | ||
| 22 | - LatexMathMultiLine(), | ||
| 23 | - HighlightedText(), | ||
| 24 | - SourceTag(), | ||
| 25 | IndentMd(), | 17 | IndentMd(), |
| 26 | ]; | 18 | ]; |
| 27 | 19 | ||
| 28 | static final List<MarkdownComponent> inlineComponents = [ | 20 | static final List<MarkdownComponent> inlineComponents = [ |
| 29 | - ImageMd(), | ||
| 30 | ATagMd(), | 21 | ATagMd(), |
| 22 | + ImageMd(), | ||
| 31 | TableMd(), | 23 | TableMd(), |
| 32 | StrikeMd(), | 24 | StrikeMd(), |
| 33 | BoldMd(), | 25 | BoldMd(), |
| 34 | ItalicMd(), | 26 | ItalicMd(), |
| 27 | + UnderLineMd(), | ||
| 35 | LatexMath(), | 28 | LatexMath(), |
| 36 | LatexMathMultiLine(), | 29 | LatexMathMultiLine(), |
| 37 | HighlightedText(), | 30 | HighlightedText(), |
| @@ -47,7 +40,7 @@ abstract class MarkdownComponent { | @@ -47,7 +40,7 @@ abstract class MarkdownComponent { | ||
| 47 | ) { | 40 | ) { |
| 48 | var components = | 41 | var components = |
| 49 | includeGlobalComponents | 42 | includeGlobalComponents |
| 50 | - ? config.components ?? MarkdownComponent.components | 43 | + ? config.components ?? MarkdownComponent.globalComponents |
| 51 | : config.inlineComponents ?? MarkdownComponent.inlineComponents; | 44 | : config.inlineComponents ?? MarkdownComponent.inlineComponents; |
| 52 | List<InlineSpan> spans = []; | 45 | List<InlineSpan> spans = []; |
| 53 | Iterable<String> regexes = components.map<String>((e) => e.exp.pattern); | 46 | Iterable<String> regexes = components.map<String>((e) => e.exp.pattern); |
| @@ -75,6 +68,14 @@ abstract class MarkdownComponent { | @@ -75,6 +68,14 @@ abstract class MarkdownComponent { | ||
| 75 | return ""; | 68 | return ""; |
| 76 | }, | 69 | }, |
| 77 | onNonMatch: (p0) { | 70 | onNonMatch: (p0) { |
| 71 | + if (p0.isEmpty) { | ||
| 72 | + return ""; | ||
| 73 | + } | ||
| 74 | + if (includeGlobalComponents) { | ||
| 75 | + var newSpans = generate(context, p0, config.copyWith(), false); | ||
| 76 | + spans.addAll(newSpans); | ||
| 77 | + return ""; | ||
| 78 | + } | ||
| 78 | spans.add(TextSpan(text: p0, style: config.style)); | 79 | spans.add(TextSpan(text: p0, style: config.style)); |
| 79 | return ""; | 80 | return ""; |
| 80 | }, | 81 | }, |
| @@ -112,7 +113,8 @@ abstract class BlockMd extends MarkdownComponent { | @@ -112,7 +113,8 @@ abstract class BlockMd extends MarkdownComponent { | ||
| 112 | bool get inline => false; | 113 | bool get inline => false; |
| 113 | 114 | ||
| 114 | @override | 115 | @override |
| 115 | - RegExp get exp => RegExp(r'^\ *?' + expString, dotAll: true, multiLine: true); | 116 | + RegExp get exp => |
| 117 | + RegExp(r'^\ *?' + expString + r"$", dotAll: true, multiLine: true); | ||
| 116 | 118 | ||
| 117 | String get expString; | 119 | String get expString; |
| 118 | 120 | ||
| @@ -134,7 +136,10 @@ abstract class BlockMd extends MarkdownComponent { | @@ -134,7 +136,10 @@ abstract class BlockMd extends MarkdownComponent { | ||
| 134 | child: child, | 136 | child: child, |
| 135 | ); | 137 | ); |
| 136 | } | 138 | } |
| 137 | - child = Row(children: [Flexible(child: child)]); | 139 | + child = Row( |
| 140 | + mainAxisSize: MainAxisSize.min, | ||
| 141 | + children: [Flexible(child: child)], | ||
| 142 | + ); | ||
| 138 | return WidgetSpan( | 143 | return WidgetSpan( |
| 139 | child: child, | 144 | child: child, |
| 140 | alignment: PlaceholderAlignment.baseline, | 145 | alignment: PlaceholderAlignment.baseline, |
| @@ -164,6 +169,7 @@ class IndentMd extends BlockMd { | @@ -164,6 +169,7 @@ class IndentMd extends BlockMd { | ||
| 164 | return Directionality( | 169 | return Directionality( |
| 165 | textDirection: config.textDirection, | 170 | textDirection: config.textDirection, |
| 166 | child: Row( | 171 | child: Row( |
| 172 | + mainAxisSize: MainAxisSize.min, | ||
| 167 | children: [ | 173 | children: [ |
| 168 | Flexible( | 174 | Flexible( |
| 169 | child: config.getRich( | 175 | child: config.getRich( |
| @@ -196,14 +202,15 @@ class HTag extends BlockMd { | @@ -196,14 +202,15 @@ class HTag extends BlockMd { | ||
| 196 | var theme = GptMarkdownTheme.of(context); | 202 | var theme = GptMarkdownTheme.of(context); |
| 197 | var match = this.exp.firstMatch(text.trim()); | 203 | var match = this.exp.firstMatch(text.trim()); |
| 198 | var conf = config.copyWith( | 204 | var conf = config.copyWith( |
| 199 | - style: [ | ||
| 200 | - theme.h1, | ||
| 201 | - theme.h2, | ||
| 202 | - theme.h3, | ||
| 203 | - theme.h4, | ||
| 204 | - theme.h5, | ||
| 205 | - theme.h6, | ||
| 206 | - ][match![1]!.length - 1]?.copyWith(color: config.style?.color), | 205 | + style: |
| 206 | + [ | ||
| 207 | + theme.h1, | ||
| 208 | + theme.h2, | ||
| 209 | + theme.h3, | ||
| 210 | + theme.h4, | ||
| 211 | + theme.h5, | ||
| 212 | + theme.h6, | ||
| 213 | + ][match![1]!.length - 1], | ||
| 207 | ); | 214 | ); |
| 208 | return config.getRich( | 215 | return config.getRich( |
| 209 | TextSpan( | 216 | TextSpan( |
| @@ -257,7 +264,7 @@ class NewLines extends InlineMd { | @@ -257,7 +264,7 @@ class NewLines extends InlineMd { | ||
| 257 | /// Horizontal line component | 264 | /// Horizontal line component |
| 258 | class HrLine extends BlockMd { | 265 | class HrLine extends BlockMd { |
| 259 | @override | 266 | @override |
| 260 | - String get expString => (r"(--)[-]+$"); | 267 | + String get expString => (r"⸻|((--)[-]+)$"); |
| 261 | @override | 268 | @override |
| 262 | Widget build( | 269 | Widget build( |
| 263 | BuildContext context, | 270 | BuildContext context, |
| @@ -277,7 +284,6 @@ class HrLine extends BlockMd { | @@ -277,7 +284,6 @@ class HrLine extends BlockMd { | ||
| 277 | class CheckBoxMd extends BlockMd { | 284 | class CheckBoxMd extends BlockMd { |
| 278 | @override | 285 | @override |
| 279 | String get expString => (r"\[((?:\x|\ ))\]\ (\S[^\n]*?)$"); | 286 | String get expString => (r"\[((?:\x|\ ))\]\ (\S[^\n]*?)$"); |
| 280 | - get onLinkTab => null; | ||
| 281 | 287 | ||
| 282 | @override | 288 | @override |
| 283 | Widget build( | 289 | Widget build( |
| @@ -289,7 +295,7 @@ class CheckBoxMd extends BlockMd { | @@ -289,7 +295,7 @@ class CheckBoxMd extends BlockMd { | ||
| 289 | return CustomCb( | 295 | return CustomCb( |
| 290 | value: ("${match?[1]}" == "x"), | 296 | value: ("${match?[1]}" == "x"), |
| 291 | textDirection: config.textDirection, | 297 | textDirection: config.textDirection, |
| 292 | - child: MdWidget("${match?[2]}", false, config: config), | 298 | + child: MdWidget(context, "${match?[2]}", false, config: config), |
| 293 | ); | 299 | ); |
| 294 | } | 300 | } |
| 295 | } | 301 | } |
| @@ -298,7 +304,6 @@ class CheckBoxMd extends BlockMd { | @@ -298,7 +304,6 @@ class CheckBoxMd extends BlockMd { | ||
| 298 | class RadioButtonMd extends BlockMd { | 304 | class RadioButtonMd extends BlockMd { |
| 299 | @override | 305 | @override |
| 300 | String get expString => (r"\(((?:\x|\ ))\)\ (\S[^\n]*)$"); | 306 | String get expString => (r"\(((?:\x|\ ))\)\ (\S[^\n]*)$"); |
| 301 | - get onLinkTab => null; | ||
| 302 | 307 | ||
| 303 | @override | 308 | @override |
| 304 | Widget build( | 309 | Widget build( |
| @@ -310,7 +315,7 @@ class RadioButtonMd extends BlockMd { | @@ -310,7 +315,7 @@ class RadioButtonMd extends BlockMd { | ||
| 310 | return CustomRb( | 315 | return CustomRb( |
| 311 | value: ("${match?[1]}" == "x"), | 316 | value: ("${match?[1]}" == "x"), |
| 312 | textDirection: config.textDirection, | 317 | textDirection: config.textDirection, |
| 313 | - child: MdWidget("${match?[2]}", false, config: config), | 318 | + child: MdWidget(context, "${match?[2]}", false, config: config), |
| 314 | ); | 319 | ); |
| 315 | } | 320 | } |
| 316 | } | 321 | } |
| @@ -389,7 +394,7 @@ class UnOrderedList extends BlockMd { | @@ -389,7 +394,7 @@ class UnOrderedList extends BlockMd { | ||
| 389 | ) { | 394 | ) { |
| 390 | var match = this.exp.firstMatch(text); | 395 | var match = this.exp.firstMatch(text); |
| 391 | 396 | ||
| 392 | - var child = MdWidget("${match?[1]?.trim()}", true, config: config); | 397 | + var child = MdWidget(context, "${match?[1]?.trim()}", true, config: config); |
| 393 | 398 | ||
| 394 | return config.unOrderedListBuilder?.call( | 399 | return config.unOrderedListBuilder?.call( |
| 395 | context, | 400 | context, |
| @@ -423,11 +428,11 @@ class OrderedList extends BlockMd { | @@ -423,11 +428,11 @@ class OrderedList extends BlockMd { | ||
| 423 | String text, | 428 | String text, |
| 424 | final GptMarkdownConfig config, | 429 | final GptMarkdownConfig config, |
| 425 | ) { | 430 | ) { |
| 426 | - var match = this.exp.firstMatch(text.trim()); | 431 | + var match = this.exp.firstMatch(text); |
| 427 | 432 | ||
| 428 | - var no = "${match?[1]}"; | 433 | + var no = "${match?[1]}".trim(); |
| 429 | 434 | ||
| 430 | - var child = MdWidget("${match?[2]?.trim()}", true, config: config); | 435 | + var child = MdWidget(context, "${match?[2]}".trim(), true, config: config); |
| 431 | return config.orderedListBuilder?.call( | 436 | return config.orderedListBuilder?.call( |
| 432 | context, | 437 | context, |
| 433 | no, | 438 | no, |
| @@ -779,7 +784,7 @@ class SourceTag extends InlineMd { | @@ -779,7 +784,7 @@ class SourceTag extends InlineMd { | ||
| 779 | /// Link text component | 784 | /// Link text component |
| 780 | class ATagMd extends InlineMd { | 785 | class ATagMd extends InlineMd { |
| 781 | @override | 786 | @override |
| 782 | - RegExp get exp => RegExp(r"\[([^\s\*\[][^\n]*?[^\s]?)?\]\(([^\s\*]*[^\)])\)"); | 787 | + RegExp get exp => RegExp(r"(?<!\!)\[.*\]\([^\s]*\)"); |
| 783 | 788 | ||
| 784 | @override | 789 | @override |
| 785 | InlineSpan span( | 790 | InlineSpan span( |
| @@ -787,24 +792,91 @@ class ATagMd extends InlineMd { | @@ -787,24 +792,91 @@ class ATagMd extends InlineMd { | ||
| 787 | String text, | 792 | String text, |
| 788 | final GptMarkdownConfig config, | 793 | final GptMarkdownConfig config, |
| 789 | ) { | 794 | ) { |
| 790 | - var match = exp.firstMatch(text.trim()); | ||
| 791 | - if (match?[1] == null && match?[2] == null) { | 795 | + var bracketCount = 0; |
| 796 | + var start = 1; | ||
| 797 | + var end = 0; | ||
| 798 | + for (var i = 0; i < text.length; i++) { | ||
| 799 | + if (text[i] == '[') { | ||
| 800 | + bracketCount++; | ||
| 801 | + } else if (text[i] == ']') { | ||
| 802 | + bracketCount--; | ||
| 803 | + if (bracketCount == 0) { | ||
| 804 | + end = i; | ||
| 805 | + break; | ||
| 806 | + } | ||
| 807 | + } | ||
| 808 | + } | ||
| 809 | + | ||
| 810 | + if (text[end + 1] != '(') { | ||
| 792 | return const TextSpan(); | 811 | return const TextSpan(); |
| 793 | } | 812 | } |
| 794 | 813 | ||
| 795 | - final linkText = match?[1] ?? ""; | ||
| 796 | - final url = match?[2] ?? ""; | 814 | + // First try to find the basic pattern |
| 815 | + // final basicMatch = RegExp(r'(?<!\!)\[(.*)\]\(').firstMatch(text.trim()); | ||
| 816 | + // if (basicMatch == null) { | ||
| 817 | + // return const TextSpan(); | ||
| 818 | + // } | ||
| 819 | + | ||
| 820 | + final linkText = text.substring(start, end); | ||
| 821 | + final urlStart = end + 2; | ||
| 822 | + | ||
| 823 | + // Now find the balanced closing parenthesis | ||
| 824 | + int parenCount = 0; | ||
| 825 | + int urlEnd = urlStart; | ||
| 826 | + | ||
| 827 | + for (int i = urlStart; i < text.length; i++) { | ||
| 828 | + final char = text[i]; | ||
| 829 | + | ||
| 830 | + if (char == '(') { | ||
| 831 | + parenCount++; | ||
| 832 | + } else if (char == ')') { | ||
| 833 | + if (parenCount == 0) { | ||
| 834 | + // This is the closing parenthesis of the link | ||
| 835 | + urlEnd = i; | ||
| 836 | + break; | ||
| 837 | + } else { | ||
| 838 | + parenCount--; | ||
| 839 | + } | ||
| 840 | + } | ||
| 841 | + } | ||
| 842 | + | ||
| 843 | + if (urlEnd == urlStart) { | ||
| 844 | + // No closing parenthesis found | ||
| 845 | + return const TextSpan(); | ||
| 846 | + } | ||
| 847 | + | ||
| 848 | + final url = text.substring(urlStart, urlEnd).trim(); | ||
| 797 | 849 | ||
| 798 | var builder = config.linkBuilder; | 850 | var builder = config.linkBuilder; |
| 799 | 851 | ||
| 852 | + var ending = text.substring(urlEnd + 1); | ||
| 853 | + | ||
| 854 | + var endingSpans = MarkdownComponent.generate( | ||
| 855 | + context, | ||
| 856 | + ending, | ||
| 857 | + config, | ||
| 858 | + false, | ||
| 859 | + ); | ||
| 860 | + var theme = GptMarkdownTheme.of(context); | ||
| 861 | + var linkTextSpan = TextSpan( | ||
| 862 | + children: MarkdownComponent.generate(context, linkText, config, false), | ||
| 863 | + style: config.style?.copyWith( | ||
| 864 | + color: theme.linkColor, | ||
| 865 | + decorationColor: theme.linkColor, | ||
| 866 | + ), | ||
| 867 | + ); | ||
| 868 | + | ||
| 800 | // Use custom builder if provided | 869 | // Use custom builder if provided |
| 870 | + WidgetSpan? child; | ||
| 801 | if (builder != null) { | 871 | if (builder != null) { |
| 802 | - return WidgetSpan( | 872 | + child = WidgetSpan( |
| 873 | + baseline: TextBaseline.alphabetic, | ||
| 874 | + alignment: PlaceholderAlignment.baseline, | ||
| 803 | child: GestureDetector( | 875 | child: GestureDetector( |
| 804 | - onTap: () => config.onLinkTab?.call(url, linkText), | 876 | + onTap: () => config.onLinkTap?.call(url, linkText), |
| 805 | child: builder( | 877 | child: builder( |
| 806 | context, | 878 | context, |
| 807 | - linkText, | 879 | + linkTextSpan, |
| 808 | url, | 880 | url, |
| 809 | config.style ?? const TextStyle(), | 881 | config.style ?? const TextStyle(), |
| 810 | ), | 882 | ), |
| @@ -813,25 +885,29 @@ class ATagMd extends InlineMd { | @@ -813,25 +885,29 @@ class ATagMd extends InlineMd { | ||
| 813 | } | 885 | } |
| 814 | 886 | ||
| 815 | // Default rendering | 887 | // Default rendering |
| 816 | - var theme = GptMarkdownTheme.of(context); | ||
| 817 | - return WidgetSpan( | 888 | + child ??= WidgetSpan( |
| 889 | + alignment: PlaceholderAlignment.baseline, | ||
| 890 | + baseline: TextBaseline.alphabetic, | ||
| 818 | child: LinkButton( | 891 | child: LinkButton( |
| 819 | hoverColor: theme.linkHoverColor, | 892 | hoverColor: theme.linkHoverColor, |
| 820 | color: theme.linkColor, | 893 | color: theme.linkColor, |
| 821 | onPressed: () { | 894 | onPressed: () { |
| 822 | - config.onLinkTab?.call(url, linkText); | 895 | + config.onLinkTap?.call(url, linkText); |
| 823 | }, | 896 | }, |
| 824 | text: linkText, | 897 | text: linkText, |
| 825 | config: config, | 898 | config: config, |
| 899 | + child: config.getRich(linkTextSpan), | ||
| 826 | ), | 900 | ), |
| 827 | ); | 901 | ); |
| 902 | + var textSpan = TextSpan(children: [child, ...endingSpans]); | ||
| 903 | + return textSpan; | ||
| 828 | } | 904 | } |
| 829 | } | 905 | } |
| 830 | 906 | ||
| 831 | /// Image component | 907 | /// Image component |
| 832 | class ImageMd extends InlineMd { | 908 | class ImageMd extends InlineMd { |
| 833 | @override | 909 | @override |
| 834 | - RegExp get exp => RegExp(r"\!\[([^\s][^\n]*[^\s]?)?\]\(([^\s]+?)\)"); | 910 | + RegExp get exp => RegExp(r"\!\[[^\[\]]*\]\([^\s]*\)"); |
| 835 | 911 | ||
| 836 | @override | 912 | @override |
| 837 | InlineSpan span( | 913 | InlineSpan span( |
| @@ -839,25 +915,59 @@ class ImageMd extends InlineMd { | @@ -839,25 +915,59 @@ class ImageMd extends InlineMd { | ||
| 839 | String text, | 915 | String text, |
| 840 | final GptMarkdownConfig config, | 916 | final GptMarkdownConfig config, |
| 841 | ) { | 917 | ) { |
| 842 | - var match = exp.firstMatch(text.trim()); | 918 | + // First try to find the basic pattern |
| 919 | + final basicMatch = RegExp(r'\!\[([^\[\]]*)\]\(').firstMatch(text.trim()); | ||
| 920 | + if (basicMatch == null) { | ||
| 921 | + return const TextSpan(); | ||
| 922 | + } | ||
| 923 | + | ||
| 924 | + final altText = basicMatch.group(1) ?? ''; | ||
| 925 | + final urlStart = basicMatch.end; | ||
| 926 | + | ||
| 927 | + // Now find the balanced closing parenthesis | ||
| 928 | + int parenCount = 0; | ||
| 929 | + int urlEnd = urlStart; | ||
| 930 | + | ||
| 931 | + for (int i = urlStart; i < text.length; i++) { | ||
| 932 | + final char = text[i]; | ||
| 933 | + | ||
| 934 | + if (char == '(') { | ||
| 935 | + parenCount++; | ||
| 936 | + } else if (char == ')') { | ||
| 937 | + if (parenCount == 0) { | ||
| 938 | + // This is the closing parenthesis of the image | ||
| 939 | + urlEnd = i; | ||
| 940 | + break; | ||
| 941 | + } else { | ||
| 942 | + parenCount--; | ||
| 943 | + } | ||
| 944 | + } | ||
| 945 | + } | ||
| 946 | + | ||
| 947 | + if (urlEnd == urlStart) { | ||
| 948 | + // No closing parenthesis found | ||
| 949 | + return const TextSpan(); | ||
| 950 | + } | ||
| 951 | + | ||
| 952 | + final url = text.substring(urlStart, urlEnd).trim(); | ||
| 953 | + | ||
| 843 | double? height; | 954 | double? height; |
| 844 | double? width; | 955 | double? width; |
| 845 | - if (match?[1] != null) { | ||
| 846 | - var size = RegExp( | ||
| 847 | - r"^([0-9]+)?x?([0-9]+)?", | ||
| 848 | - ).firstMatch(match![1].toString().trim()); | 956 | + if (altText.isNotEmpty) { |
| 957 | + var size = RegExp(r"^([0-9]+)?x?([0-9]+)?").firstMatch(altText.trim()); | ||
| 849 | width = double.tryParse(size?[1]?.toString().trim() ?? 'a'); | 958 | width = double.tryParse(size?[1]?.toString().trim() ?? 'a'); |
| 850 | height = double.tryParse(size?[2]?.toString().trim() ?? 'a'); | 959 | height = double.tryParse(size?[2]?.toString().trim() ?? 'a'); |
| 851 | } | 960 | } |
| 961 | + | ||
| 852 | final Widget image; | 962 | final Widget image; |
| 853 | if (config.imageBuilder != null) { | 963 | if (config.imageBuilder != null) { |
| 854 | - image = config.imageBuilder!(context, '${match?[2]}'); | 964 | + image = config.imageBuilder!(context, url); |
| 855 | } else { | 965 | } else { |
| 856 | image = SizedBox( | 966 | image = SizedBox( |
| 857 | width: width, | 967 | width: width, |
| 858 | height: height, | 968 | height: height, |
| 859 | child: Image( | 969 | child: Image( |
| 860 | - image: NetworkImage("${match?[2]}"), | 970 | + image: NetworkImage(url), |
| 861 | loadingBuilder: ( | 971 | loadingBuilder: ( |
| 862 | BuildContext context, | 972 | BuildContext context, |
| 863 | Widget child, | 973 | Widget child, |
| @@ -909,19 +1019,80 @@ class TableMd extends BlockMd { | @@ -909,19 +1019,80 @@ class TableMd extends BlockMd { | ||
| 909 | .asMap(), | 1019 | .asMap(), |
| 910 | ) | 1020 | ) |
| 911 | .toList(); | 1021 | .toList(); |
| 912 | - bool heading = RegExp( | ||
| 913 | - r"^\|.*?\|\n\|-[-\\ |]*?-\|$", | ||
| 914 | - multiLine: true, | ||
| 915 | - ).hasMatch(text.trim()); | 1022 | + |
| 1023 | + // Check if table has a header and separator row | ||
| 1024 | + bool hasHeader = value.length >= 2; | ||
| 1025 | + List<TextAlign> columnAlignments = []; | ||
| 1026 | + | ||
| 1027 | + if (hasHeader) { | ||
| 1028 | + // Parse alignment from the separator row (second row) | ||
| 1029 | + var separatorRow = value[1]; | ||
| 1030 | + columnAlignments = List.generate(separatorRow.length, (index) { | ||
| 1031 | + String separator = separatorRow[index] ?? ""; | ||
| 1032 | + separator = separator.trim(); | ||
| 1033 | + | ||
| 1034 | + // Check for alignment indicators | ||
| 1035 | + bool hasLeftColon = separator.startsWith(':'); | ||
| 1036 | + bool hasRightColon = separator.endsWith(':'); | ||
| 1037 | + | ||
| 1038 | + if (hasLeftColon && hasRightColon) { | ||
| 1039 | + return TextAlign.center; | ||
| 1040 | + } else if (hasRightColon) { | ||
| 1041 | + return TextAlign.right; | ||
| 1042 | + } else if (hasLeftColon) { | ||
| 1043 | + return TextAlign.left; | ||
| 1044 | + } else { | ||
| 1045 | + return TextAlign.left; // Default alignment | ||
| 1046 | + } | ||
| 1047 | + }); | ||
| 1048 | + } | ||
| 1049 | + | ||
| 916 | int maxCol = 0; | 1050 | int maxCol = 0; |
| 917 | for (final each in value) { | 1051 | for (final each in value) { |
| 918 | if (maxCol < each.keys.length) { | 1052 | if (maxCol < each.keys.length) { |
| 919 | maxCol = each.keys.length; | 1053 | maxCol = each.keys.length; |
| 920 | } | 1054 | } |
| 921 | } | 1055 | } |
| 1056 | + | ||
| 922 | if (maxCol == 0) { | 1057 | if (maxCol == 0) { |
| 923 | return Text("", style: config.style); | 1058 | return Text("", style: config.style); |
| 924 | } | 1059 | } |
| 1060 | + | ||
| 1061 | + // Ensure we have alignment for all columns | ||
| 1062 | + while (columnAlignments.length < maxCol) { | ||
| 1063 | + columnAlignments.add(TextAlign.left); | ||
| 1064 | + } | ||
| 1065 | + | ||
| 1066 | + var tableBuilder = config.tableBuilder; | ||
| 1067 | + | ||
| 1068 | + if (tableBuilder != null) { | ||
| 1069 | + var customTable = | ||
| 1070 | + List<CustomTableRow?>.generate(value.length, (index) { | ||
| 1071 | + var isHeader = index == 0; | ||
| 1072 | + var row = value[index]; | ||
| 1073 | + if (row.isEmpty) { | ||
| 1074 | + return null; | ||
| 1075 | + } | ||
| 1076 | + if (index == 1) { | ||
| 1077 | + return null; | ||
| 1078 | + } | ||
| 1079 | + var fields = List<CustomTableField>.generate(maxCol, (index) { | ||
| 1080 | + var field = row[index]; | ||
| 1081 | + return CustomTableField( | ||
| 1082 | + data: field ?? "", | ||
| 1083 | + alignment: columnAlignments[index], | ||
| 1084 | + ); | ||
| 1085 | + }); | ||
| 1086 | + return CustomTableRow(isHeader: isHeader, fields: fields); | ||
| 1087 | + }).nonNulls.toList(); | ||
| 1088 | + return tableBuilder( | ||
| 1089 | + context, | ||
| 1090 | + customTable, | ||
| 1091 | + config.style ?? const TextStyle(), | ||
| 1092 | + config, | ||
| 1093 | + ); | ||
| 1094 | + } | ||
| 1095 | + | ||
| 925 | final controller = ScrollController(); | 1096 | final controller = ScrollController(); |
| 926 | return Scrollbar( | 1097 | return Scrollbar( |
| 927 | controller: controller, | 1098 | controller: controller, |
| @@ -940,17 +1111,22 @@ class TableMd extends BlockMd { | @@ -940,17 +1111,22 @@ class TableMd extends BlockMd { | ||
| 940 | value | 1111 | value |
| 941 | .asMap() | 1112 | .asMap() |
| 942 | .entries | 1113 | .entries |
| 1114 | + .where((entry) { | ||
| 1115 | + // Skip the separator row (second row) from rendering | ||
| 1116 | + if (hasHeader && entry.key == 1) { | ||
| 1117 | + return false; | ||
| 1118 | + } | ||
| 1119 | + return true; | ||
| 1120 | + }) | ||
| 943 | .map<TableRow>( | 1121 | .map<TableRow>( |
| 944 | (entry) => TableRow( | 1122 | (entry) => TableRow( |
| 945 | decoration: | 1123 | decoration: |
| 946 | - (heading) | 1124 | + (hasHeader && entry.key == 0) |
| 947 | ? BoxDecoration( | 1125 | ? BoxDecoration( |
| 948 | color: | 1126 | color: |
| 949 | - (entry.key == 0) | ||
| 950 | - ? Theme.of( | ||
| 951 | - context, | ||
| 952 | - ).colorScheme.surfaceContainerHighest | ||
| 953 | - : null, | 1127 | + Theme.of( |
| 1128 | + context, | ||
| 1129 | + ).colorScheme.surfaceContainerHighest, | ||
| 954 | ) | 1130 | ) |
| 955 | : null, | 1131 | : null, |
| 956 | children: List.generate(maxCol, (index) { | 1132 | children: List.generate(maxCol, (index) { |
| @@ -961,19 +1137,41 @@ class TableMd extends BlockMd { | @@ -961,19 +1137,41 @@ class TableMd extends BlockMd { | ||
| 961 | return const SizedBox(); | 1137 | return const SizedBox(); |
| 962 | } | 1138 | } |
| 963 | 1139 | ||
| 964 | - return Center( | ||
| 965 | - child: Padding( | ||
| 966 | - padding: const EdgeInsets.symmetric( | ||
| 967 | - horizontal: 8, | ||
| 968 | - vertical: 4, | ||
| 969 | - ), | ||
| 970 | - child: MdWidget( | ||
| 971 | - (e[index] ?? "").trim(), | ||
| 972 | - false, | ||
| 973 | - config: config, | ||
| 974 | - ), | 1140 | + // Apply alignment based on column alignment |
| 1141 | + Widget content = Padding( | ||
| 1142 | + padding: const EdgeInsets.symmetric( | ||
| 1143 | + horizontal: 8, | ||
| 1144 | + vertical: 4, | ||
| 1145 | + ), | ||
| 1146 | + child: MdWidget( | ||
| 1147 | + context, | ||
| 1148 | + (e[index] ?? "").trim(), | ||
| 1149 | + false, | ||
| 1150 | + config: config, | ||
| 975 | ), | 1151 | ), |
| 976 | ); | 1152 | ); |
| 1153 | + | ||
| 1154 | + // Wrap with alignment widget | ||
| 1155 | + switch (columnAlignments[index]) { | ||
| 1156 | + case TextAlign.center: | ||
| 1157 | + content = Center(child: content); | ||
| 1158 | + break; | ||
| 1159 | + case TextAlign.right: | ||
| 1160 | + content = Align( | ||
| 1161 | + alignment: Alignment.centerRight, | ||
| 1162 | + child: content, | ||
| 1163 | + ); | ||
| 1164 | + break; | ||
| 1165 | + case TextAlign.left: | ||
| 1166 | + default: | ||
| 1167 | + content = Align( | ||
| 1168 | + alignment: Alignment.centerLeft, | ||
| 1169 | + child: content, | ||
| 1170 | + ); | ||
| 1171 | + break; | ||
| 1172 | + } | ||
| 1173 | + | ||
| 1174 | + return content; | ||
| 977 | }), | 1175 | }), |
| 978 | ), | 1176 | ), |
| 979 | ) | 1177 | ) |
| @@ -995,10 +1193,54 @@ class CodeBlockMd extends BlockMd { | @@ -995,10 +1193,54 @@ class CodeBlockMd extends BlockMd { | ||
| 995 | ) { | 1193 | ) { |
| 996 | String codes = this.exp.firstMatch(text)?[2] ?? ""; | 1194 | String codes = this.exp.firstMatch(text)?[2] ?? ""; |
| 997 | String name = this.exp.firstMatch(text)?[1] ?? ""; | 1195 | String name = this.exp.firstMatch(text)?[1] ?? ""; |
| 998 | - codes = codes.replaceAll(r"```", "").trim(); | 1196 | + codes = codes.replaceAll(r"```", ""); |
| 999 | bool closed = text.endsWith("```"); | 1197 | bool closed = text.endsWith("```"); |
| 1000 | 1198 | ||
| 1001 | return config.codeBuilder?.call(context, name, codes, closed) ?? | 1199 | return config.codeBuilder?.call(context, name, codes, closed) ?? |
| 1002 | CodeField(name: name, codes: codes); | 1200 | CodeField(name: name, codes: codes); |
| 1003 | } | 1201 | } |
| 1004 | } | 1202 | } |
| 1203 | + | ||
| 1204 | +class UnderLineMd extends InlineMd { | ||
| 1205 | + @override | ||
| 1206 | + RegExp get exp => | ||
| 1207 | + RegExp(r"<u>(.*?)(?:</u>|$)", multiLine: true, dotAll: true); | ||
| 1208 | + | ||
| 1209 | + @override | ||
| 1210 | + InlineSpan span( | ||
| 1211 | + BuildContext context, | ||
| 1212 | + String text, | ||
| 1213 | + final GptMarkdownConfig config, | ||
| 1214 | + ) { | ||
| 1215 | + var match = exp.firstMatch(text.trim()); | ||
| 1216 | + var conf = config.copyWith( | ||
| 1217 | + style: (config.style ?? const TextStyle()).copyWith( | ||
| 1218 | + decoration: TextDecoration.underline, | ||
| 1219 | + decorationColor: config.style?.color, | ||
| 1220 | + ), | ||
| 1221 | + ); | ||
| 1222 | + return TextSpan( | ||
| 1223 | + children: MarkdownComponent.generate( | ||
| 1224 | + context, | ||
| 1225 | + "${match?[1]}", | ||
| 1226 | + conf, | ||
| 1227 | + false, | ||
| 1228 | + ), | ||
| 1229 | + style: conf.style, | ||
| 1230 | + ); | ||
| 1231 | + } | ||
| 1232 | +} | ||
| 1233 | + | ||
| 1234 | +class CustomTableField { | ||
| 1235 | + final String data; | ||
| 1236 | + final TextAlign alignment; | ||
| 1237 | + | ||
| 1238 | + CustomTableField({required this.data, this.alignment = TextAlign.left}); | ||
| 1239 | +} | ||
| 1240 | + | ||
| 1241 | +class CustomTableRow { | ||
| 1242 | + final bool isHeader; | ||
| 1243 | + final List<CustomTableField> fields; | ||
| 1244 | + | ||
| 1245 | + CustomTableRow({this.isHeader = false, required this.fields}); | ||
| 1246 | +} |
| 1 | part of 'gpt_markdown.dart'; | 1 | part of 'gpt_markdown.dart'; |
| 2 | 2 | ||
| 3 | /// It creates a markdown widget closed to each other. | 3 | /// It creates a markdown widget closed to each other. |
| 4 | -class MdWidget extends StatelessWidget { | 4 | +class MdWidget extends StatefulWidget { |
| 5 | const MdWidget( | 5 | const MdWidget( |
| 6 | + this.context, | ||
| 6 | this.exp, | 7 | this.exp, |
| 7 | this.includeGlobalComponents, { | 8 | this.includeGlobalComponents, { |
| 8 | super.key, | 9 | super.key, |
| @@ -11,6 +12,7 @@ class MdWidget extends StatelessWidget { | @@ -11,6 +12,7 @@ class MdWidget extends StatelessWidget { | ||
| 11 | 12 | ||
| 12 | /// The expression to be displayed. | 13 | /// The expression to be displayed. |
| 13 | final String exp; | 14 | final String exp; |
| 15 | + final BuildContext context; | ||
| 14 | 16 | ||
| 15 | /// Whether to include global components. | 17 | /// Whether to include global components. |
| 16 | final bool includeGlobalComponents; | 18 | final bool includeGlobalComponents; |
| @@ -19,25 +21,46 @@ class MdWidget extends StatelessWidget { | @@ -19,25 +21,46 @@ class MdWidget extends StatelessWidget { | ||
| 19 | final GptMarkdownConfig config; | 21 | final GptMarkdownConfig config; |
| 20 | 22 | ||
| 21 | @override | 23 | @override |
| 22 | - Widget build(BuildContext context) { | ||
| 23 | - List<InlineSpan> list = MarkdownComponent.generate( | ||
| 24 | - context, | ||
| 25 | - exp, | ||
| 26 | - // .replaceAllMapped( | ||
| 27 | - // RegExp( | ||
| 28 | - // r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})", | ||
| 29 | - // multiLine: true, | ||
| 30 | - // dotAll: true, | ||
| 31 | - // ), (match) { | ||
| 32 | - // // | ||
| 33 | - // String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? ""; | ||
| 34 | - // return "\\[$body\\]"; | ||
| 35 | - // }), | ||
| 36 | - config, | ||
| 37 | - includeGlobalComponents, | 24 | + State<MdWidget> createState() => _MdWidgetState(); |
| 25 | +} | ||
| 26 | + | ||
| 27 | +class _MdWidgetState extends State<MdWidget> { | ||
| 28 | + List<InlineSpan> list = []; | ||
| 29 | + @override | ||
| 30 | + void initState() { | ||
| 31 | + super.initState(); | ||
| 32 | + list = MarkdownComponent.generate( | ||
| 33 | + widget.context, | ||
| 34 | + widget.exp, | ||
| 35 | + widget.config, | ||
| 36 | + widget.includeGlobalComponents, | ||
| 38 | ); | 37 | ); |
| 39 | - return config.getRich( | ||
| 40 | - TextSpan(children: list, style: config.style?.copyWith()), | 38 | + } |
| 39 | + | ||
| 40 | + @override | ||
| 41 | + void didUpdateWidget(covariant MdWidget oldWidget) { | ||
| 42 | + super.didUpdateWidget(oldWidget); | ||
| 43 | + if (oldWidget.exp != widget.exp || | ||
| 44 | + !oldWidget.config.isSame(widget.config)) { | ||
| 45 | + list = MarkdownComponent.generate( | ||
| 46 | + context, | ||
| 47 | + widget.exp, | ||
| 48 | + widget.config, | ||
| 49 | + widget.includeGlobalComponents, | ||
| 50 | + ); | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + @override | ||
| 55 | + Widget build(BuildContext context) { | ||
| 56 | + // List<InlineSpan> list = MarkdownComponent.generate( | ||
| 57 | + // context, | ||
| 58 | + // widget.exp, | ||
| 59 | + // widget.config, | ||
| 60 | + // widget.includeGlobalComponents, | ||
| 61 | + // ); | ||
| 62 | + return widget.config.getRich( | ||
| 63 | + TextSpan(children: list, style: widget.config.style?.copyWith()), | ||
| 41 | ); | 64 | ); |
| 42 | } | 65 | } |
| 43 | } | 66 | } |
| 1 | name: gpt_markdown | 1 | name: gpt_markdown |
| 2 | -description: "Powerful Markdown & LaTeX Renderer for Flutter: Rich Text, Math, Tables, Links, and Text Selection. Ideal for ChatGPT, Gemini, and more." | ||
| 3 | -version: 1.0.16 | 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.4 | ||
| 4 | homepage: https://github.com/Infinitix-LLC/gpt_markdown | 4 | homepage: https://github.com/Infinitix-LLC/gpt_markdown |
| 5 | 5 | ||
| 6 | environment: | 6 | environment: |
| @@ -14,9 +14,13 @@ dependencies: | @@ -14,9 +14,13 @@ dependencies: | ||
| 14 | dev_dependencies: | 14 | dev_dependencies: |
| 15 | flutter_test: | 15 | flutter_test: |
| 16 | sdk: flutter | 16 | sdk: flutter |
| 17 | -# flutter_lints: ^5.0.0 | 17 | +# flutter_lints: ^6.0.0 |
| 18 | 18 | ||
| 19 | flutter: | 19 | flutter: |
| 20 | + fonts: | ||
| 21 | + - family: JetBrainsMono | ||
| 22 | + fonts: | ||
| 23 | + - asset: lib/fonts/JetBrainsMono-Regular.ttf | ||
| 20 | 24 | ||
| 21 | topics: | 25 | topics: |
| 22 | - markdown | 26 | - markdown |
| @@ -24,3 +28,13 @@ topics: | @@ -24,3 +28,13 @@ topics: | ||
| 24 | - selectable | 28 | - selectable |
| 25 | - chatgpt | 29 | - chatgpt |
| 26 | - gemini | 30 | - gemini |
| 31 | + | ||
| 32 | +keywords: | ||
| 33 | + - flutter | ||
| 34 | + - markdown | ||
| 35 | + - flutter markdown | ||
| 36 | + - gpt | ||
| 37 | + - latex | ||
| 38 | + - chatgpt | ||
| 39 | + - rich text | ||
| 40 | + - ai |
-
Please register or login to post a comment