saminsohag

bug fixed

  1 +{
  2 + "cSpell.words": [
  3 + "cellspacing",
  4 + "Sohag",
  5 + "stylesheet"
  6 + ]
  7 +}
  1 +## 0.0.9
  2 +
  3 +* To html added.
  4 +
  5 +## 0.0.8
  6 +
  7 +* Bug fixes.
  8 +
  9 +## 0.0.7
  10 +
  11 +* Bug fixes.
  12 +
1 ## 0.0.6 13 ## 0.0.6
2 14
3 * Bug fixes. 15 * Bug fixes.
1 import 'dart:developer'; 1 import 'dart:developer';
2 2
3 import 'package:flutter/material.dart'; 3 import 'package:flutter/material.dart';
  4 +import 'package:flutter/services.dart';
4 import 'package:tex_markdown/tex_markdown.dart'; 5 import 'package:tex_markdown/tex_markdown.dart';
5 6
6 void main() { 7 void main() {
@@ -105,8 +106,8 @@ class _MyHomePageState extends State<MyHomePage> { @@ -105,8 +106,8 @@ class _MyHomePageState extends State<MyHomePage> {
105 log(url, name: "url"); 106 log(url, name: "url");
106 }, 107 },
107 style: const TextStyle( 108 style: const TextStyle(
108 - // color: Colors.red,  
109 - ), 109 + color: Colors.green,
  110 + ),
110 ); 111 );
111 }), 112 }),
112 ], 113 ],
@@ -114,14 +115,37 @@ class _MyHomePageState extends State<MyHomePage> { @@ -114,14 +115,37 @@ class _MyHomePageState extends State<MyHomePage> {
114 ), 115 ),
115 ConstrainedBox( 116 ConstrainedBox(
116 constraints: const BoxConstraints(maxHeight: 200), 117 constraints: const BoxConstraints(maxHeight: 200),
117 - child: Padding(  
118 - padding: const EdgeInsets.all(8.0),  
119 - child: TextField(  
120 - decoration: const InputDecoration(  
121 - border: OutlineInputBorder(), label: Text("Type here:")),  
122 - maxLines: null,  
123 - controller: _controller,  
124 - ), 118 + child: Stack(
  119 + children: [
  120 + Padding(
  121 + padding: const EdgeInsets.all(8.0),
  122 + child: TextField(
  123 + decoration: const InputDecoration(
  124 + border: OutlineInputBorder(),
  125 + label: Text("Type here:")),
  126 + maxLines: null,
  127 + controller: _controller,
  128 + ),
  129 + ),
  130 + IconButton(
  131 + onPressed: () {
  132 + String html = '''<!DOCTYPE html>
  133 +<html lang="en">
  134 +<head>
  135 +<meta charset="UTF-8"><title>markdown</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.css" integrity="sha384-zB1R0rpPzHqg7Kpt0Aljp8JPLqbXI3bhnPWROx27a9N0Ll6ZP/+DiW/UqRcLbRjq" crossorigin="anonymous">
  136 +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/katex.min.js" integrity="sha384-y23I5Q6l+B6vatafAwxRu/0oK/79VlbSz7Q9aiSZUvyWYIYsd+qj+o24G5ZU2zJz" crossorigin="anonymous"></script>
  137 +<script defer src="https://cdn.jsdelivr.net/npm/katex@0.11.1/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI" crossorigin="anonymous" onload="renderMathInElement(document.body);"></script>
  138 +</head>
  139 +<body>
  140 +${TexMarkdown.toHtml(_controller.text)}
  141 +</body>
  142 +</html>
  143 +''';
  144 + Clipboard.setData(ClipboardData(text: html));
  145 + },
  146 + icon: const Icon(Icons.html),
  147 + ),
  148 + ],
125 ), 149 ),
126 ), 150 ),
127 ], 151 ],
@@ -238,15 +238,15 @@ packages: @@ -238,15 +238,15 @@ packages:
238 path: ".." 238 path: ".."
239 relative: true 239 relative: true
240 source: path 240 source: path
241 - version: "0.0.6" 241 + version: "0.0.9"
242 tex_text: 242 tex_text:
243 dependency: transitive 243 dependency: transitive
244 description: 244 description:
245 name: tex_text 245 name: tex_text
246 - sha256: "562415e16b9c46816d8c2ed128fc46b299bb27bca9fac65db0c07535be0d0c60" 246 + sha256: "58c556fb09dff7034d6f29b77b3be6b90e7f1257817daa0000c6f1609094490a"
247 url: "https://pub.dev" 247 url: "https://pub.dev"
248 source: hosted 248 source: hosted
249 - version: "0.0.8" 249 + version: "0.1.2"
250 tuple: 250 tuple:
251 dependency: transitive 251 dependency: transitive
252 description: 252 description:
  1 +import 'package:flutter/material.dart';
  2 +import 'package:tex_text/tex_text.dart';
  3 +import 'md_widget.dart';
  4 +
  5 +/// Markdown components
  6 +abstract class MarkdownComponent {
  7 + static final List<MarkdownComponent> components = [
  8 + HTag(),
  9 + BoldMd(),
  10 + ItalicMd(),
  11 + ATagMd(),
  12 + ImageMd(),
  13 + UnOrderedList(),
  14 + OrderedList(),
  15 + RadioButtonMd(),
  16 + CheckBoxMd(),
  17 + HrLine(),
  18 + TextMd(),
  19 + ];
  20 + static String toHtml(String text) {
  21 + String html = "";
  22 + text.split(RegExp(r"\n+")).forEach((element) {
  23 + for (var each in components) {
  24 + if (each.exp.hasMatch(element.trim())) {
  25 + if (each is InlineMd) {
  26 + html += "${each.toHtml(element)}\n";
  27 + break;
  28 + } else if (each is BlockMd) {
  29 + html += "${each.toHtml(element)}\n";
  30 + break;
  31 + }
  32 + }
  33 + }
  34 + });
  35 + return html;
  36 + }
  37 +
  38 + static Widget generate(
  39 + BuildContext context,
  40 + String text,
  41 + TextStyle? style,
  42 + final void Function(String url, String title)? onLinkTab,
  43 + ) {
  44 + List<Widget> children = [];
  45 + List<InlineSpan> spans = [];
  46 + text.split(RegExp(r"\n+")).forEach(
  47 + (element) {
  48 + for (var each in components) {
  49 + if (each.exp.hasMatch(element.trim())) {
  50 + if (each is InlineMd) {
  51 + if (spans.isNotEmpty) {
  52 + spans.add(
  53 + TextSpan(text: " ", style: style),
  54 + );
  55 + }
  56 + spans.add(each.inlineSpan(
  57 + context,
  58 + element,
  59 + style,
  60 + onLinkTab,
  61 + ));
  62 + } else {
  63 + if (spans.isNotEmpty) {
  64 + children.add(
  65 + Text.rich(
  66 + TextSpan(children: List.from(spans)),
  67 + textAlign: TextAlign.left,
  68 + ),
  69 + );
  70 + spans.clear();
  71 + }
  72 + if (each is BlockMd) {
  73 + children.add(
  74 + each.build(context, element, style, onLinkTab),
  75 + );
  76 + }
  77 + }
  78 + return;
  79 + }
  80 + }
  81 + },
  82 + );
  83 + if (spans.isNotEmpty) {
  84 + children.add(
  85 + Text.rich(
  86 + TextSpan(children: List.from(spans)),
  87 + textAlign: TextAlign.left,
  88 + ),
  89 + );
  90 + }
  91 + return Column(
  92 + crossAxisAlignment: CrossAxisAlignment.start,
  93 + children: children,
  94 + );
  95 + }
  96 +
  97 + RegExp get exp;
  98 + bool get inline;
  99 +}
  100 +
  101 +abstract class InlineMd extends MarkdownComponent {
  102 + @override
  103 + bool get inline => true;
  104 + InlineSpan inlineSpan(
  105 + BuildContext context,
  106 + String text,
  107 + TextStyle? style,
  108 + final void Function(String url, String title)? onLinkTab,
  109 + );
  110 + String toHtml(String text);
  111 +}
  112 +
  113 +abstract class BlockMd extends MarkdownComponent {
  114 + @override
  115 + bool get inline => false;
  116 + Widget build(
  117 + BuildContext context,
  118 + String text,
  119 + TextStyle? style,
  120 + final void Function(String url, String title)? onLinkTab,
  121 + );
  122 + String toHtml(String text);
  123 +}
  124 +
  125 +class HTag extends BlockMd {
  126 + @override
  127 + final RegExp exp = RegExp(r"^(#{1,6})\s([^\n]+)$");
  128 + @override
  129 + Widget build(
  130 + BuildContext context,
  131 + String text,
  132 + TextStyle? style,
  133 + final void Function(String url, String title)? onLinkTab,
  134 + ) {
  135 + var match = exp.firstMatch(text.trim());
  136 + return Column(
  137 + children: [
  138 + Row(
  139 + children: [
  140 + Expanded(
  141 + child: TexText(
  142 + "${match?[2]}",
  143 + style: [
  144 + Theme.of(context)
  145 + .textTheme
  146 + .headlineLarge
  147 + ?.copyWith(color: style?.color),
  148 + Theme.of(context)
  149 + .textTheme
  150 + .headlineMedium
  151 + ?.copyWith(color: style?.color),
  152 + Theme.of(context)
  153 + .textTheme
  154 + .headlineSmall
  155 + ?.copyWith(color: style?.color),
  156 + Theme.of(context)
  157 + .textTheme
  158 + .titleLarge
  159 + ?.copyWith(color: style?.color),
  160 + Theme.of(context)
  161 + .textTheme
  162 + .titleMedium
  163 + ?.copyWith(color: style?.color),
  164 + Theme.of(context)
  165 + .textTheme
  166 + .titleSmall
  167 + ?.copyWith(color: style?.color),
  168 + ][match![1]!.length - 1],
  169 + ),
  170 + ),
  171 + ],
  172 + ),
  173 + if (match[1]!.length == 1) const Divider(height: 6),
  174 + ],
  175 + );
  176 + }
  177 +
  178 + @override
  179 + String toHtml(String text) {
  180 + var match = exp.firstMatch(text.trim());
  181 + return "<h${match![1]!.length}>${TexText.toHtmlData(match[2].toString().trim())}<h${match[1]!.length}>";
  182 + }
  183 +}
  184 +
  185 +class HrLine extends BlockMd {
  186 + @override
  187 + final RegExp exp = RegExp(r"^(--)[-]+$");
  188 + @override
  189 + Widget build(
  190 + BuildContext context,
  191 + String text,
  192 + TextStyle? style,
  193 + final void Function(String url, String title)? onLinkTab,
  194 + ) {
  195 + return const Divider(
  196 + height: 5,
  197 + );
  198 + }
  199 +
  200 + @override
  201 + String toHtml(String text) {
  202 + return "<hr/>";
  203 + }
  204 +}
  205 +
  206 +class CheckBoxMd extends BlockMd {
  207 + get onLinkTab => null;
  208 +
  209 + @override
  210 + Widget build(
  211 + BuildContext context,
  212 + String text,
  213 + TextStyle? style,
  214 + final void Function(String url, String title)? onLinkTab,
  215 + ) {
  216 + var match = exp.firstMatch(text.trim());
  217 + return Row(
  218 + crossAxisAlignment: CrossAxisAlignment.center,
  219 + mainAxisSize: MainAxisSize.min,
  220 + children: [
  221 + Padding(
  222 + padding: const EdgeInsets.symmetric(horizontal: 10),
  223 + child: Checkbox(
  224 + // value: true,
  225 + value: ("${match?[1]}" == "x"),
  226 + onChanged: (value) {},
  227 + fillColor: ButtonStyleButton.allOrNull(style?.color),
  228 + ),
  229 + ),
  230 + Expanded(
  231 + child: MdWidget(
  232 + "${match?[2]}",
  233 + onLinkTab: onLinkTab,
  234 + style: style,
  235 + ),
  236 + ),
  237 + ],
  238 + );
  239 + }
  240 +
  241 + @override
  242 + final RegExp exp = RegExp(r"^\[(\x?)\]\s(\S.*)$");
  243 +
  244 + @override
  245 + String toHtml(String text) {
  246 + var match = exp.firstMatch(text.trim());
  247 + return '<p><input type="checkbox"${("${match?[1]}" == "x") ? "checked" : ""}>${MdWidget.toHtml((match?[2]).toString()).trim()}</p>';
  248 + }
  249 +}
  250 +
  251 +class RadioButtonMd extends BlockMd {
  252 + get onLinkTab => null;
  253 +
  254 + @override
  255 + Widget build(
  256 + BuildContext context,
  257 + String text,
  258 + TextStyle? style,
  259 + final void Function(String url, String title)? onLinkTab,
  260 + ) {
  261 + var match = exp.firstMatch(text.trim());
  262 + return Row(
  263 + crossAxisAlignment: CrossAxisAlignment.center,
  264 + mainAxisSize: MainAxisSize.min,
  265 + children: [
  266 + Padding(
  267 + padding: const EdgeInsets.symmetric(horizontal: 10),
  268 + child: Radio(
  269 + value: true,
  270 + groupValue: ("${match?[1]}" == "x"),
  271 + onChanged: (value) {},
  272 + fillColor: ButtonStyleButton.allOrNull(style?.color),
  273 + ),
  274 + ),
  275 + Expanded(
  276 + child: MdWidget(
  277 + "${match?[2]}",
  278 + onLinkTab: onLinkTab,
  279 + style: style,
  280 + ),
  281 + ),
  282 + ],
  283 + );
  284 + }
  285 +
  286 + @override
  287 + final RegExp exp = RegExp(r"^\((\x?)\)\s(\S.*)$");
  288 +
  289 + @override
  290 + String toHtml(String text) {
  291 + var match = exp.firstMatch(text.trim());
  292 + return '<p><input type="radio"${("${match?[1]}" == "x") ? "checked" : ""}>${MdWidget.toHtml((match?[2]).toString().trim())}</p>';
  293 + }
  294 +}
  295 +
  296 +class UnOrderedList extends BlockMd {
  297 + get onLinkTab => null;
  298 +
  299 + @override
  300 + Widget build(
  301 + BuildContext context,
  302 + String text,
  303 + TextStyle? style,
  304 + final void Function(String url, String title)? onLinkTab,
  305 + ) {
  306 + var match = exp.firstMatch(text.trim());
  307 + return Row(
  308 + crossAxisAlignment: CrossAxisAlignment.center,
  309 + mainAxisSize: MainAxisSize.max,
  310 + children: [
  311 + Padding(
  312 + padding: const EdgeInsets.symmetric(horizontal: 10),
  313 + child: Icon(
  314 + Icons.circle,
  315 + color: style?.color,
  316 + size: 8,
  317 + ),
  318 + ),
  319 + Expanded(
  320 + child: MdWidget(
  321 + "${match?[2]}",
  322 + onLinkTab: onLinkTab,
  323 + style: style,
  324 + ),
  325 + ),
  326 + ],
  327 + );
  328 + }
  329 +
  330 + @override
  331 + final RegExp exp = RegExp(r"^(\-|\*)\s([^\n]+)$");
  332 +
  333 + @override
  334 + String toHtml(String text) {
  335 + var match = exp.firstMatch(text.trim());
  336 + return "<ul><li>${MdWidget.toHtml((match?[2]).toString())}</li></ul>";
  337 + }
  338 +}
  339 +
  340 +class OrderedList extends BlockMd {
  341 + @override
  342 + final RegExp exp = RegExp(r"^([0-9]+\.)\s([^\n]+)$");
  343 +
  344 + get onLinkTab => null;
  345 +
  346 + @override
  347 + Widget build(
  348 + BuildContext context,
  349 + String text,
  350 + TextStyle? style,
  351 + final void Function(String url, String title)? onLinkTab,
  352 + ) {
  353 + var match = exp.firstMatch(text.trim());
  354 + return Row(
  355 + crossAxisAlignment: CrossAxisAlignment.center,
  356 + mainAxisSize: MainAxisSize.max,
  357 + children: [
  358 + Padding(
  359 + padding: const EdgeInsets.symmetric(horizontal: 11),
  360 + child: Text(
  361 + "${match?[1]}",
  362 + style: (style ?? const TextStyle())
  363 + .copyWith(fontWeight: FontWeight.bold),
  364 + ),
  365 + ),
  366 + Expanded(
  367 + child: MdWidget(
  368 + "${match?[2]}",
  369 + onLinkTab: onLinkTab,
  370 + style: style,
  371 + ),
  372 + ),
  373 + ],
  374 + );
  375 + }
  376 +
  377 + @override
  378 + String toHtml(String text) {
  379 + var match = exp.firstMatch(text.trim());
  380 + return '<ol start="${match?[1]}"><li>${MdWidget.toHtml((match?[2]).toString())}</li></ol>';
  381 + }
  382 +}
  383 +
  384 +class BoldMd extends InlineMd {
  385 + @override
  386 + final RegExp exp = RegExp(r"^\*{2}(([\S^\*].*)?[\S^\*])\*{2}$");
  387 +
  388 + @override
  389 + InlineSpan inlineSpan(
  390 + BuildContext context,
  391 + String text,
  392 + TextStyle? style,
  393 + final void Function(String url, String title)? onLinkTab,
  394 + ) {
  395 + var match = exp.firstMatch(text.trim());
  396 + return WidgetSpan(
  397 + // text: "${match?[1]}",
  398 + child: TexText(
  399 + "${match?[1]}",
  400 + style: style?.copyWith(fontWeight: FontWeight.bold) ??
  401 + const TextStyle(fontWeight: FontWeight.bold),
  402 + ),
  403 + style: style?.copyWith(fontWeight: FontWeight.bold) ??
  404 + const TextStyle(fontWeight: FontWeight.bold),
  405 + );
  406 + }
  407 +
  408 + @override
  409 + String toHtml(String text) {
  410 + var match = exp.firstMatch(text.trim());
  411 + return '<b>${TexText.toHtmlData((match?[1]).toString())}</b>';
  412 + }
  413 +}
  414 +
  415 +class ItalicMd extends InlineMd {
  416 + @override
  417 + final RegExp exp = RegExp(r"^\*{1}(([\S^\*].*)?[\S^\*])\*{1}$");
  418 +
  419 + @override
  420 + InlineSpan inlineSpan(
  421 + BuildContext context,
  422 + String text,
  423 + TextStyle? style,
  424 + final void Function(String url, String title)? onLinkTab,
  425 + ) {
  426 + var match = exp.firstMatch(text.trim());
  427 + return WidgetSpan(
  428 + child: TexText(
  429 + "${match?[1]}",
  430 + style:
  431 + (style ?? const TextStyle()).copyWith(fontStyle: FontStyle.italic),
  432 + ),
  433 + style: (style ?? const TextStyle()).copyWith(fontStyle: FontStyle.italic),
  434 + );
  435 + }
  436 +
  437 + @override
  438 + String toHtml(String text) {
  439 + var match = exp.firstMatch(text.trim());
  440 + return '<i>${TexText.toHtmlData((match?[1]).toString())}</i>';
  441 + }
  442 +}
  443 +
  444 +class ATagMd extends InlineMd {
  445 + @override
  446 + final RegExp exp = RegExp(r"^\[([^\s\*].*[^\s]?)?\]\(([^\s\*]+)?\)$");
  447 +
  448 + @override
  449 + InlineSpan inlineSpan(
  450 + BuildContext context,
  451 + String text,
  452 + TextStyle? style,
  453 + final void Function(String url, String title)? onLinkTab,
  454 + ) {
  455 + var match = exp.firstMatch(text.trim());
  456 + if (match?[1] == null && match?[2] == null) {
  457 + return const TextSpan();
  458 + }
  459 + return WidgetSpan(
  460 + child: GestureDetector(
  461 + onTap: () {
  462 + if (onLinkTab == null) {
  463 + return;
  464 + }
  465 + onLinkTab("${match?[2]}", "${match?[1]}");
  466 + },
  467 + child: TexText(
  468 + "${match?[1]}",
  469 + style: (style ?? const TextStyle()).copyWith(
  470 + color: Colors.blueAccent,
  471 + decorationColor: Colors.blue,
  472 + decoration: TextDecoration.underline,
  473 + ),
  474 + ),
  475 + ),
  476 + );
  477 + }
  478 +
  479 + @override
  480 + String toHtml(String text) {
  481 + var match = exp.firstMatch(text.trim());
  482 + return '<a href="${match?[2]}">${TexText.toHtmlData((match?[1]).toString())}</a>';
  483 + }
  484 +}
  485 +
  486 +class ImageMd extends InlineMd {
  487 + @override
  488 + final RegExp exp = RegExp(r"^\!\[([^\s].*[^\s]?)?\]\(([^\s]+)\)$");
  489 +
  490 + @override
  491 + InlineSpan inlineSpan(
  492 + BuildContext context,
  493 + String text,
  494 + TextStyle? style,
  495 + final void Function(String url, String title)? onLinkTab,
  496 + ) {
  497 + var match = exp.firstMatch(text.trim());
  498 + double? height;
  499 + double? width;
  500 + if (match?[1] != null) {
  501 + var size = RegExp(r"^([0-9]+)?x?([0-9]+)?")
  502 + .firstMatch(match![1].toString().trim());
  503 + width = double.tryParse(size?[1]?.toString() ?? 'a');
  504 + height = double.tryParse(size?[2]?.toString() ?? 'a');
  505 + }
  506 + return WidgetSpan(
  507 + // alignment: PlaceholderAlignment.middle,
  508 + child: SizedBox(
  509 + width: width,
  510 + height: height,
  511 + child: Image.network(
  512 + "${match?[2]}",
  513 + fit: BoxFit.fill,
  514 + errorBuilder: (context, error, stackTrace) {
  515 + return Placeholder(
  516 + color: Theme.of(context).colorScheme.error,
  517 + child: Text(
  518 + "${match?[2]}\n$error",
  519 + style: style,
  520 + ),
  521 + );
  522 + },
  523 + ),
  524 + ),
  525 + );
  526 + }
  527 +
  528 + @override
  529 + String toHtml(String text) {
  530 + var match = exp.firstMatch(text.trim());
  531 + return '<img src="${match?[2]}">';
  532 + }
  533 +}
  534 +
  535 +class TextMd extends InlineMd {
  536 + @override
  537 + final RegExp exp = RegExp(".*");
  538 +
  539 + @override
  540 + InlineSpan inlineSpan(BuildContext context, String text, TextStyle? style,
  541 + void Function(String url, String title)? onLinkTab) {
  542 + return WidgetSpan(
  543 + child: TexText(
  544 + text,
  545 + style: style,
  546 + ));
  547 + }
  548 +
  549 + @override
  550 + String toHtml(String text) {
  551 + return TexText.toHtmlData(text);
  552 + }
  553 +}
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 -import 'package:tex_text/tex_text.dart'; 2 +import 'package:tex_markdown/markdown_component.dart';
3 3
4 /// It creates a markdown widget closed to each other. 4 /// It creates a markdown widget closed to each other.
5 class MdWidget extends StatelessWidget { 5 class MdWidget extends StatelessWidget {
@@ -9,18 +9,43 @@ class MdWidget extends StatelessWidget { @@ -9,18 +9,43 @@ class MdWidget extends StatelessWidget {
9 final TextStyle? style; 9 final TextStyle? style;
10 final void Function(String url, String title)? onLinkTab; 10 final void Function(String url, String title)? onLinkTab;
11 final bool followLinkColor; 11 final bool followLinkColor;
  12 +
  13 + /// To convert markdown text to html text.
  14 + static String toHtml(String text) {
  15 + final RegExp table = RegExp(
  16 + r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)?$",
  17 + );
  18 + if (table.hasMatch(text)) {
  19 + final String value = text.trim().splitMapJoin(
  20 + RegExp(r'^\||\|\n\||\|$'),
  21 + onMatch: (p0) => "\n",
  22 + onNonMatch: (p0) {
  23 + if (p0.trim().isEmpty) {
  24 + return "";
  25 + }
  26 + // return p0;
  27 + return '<tr>${p0.trim().splitMapJoin(
  28 + '|',
  29 + onMatch: (p0) {
  30 + return "";
  31 + },
  32 + onNonMatch: (p0) {
  33 + return '<td>$p0</td>';
  34 + },
  35 + )}</tr>';
  36 + },
  37 + );
  38 + return '''
  39 +<table border="1" cellspacing="0">
  40 +$value
  41 +</table>
  42 +''';
  43 + }
  44 + return MarkdownComponent.toHtml(text);
  45 + }
  46 +
12 @override 47 @override
13 Widget build(BuildContext context) { 48 Widget build(BuildContext context) {
14 - final RegExp h = RegExp(r"^(#{1,6})\s([^\n]+)$");  
15 - final RegExp b = RegExp(r"^\*{2}(([\S^\*].*)?[\S^\*])\*{2}$");  
16 - final RegExp i = RegExp(r"^\*{1}(([\S^\*].*)?[\S^\*])\*{1}$");  
17 - final RegExp a = RegExp(r"^\[([^\s\*].*[^\s]?)?\]\(([^\s\*]+)?\)$");  
18 - final RegExp img = RegExp(r"^\!\[([^\s].*[^\s]?)?\]\(([^\s]+)\)$");  
19 - final RegExp ul = RegExp(r"^(\-|\*)\s([^\n]+)$");  
20 - final RegExp ol = RegExp(r"^([0-9]+\.)\s([^\n]+)$");  
21 - final RegExp rb = RegExp(r"^\((\x?)\)\s(\S.*)$");  
22 - final RegExp cb = RegExp(r"^\[(\x?)\]\s(\S.*)$");  
23 - final RegExp hr = RegExp(r"^(--)[-]+$");  
24 final RegExp table = RegExp( 49 final RegExp table = RegExp(
25 r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)?$", 50 r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)?$",
26 ); 51 );
@@ -42,7 +67,6 @@ class MdWidget extends StatelessWidget { @@ -42,7 +67,6 @@ class MdWidget extends StatelessWidget {
42 } 67 }
43 } 68 }
44 if (maxCol == 0) { 69 if (maxCol == 0) {
45 - // return const SizedBox();  
46 return Text("", style: style); 70 return Text("", style: style);
47 } 71 }
48 return Table( 72 return Table(
@@ -69,256 +93,6 @@ class MdWidget extends StatelessWidget { @@ -69,256 +93,6 @@ class MdWidget extends StatelessWidget {
69 .toList(), 93 .toList(),
70 ); 94 );
71 } 95 }
72 - if (h.hasMatch(exp)) {  
73 - var match = h.firstMatch(exp.trim());  
74 - return Column(  
75 - children: [  
76 - Row(  
77 - children: [  
78 - Expanded(  
79 - child: TexText(  
80 - "${match?[2]}",  
81 - style: [  
82 - Theme.of(context)  
83 - .textTheme  
84 - .headlineLarge  
85 - ?.copyWith(color: style?.color),  
86 - Theme.of(context)  
87 - .textTheme  
88 - .headlineMedium  
89 - ?.copyWith(color: style?.color),  
90 - Theme.of(context)  
91 - .textTheme  
92 - .headlineSmall  
93 - ?.copyWith(color: style?.color),  
94 - Theme.of(context)  
95 - .textTheme  
96 - .titleLarge  
97 - ?.copyWith(color: style?.color),  
98 - Theme.of(context)  
99 - .textTheme  
100 - .titleMedium  
101 - ?.copyWith(color: style?.color),  
102 - Theme.of(context)  
103 - .textTheme  
104 - .titleSmall  
105 - ?.copyWith(color: style?.color),  
106 - ][match![1]!.length - 1],  
107 - ),  
108 - ),  
109 - ],  
110 - ),  
111 - if (match[1]!.length == 1) const Divider(height: 6),  
112 - ],  
113 - );  
114 - }  
115 - if (hr.hasMatch(exp)) {  
116 - return const Divider(  
117 - height: 5,  
118 - );  
119 - }  
120 - if (cb.hasMatch(exp)) {  
121 - var match = cb.firstMatch(exp.trim());  
122 - return Row(  
123 - crossAxisAlignment: CrossAxisAlignment.center,  
124 - mainAxisSize: MainAxisSize.min,  
125 - children: [  
126 - Padding(  
127 - padding: const EdgeInsets.symmetric(horizontal: 10),  
128 - child: Checkbox(  
129 - // value: true,  
130 - value: ("${match?[1]}" == "x"),  
131 - onChanged: (value) {},  
132 - fillColor: ButtonStyleButton.allOrNull(style?.color),  
133 - ),  
134 - ),  
135 - Expanded(  
136 - child: MdWidget(  
137 - "${match?[2]}",  
138 - onLinkTab: onLinkTab,  
139 - style: style,  
140 - ),  
141 - ),  
142 - ],  
143 - );  
144 - }  
145 - if (rb.hasMatch(exp)) {  
146 - var match = rb.firstMatch(exp.trim());  
147 - return Row(  
148 - crossAxisAlignment: CrossAxisAlignment.center,  
149 - mainAxisSize: MainAxisSize.min,  
150 - children: [  
151 - Padding(  
152 - padding: const EdgeInsets.symmetric(horizontal: 10),  
153 - child: Radio(  
154 - value: true,  
155 - groupValue: ("${match?[1]}" == "x"),  
156 - onChanged: (value) {},  
157 - fillColor: ButtonStyleButton.allOrNull(style?.color),  
158 - ),  
159 - ),  
160 - Expanded(  
161 - child: MdWidget(  
162 - "${match?[2]}",  
163 - onLinkTab: onLinkTab,  
164 - style: style,  
165 - ),  
166 - ),  
167 - ],  
168 - );  
169 - }  
170 - if (ul.hasMatch(exp)) {  
171 - var match = ul.firstMatch(exp.trim());  
172 - return Row(  
173 - crossAxisAlignment: CrossAxisAlignment.center,  
174 - mainAxisSize: MainAxisSize.max,  
175 - children: [  
176 - Padding(  
177 - padding: const EdgeInsets.symmetric(horizontal: 10),  
178 - child: Icon(  
179 - Icons.circle,  
180 - color: style?.color,  
181 - size: 8,  
182 - ),  
183 - ),  
184 - Expanded(  
185 - child: MdWidget(  
186 - "${match?[2]}",  
187 - onLinkTab: onLinkTab,  
188 - style: style,  
189 - ),  
190 - ),  
191 - ],  
192 - );  
193 - }  
194 - if (ol.hasMatch(exp)) {  
195 - var match = ol.firstMatch(exp.trim());  
196 - return Row(  
197 - crossAxisAlignment: CrossAxisAlignment.center,  
198 - mainAxisSize: MainAxisSize.max,  
199 - children: [  
200 - Padding(  
201 - padding: const EdgeInsets.symmetric(horizontal: 11),  
202 - child: Text(  
203 - "${match?[1]}",  
204 - style: (style ?? const TextStyle())  
205 - .copyWith(fontWeight: FontWeight.bold),  
206 - ),  
207 - ),  
208 - Expanded(  
209 - child: MdWidget(  
210 - "${match?[2]}",  
211 - onLinkTab: onLinkTab,  
212 - style: style,  
213 - ),  
214 - ),  
215 - ],  
216 - );  
217 - }  
218 - if (b.hasMatch(exp)) {  
219 - var match = b.firstMatch(exp.trim());  
220 - return TexText(  
221 - "${match?[1]}",  
222 - style: style?.copyWith(fontWeight: FontWeight.bold) ??  
223 - const TextStyle(fontWeight: FontWeight.bold),  
224 - );  
225 - }  
226 - if (i.hasMatch(exp)) {  
227 - var match = i.firstMatch(exp.trim());  
228 - return TexText(  
229 - "${match?[1]}",  
230 - style:  
231 - (style ?? const TextStyle()).copyWith(fontStyle: FontStyle.italic),  
232 - );  
233 - }  
234 - if (a.hasMatch(exp)) {  
235 - var match = a.firstMatch(exp.trim());  
236 - if (match?[1] == null && match?[2] == null) {  
237 - return const SizedBox();  
238 - }  
239 - return GestureDetector(  
240 - onTap: () {  
241 - if (onLinkTab == null) {  
242 - return;  
243 - }  
244 - onLinkTab!("${match?[2]}", "${match?[1]}");  
245 - },  
246 - child: MdWidget(  
247 - "${match?[1]}",  
248 - onLinkTab: onLinkTab,  
249 - style: ((followLinkColor && style != null)  
250 - ? style  
251 - : const TextStyle(color: Colors.blueAccent))  
252 - ?.copyWith(decoration: TextDecoration.underline),  
253 - ),  
254 - );  
255 - }  
256 - if (img.hasMatch(exp)) {  
257 - var match = img.firstMatch(exp.trim());  
258 - double? height;  
259 - double? width;  
260 - if (match?[1] != null) {  
261 - var size = RegExp(r"^([0-9]+)?x?([0-9]+)?")  
262 - .firstMatch(match![1].toString().trim());  
263 - width = double.tryParse(size?[1]?.toString() ?? 'a');  
264 - height = double.tryParse(size?[2]?.toString() ?? 'a');  
265 - }  
266 - return SizedBox(  
267 - width: width,  
268 - height: height,  
269 - child: Image.network(  
270 - "${match?[2]}",  
271 - fit: BoxFit.fill,  
272 - errorBuilder: (context, error, stackTrace) {  
273 - return Placeholder(  
274 - color: Theme.of(context).colorScheme.error,  
275 - child: Text(  
276 - "${match?[2]}\n$error",  
277 - style: style,  
278 - ),  
279 - );  
280 - },  
281 - ),  
282 - );  
283 - }  
284 - List<String> expL = exp.split('\n');  
285 - // .map(  
286 - // (e) => e.trim(),  
287 - // )  
288 - // .toList();  
289 - bool hasMatch = false;  
290 -  
291 - for (final each in expL) {  
292 - if (a.hasMatch(each) ||  
293 - b.hasMatch(each) ||  
294 - i.hasMatch(each) ||  
295 - h.hasMatch(each) ||  
296 - hr.hasMatch(each) ||  
297 - ol.hasMatch(each) ||  
298 - rb.hasMatch(each) ||  
299 - cb.hasMatch(each) ||  
300 - img.hasMatch(each) ||  
301 - ul.hasMatch(each)) {  
302 - hasMatch = true;  
303 - }  
304 - }  
305 - if (hasMatch) {  
306 - return Wrap(  
307 - crossAxisAlignment: WrapCrossAlignment.center,  
308 - spacing: 10,  
309 - children: exp  
310 - .split("\n")  
311 - .map<Widget>((e) => MdWidget(  
312 - e,  
313 - onLinkTab: onLinkTab,  
314 - style: style,  
315 - ))  
316 - .toList(),  
317 - );  
318 - }  
319 - return TexText(  
320 - exp,  
321 - style: style,  
322 - ); 96 + return MarkdownComponent.generate(context, exp, style, onLinkTab);
323 } 97 }
324 } 98 }
@@ -17,6 +17,13 @@ class TexMarkdown extends StatelessWidget { @@ -17,6 +17,13 @@ class TexMarkdown extends StatelessWidget {
17 final TextStyle? style; 17 final TextStyle? style;
18 final void Function(String url, String title)? onLinkTab; 18 final void Function(String url, String title)? onLinkTab;
19 final bool followLinkColor; 19 final bool followLinkColor;
  20 + static String toHtml(String text) {
  21 + String html = "";
  22 + text.trim().split(RegExp(r"\n\n+")).forEach((element) {
  23 + html += MdWidget.toHtml(element);
  24 + });
  25 + return html;
  26 + }
20 27
21 @override 28 @override
22 Widget build(BuildContext context) { 29 Widget build(BuildContext context) {
1 name: tex_markdown 1 name: tex_markdown
2 description: This package is used to create flutter widget that can render markdown and latex formulas. It is very simple to use and uses native flutter components. 2 description: This package is used to create flutter widget that can render markdown and latex formulas. It is very simple to use and uses native flutter components.
3 -version: 0.0.6 3 +version: 0.0.9
4 homepage: https://github.com/saminsohag/flutter_packages/tree/main/tex_markdown 4 homepage: https://github.com/saminsohag/flutter_packages/tree/main/tex_markdown
5 5
6 environment: 6 environment:
@@ -10,7 +10,7 @@ environment: @@ -10,7 +10,7 @@ environment:
10 dependencies: 10 dependencies:
11 flutter: 11 flutter:
12 sdk: flutter 12 sdk: flutter
13 - tex_text: ^0.0.8 13 + tex_text: ^0.1.2
14 14
15 dev_dependencies: 15 dev_dependencies:
16 flutter_test: 16 flutter_test: