Showing
8 changed files
with
146 additions
and
74 deletions
| 1 | +## 1.0.6 | ||
| 2 | + | ||
| 3 | +* `_italic_` and `>Indentation` syntax added. | ||
| 4 | +* `linkBuilder` and `highlightBuilder` added `[f45132b](https://github.com/Infinitix-LLC/gpt_markdown/commit/f45132b2cd4b069d3e5703561deb5c7e51d3c560)`. | ||
| 5 | + | ||
| 1 | ## 1.0.5 | 6 | ## 1.0.5 |
| 2 | 7 | ||
| 3 | * Fixed the order of inline and block latex in markdown. | 8 | * Fixed the order of inline and block latex in markdown. |
| @@ -269,22 +269,33 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -269,22 +269,33 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
| 269 | ), | 269 | ), |
| 270 | highlightBuilder: (context, text, style) { | 270 | highlightBuilder: (context, text, style) { |
| 271 | return Container( | 271 | return Container( |
| 272 | - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), | 272 | + padding: const EdgeInsets.symmetric( |
| 273 | + horizontal: 4, vertical: 2), | ||
| 273 | decoration: BoxDecoration( | 274 | decoration: BoxDecoration( |
| 274 | - color: Theme.of(context).colorScheme.secondaryContainer, | ||
| 275 | - borderRadius: BorderRadius.circular(4), | 275 | + color: Theme.of(context) |
| 276 | + .colorScheme | ||
| 277 | + .secondaryContainer, | ||
| 278 | + borderRadius: | ||
| 279 | + BorderRadius.circular(4), | ||
| 276 | border: Border.all( | 280 | border: Border.all( |
| 277 | - color: Theme.of(context).colorScheme.secondary.withOpacity(0.5), | 281 | + color: Theme.of(context) |
| 282 | + .colorScheme | ||
| 283 | + .secondary | ||
| 284 | + .withValues(alpha: 0.5), | ||
| 278 | width: 1, | 285 | width: 1, |
| 279 | ), | 286 | ), |
| 280 | ), | 287 | ), |
| 281 | child: Text( | 288 | child: Text( |
| 282 | text, | 289 | text, |
| 283 | style: TextStyle( | 290 | style: TextStyle( |
| 284 | - color: Theme.of(context).colorScheme.onSecondaryContainer, | 291 | + color: Theme.of(context) |
| 292 | + .colorScheme | ||
| 293 | + .onSecondaryContainer, | ||
| 285 | fontFamily: 'monospace', | 294 | fontFamily: 'monospace', |
| 286 | fontWeight: FontWeight.bold, | 295 | fontWeight: FontWeight.bold, |
| 287 | - fontSize: style.fontSize != null ? style.fontSize! * 0.9 : 13.5, | 296 | + fontSize: style.fontSize != null |
| 297 | + ? style.fontSize! * 0.9 | ||
| 298 | + : 13.5, | ||
| 288 | height: style.height, | 299 | height: style.height, |
| 289 | ), | 300 | ), |
| 290 | ), | 301 | ), |
| @@ -400,21 +411,29 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -400,21 +411,29 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
| 400 | ), | 411 | ), |
| 401 | ); | 412 | ); |
| 402 | }, | 413 | }, |
| 414 | + linkBuilder: | ||
| 415 | + (context, label, path, style) { | ||
| 416 | + // | ||
| 417 | + return Text(path); | ||
| 418 | + }, | ||
| 419 | + // codeBuilder: (context, name, code) { | ||
| 420 | + // return Padding( | ||
| 421 | + // padding: const EdgeInsets.symmetric( | ||
| 422 | + // horizontal: 16), | ||
| 423 | + // child: Text( | ||
| 424 | + // code.trim(), | ||
| 425 | + // style: TextStyle( | ||
| 426 | + // fontFamily: 'JetBrains Mono', | ||
| 427 | + // fontSize: 14, | ||
| 428 | + // height: 1.5, | ||
| 429 | + // color: Theme.of(context) | ||
| 430 | + // .colorScheme | ||
| 431 | + // .onSurface, | ||
| 432 | + // ), | ||
| 433 | + // ), | ||
| 434 | + // ); | ||
| 435 | + // } | ||
| 403 | ); | 436 | ); |
| 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 | - }; | ||
| 418 | if (selectable) { | 437 | if (selectable) { |
| 419 | child = SelectionArea( | 438 | child = SelectionArea( |
| 420 | child: child, | 439 | child: child, |
| @@ -126,7 +126,7 @@ packages: | @@ -126,7 +126,7 @@ packages: | ||
| 126 | path: ".." | 126 | path: ".." |
| 127 | relative: true | 127 | relative: true |
| 128 | source: path | 128 | source: path |
| 129 | - version: "1.0.5" | 129 | + version: "1.0.6" |
| 130 | http: | 130 | http: |
| 131 | dependency: transitive | 131 | dependency: transitive |
| 132 | description: | 132 | description: |
lib/custom_widgets/indent_widget.dart
0 → 100644
| 1 | +import 'package:flutter/material.dart'; | ||
| 2 | + | ||
| 3 | +class IndentWidget extends StatelessWidget { | ||
| 4 | + const IndentWidget({ | ||
| 5 | + super.key, | ||
| 6 | + required this.child, | ||
| 7 | + required this.direction, | ||
| 8 | + required this.color, | ||
| 9 | + }); | ||
| 10 | + final Widget child; | ||
| 11 | + final TextDirection direction; | ||
| 12 | + final Color color; | ||
| 13 | + | ||
| 14 | + @override | ||
| 15 | + Widget build(BuildContext context) { | ||
| 16 | + return CustomPaint( | ||
| 17 | + foregroundPainter: IndentPainter(color, direction), | ||
| 18 | + child: child, | ||
| 19 | + ); | ||
| 20 | + } | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +class IndentPainter extends CustomPainter { | ||
| 24 | + IndentPainter(this.color, this.direction); | ||
| 25 | + final Color color; | ||
| 26 | + final TextDirection direction; | ||
| 27 | + @override | ||
| 28 | + void paint(Canvas canvas, Size size) { | ||
| 29 | + var left = direction == TextDirection.ltr; | ||
| 30 | + var start = left ? 0.0 : size.width - 4; | ||
| 31 | + var rect = Rect.fromLTWH(start, 0, 4, size.height); | ||
| 32 | + var paint = Paint()..color = color; | ||
| 33 | + canvas.drawRect(rect, paint); | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + @override | ||
| 37 | + bool shouldRepaint(covariant CustomPainter oldDelegate) { | ||
| 38 | + return true; | ||
| 39 | + } | ||
| 40 | +} |
| @@ -34,8 +34,11 @@ class GptMarkdownConfig { | @@ -34,8 +34,11 @@ class GptMarkdownConfig { | ||
| 34 | codeBuilder; | 34 | codeBuilder; |
| 35 | final int? maxLines; | 35 | final int? maxLines; |
| 36 | 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; | 37 | + final Widget Function(BuildContext context, String text, TextStyle style)? |
| 38 | + highlightBuilder; | ||
| 39 | + final Widget Function( | ||
| 40 | + BuildContext context, String text, String url, TextStyle style)? | ||
| 41 | + linkBuilder; | ||
| 39 | 42 | ||
| 40 | GptMarkdownConfig copyWith({ | 43 | GptMarkdownConfig copyWith({ |
| 41 | TextStyle? style, | 44 | TextStyle? style, |
| @@ -55,8 +58,11 @@ class GptMarkdownConfig { | @@ -55,8 +58,11 @@ class GptMarkdownConfig { | ||
| 55 | codeBuilder, | 58 | codeBuilder, |
| 56 | final int? maxLines, | 59 | final int? maxLines, |
| 57 | final TextOverflow? overflow, | 60 | 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, | 61 | + final Widget Function(BuildContext context, String text, TextStyle style)? |
| 62 | + highlightBuilder, | ||
| 63 | + final Widget Function( | ||
| 64 | + BuildContext context, String text, String url, TextStyle style)? | ||
| 65 | + linkBuilder, | ||
| 60 | }) { | 66 | }) { |
| 61 | return GptMarkdownConfig( | 67 | return GptMarkdownConfig( |
| 62 | style: style ?? this.style, | 68 | style: style ?? this.style, |
| @@ -13,6 +13,7 @@ import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart'; | @@ -13,6 +13,7 @@ import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart'; | ||
| 13 | import 'dart:math'; | 13 | import 'dart:math'; |
| 14 | 14 | ||
| 15 | import 'custom_widgets/code_field.dart'; | 15 | import 'custom_widgets/code_field.dart'; |
| 16 | +import 'custom_widgets/indent_widget.dart'; | ||
| 16 | import 'custom_widgets/link_button.dart'; | 17 | import 'custom_widgets/link_button.dart'; |
| 17 | 18 | ||
| 18 | part 'theme.dart'; | 19 | part 'theme.dart'; |
| @@ -55,8 +56,11 @@ class GptMarkdown extends StatelessWidget { | @@ -55,8 +56,11 @@ class GptMarkdown extends StatelessWidget { | ||
| 55 | final Widget Function(BuildContext context, String name, String code)? | 56 | final Widget Function(BuildContext context, String name, String code)? |
| 56 | codeBuilder; | 57 | codeBuilder; |
| 57 | final Widget Function(BuildContext, String, TextStyle)? sourceTagBuilder; | 58 | 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; | 59 | + final Widget Function(BuildContext context, String text, TextStyle style)? |
| 60 | + highlightBuilder; | ||
| 61 | + final Widget Function( | ||
| 62 | + BuildContext context, String text, String url, TextStyle style)? | ||
| 63 | + linkBuilder; | ||
| 60 | 64 | ||
| 61 | @override | 65 | @override |
| 62 | Widget build(BuildContext context) { | 66 | Widget build(BuildContext context) { |
| @@ -2,28 +2,28 @@ part of 'gpt_markdown.dart'; | @@ -2,28 +2,28 @@ 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 = [ | ||
| 6 | - CodeBlockMd(), | ||
| 7 | - NewLines(), | ||
| 8 | - // IndentMd(), | ||
| 9 | - ImageMd(), | ||
| 10 | - TableMd(), | ||
| 11 | - HTag(), | ||
| 12 | - UnOrderedList(), | ||
| 13 | - OrderedList(), | ||
| 14 | - RadioButtonMd(), | ||
| 15 | - CheckBoxMd(), | ||
| 16 | - HrLine(), | ||
| 17 | - LatexMath(), | ||
| 18 | - LatexMathMultiLine(), | ||
| 19 | - ImageMd(), | ||
| 20 | - HighlightedText(), | ||
| 21 | - StrikeMd(), | ||
| 22 | - BoldMd(), | ||
| 23 | - ItalicMd(), | ||
| 24 | - ATagMd(), | ||
| 25 | - SourceTag(), | ||
| 26 | - ]; | 5 | + static List<MarkdownComponent> get components => [ |
| 6 | + CodeBlockMd(), | ||
| 7 | + NewLines(), | ||
| 8 | + IndentMd(), | ||
| 9 | + ImageMd(), | ||
| 10 | + TableMd(), | ||
| 11 | + HTag(), | ||
| 12 | + UnOrderedList(), | ||
| 13 | + OrderedList(), | ||
| 14 | + RadioButtonMd(), | ||
| 15 | + CheckBoxMd(), | ||
| 16 | + HrLine(), | ||
| 17 | + LatexMath(), | ||
| 18 | + LatexMathMultiLine(), | ||
| 19 | + ImageMd(), | ||
| 20 | + HighlightedText(), | ||
| 21 | + StrikeMd(), | ||
| 22 | + BoldMd(), | ||
| 23 | + ItalicMd(), | ||
| 24 | + ATagMd(), | ||
| 25 | + SourceTag(), | ||
| 26 | + ]; | ||
| 27 | 27 | ||
| 28 | /// Generate widget for markdown widget | 28 | /// Generate widget for markdown widget |
| 29 | static List<InlineSpan> generate( | 29 | static List<InlineSpan> generate( |
| @@ -311,7 +311,7 @@ class IndentMd extends InlineMd { | @@ -311,7 +311,7 @@ class IndentMd extends InlineMd { | ||
| 311 | @override | 311 | @override |
| 312 | RegExp get exp => | 312 | RegExp get exp => |
| 313 | // RegExp(r"(?<=\n\n)(\ +)(.+?)(?=\n\n)", dotAll: true, multiLine: true); | 313 | // RegExp(r"(?<=\n\n)(\ +)(.+?)(?=\n\n)", dotAll: true, multiLine: true); |
| 314 | - RegExp(r"(?:\n\n+)^(\ *)(.+?)$(?:\n\n+)", dotAll: true, multiLine: true); | 314 | + RegExp(r"^>([^\n]+)$", dotAll: true, multiLine: true); |
| 315 | 315 | ||
| 316 | @override | 316 | @override |
| 317 | InlineSpan span( | 317 | InlineSpan span( |
| @@ -320,8 +320,7 @@ class IndentMd extends InlineMd { | @@ -320,8 +320,7 @@ class IndentMd extends InlineMd { | ||
| 320 | final GptMarkdownConfig config, | 320 | final GptMarkdownConfig config, |
| 321 | ) { | 321 | ) { |
| 322 | var match = exp.firstMatch(text); | 322 | var match = exp.firstMatch(text); |
| 323 | - int spaces = (match?[1] ?? "").length; | ||
| 324 | - var data = "${match?[2]}".trim(); | 323 | + var data = "${match?[1]}".trim(); |
| 325 | // data = data.replaceAll(RegExp(r'\n\ {' '$spaces' '}'), '\n').trim(); | 324 | // data = data.replaceAll(RegExp(r'\n\ {' '$spaces' '}'), '\n').trim(); |
| 326 | data = data.trim(); | 325 | data = data.trim(); |
| 327 | var child = TextSpan( | 326 | var child = TextSpan( |
| @@ -331,23 +330,21 @@ class IndentMd extends InlineMd { | @@ -331,23 +330,21 @@ class IndentMd extends InlineMd { | ||
| 331 | config, | 330 | config, |
| 332 | ), | 331 | ), |
| 333 | ); | 332 | ); |
| 334 | - var leftPadding = 20.0; | ||
| 335 | - if (spaces < 4) { | ||
| 336 | - leftPadding = 0; | ||
| 337 | - } | ||
| 338 | - var padding = config.style?.fontSize ?? 16.0; | ||
| 339 | return TextSpan( | 333 | return TextSpan( |
| 340 | children: [ | 334 | children: [ |
| 341 | WidgetSpan( | 335 | WidgetSpan( |
| 342 | - child: Padding( | ||
| 343 | - padding: EdgeInsets.symmetric(vertical: padding), | ||
| 344 | - child: Row( | ||
| 345 | - children: [ | ||
| 346 | - SizedBox( | ||
| 347 | - width: leftPadding, | 336 | + child: Directionality( |
| 337 | + textDirection: config.textDirection, | ||
| 338 | + child: Padding( | ||
| 339 | + padding: const EdgeInsets.symmetric(vertical: 2), | ||
| 340 | + child: IndentWidget( | ||
| 341 | + color: Theme.of(context).colorScheme.onSurfaceVariant, | ||
| 342 | + direction: config.textDirection, | ||
| 343 | + child: Padding( | ||
| 344 | + padding: const EdgeInsetsDirectional.only(start: 10.0), | ||
| 345 | + child: Expanded(child: config.getRich(child)), | ||
| 348 | ), | 346 | ), |
| 349 | - Expanded(child: config.getRich(child)), | ||
| 350 | - ], | 347 | + ), |
| 351 | ), | 348 | ), |
| 352 | ), | 349 | ), |
| 353 | ), | 350 | ), |
| @@ -517,7 +514,8 @@ class StrikeMd extends InlineMd { | @@ -517,7 +514,8 @@ class StrikeMd extends InlineMd { | ||
| 517 | class ItalicMd extends InlineMd { | 514 | class ItalicMd extends InlineMd { |
| 518 | @override | 515 | @override |
| 519 | RegExp get exp => | 516 | RegExp get exp => |
| 520 | - RegExp(r"(?<!\*)\*(?<!\s)(.+?)(?<!\s)\*(?!\*)", dotAll: true); | 517 | + RegExp(r"(?<!\*)\*(?<!\s)(.+?)(?<!\s)\*(?!\*)|\_(?<!\s)(.+?)(?<!\s)\_", |
| 518 | + dotAll: true); | ||
| 521 | 519 | ||
| 522 | @override | 520 | @override |
| 523 | InlineSpan span( | 521 | InlineSpan span( |
| @@ -526,13 +524,14 @@ class ItalicMd extends InlineMd { | @@ -526,13 +524,14 @@ class ItalicMd extends InlineMd { | ||
| 526 | final GptMarkdownConfig config, | 524 | final GptMarkdownConfig config, |
| 527 | ) { | 525 | ) { |
| 528 | var match = exp.firstMatch(text.trim()); | 526 | var match = exp.firstMatch(text.trim()); |
| 527 | + var data = match?[1] ?? match?[2]; | ||
| 529 | var conf = config.copyWith( | 528 | var conf = config.copyWith( |
| 530 | style: (config.style ?? const TextStyle()) | 529 | style: (config.style ?? const TextStyle()) |
| 531 | .copyWith(fontStyle: FontStyle.italic)); | 530 | .copyWith(fontStyle: FontStyle.italic)); |
| 532 | return TextSpan( | 531 | return TextSpan( |
| 533 | children: MarkdownComponent.generate( | 532 | children: MarkdownComponent.generate( |
| 534 | context, | 533 | context, |
| 535 | - "${match?[1]}", | 534 | + "$data", |
| 536 | conf, | 535 | conf, |
| 537 | ), | 536 | ), |
| 538 | style: conf.style, | 537 | style: conf.style, |
| @@ -733,10 +732,10 @@ class ATagMd extends InlineMd { | @@ -733,10 +732,10 @@ class ATagMd extends InlineMd { | ||
| 733 | if (match?[1] == null && match?[2] == null) { | 732 | if (match?[1] == null && match?[2] == null) { |
| 734 | return const TextSpan(); | 733 | return const TextSpan(); |
| 735 | } | 734 | } |
| 736 | - | 735 | + |
| 737 | final linkText = match?[1] ?? ""; | 736 | final linkText = match?[1] ?? ""; |
| 738 | final url = match?[2] ?? ""; | 737 | final url = match?[2] ?? ""; |
| 739 | - | 738 | + |
| 740 | // Use custom builder if provided | 739 | // Use custom builder if provided |
| 741 | if (config.linkBuilder != null) { | 740 | if (config.linkBuilder != null) { |
| 742 | return WidgetSpan( | 741 | return WidgetSpan( |
| @@ -924,9 +923,8 @@ class CodeBlockMd extends BlockMd { | @@ -924,9 +923,8 @@ class CodeBlockMd extends BlockMd { | ||
| 924 | String codes = this.exp.firstMatch(text)?[2] ?? ""; | 923 | String codes = this.exp.firstMatch(text)?[2] ?? ""; |
| 925 | String name = this.exp.firstMatch(text)?[1] ?? ""; | 924 | String name = this.exp.firstMatch(text)?[1] ?? ""; |
| 926 | codes = codes.replaceAll(r"```", "").trim(); | 925 | codes = codes.replaceAll(r"```", "").trim(); |
| 927 | - | ||
| 928 | - return config.codeBuilder != null | ||
| 929 | - ? config.codeBuilder!(context, name, codes) | ||
| 930 | - : CodeField(name: name, codes: codes); | 926 | + |
| 927 | + return config.codeBuilder?.call(context, name, codes) ?? | ||
| 928 | + CodeField(name: name, codes: codes); | ||
| 931 | } | 929 | } |
| 932 | } | 930 | } |
| 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." | 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.5 | 3 | +version: 1.0.6 |
| 4 | homepage: https://github.com/Infinitix-LLC/gpt_markdown | 4 | homepage: https://github.com/Infinitix-LLC/gpt_markdown |
| 5 | 5 | ||
| 6 | environment: | 6 | environment: |
-
Please register or login to post a comment