Committed by
GitHub
Merge pull request #26 from DvdNss/main
+ highlightBuilder && - default padding on code block
Showing
4 changed files
with
95 additions
and
13 deletions
| @@ -99,6 +99,7 @@ class MarkdownHelper { | @@ -99,6 +99,7 @@ class MarkdownHelper { | ||
| 99 | 99 | ||
| 100 | You can use Markdown to format text easily. Here are some examples: | 100 | You can use Markdown to format text easily. Here are some examples: |
| 101 | 101 | ||
| 102 | +- `Highlighted Text`: `This text is highlighted` | ||
| 102 | - **Bold Text**: **This text is bold** | 103 | - **Bold Text**: **This text is bold** |
| 103 | - *Italic Text*: *This text is italicized* | 104 | - *Italic Text*: *This text is italicized* |
| 104 | - [Link](https://www.example.com): [This is a link](https://www.example.com) | 105 | - [Link](https://www.example.com): [This is a link](https://www.example.com) |
| @@ -266,6 +267,29 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -266,6 +267,29 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
| 266 | style: const TextStyle( | 267 | style: const TextStyle( |
| 267 | fontSize: 15, | 268 | fontSize: 15, |
| 268 | ), | 269 | ), |
| 270 | + highlightBuilder: (context, text, style) { | ||
| 271 | + return Container( | ||
| 272 | + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), | ||
| 273 | + decoration: BoxDecoration( | ||
| 274 | + color: Theme.of(context).colorScheme.secondaryContainer, | ||
| 275 | + borderRadius: BorderRadius.circular(4), | ||
| 276 | + border: Border.all( | ||
| 277 | + color: Theme.of(context).colorScheme.secondary.withOpacity(0.5), | ||
| 278 | + width: 1, | ||
| 279 | + ), | ||
| 280 | + ), | ||
| 281 | + child: Text( | ||
| 282 | + text, | ||
| 283 | + style: TextStyle( | ||
| 284 | + color: Theme.of(context).colorScheme.onSecondaryContainer, | ||
| 285 | + fontFamily: 'monospace', | ||
| 286 | + fontWeight: FontWeight.bold, | ||
| 287 | + fontSize: style.fontSize != null ? style.fontSize! * 0.9 : 13.5, | ||
| 288 | + height: style.height, | ||
| 289 | + ), | ||
| 290 | + ), | ||
| 291 | + ); | ||
| 292 | + }, | ||
| 269 | latexWorkaround: (tex) { | 293 | latexWorkaround: (tex) { |
| 270 | List<String> stack = []; | 294 | List<String> stack = []; |
| 271 | tex = tex.splitMapJoin( | 295 | tex = tex.splitMapJoin( |
| @@ -377,6 +401,20 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -377,6 +401,20 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
| 377 | ); | 401 | ); |
| 378 | }, | 402 | }, |
| 379 | ); | 403 | ); |
| 404 | + codeBuilder: (context, name, code) { | ||
| 405 | + return Padding( | ||
| 406 | + padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 407 | + child: Text( | ||
| 408 | + code.trim(), | ||
| 409 | + style: TextStyle( | ||
| 410 | + fontFamily: 'JetBrains Mono', | ||
| 411 | + fontSize: 14, | ||
| 412 | + height: 1.5, | ||
| 413 | + color: Theme.of(context).colorScheme.onSurface, | ||
| 414 | + ), | ||
| 415 | + ), | ||
| 416 | + ); | ||
| 417 | + }; | ||
| 380 | if (selectable) { | 418 | if (selectable) { |
| 381 | child = SelectionArea( | 419 | child = SelectionArea( |
| 382 | child: child, | 420 | child: child, |
| @@ -12,6 +12,8 @@ class GptMarkdownConfig { | @@ -12,6 +12,8 @@ class GptMarkdownConfig { | ||
| 12 | this.followLinkColor = false, | 12 | this.followLinkColor = false, |
| 13 | this.codeBuilder, | 13 | this.codeBuilder, |
| 14 | this.sourceTagBuilder, | 14 | this.sourceTagBuilder, |
| 15 | + this.highlightBuilder, | ||
| 16 | + this.linkBuilder, | ||
| 15 | this.maxLines, | 17 | this.maxLines, |
| 16 | this.overflow, | 18 | this.overflow, |
| 17 | }); | 19 | }); |
| @@ -32,6 +34,8 @@ class GptMarkdownConfig { | @@ -32,6 +34,8 @@ class GptMarkdownConfig { | ||
| 32 | codeBuilder; | 34 | codeBuilder; |
| 33 | final int? maxLines; | 35 | final int? maxLines; |
| 34 | final TextOverflow? overflow; | 36 | final TextOverflow? overflow; |
| 37 | + final Widget Function(BuildContext context, String text, TextStyle style)? highlightBuilder; | ||
| 38 | + final Widget Function(BuildContext context, String text, String url, TextStyle style)? linkBuilder; | ||
| 35 | 39 | ||
| 36 | GptMarkdownConfig copyWith({ | 40 | GptMarkdownConfig copyWith({ |
| 37 | TextStyle? style, | 41 | TextStyle? style, |
| @@ -51,6 +55,8 @@ class GptMarkdownConfig { | @@ -51,6 +55,8 @@ class GptMarkdownConfig { | ||
| 51 | codeBuilder, | 55 | codeBuilder, |
| 52 | final int? maxLines, | 56 | final int? maxLines, |
| 53 | final TextOverflow? overflow, | 57 | final TextOverflow? overflow, |
| 58 | + final Widget Function(BuildContext context, String text, TextStyle style)? highlightBuilder, | ||
| 59 | + final Widget Function(BuildContext context, String text, String url, TextStyle style)? linkBuilder, | ||
| 54 | }) { | 60 | }) { |
| 55 | return GptMarkdownConfig( | 61 | return GptMarkdownConfig( |
| 56 | style: style ?? this.style, | 62 | style: style ?? this.style, |
| @@ -65,6 +71,8 @@ class GptMarkdownConfig { | @@ -65,6 +71,8 @@ class GptMarkdownConfig { | ||
| 65 | sourceTagBuilder: sourceTagBuilder ?? this.sourceTagBuilder, | 71 | sourceTagBuilder: sourceTagBuilder ?? this.sourceTagBuilder, |
| 66 | maxLines: maxLines ?? this.maxLines, | 72 | maxLines: maxLines ?? this.maxLines, |
| 67 | overflow: overflow ?? this.overflow, | 73 | overflow: overflow ?? this.overflow, |
| 74 | + highlightBuilder: highlightBuilder ?? this.highlightBuilder, | ||
| 75 | + linkBuilder: linkBuilder ?? this.linkBuilder, | ||
| 68 | ); | 76 | ); |
| 69 | } | 77 | } |
| 70 | 78 |
| @@ -34,6 +34,8 @@ class GptMarkdown extends StatelessWidget { | @@ -34,6 +34,8 @@ class GptMarkdown extends StatelessWidget { | ||
| 34 | this.latexBuilder, | 34 | this.latexBuilder, |
| 35 | this.codeBuilder, | 35 | this.codeBuilder, |
| 36 | this.sourceTagBuilder, | 36 | this.sourceTagBuilder, |
| 37 | + this.highlightBuilder, | ||
| 38 | + this.linkBuilder, | ||
| 37 | this.maxLines, | 39 | this.maxLines, |
| 38 | this.overflow, | 40 | this.overflow, |
| 39 | }); | 41 | }); |
| @@ -53,6 +55,8 @@ class GptMarkdown extends StatelessWidget { | @@ -53,6 +55,8 @@ class GptMarkdown extends StatelessWidget { | ||
| 53 | final Widget Function(BuildContext context, String name, String code)? | 55 | final Widget Function(BuildContext context, String name, String code)? |
| 54 | codeBuilder; | 56 | codeBuilder; |
| 55 | final Widget Function(BuildContext, String, TextStyle)? sourceTagBuilder; | 57 | final Widget Function(BuildContext, String, TextStyle)? sourceTagBuilder; |
| 58 | + final Widget Function(BuildContext context, String text, TextStyle style)? highlightBuilder; | ||
| 59 | + final Widget Function(BuildContext context, String text, String url, TextStyle style)? linkBuilder; | ||
| 56 | 60 | ||
| 57 | @override | 61 | @override |
| 58 | Widget build(BuildContext context) { | 62 | Widget build(BuildContext context) { |
| @@ -93,6 +97,8 @@ class GptMarkdown extends StatelessWidget { | @@ -93,6 +97,8 @@ class GptMarkdown extends StatelessWidget { | ||
| 93 | maxLines: maxLines, | 97 | maxLines: maxLines, |
| 94 | overflow: overflow, | 98 | overflow: overflow, |
| 95 | sourceTagBuilder: sourceTagBuilder, | 99 | sourceTagBuilder: sourceTagBuilder, |
| 100 | + highlightBuilder: highlightBuilder, | ||
| 101 | + linkBuilder: linkBuilder, | ||
| 96 | ), | 102 | ), |
| 97 | )); | 103 | )); |
| 98 | } | 104 | } |
| @@ -422,8 +422,20 @@ class HighlightedText extends InlineMd { | @@ -422,8 +422,20 @@ class HighlightedText extends InlineMd { | ||
| 422 | final GptMarkdownConfig config, | 422 | final GptMarkdownConfig config, |
| 423 | ) { | 423 | ) { |
| 424 | var match = exp.firstMatch(text.trim()); | 424 | var match = exp.firstMatch(text.trim()); |
| 425 | - var conf = config.copyWith( | ||
| 426 | - style: config.style?.copyWith( | 425 | + var highlightedText = match?[1] ?? ""; |
| 426 | + | ||
| 427 | + if (config.highlightBuilder != null) { | ||
| 428 | + return WidgetSpan( | ||
| 429 | + alignment: PlaceholderAlignment.middle, | ||
| 430 | + child: config.highlightBuilder!( | ||
| 431 | + context, | ||
| 432 | + highlightedText, | ||
| 433 | + config.style ?? const TextStyle(), | ||
| 434 | + ), | ||
| 435 | + ); | ||
| 436 | + } | ||
| 437 | + | ||
| 438 | + var style = config.style?.copyWith( | ||
| 427 | fontWeight: FontWeight.bold, | 439 | fontWeight: FontWeight.bold, |
| 428 | background: Paint() | 440 | background: Paint() |
| 429 | ..color = GptMarkdownTheme.of(context).highlightColor | 441 | ..color = GptMarkdownTheme.of(context).highlightColor |
| @@ -436,11 +448,11 @@ class HighlightedText extends InlineMd { | @@ -436,11 +448,11 @@ class HighlightedText extends InlineMd { | ||
| 436 | ..color = GptMarkdownTheme.of(context).highlightColor | 448 | ..color = GptMarkdownTheme.of(context).highlightColor |
| 437 | ..strokeCap = StrokeCap.round | 449 | ..strokeCap = StrokeCap.round |
| 438 | ..strokeJoin = StrokeJoin.round, | 450 | ..strokeJoin = StrokeJoin.round, |
| 439 | - ), | ||
| 440 | ); | 451 | ); |
| 452 | + | ||
| 441 | return TextSpan( | 453 | return TextSpan( |
| 442 | - text: match?[1], | ||
| 443 | - style: conf.style, | 454 | + text: highlightedText, |
| 455 | + style: style, | ||
| 444 | ); | 456 | ); |
| 445 | } | 457 | } |
| 446 | } | 458 | } |
| @@ -721,15 +733,35 @@ class ATagMd extends InlineMd { | @@ -721,15 +733,35 @@ class ATagMd extends InlineMd { | ||
| 721 | if (match?[1] == null && match?[2] == null) { | 733 | if (match?[1] == null && match?[2] == null) { |
| 722 | return const TextSpan(); | 734 | return const TextSpan(); |
| 723 | } | 735 | } |
| 736 | + | ||
| 737 | + final linkText = match?[1] ?? ""; | ||
| 738 | + final url = match?[2] ?? ""; | ||
| 739 | + | ||
| 740 | + // Use custom builder if provided | ||
| 741 | + if (config.linkBuilder != null) { | ||
| 742 | + return WidgetSpan( | ||
| 743 | + child: GestureDetector( | ||
| 744 | + onTap: () => config.onLinkTab?.call(url, linkText), | ||
| 745 | + child: config.linkBuilder!( | ||
| 746 | + context, | ||
| 747 | + linkText, | ||
| 748 | + url, | ||
| 749 | + config.style ?? const TextStyle(), | ||
| 750 | + ), | ||
| 751 | + ), | ||
| 752 | + ); | ||
| 753 | + } | ||
| 754 | + | ||
| 755 | + // Default rendering | ||
| 724 | var theme = GptMarkdownTheme.of(context); | 756 | var theme = GptMarkdownTheme.of(context); |
| 725 | return WidgetSpan( | 757 | return WidgetSpan( |
| 726 | child: LinkButton( | 758 | child: LinkButton( |
| 727 | hoverColor: theme.linkHoverColor, | 759 | hoverColor: theme.linkHoverColor, |
| 728 | color: theme.linkColor, | 760 | color: theme.linkColor, |
| 729 | onPressed: () { | 761 | onPressed: () { |
| 730 | - config.onLinkTab?.call("${match?[2]}", "${match?[1]}"); | 762 | + config.onLinkTab?.call(url, linkText); |
| 731 | }, | 763 | }, |
| 732 | - text: match?[1] ?? "", | 764 | + text: linkText, |
| 733 | config: config, | 765 | config: config, |
| 734 | ), | 766 | ), |
| 735 | ); | 767 | ); |
| @@ -892,11 +924,9 @@ class CodeBlockMd extends BlockMd { | @@ -892,11 +924,9 @@ class CodeBlockMd extends BlockMd { | ||
| 892 | String codes = this.exp.firstMatch(text)?[2] ?? ""; | 924 | String codes = this.exp.firstMatch(text)?[2] ?? ""; |
| 893 | String name = this.exp.firstMatch(text)?[1] ?? ""; | 925 | String name = this.exp.firstMatch(text)?[1] ?? ""; |
| 894 | codes = codes.replaceAll(r"```", "").trim(); | 926 | codes = codes.replaceAll(r"```", "").trim(); |
| 895 | - return Padding( | ||
| 896 | - padding: const EdgeInsets.all(16.0), | ||
| 897 | - child: config.codeBuilder != null | ||
| 898 | - ? config.codeBuilder?.call(context, name, codes) | ||
| 899 | - : CodeField(name: name, codes: codes), | ||
| 900 | - ); | 927 | + |
| 928 | + return config.codeBuilder != null | ||
| 929 | + ? config.codeBuilder!(context, name, codes) | ||
| 930 | + : CodeField(name: name, codes: codes); | ||
| 901 | } | 931 | } |
| 902 | } | 932 | } |
-
Please register or login to post a comment