Showing
15 changed files
with
746 additions
and
271 deletions
1 | +## 1.0.0 | ||
2 | + | ||
3 | +* `TexMarkdown` is renamed to `GptMarkdown`. | ||
4 | +* `h1` to `h6` style added to `GptMarkdownThemeData` class. | ||
5 | +* `hrLineThickness` value added to `GptMarkdownThemeData` class. | ||
6 | +* `hrLineColor` Color added to `GptMarkdownThemeData` class. | ||
7 | +* `linkColor` Color added to `GptMarkdownThemeData` class. | ||
8 | +* `linkHoverColor` Color added to `GptMarkdownThemeData` class. | ||
9 | +* Indentation improved. | ||
10 | +* Math equations are now default selectable. | ||
11 | +* `SelectableAdapter` Widget added to make any widget selectable. | ||
12 | + | ||
13 | +## 0.1.15 | ||
14 | + | ||
15 | +* `CodeBlock` is moved out of `gpt_markdown.dart` library. | ||
16 | + | ||
1 | ## 0.1.14 | 17 | ## 0.1.14 |
2 | 18 | ||
3 | -* Cnaged `withOpacity` to `withAlpha` in `theme.dart` for highlightColor. | 19 | +* Changed `withOpacity` to `withAlpha` in `theme.dart` for highlightColor. |
4 | 20 | ||
5 | ## 0.1.13 | 21 | ## 0.1.13 |
6 | 22 |
@@ -81,7 +81,7 @@ Check the documentation [here.](https://github.com/saminsohag/flutter_packages/t | @@ -81,7 +81,7 @@ Check the documentation [here.](https://github.com/saminsohag/flutter_packages/t | ||
81 | import 'package:flutter/material.dart'; | 81 | import 'package:flutter/material.dart'; |
82 | import 'package:gpt_markdown/gpt_markdown.dart'; | 82 | import 'package:gpt_markdown/gpt_markdown.dart'; |
83 | 83 | ||
84 | -return TexMarkdown( | 84 | +return GptMarkdown( |
85 | ''' | 85 | ''' |
86 | * This is a unordered list. | 86 | * This is a unordered list. |
87 | ''', | 87 | ''', |
@@ -48,7 +48,7 @@ class _MyHomePageState extends State<MyHomePage> { | @@ -48,7 +48,7 @@ class _MyHomePageState extends State<MyHomePage> { | ||
48 | AnimatedBuilder( | 48 | AnimatedBuilder( |
49 | animation: _controller, | 49 | animation: _controller, |
50 | builder: (context, _) { | 50 | builder: (context, _) { |
51 | - return TexMarkdown( | 51 | + return GptMarkdown( |
52 | _controller.text, | 52 | _controller.text, |
53 | style: const TextStyle( | 53 | style: const TextStyle( |
54 | color: Colors.red, | 54 | color: Colors.red, |
@@ -30,6 +30,12 @@ class _MyAppState extends State<MyApp> { | @@ -30,6 +30,12 @@ class _MyAppState extends State<MyApp> { | ||
30 | useMaterial3: true, | 30 | useMaterial3: true, |
31 | brightness: Brightness.light, | 31 | brightness: Brightness.light, |
32 | colorSchemeSeed: Colors.blue, | 32 | colorSchemeSeed: Colors.blue, |
33 | + extensions: [ | ||
34 | + GptMarkdownThemeData( | ||
35 | + brightness: Brightness.light, | ||
36 | + highlightColor: Colors.red, | ||
37 | + ), | ||
38 | + ], | ||
33 | ), | 39 | ), |
34 | darkTheme: ThemeData( | 40 | darkTheme: ThemeData( |
35 | useMaterial3: true, | 41 | useMaterial3: true, |
@@ -37,9 +43,11 @@ class _MyAppState extends State<MyApp> { | @@ -37,9 +43,11 @@ class _MyAppState extends State<MyApp> { | ||
37 | colorSchemeSeed: Colors.blue, | 43 | colorSchemeSeed: Colors.blue, |
38 | extensions: [ | 44 | extensions: [ |
39 | GptMarkdownThemeData( | 45 | GptMarkdownThemeData( |
46 | + brightness: Brightness.dark, | ||
40 | highlightColor: Colors.red, | 47 | highlightColor: Colors.red, |
41 | ), | 48 | ), |
42 | - ]), | 49 | + ], |
50 | + ), | ||
43 | home: MyHomePage( | 51 | home: MyHomePage( |
44 | title: 'GptMarkdown', | 52 | title: 'GptMarkdown', |
45 | onPressed: () { | 53 | onPressed: () { |
@@ -170,7 +178,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -170,7 +178,10 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
170 | Icons.select_all_outlined, | 178 | Icons.select_all_outlined, |
171 | color: selectable | 179 | color: selectable |
172 | ? Theme.of(context).colorScheme.onSurfaceVariant | 180 | ? Theme.of(context).colorScheme.onSurfaceVariant |
173 | - : Theme.of(context).colorScheme.onSurface.withOpacity(0.38), | 181 | + : Theme.of(context) |
182 | + .colorScheme | ||
183 | + .onSurface | ||
184 | + .withValues(alpha: 0.38), | ||
174 | ), | 185 | ), |
175 | ), | 186 | ), |
176 | IconButton( | 187 | IconButton( |
@@ -243,7 +254,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -243,7 +254,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
243 | // ), | 254 | // ), |
244 | child: Builder( | 255 | child: Builder( |
245 | builder: (context) { | 256 | builder: (context) { |
246 | - Widget child = TexMarkdown( | 257 | + Widget child = GptMarkdown( |
247 | _controller.text, | 258 | _controller.text, |
248 | textDirection: _direction, | 259 | textDirection: _direction, |
249 | onLinkTab: (url, title) { | 260 | onLinkTab: (url, title) { |
@@ -301,7 +312,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | @@ -301,7 +312,7 @@ Markdown and LaTeX can be powerful tools for formatting text and mathematical ex | ||
301 | ..insert(1, "|---|"); | 312 | ..insert(1, "|---|"); |
302 | tableString = | 313 | tableString = |
303 | tableStringList.join("\n"); | 314 | tableStringList.join("\n"); |
304 | - return TexMarkdown(tableString); | 315 | + return GptMarkdown(tableString); |
305 | } | 316 | } |
306 | var controller = ScrollController(); | 317 | var controller = ScrollController(); |
307 | Widget child = Math.tex( | 318 | Widget child = Math.tex( |
@@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { | @@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate { | ||
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { | 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { |
7 | return true | 7 | return true |
8 | } | 8 | } |
9 | + | ||
10 | + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { | ||
11 | + return true | ||
12 | + } | ||
9 | } | 13 | } |
@@ -5,10 +5,10 @@ packages: | @@ -5,10 +5,10 @@ packages: | ||
5 | dependency: transitive | 5 | dependency: transitive |
6 | description: | 6 | description: |
7 | name: args | 7 | name: args |
8 | - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" | 8 | + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 |
9 | url: "https://pub.dev" | 9 | url: "https://pub.dev" |
10 | source: hosted | 10 | source: hosted |
11 | - version: "2.5.0" | 11 | + version: "2.6.0" |
12 | async: | 12 | async: |
13 | dependency: transitive | 13 | dependency: transitive |
14 | description: | 14 | description: |
@@ -45,10 +45,10 @@ packages: | @@ -45,10 +45,10 @@ packages: | ||
45 | dependency: transitive | 45 | dependency: transitive |
46 | description: | 46 | description: |
47 | name: collection | 47 | name: collection |
48 | - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a | 48 | + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf |
49 | url: "https://pub.dev" | 49 | url: "https://pub.dev" |
50 | source: hosted | 50 | source: hosted |
51 | - version: "1.18.0" | 51 | + version: "1.19.0" |
52 | cross_file: | 52 | cross_file: |
53 | dependency: transitive | 53 | dependency: transitive |
54 | description: | 54 | description: |
@@ -98,18 +98,18 @@ packages: | @@ -98,18 +98,18 @@ packages: | ||
98 | dependency: "direct main" | 98 | dependency: "direct main" |
99 | description: | 99 | description: |
100 | name: flutter_math_fork | 100 | name: flutter_math_fork |
101 | - sha256: "94bee4642892a94939af0748c6a7de0ff8318feee588379dcdfea7dc5cba06c8" | 101 | + sha256: "284bab89b2fbf1bc3a0baf13d011c1dd324d004e35d177626b77f2fc056366ac" |
102 | url: "https://pub.dev" | 102 | url: "https://pub.dev" |
103 | source: hosted | 103 | source: hosted |
104 | - version: "0.7.2" | 104 | + version: "0.7.3" |
105 | flutter_svg: | 105 | flutter_svg: |
106 | dependency: transitive | 106 | dependency: transitive |
107 | description: | 107 | description: |
108 | name: flutter_svg | 108 | name: flutter_svg |
109 | - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" | 109 | + sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" |
110 | url: "https://pub.dev" | 110 | url: "https://pub.dev" |
111 | source: hosted | 111 | source: hosted |
112 | - version: "2.0.10+1" | 112 | + version: "2.0.16" |
113 | flutter_test: | 113 | flutter_test: |
114 | dependency: "direct dev" | 114 | dependency: "direct dev" |
115 | description: flutter | 115 | description: flutter |
@@ -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: "0.1.14" | 129 | + version: "1.0.0" |
130 | http: | 130 | http: |
131 | dependency: transitive | 131 | dependency: transitive |
132 | description: | 132 | description: |
@@ -139,26 +139,26 @@ packages: | @@ -139,26 +139,26 @@ packages: | ||
139 | dependency: transitive | 139 | dependency: transitive |
140 | description: | 140 | description: |
141 | name: http_parser | 141 | name: http_parser |
142 | - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" | 142 | + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" |
143 | url: "https://pub.dev" | 143 | url: "https://pub.dev" |
144 | source: hosted | 144 | source: hosted |
145 | - version: "4.0.2" | 145 | + version: "4.1.1" |
146 | leak_tracker: | 146 | leak_tracker: |
147 | dependency: transitive | 147 | dependency: transitive |
148 | description: | 148 | description: |
149 | name: leak_tracker | 149 | name: leak_tracker |
150 | - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" | 150 | + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" |
151 | url: "https://pub.dev" | 151 | url: "https://pub.dev" |
152 | source: hosted | 152 | source: hosted |
153 | - version: "10.0.5" | 153 | + version: "10.0.7" |
154 | leak_tracker_flutter_testing: | 154 | leak_tracker_flutter_testing: |
155 | dependency: transitive | 155 | dependency: transitive |
156 | description: | 156 | description: |
157 | name: leak_tracker_flutter_testing | 157 | name: leak_tracker_flutter_testing |
158 | - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" | 158 | + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" |
159 | url: "https://pub.dev" | 159 | url: "https://pub.dev" |
160 | source: hosted | 160 | source: hosted |
161 | - version: "3.0.5" | 161 | + version: "3.0.8" |
162 | leak_tracker_testing: | 162 | leak_tracker_testing: |
163 | dependency: transitive | 163 | dependency: transitive |
164 | description: | 164 | description: |
@@ -219,10 +219,10 @@ packages: | @@ -219,10 +219,10 @@ packages: | ||
219 | dependency: transitive | 219 | dependency: transitive |
220 | description: | 220 | description: |
221 | name: path_parsing | 221 | name: path_parsing |
222 | - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf | 222 | + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" |
223 | url: "https://pub.dev" | 223 | url: "https://pub.dev" |
224 | source: hosted | 224 | source: hosted |
225 | - version: "1.0.1" | 225 | + version: "1.1.0" |
226 | petitparser: | 226 | petitparser: |
227 | dependency: transitive | 227 | dependency: transitive |
228 | description: | 228 | description: |
@@ -243,7 +243,7 @@ packages: | @@ -243,7 +243,7 @@ packages: | ||
243 | dependency: transitive | 243 | dependency: transitive |
244 | description: flutter | 244 | description: flutter |
245 | source: sdk | 245 | source: sdk |
246 | - version: "0.0.99" | 246 | + version: "0.0.0" |
247 | source_span: | 247 | source_span: |
248 | dependency: transitive | 248 | dependency: transitive |
249 | description: | 249 | description: |
@@ -256,10 +256,10 @@ packages: | @@ -256,10 +256,10 @@ packages: | ||
256 | dependency: transitive | 256 | dependency: transitive |
257 | description: | 257 | description: |
258 | name: stack_trace | 258 | name: stack_trace |
259 | - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" | 259 | + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" |
260 | url: "https://pub.dev" | 260 | url: "https://pub.dev" |
261 | source: hosted | 261 | source: hosted |
262 | - version: "1.11.1" | 262 | + version: "1.12.0" |
263 | stream_channel: | 263 | stream_channel: |
264 | dependency: transitive | 264 | dependency: transitive |
265 | description: | 265 | description: |
@@ -272,10 +272,10 @@ packages: | @@ -272,10 +272,10 @@ packages: | ||
272 | dependency: transitive | 272 | dependency: transitive |
273 | description: | 273 | description: |
274 | name: string_scanner | 274 | name: string_scanner |
275 | - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" | 275 | + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" |
276 | url: "https://pub.dev" | 276 | url: "https://pub.dev" |
277 | source: hosted | 277 | source: hosted |
278 | - version: "1.2.0" | 278 | + version: "1.3.0" |
279 | term_glyph: | 279 | term_glyph: |
280 | dependency: transitive | 280 | dependency: transitive |
281 | description: | 281 | description: |
@@ -288,10 +288,10 @@ packages: | @@ -288,10 +288,10 @@ packages: | ||
288 | dependency: transitive | 288 | dependency: transitive |
289 | description: | 289 | description: |
290 | name: test_api | 290 | name: test_api |
291 | - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" | 291 | + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" |
292 | url: "https://pub.dev" | 292 | url: "https://pub.dev" |
293 | source: hosted | 293 | source: hosted |
294 | - version: "0.7.2" | 294 | + version: "0.7.3" |
295 | tuple: | 295 | tuple: |
296 | dependency: transitive | 296 | dependency: transitive |
297 | description: | 297 | description: |
@@ -304,34 +304,34 @@ packages: | @@ -304,34 +304,34 @@ packages: | ||
304 | dependency: transitive | 304 | dependency: transitive |
305 | description: | 305 | description: |
306 | name: typed_data | 306 | name: typed_data |
307 | - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c | 307 | + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 |
308 | url: "https://pub.dev" | 308 | url: "https://pub.dev" |
309 | source: hosted | 309 | source: hosted |
310 | - version: "1.3.2" | 310 | + version: "1.4.0" |
311 | vector_graphics: | 311 | vector_graphics: |
312 | dependency: transitive | 312 | dependency: transitive |
313 | description: | 313 | description: |
314 | name: vector_graphics | 314 | name: vector_graphics |
315 | - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" | 315 | + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" |
316 | url: "https://pub.dev" | 316 | url: "https://pub.dev" |
317 | source: hosted | 317 | source: hosted |
318 | - version: "1.1.11+1" | 318 | + version: "1.1.15" |
319 | vector_graphics_codec: | 319 | vector_graphics_codec: |
320 | dependency: transitive | 320 | dependency: transitive |
321 | description: | 321 | description: |
322 | name: vector_graphics_codec | 322 | name: vector_graphics_codec |
323 | - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da | 323 | + sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" |
324 | url: "https://pub.dev" | 324 | url: "https://pub.dev" |
325 | source: hosted | 325 | source: hosted |
326 | - version: "1.1.11+1" | 326 | + version: "1.1.12" |
327 | vector_graphics_compiler: | 327 | vector_graphics_compiler: |
328 | dependency: transitive | 328 | dependency: transitive |
329 | description: | 329 | description: |
330 | name: vector_graphics_compiler | 330 | name: vector_graphics_compiler |
331 | - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" | 331 | + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" |
332 | url: "https://pub.dev" | 332 | url: "https://pub.dev" |
333 | source: hosted | 333 | source: hosted |
334 | - version: "1.1.11+1" | 334 | + version: "1.1.16" |
335 | vector_math: | 335 | vector_math: |
336 | dependency: transitive | 336 | dependency: transitive |
337 | description: | 337 | description: |
@@ -344,26 +344,26 @@ packages: | @@ -344,26 +344,26 @@ packages: | ||
344 | dependency: transitive | 344 | dependency: transitive |
345 | description: | 345 | description: |
346 | name: vm_service | 346 | name: vm_service |
347 | - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" | 347 | + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b |
348 | url: "https://pub.dev" | 348 | url: "https://pub.dev" |
349 | source: hosted | 349 | source: hosted |
350 | - version: "14.2.5" | 350 | + version: "14.3.0" |
351 | watcher: | 351 | watcher: |
352 | dependency: "direct main" | 352 | dependency: "direct main" |
353 | description: | 353 | description: |
354 | name: watcher | 354 | name: watcher |
355 | - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" | 355 | + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" |
356 | url: "https://pub.dev" | 356 | url: "https://pub.dev" |
357 | source: hosted | 357 | source: hosted |
358 | - version: "1.1.0" | 358 | + version: "1.1.1" |
359 | web: | 359 | web: |
360 | dependency: transitive | 360 | dependency: transitive |
361 | description: | 361 | description: |
362 | name: web | 362 | name: web |
363 | - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" | 363 | + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb |
364 | url: "https://pub.dev" | 364 | url: "https://pub.dev" |
365 | source: hosted | 365 | source: hosted |
366 | - version: "0.5.1" | 366 | + version: "1.1.0" |
367 | xml: | 367 | xml: |
368 | dependency: transitive | 368 | dependency: transitive |
369 | description: | 369 | description: |
@@ -374,4 +374,4 @@ packages: | @@ -374,4 +374,4 @@ packages: | ||
374 | version: "6.5.0" | 374 | version: "6.5.0" |
375 | sdks: | 375 | sdks: |
376 | dart: ">=3.5.0 <4.0.0" | 376 | dart: ">=3.5.0 <4.0.0" |
377 | - flutter: ">=3.18.0-18.0.pre.54" | 377 | + flutter: ">=3.22.0" |
lib/custom_widgets/code_field.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | +import 'package:flutter/services.dart'; | ||
3 | + | ||
4 | +class CodeField extends StatefulWidget { | ||
5 | + const CodeField({super.key, required this.name, required this.codes}); | ||
6 | + final String name; | ||
7 | + final String codes; | ||
8 | + | ||
9 | + @override | ||
10 | + State<CodeField> createState() => _CodeFieldState(); | ||
11 | +} | ||
12 | + | ||
13 | +class _CodeFieldState extends State<CodeField> { | ||
14 | + bool _copied = false; | ||
15 | + @override | ||
16 | + Widget build(BuildContext context) { | ||
17 | + return Material( | ||
18 | + color: Theme.of(context).colorScheme.onInverseSurface, | ||
19 | + shape: RoundedRectangleBorder( | ||
20 | + borderRadius: BorderRadius.circular(8), | ||
21 | + ), | ||
22 | + child: Column( | ||
23 | + crossAxisAlignment: CrossAxisAlignment.stretch, | ||
24 | + children: [ | ||
25 | + Row( | ||
26 | + children: [ | ||
27 | + Padding( | ||
28 | + padding: | ||
29 | + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), | ||
30 | + child: Text(widget.name), | ||
31 | + ), | ||
32 | + const Spacer(), | ||
33 | + TextButton.icon( | ||
34 | + style: TextButton.styleFrom( | ||
35 | + foregroundColor: Theme.of(context).colorScheme.onSurface, | ||
36 | + textStyle: const TextStyle( | ||
37 | + fontWeight: FontWeight.normal, | ||
38 | + ), | ||
39 | + ), | ||
40 | + onPressed: () async { | ||
41 | + await Clipboard.setData(ClipboardData(text: widget.codes)) | ||
42 | + .then((value) { | ||
43 | + setState(() { | ||
44 | + _copied = true; | ||
45 | + }); | ||
46 | + }); | ||
47 | + await Future.delayed(const Duration(seconds: 2)); | ||
48 | + setState(() { | ||
49 | + _copied = false; | ||
50 | + }); | ||
51 | + }, | ||
52 | + icon: Icon( | ||
53 | + (_copied) ? Icons.done : Icons.content_paste, | ||
54 | + size: 15, | ||
55 | + ), | ||
56 | + label: Text((_copied) ? "Copied!" : "Copy code"), | ||
57 | + ), | ||
58 | + ], | ||
59 | + ), | ||
60 | + const Divider( | ||
61 | + height: 1, | ||
62 | + ), | ||
63 | + SingleChildScrollView( | ||
64 | + scrollDirection: Axis.horizontal, | ||
65 | + padding: const EdgeInsets.all(16), | ||
66 | + child: Text( | ||
67 | + widget.codes, | ||
68 | + ), | ||
69 | + ), | ||
70 | + ], | ||
71 | + ), | ||
72 | + ); | ||
73 | + } | ||
74 | +} |
@@ -8,11 +8,15 @@ class LinkButton extends StatefulWidget { | @@ -8,11 +8,15 @@ class LinkButton extends StatefulWidget { | ||
8 | final TextStyle? textStyle; | 8 | final TextStyle? textStyle; |
9 | final String? url; | 9 | final String? url; |
10 | final GptMarkdownConfig config; | 10 | final GptMarkdownConfig config; |
11 | + final Color color; | ||
12 | + final Color hoverColor; | ||
11 | 13 | ||
12 | const LinkButton( | 14 | const LinkButton( |
13 | {super.key, | 15 | {super.key, |
14 | required this.text, | 16 | required this.text, |
15 | required this.config, | 17 | required this.config, |
18 | + required this.color, | ||
19 | + required this.hoverColor, | ||
16 | this.onPressed, | 20 | this.onPressed, |
17 | this.textStyle, | 21 | this.textStyle, |
18 | this.url}); | 22 | this.url}); |
@@ -27,9 +31,9 @@ class _LinkButtonState extends State<LinkButton> { | @@ -27,9 +31,9 @@ class _LinkButtonState extends State<LinkButton> { | ||
27 | @override | 31 | @override |
28 | Widget build(BuildContext context) { | 32 | Widget build(BuildContext context) { |
29 | var style = (widget.config.style ?? const TextStyle()).copyWith( | 33 | var style = (widget.config.style ?? const TextStyle()).copyWith( |
30 | - color: _isHovering ? Colors.red : Colors.blue, | 34 | + color: _isHovering ? widget.hoverColor : widget.color, |
31 | decoration: TextDecoration.underline, | 35 | decoration: TextDecoration.underline, |
32 | - decorationColor: _isHovering ? Colors.red : Colors.blue, | 36 | + decorationColor: _isHovering ? widget.hoverColor : widget.color, |
33 | ); | 37 | ); |
34 | return MouseRegion( | 38 | return MouseRegion( |
35 | cursor: SystemMouseCursors.click, | 39 | cursor: SystemMouseCursors.click, |
lib/custom_widgets/selectable_adapter.dart
0 → 100644
1 | +import 'package:flutter/material.dart'; | ||
2 | +import 'package:flutter/rendering.dart'; | ||
3 | + | ||
4 | +class SelectableAdapter extends StatelessWidget { | ||
5 | + const SelectableAdapter( | ||
6 | + {super.key, required this.selectedText, required this.child}); | ||
7 | + | ||
8 | + final Widget child; | ||
9 | + final String selectedText; | ||
10 | + | ||
11 | + @override | ||
12 | + Widget build(BuildContext context) { | ||
13 | + final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context); | ||
14 | + if (registrar == null) { | ||
15 | + return child; | ||
16 | + } | ||
17 | + return MouseRegion( | ||
18 | + cursor: SystemMouseCursors.text, | ||
19 | + child: _SelectableAdapter( | ||
20 | + registrar: registrar, | ||
21 | + selectedText: selectedText, | ||
22 | + child: child, | ||
23 | + ), | ||
24 | + ); | ||
25 | + } | ||
26 | +} | ||
27 | + | ||
28 | +class _SelectableAdapter extends SingleChildRenderObjectWidget { | ||
29 | + const _SelectableAdapter({ | ||
30 | + required this.registrar, | ||
31 | + required Widget child, | ||
32 | + required this.selectedText, | ||
33 | + }) : super(child: child); | ||
34 | + | ||
35 | + final SelectionRegistrar registrar; | ||
36 | + final String selectedText; | ||
37 | + | ||
38 | + @override | ||
39 | + _RenderSelectableAdapter createRenderObject(BuildContext context) { | ||
40 | + return _RenderSelectableAdapter( | ||
41 | + DefaultSelectionStyle.of(context).selectionColor!, | ||
42 | + selectedText, | ||
43 | + registrar, | ||
44 | + ); | ||
45 | + } | ||
46 | + | ||
47 | + @override | ||
48 | + void updateRenderObject( | ||
49 | + BuildContext context, _RenderSelectableAdapter renderObject) { | ||
50 | + renderObject | ||
51 | + ..selectionColor = DefaultSelectionStyle.of(context).selectionColor! | ||
52 | + ..registrar = registrar; | ||
53 | + } | ||
54 | +} | ||
55 | + | ||
56 | +class _RenderSelectableAdapter extends RenderProxyBox | ||
57 | + with Selectable, SelectionRegistrant { | ||
58 | + String selectionText; | ||
59 | + _RenderSelectableAdapter( | ||
60 | + Color selectionColor, | ||
61 | + this.selectionText, | ||
62 | + SelectionRegistrar registrar, | ||
63 | + ) : _selectionColor = selectionColor, | ||
64 | + _geometry = ValueNotifier<SelectionGeometry>(_noSelection) { | ||
65 | + this.registrar = registrar; | ||
66 | + _geometry.addListener(markNeedsPaint); | ||
67 | + } | ||
68 | + | ||
69 | + static const SelectionGeometry _noSelection = | ||
70 | + SelectionGeometry(status: SelectionStatus.none, hasContent: true); | ||
71 | + final ValueNotifier<SelectionGeometry> _geometry; | ||
72 | + | ||
73 | + Color get selectionColor => _selectionColor; | ||
74 | + late Color _selectionColor; | ||
75 | + set selectionColor(Color value) { | ||
76 | + if (_selectionColor == value) { | ||
77 | + return; | ||
78 | + } | ||
79 | + _selectionColor = value; | ||
80 | + markNeedsPaint(); | ||
81 | + } | ||
82 | + | ||
83 | + // ValueListenable APIs | ||
84 | + | ||
85 | + @override | ||
86 | + void addListener(VoidCallback listener) => _geometry.addListener(listener); | ||
87 | + | ||
88 | + @override | ||
89 | + void removeListener(VoidCallback listener) => | ||
90 | + _geometry.removeListener(listener); | ||
91 | + | ||
92 | + @override | ||
93 | + SelectionGeometry get value => _geometry.value; | ||
94 | + | ||
95 | + // Selectable APIs. | ||
96 | + | ||
97 | + @override | ||
98 | + List<Rect> get boundingBoxes => <Rect>[paintBounds]; | ||
99 | + | ||
100 | + // Adjust this value to enlarge or shrink the selection highlight. | ||
101 | + static const double _padding = 0.0; | ||
102 | + Rect _getSelectionHighlightRect() { | ||
103 | + return Rect.fromLTWH(0 - _padding, 0 - _padding, size.width + _padding * 2, | ||
104 | + size.height + _padding * 2); | ||
105 | + } | ||
106 | + | ||
107 | + Offset? _start; | ||
108 | + Offset? _end; | ||
109 | + void _updateGeometry() { | ||
110 | + if (_start == null || _end == null) { | ||
111 | + _geometry.value = _noSelection; | ||
112 | + return; | ||
113 | + } | ||
114 | + final Rect renderObjectRect = Rect.fromLTWH(0, 0, size.width, size.height); | ||
115 | + final Rect selectionRect = Rect.fromPoints(_start!, _end!); | ||
116 | + if (renderObjectRect.intersect(selectionRect).isEmpty) { | ||
117 | + _geometry.value = _noSelection; | ||
118 | + } else { | ||
119 | + final Rect selectionRect = _getSelectionHighlightRect(); | ||
120 | + final SelectionPoint firstSelectionPoint = SelectionPoint( | ||
121 | + localPosition: selectionRect.bottomLeft, | ||
122 | + lineHeight: selectionRect.size.height, | ||
123 | + handleType: TextSelectionHandleType.left, | ||
124 | + ); | ||
125 | + final SelectionPoint secondSelectionPoint = SelectionPoint( | ||
126 | + localPosition: selectionRect.bottomRight, | ||
127 | + lineHeight: selectionRect.size.height, | ||
128 | + handleType: TextSelectionHandleType.right, | ||
129 | + ); | ||
130 | + final bool isReversed; | ||
131 | + if (_start!.dy > _end!.dy) { | ||
132 | + isReversed = true; | ||
133 | + } else if (_start!.dy < _end!.dy) { | ||
134 | + isReversed = false; | ||
135 | + } else { | ||
136 | + isReversed = _start!.dx > _end!.dx; | ||
137 | + } | ||
138 | + _geometry.value = SelectionGeometry( | ||
139 | + status: SelectionStatus.uncollapsed, | ||
140 | + hasContent: true, | ||
141 | + startSelectionPoint: | ||
142 | + isReversed ? secondSelectionPoint : firstSelectionPoint, | ||
143 | + endSelectionPoint: | ||
144 | + isReversed ? firstSelectionPoint : secondSelectionPoint, | ||
145 | + selectionRects: <Rect>[selectionRect], | ||
146 | + ); | ||
147 | + } | ||
148 | + } | ||
149 | + | ||
150 | + @override | ||
151 | + SelectionResult dispatchSelectionEvent(SelectionEvent event) { | ||
152 | + SelectionResult result = SelectionResult.none; | ||
153 | + switch (event.type) { | ||
154 | + case SelectionEventType.startEdgeUpdate: | ||
155 | + case SelectionEventType.endEdgeUpdate: | ||
156 | + final Rect renderObjectRect = | ||
157 | + Rect.fromLTWH(0, 0, size.width, size.height); | ||
158 | + // Normalize offset in case it is out side of the rect. | ||
159 | + final Offset point = | ||
160 | + globalToLocal((event as SelectionEdgeUpdateEvent).globalPosition); | ||
161 | + final Offset adjustedPoint = | ||
162 | + SelectionUtils.adjustDragOffset(renderObjectRect, point); | ||
163 | + if (event.type == SelectionEventType.startEdgeUpdate) { | ||
164 | + _start = adjustedPoint; | ||
165 | + } else { | ||
166 | + _end = adjustedPoint; | ||
167 | + } | ||
168 | + result = SelectionUtils.getResultBasedOnRect(renderObjectRect, point); | ||
169 | + case SelectionEventType.clear: | ||
170 | + _start = _end = null; | ||
171 | + case SelectionEventType.selectAll: | ||
172 | + case SelectionEventType.selectWord: | ||
173 | + case SelectionEventType.selectParagraph: | ||
174 | + _start = Offset.zero; | ||
175 | + _end = Offset.infinite; | ||
176 | + case SelectionEventType.granularlyExtendSelection: | ||
177 | + result = SelectionResult.end; | ||
178 | + final GranularlyExtendSelectionEvent extendSelectionEvent = | ||
179 | + event as GranularlyExtendSelectionEvent; | ||
180 | + // Initialize the offset it there is no ongoing selection. | ||
181 | + if (_start == null || _end == null) { | ||
182 | + if (extendSelectionEvent.forward) { | ||
183 | + _start = _end = Offset.zero; | ||
184 | + } else { | ||
185 | + _start = _end = Offset.infinite; | ||
186 | + } | ||
187 | + } | ||
188 | + // Move the corresponding selection edge. | ||
189 | + final Offset newOffset = | ||
190 | + extendSelectionEvent.forward ? Offset.infinite : Offset.zero; | ||
191 | + if (extendSelectionEvent.isEnd) { | ||
192 | + if (newOffset == _end) { | ||
193 | + result = extendSelectionEvent.forward | ||
194 | + ? SelectionResult.next | ||
195 | + : SelectionResult.previous; | ||
196 | + } | ||
197 | + _end = newOffset; | ||
198 | + } else { | ||
199 | + if (newOffset == _start) { | ||
200 | + result = extendSelectionEvent.forward | ||
201 | + ? SelectionResult.next | ||
202 | + : SelectionResult.previous; | ||
203 | + } | ||
204 | + _start = newOffset; | ||
205 | + } | ||
206 | + case SelectionEventType.directionallyExtendSelection: | ||
207 | + result = SelectionResult.end; | ||
208 | + final DirectionallyExtendSelectionEvent extendSelectionEvent = | ||
209 | + event as DirectionallyExtendSelectionEvent; | ||
210 | + // Convert to local coordinates. | ||
211 | + final double horizontalBaseLine = globalToLocal(Offset(event.dx, 0)).dx; | ||
212 | + final Offset newOffset; | ||
213 | + final bool forward; | ||
214 | + switch (extendSelectionEvent.direction) { | ||
215 | + case SelectionExtendDirection.backward: | ||
216 | + case SelectionExtendDirection.previousLine: | ||
217 | + forward = false; | ||
218 | + // Initialize the offset it there is no ongoing selection. | ||
219 | + if (_start == null || _end == null) { | ||
220 | + _start = _end = Offset.infinite; | ||
221 | + } | ||
222 | + // Move the corresponding selection edge. | ||
223 | + if (extendSelectionEvent.direction == | ||
224 | + SelectionExtendDirection.previousLine || | ||
225 | + horizontalBaseLine < 0) { | ||
226 | + newOffset = Offset.zero; | ||
227 | + } else { | ||
228 | + newOffset = Offset.infinite; | ||
229 | + } | ||
230 | + case SelectionExtendDirection.nextLine: | ||
231 | + case SelectionExtendDirection.forward: | ||
232 | + forward = true; | ||
233 | + // Initialize the offset it there is no ongoing selection. | ||
234 | + if (_start == null || _end == null) { | ||
235 | + _start = _end = Offset.zero; | ||
236 | + } | ||
237 | + // Move the corresponding selection edge. | ||
238 | + if (extendSelectionEvent.direction == | ||
239 | + SelectionExtendDirection.nextLine || | ||
240 | + horizontalBaseLine > size.width) { | ||
241 | + newOffset = Offset.infinite; | ||
242 | + } else { | ||
243 | + newOffset = Offset.zero; | ||
244 | + } | ||
245 | + } | ||
246 | + if (extendSelectionEvent.isEnd) { | ||
247 | + if (newOffset == _end) { | ||
248 | + result = forward ? SelectionResult.next : SelectionResult.previous; | ||
249 | + } | ||
250 | + _end = newOffset; | ||
251 | + } else { | ||
252 | + if (newOffset == _start) { | ||
253 | + result = forward ? SelectionResult.next : SelectionResult.previous; | ||
254 | + } | ||
255 | + _start = newOffset; | ||
256 | + } | ||
257 | + } | ||
258 | + _updateGeometry(); | ||
259 | + return result; | ||
260 | + } | ||
261 | + | ||
262 | + // This method is called when users want to copy selected content in this | ||
263 | + // widget into clipboard. | ||
264 | + @override | ||
265 | + SelectedContent? getSelectedContent() { | ||
266 | + return value.hasSelection | ||
267 | + ? SelectedContent(plainText: selectionText) | ||
268 | + : null; | ||
269 | + } | ||
270 | + | ||
271 | + LayerLink? _startHandle; | ||
272 | + LayerLink? _endHandle; | ||
273 | + | ||
274 | + @override | ||
275 | + void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) { | ||
276 | + if (_startHandle == startHandle && _endHandle == endHandle) { | ||
277 | + return; | ||
278 | + } | ||
279 | + _startHandle = startHandle; | ||
280 | + _endHandle = endHandle; | ||
281 | + markNeedsPaint(); | ||
282 | + } | ||
283 | + | ||
284 | + @override | ||
285 | + void paint(PaintingContext context, Offset offset) { | ||
286 | + super.paint(context, offset); | ||
287 | + if (!_geometry.value.hasSelection) { | ||
288 | + return; | ||
289 | + } | ||
290 | + // Draw the selection highlight. | ||
291 | + final Paint selectionPaint = Paint() | ||
292 | + ..style = PaintingStyle.fill | ||
293 | + ..color = _selectionColor; | ||
294 | + context.canvas | ||
295 | + .drawRect(_getSelectionHighlightRect().shift(offset), selectionPaint); | ||
296 | + | ||
297 | + // Push the layer links if any. | ||
298 | + if (_startHandle != null) { | ||
299 | + context.pushLayer( | ||
300 | + LeaderLayer( | ||
301 | + link: _startHandle!, | ||
302 | + offset: offset + value.startSelectionPoint!.localPosition, | ||
303 | + ), | ||
304 | + (PaintingContext context, Offset offset) {}, | ||
305 | + Offset.zero, | ||
306 | + ); | ||
307 | + } | ||
308 | + if (_endHandle != null) { | ||
309 | + context.pushLayer( | ||
310 | + LeaderLayer( | ||
311 | + link: _endHandle!, | ||
312 | + offset: offset + value.endSelectionPoint!.localPosition, | ||
313 | + ), | ||
314 | + (PaintingContext context, Offset offset) {}, | ||
315 | + Offset.zero, | ||
316 | + ); | ||
317 | + } | ||
318 | + } | ||
319 | + | ||
320 | + @override | ||
321 | + void dispose() { | ||
322 | + _geometry.dispose(); | ||
323 | + super.dispose(); | ||
324 | + } | ||
325 | +} |
@@ -4,14 +4,15 @@ import 'package:flutter/material.dart'; | @@ -4,14 +4,15 @@ import 'package:flutter/material.dart'; | ||
4 | import 'package:gpt_markdown/custom_widgets/markdow_config.dart'; | 4 | import 'package:gpt_markdown/custom_widgets/markdow_config.dart'; |
5 | 5 | ||
6 | import 'package:flutter/foundation.dart'; | 6 | import 'package:flutter/foundation.dart'; |
7 | -import 'package:flutter/services.dart'; | ||
8 | import 'package:flutter_math_fork/flutter_math.dart'; | 7 | import 'package:flutter_math_fork/flutter_math.dart'; |
9 | import 'package:gpt_markdown/custom_widgets/custom_divider.dart'; | 8 | import 'package:gpt_markdown/custom_widgets/custom_divider.dart'; |
10 | import 'package:gpt_markdown/custom_widgets/custom_error_image.dart'; | 9 | import 'package:gpt_markdown/custom_widgets/custom_error_image.dart'; |
11 | import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart'; | 10 | import 'package:gpt_markdown/custom_widgets/custom_rb_cb.dart'; |
11 | +import 'package:gpt_markdown/custom_widgets/selectable_adapter.dart'; | ||
12 | import 'package:gpt_markdown/custom_widgets/unordered_ordered_list.dart'; | 12 | 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/link_button.dart'; | 16 | import 'custom_widgets/link_button.dart'; |
16 | 17 | ||
17 | part 'theme.dart'; | 18 | part 'theme.dart'; |
@@ -19,8 +20,8 @@ part 'markdown_component.dart'; | @@ -19,8 +20,8 @@ part 'markdown_component.dart'; | ||
19 | part 'md_widget.dart'; | 20 | part 'md_widget.dart'; |
20 | 21 | ||
21 | /// This widget create a full markdown widget as a column view. | 22 | /// This widget create a full markdown widget as a column view. |
22 | -class TexMarkdown extends StatelessWidget { | ||
23 | - const TexMarkdown( | 23 | +class GptMarkdown extends StatelessWidget { |
24 | + const GptMarkdown( | ||
24 | this.data, { | 25 | this.data, { |
25 | super.key, | 26 | super.key, |
26 | this.style, | 27 | this.style, |
@@ -2,24 +2,24 @@ part of 'gpt_markdown.dart'; | @@ -2,24 +2,24 @@ part of 'gpt_markdown.dart'; | ||
2 | 2 | ||
3 | /// Markdown components | 3 | /// Markdown components |
4 | abstract class MarkdownComponent { | 4 | abstract class MarkdownComponent { |
5 | - static List<MarkdownComponent> components = [ | 5 | + static final List<MarkdownComponent> components = [ |
6 | CodeBlockMd(), | 6 | CodeBlockMd(), |
7 | NewLines(), | 7 | NewLines(), |
8 | TableMd(), | 8 | TableMd(), |
9 | HTag(), | 9 | HTag(), |
10 | - IndentMd(), | ||
11 | UnOrderedList(), | 10 | UnOrderedList(), |
12 | OrderedList(), | 11 | OrderedList(), |
13 | RadioButtonMd(), | 12 | RadioButtonMd(), |
14 | CheckBoxMd(), | 13 | CheckBoxMd(), |
15 | HrLine(), | 14 | HrLine(), |
15 | + IndentMd(), | ||
16 | + LatexMathMultyLine(), | ||
17 | + LatexMath(), | ||
16 | ImageMd(), | 18 | ImageMd(), |
17 | HighlightedText(), | 19 | HighlightedText(), |
18 | StrikeMd(), | 20 | StrikeMd(), |
19 | BoldMd(), | 21 | BoldMd(), |
20 | ItalicMd(), | 22 | ItalicMd(), |
21 | - LatexMathMultyLine(), | ||
22 | - LatexMath(), | ||
23 | ATagMd(), | 23 | ATagMd(), |
24 | SourceTag(), | 24 | SourceTag(), |
25 | ]; | 25 | ]; |
@@ -38,22 +38,19 @@ abstract class MarkdownComponent { | @@ -38,22 +38,19 @@ abstract class MarkdownComponent { | ||
38 | multiLine: true, | 38 | multiLine: true, |
39 | dotAll: true, | 39 | dotAll: true, |
40 | ); | 40 | ); |
41 | - List<String> elements = []; | ||
42 | text.splitMapJoin( | 41 | text.splitMapJoin( |
43 | combinedRegex, | 42 | combinedRegex, |
44 | onMatch: (p0) { | 43 | onMatch: (p0) { |
45 | String element = p0[0] ?? ""; | 44 | String element = p0[0] ?? ""; |
46 | - elements.add(element); | ||
47 | for (var each in components) { | 45 | for (var each in components) { |
48 | if (each.exp.hasMatch(element)) { | 46 | if (each.exp.hasMatch(element)) { |
49 | - if (each is InlineMd) { | 47 | + if (each.inline) { |
50 | spans.add(each.span( | 48 | spans.add(each.span( |
51 | context, | 49 | context, |
52 | element, | 50 | element, |
53 | config, | 51 | config, |
54 | )); | 52 | )); |
55 | } else { | 53 | } else { |
56 | - if (each is BlockMd) { | ||
57 | spans.addAll([ | 54 | spans.addAll([ |
58 | TextSpan( | 55 | TextSpan( |
59 | text: "\n ", | 56 | text: "\n ", |
@@ -78,7 +75,6 @@ abstract class MarkdownComponent { | @@ -78,7 +75,6 @@ abstract class MarkdownComponent { | ||
78 | ), | 75 | ), |
79 | ]); | 76 | ]); |
80 | } | 77 | } |
81 | - } | ||
82 | return ""; | 78 | return ""; |
83 | } | 79 | } |
84 | } | 80 | } |
@@ -125,18 +121,38 @@ abstract class InlineMd extends MarkdownComponent { | @@ -125,18 +121,38 @@ abstract class InlineMd extends MarkdownComponent { | ||
125 | abstract class BlockMd extends MarkdownComponent { | 121 | abstract class BlockMd extends MarkdownComponent { |
126 | @override | 122 | @override |
127 | bool get inline => false; | 123 | bool get inline => false; |
124 | + | ||
125 | + @override | ||
126 | + RegExp get exp => RegExp( | ||
127 | + r'^\ *?' + expString, | ||
128 | + dotAll: true, | ||
129 | + multiLine: true, | ||
130 | + ); | ||
131 | + | ||
132 | + String get expString; | ||
133 | + | ||
128 | @override | 134 | @override |
129 | InlineSpan span( | 135 | InlineSpan span( |
130 | BuildContext context, | 136 | BuildContext context, |
131 | String text, | 137 | String text, |
132 | final GptMarkdownConfig config, | 138 | final GptMarkdownConfig config, |
133 | ) { | 139 | ) { |
134 | - return WidgetSpan( | ||
135 | - child: build( | 140 | + var matches = RegExp(r'^(?<spaces>\ \ +).*').firstMatch(text); |
141 | + var spaces = matches?.namedGroup('spaces'); | ||
142 | + var length = spaces?.length ?? 0; | ||
143 | + var child = build( | ||
136 | context, | 144 | context, |
137 | text, | 145 | text, |
138 | config, | 146 | config, |
139 | - ), | 147 | + ); |
148 | + if (length > 0) { | ||
149 | + child = UnorderedListView( | ||
150 | + spacing: length.toDouble() * 6, | ||
151 | + child: child, | ||
152 | + ); | ||
153 | + } | ||
154 | + return WidgetSpan( | ||
155 | + child: child, | ||
140 | alignment: PlaceholderAlignment.middle, | 156 | alignment: PlaceholderAlignment.middle, |
141 | ); | 157 | ); |
142 | } | 158 | } |
@@ -151,57 +167,44 @@ abstract class BlockMd extends MarkdownComponent { | @@ -151,57 +167,44 @@ abstract class BlockMd extends MarkdownComponent { | ||
151 | /// Heading component | 167 | /// Heading component |
152 | class HTag extends BlockMd { | 168 | class HTag extends BlockMd { |
153 | @override | 169 | @override |
154 | - RegExp get exp => RegExp(r"^(#{1,6})\ ([^\n]+?)$"); | 170 | + String get expString => (r"(?<hash>#{1,6})\ (?<data>[^\n]+?)$"); |
155 | @override | 171 | @override |
156 | Widget build( | 172 | Widget build( |
157 | BuildContext context, | 173 | BuildContext context, |
158 | String text, | 174 | String text, |
159 | final GptMarkdownConfig config, | 175 | final GptMarkdownConfig config, |
160 | ) { | 176 | ) { |
161 | - var match = exp.firstMatch(text.trim()); | 177 | + var theme = GptMarkdownTheme.of(context); |
178 | + var match = this.exp.firstMatch(text.trim()); | ||
162 | var conf = config.copyWith( | 179 | var conf = config.copyWith( |
163 | style: [ | 180 | style: [ |
164 | - Theme.of(context) | ||
165 | - .textTheme | ||
166 | - .headlineLarge | ||
167 | - ?.copyWith(color: config.style?.color), | ||
168 | - Theme.of(context) | ||
169 | - .textTheme | ||
170 | - .headlineMedium | ||
171 | - ?.copyWith(color: config.style?.color), | ||
172 | - Theme.of(context) | ||
173 | - .textTheme | ||
174 | - .headlineSmall | ||
175 | - ?.copyWith(color: config.style?.color), | ||
176 | - Theme.of(context) | ||
177 | - .textTheme | ||
178 | - .titleLarge | ||
179 | - ?.copyWith(color: config.style?.color), | ||
180 | - Theme.of(context) | ||
181 | - .textTheme | ||
182 | - .titleMedium | ||
183 | - ?.copyWith(color: config.style?.color), | ||
184 | - Theme.of(context) | ||
185 | - .textTheme | ||
186 | - .titleSmall | ||
187 | - ?.copyWith(color: config.style?.color), | ||
188 | - ][match![1]!.length - 1]); | 181 | + theme.h1, |
182 | + theme.h2, | ||
183 | + theme.h3, | ||
184 | + theme.h4, | ||
185 | + theme.h5, | ||
186 | + theme.h6, | ||
187 | + ][match![1]!.length - 1] | ||
188 | + ?.copyWith( | ||
189 | + color: config.style?.color, | ||
190 | + ), | ||
191 | + ); | ||
189 | return config.getRich( | 192 | return config.getRich( |
190 | TextSpan( | 193 | TextSpan( |
191 | children: [ | 194 | children: [ |
192 | ...(MarkdownComponent.generate( | 195 | ...(MarkdownComponent.generate( |
193 | context, | 196 | context, |
194 | - "${match[2]}", | 197 | + "${match.namedGroup('data')}", |
195 | conf, | 198 | conf, |
196 | )), | 199 | )), |
197 | - if (match[1]!.length == 1) ...[ | 200 | + if (match.namedGroup('hash')!.length == 1) ...[ |
198 | const TextSpan( | 201 | const TextSpan( |
199 | text: "\n ", | 202 | text: "\n ", |
200 | style: TextStyle(fontSize: 0, height: 0), | 203 | style: TextStyle(fontSize: 0, height: 0), |
201 | ), | 204 | ), |
202 | WidgetSpan( | 205 | WidgetSpan( |
203 | child: CustomDivider( | 206 | child: CustomDivider( |
204 | - height: 2, | 207 | + height: theme.hrLineThickness, |
205 | color: config.style?.color ?? | 208 | color: config.style?.color ?? |
206 | Theme.of(context).colorScheme.outline, | 209 | Theme.of(context).colorScheme.outline, |
207 | ), | 210 | ), |
@@ -235,16 +238,18 @@ class NewLines extends InlineMd { | @@ -235,16 +238,18 @@ class NewLines extends InlineMd { | ||
235 | /// Horizontal line component | 238 | /// Horizontal line component |
236 | class HrLine extends BlockMd { | 239 | class HrLine extends BlockMd { |
237 | @override | 240 | @override |
238 | - RegExp get exp => RegExp(r"^(--)[-]+$"); | 241 | + String get expString => (r"(--)[-]+$"); |
239 | @override | 242 | @override |
240 | Widget build( | 243 | Widget build( |
241 | BuildContext context, | 244 | BuildContext context, |
242 | String text, | 245 | String text, |
243 | final GptMarkdownConfig config, | 246 | final GptMarkdownConfig config, |
244 | ) { | 247 | ) { |
248 | + var thickness = GptMarkdownTheme.of(context).hrLineThickness; | ||
249 | + var color = GptMarkdownTheme.of(context).hrLineColor; | ||
245 | return CustomDivider( | 250 | return CustomDivider( |
246 | - height: 2, | ||
247 | - color: config.style?.color ?? Theme.of(context).colorScheme.outline, | 251 | + height: thickness, |
252 | + color: config.style?.color ?? color, | ||
248 | ); | 253 | ); |
249 | } | 254 | } |
250 | } | 255 | } |
@@ -252,7 +257,7 @@ class HrLine extends BlockMd { | @@ -252,7 +257,7 @@ class HrLine extends BlockMd { | ||
252 | /// Checkbox component | 257 | /// Checkbox component |
253 | class CheckBoxMd extends BlockMd { | 258 | class CheckBoxMd extends BlockMd { |
254 | @override | 259 | @override |
255 | - RegExp get exp => RegExp(r"^\[(\x?)\]\ (\S[^\n]*?)$"); | 260 | + String get expString => (r"\[(\x?)\]\ (\S[^\n]*?)$"); |
256 | get onLinkTab => null; | 261 | get onLinkTab => null; |
257 | 262 | ||
258 | @override | 263 | @override |
@@ -261,7 +266,7 @@ class CheckBoxMd extends BlockMd { | @@ -261,7 +266,7 @@ class CheckBoxMd extends BlockMd { | ||
261 | String text, | 266 | String text, |
262 | final GptMarkdownConfig config, | 267 | final GptMarkdownConfig config, |
263 | ) { | 268 | ) { |
264 | - var match = exp.firstMatch(text.trim()); | 269 | + var match = this.exp.firstMatch(text.trim()); |
265 | return CustomCb( | 270 | return CustomCb( |
266 | value: ("${match?[1]}" == "x"), | 271 | value: ("${match?[1]}" == "x"), |
267 | textDirection: config.textDirection, | 272 | textDirection: config.textDirection, |
@@ -276,7 +281,7 @@ class CheckBoxMd extends BlockMd { | @@ -276,7 +281,7 @@ class CheckBoxMd extends BlockMd { | ||
276 | /// Radio Button component | 281 | /// Radio Button component |
277 | class RadioButtonMd extends BlockMd { | 282 | class RadioButtonMd extends BlockMd { |
278 | @override | 283 | @override |
279 | - RegExp get exp => RegExp(r"^\((\x?)\)\ (\S[^\n]*)$"); | 284 | + String get expString => (r"\((\x?)\)\ (\S[^\n]*)$"); |
280 | get onLinkTab => null; | 285 | get onLinkTab => null; |
281 | 286 | ||
282 | @override | 287 | @override |
@@ -285,7 +290,7 @@ class RadioButtonMd extends BlockMd { | @@ -285,7 +290,7 @@ class RadioButtonMd extends BlockMd { | ||
285 | String text, | 290 | String text, |
286 | final GptMarkdownConfig config, | 291 | final GptMarkdownConfig config, |
287 | ) { | 292 | ) { |
288 | - var match = exp.firstMatch(text.trim()); | 293 | + var match = this.exp.firstMatch(text.trim()); |
289 | return CustomRb( | 294 | return CustomRb( |
290 | value: ("${match?[1]}" == "x"), | 295 | value: ("${match?[1]}" == "x"), |
291 | textDirection: config.textDirection, | 296 | textDirection: config.textDirection, |
@@ -298,36 +303,45 @@ class RadioButtonMd extends BlockMd { | @@ -298,36 +303,45 @@ class RadioButtonMd extends BlockMd { | ||
298 | } | 303 | } |
299 | 304 | ||
300 | /// Indent | 305 | /// Indent |
301 | -class IndentMd extends BlockMd { | 306 | +class IndentMd extends InlineMd { |
302 | @override | 307 | @override |
303 | - RegExp get exp => RegExp(r"^(\ \ \ \ +)([^\n]+)$"); | ||
304 | - get onLinkTab => null; | 308 | + RegExp get exp => |
309 | + RegExp(r"^(\ +)(((?!\n\n).)+)$", dotAll: true, multiLine: true); | ||
305 | 310 | ||
306 | @override | 311 | @override |
307 | - Widget build( | 312 | + InlineSpan span( |
308 | BuildContext context, | 313 | BuildContext context, |
309 | String text, | 314 | String text, |
310 | final GptMarkdownConfig config, | 315 | final GptMarkdownConfig config, |
311 | ) { | 316 | ) { |
312 | - [ | ||
313 | - r"\\\[(.*?)\\\]", | ||
314 | - r"\\\((.*?)\\\)", | ||
315 | - r"(?<!\\)\$((?:\\.|[^$])*?)\$(?!\\)", | ||
316 | - ].join("|"); | ||
317 | var match = exp.firstMatch(text); | 317 | var match = exp.firstMatch(text); |
318 | int spaces = (match?[1] ?? "").length; | 318 | int spaces = (match?[1] ?? "").length; |
319 | - return UnorderedListView( | 319 | + var data = "${match?[2]}".trim(); |
320 | + data.replaceAll(RegExp(r'\n\ *'), '\n').trim(); | ||
321 | + var child = TextSpan( | ||
322 | + children: MarkdownComponent.generate( | ||
323 | + context, | ||
324 | + data, | ||
325 | + config, | ||
326 | + ), | ||
327 | + ); | ||
328 | + if (spaces < 4) { | ||
329 | + return child; | ||
330 | + } | ||
331 | + return TextSpan( | ||
332 | + children: [ | ||
333 | + const TextSpan(text: '\n'), | ||
334 | + WidgetSpan( | ||
335 | + child: UnorderedListView( | ||
320 | bulletColor: config.style?.color, | 336 | bulletColor: config.style?.color, |
321 | padding: spaces * 5, | 337 | padding: spaces * 5, |
322 | bulletSize: 0, | 338 | bulletSize: 0, |
323 | textDirection: config.textDirection, | 339 | textDirection: config.textDirection, |
324 | - child: Text.rich(TextSpan( | ||
325 | - children: MarkdownComponent.generate( | ||
326 | - context, | ||
327 | - "${match?[2]}", | ||
328 | - config, | 340 | + child: Text.rich(child), |
329 | ), | 341 | ), |
330 | - )), | 342 | + ), |
343 | + const TextSpan(text: '\n'), | ||
344 | + ], | ||
331 | ); | 345 | ); |
332 | } | 346 | } |
333 | } | 347 | } |
@@ -335,7 +349,7 @@ class IndentMd extends BlockMd { | @@ -335,7 +349,7 @@ class IndentMd extends BlockMd { | ||
335 | /// Unordered list component | 349 | /// Unordered list component |
336 | class UnOrderedList extends BlockMd { | 350 | class UnOrderedList extends BlockMd { |
337 | @override | 351 | @override |
338 | - RegExp get exp => RegExp(r"^(?:\-|\*)\ ([^\n]+)$"); | 352 | + String get expString => (r"(?:\-|\*)\ ([^\n]+)$"); |
339 | get onLinkTab => null; | 353 | get onLinkTab => null; |
340 | 354 | ||
341 | @override | 355 | @override |
@@ -344,7 +358,7 @@ class UnOrderedList extends BlockMd { | @@ -344,7 +358,7 @@ class UnOrderedList extends BlockMd { | ||
344 | String text, | 358 | String text, |
345 | final GptMarkdownConfig config, | 359 | final GptMarkdownConfig config, |
346 | ) { | 360 | ) { |
347 | - var match = exp.firstMatch(text); | 361 | + var match = this.exp.firstMatch(text); |
348 | return UnorderedListView( | 362 | return UnorderedListView( |
349 | bulletColor: | 363 | bulletColor: |
350 | config.style?.color ?? DefaultTextStyle.of(context).style.color, | 364 | config.style?.color ?? DefaultTextStyle.of(context).style.color, |
@@ -365,7 +379,7 @@ class UnOrderedList extends BlockMd { | @@ -365,7 +379,7 @@ class UnOrderedList extends BlockMd { | ||
365 | /// Ordered list component | 379 | /// Ordered list component |
366 | class OrderedList extends BlockMd { | 380 | class OrderedList extends BlockMd { |
367 | @override | 381 | @override |
368 | - RegExp get exp => RegExp(r"^([0-9]+\.)\ ([^\n]+)$"); | 382 | + String get expString => (r"([0-9]+\.)\ ([^\n]+)$"); |
369 | 383 | ||
370 | get onLinkTab => null; | 384 | get onLinkTab => null; |
371 | 385 | ||
@@ -375,7 +389,7 @@ class OrderedList extends BlockMd { | @@ -375,7 +389,7 @@ class OrderedList extends BlockMd { | ||
375 | String text, | 389 | String text, |
376 | final GptMarkdownConfig config, | 390 | final GptMarkdownConfig config, |
377 | ) { | 391 | ) { |
378 | - var match = exp.firstMatch(text.trim()); | 392 | + var match = this.exp.firstMatch(text.trim()); |
379 | return OrderedListView( | 393 | return OrderedListView( |
380 | no: "${match?[1]}", | 394 | no: "${match?[1]}", |
381 | textDirection: config.textDirection, | 395 | textDirection: config.textDirection, |
@@ -508,10 +522,9 @@ class ItalicMd extends InlineMd { | @@ -508,10 +522,9 @@ class ItalicMd extends InlineMd { | ||
508 | 522 | ||
509 | class LatexMathMultyLine extends BlockMd { | 523 | class LatexMathMultyLine extends BlockMd { |
510 | @override | 524 | @override |
511 | - RegExp get exp => RegExp( | ||
512 | - r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})", | ||
513 | - dotAll: true, | ||
514 | - ); | 525 | + String get expString => (r"\\\[(((?!\n\n).)*)\\\]|(\\begin.*?\\end{.*?})"); |
526 | + @override | ||
527 | + RegExp get exp => RegExp(expString, dotAll: true, multiLine: true); | ||
515 | 528 | ||
516 | @override | 529 | @override |
517 | Widget build( | 530 | Widget build( |
@@ -520,13 +533,14 @@ class LatexMathMultyLine extends BlockMd { | @@ -520,13 +533,14 @@ class LatexMathMultyLine extends BlockMd { | ||
520 | final GptMarkdownConfig config, | 533 | final GptMarkdownConfig config, |
521 | ) { | 534 | ) { |
522 | var p0 = exp.firstMatch(text.trim()); | 535 | var p0 = exp.firstMatch(text.trim()); |
523 | - p0?.group(0); | ||
524 | String mathText = p0?[1] ?? p0?[2] ?? ""; | 536 | String mathText = p0?[1] ?? p0?[2] ?? ""; |
525 | var workaround = config.latexWorkaround ?? (String tex) => tex; | 537 | var workaround = config.latexWorkaround ?? (String tex) => tex; |
526 | 538 | ||
527 | var builder = config.latexBuilder ?? | 539 | var builder = config.latexBuilder ?? |
528 | (BuildContext context, String tex, TextStyle textStyle, bool inline) => | 540 | (BuildContext context, String tex, TextStyle textStyle, bool inline) => |
529 | - Math.tex( | 541 | + SelectableAdapter( |
542 | + selectedText: tex, | ||
543 | + child: Math.tex( | ||
530 | tex, | 544 | tex, |
531 | textStyle: textStyle, | 545 | textStyle: textStyle, |
532 | mathStyle: MathStyle.display, | 546 | mathStyle: MathStyle.display, |
@@ -562,6 +576,7 @@ class LatexMathMultyLine extends BlockMd { | @@ -562,6 +576,7 @@ class LatexMathMultyLine extends BlockMd { | ||
562 | : Theme.of(context).colorScheme.error), | 576 | : Theme.of(context).colorScheme.error), |
563 | ); | 577 | ); |
564 | }, | 578 | }, |
579 | + ), | ||
565 | ); | 580 | ); |
566 | return builder(context, workaround(mathText), | 581 | return builder(context, workaround(mathText), |
567 | config.style ?? const TextStyle(), false); | 582 | config.style ?? const TextStyle(), false); |
@@ -591,7 +606,9 @@ class LatexMath extends InlineMd { | @@ -591,7 +606,9 @@ class LatexMath extends InlineMd { | ||
591 | var workaround = config.latexWorkaround ?? (String tex) => tex; | 606 | var workaround = config.latexWorkaround ?? (String tex) => tex; |
592 | var builder = config.latexBuilder ?? | 607 | var builder = config.latexBuilder ?? |
593 | (BuildContext context, String tex, TextStyle textStyle, bool inline) => | 608 | (BuildContext context, String tex, TextStyle textStyle, bool inline) => |
594 | - Math.tex( | 609 | + SelectableAdapter( |
610 | + selectedText: tex, | ||
611 | + child: Math.tex( | ||
595 | tex, | 612 | tex, |
596 | textStyle: textStyle, | 613 | textStyle: textStyle, |
597 | mathStyle: MathStyle.display, | 614 | mathStyle: MathStyle.display, |
@@ -627,6 +644,7 @@ class LatexMath extends InlineMd { | @@ -627,6 +644,7 @@ class LatexMath extends InlineMd { | ||
627 | : Theme.of(context).colorScheme.error), | 644 | : Theme.of(context).colorScheme.error), |
628 | ); | 645 | ); |
629 | }, | 646 | }, |
647 | + ), | ||
630 | ); | 648 | ); |
631 | return WidgetSpan( | 649 | return WidgetSpan( |
632 | alignment: PlaceholderAlignment.baseline, | 650 | alignment: PlaceholderAlignment.baseline, |
@@ -655,7 +673,6 @@ class SourceTag extends InlineMd { | @@ -655,7 +673,6 @@ class SourceTag extends InlineMd { | ||
655 | } | 673 | } |
656 | return WidgetSpan( | 674 | return WidgetSpan( |
657 | alignment: PlaceholderAlignment.middle, | 675 | alignment: PlaceholderAlignment.middle, |
658 | - // baseline: TextBaseline.alphabetic, | ||
659 | child: Padding( | 676 | child: Padding( |
660 | padding: const EdgeInsets.all(2), | 677 | padding: const EdgeInsets.all(2), |
661 | child: config.sourceTagBuilder | 678 | child: config.sourceTagBuilder |
@@ -696,8 +713,11 @@ class ATagMd extends InlineMd { | @@ -696,8 +713,11 @@ class ATagMd extends InlineMd { | ||
696 | if (match?[1] == null && match?[2] == null) { | 713 | if (match?[1] == null && match?[2] == null) { |
697 | return const TextSpan(); | 714 | return const TextSpan(); |
698 | } | 715 | } |
716 | + var theme = GptMarkdownTheme.of(context); | ||
699 | return WidgetSpan( | 717 | return WidgetSpan( |
700 | child: LinkButton( | 718 | child: LinkButton( |
719 | + hoverColor: theme.linkHoverColor, | ||
720 | + color: theme.linkColor, | ||
701 | onPressed: () { | 721 | onPressed: () { |
702 | config.onLinkTab?.call("${match?[2]}", "${match?[1]}"); | 722 | config.onLinkTab?.call("${match?[2]}", "${match?[1]}"); |
703 | }, | 723 | }, |
@@ -762,6 +782,9 @@ class ImageMd extends InlineMd { | @@ -762,6 +782,9 @@ class ImageMd extends InlineMd { | ||
762 | /// Table component | 782 | /// Table component |
763 | class TableMd extends BlockMd { | 783 | class TableMd extends BlockMd { |
764 | @override | 784 | @override |
785 | + String get expString => | ||
786 | + (r"(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$"); | ||
787 | + @override | ||
765 | Widget build( | 788 | Widget build( |
766 | BuildContext context, | 789 | BuildContext context, |
767 | String text, | 790 | String text, |
@@ -847,28 +870,19 @@ class TableMd extends BlockMd { | @@ -847,28 +870,19 @@ class TableMd extends BlockMd { | ||
847 | ), | 870 | ), |
848 | ); | 871 | ); |
849 | } | 872 | } |
850 | - | ||
851 | - @override | ||
852 | - RegExp get exp => RegExp( | ||
853 | - r"^(((\|[^\n\|]+\|)((([^\n\|]+\|)+)?))(\n(((\|[^\n\|]+\|)(([^\n\|]+\|)+)?)))+)$", | ||
854 | - ); | ||
855 | } | 873 | } |
856 | 874 | ||
857 | class CodeBlockMd extends BlockMd { | 875 | class CodeBlockMd extends BlockMd { |
858 | @override | 876 | @override |
859 | - RegExp get exp => RegExp( | ||
860 | - r"\s*?```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$", | ||
861 | - multiLine: true, | ||
862 | - dotAll: true, | ||
863 | - ); | 877 | + String get expString => r"```(.*?)\n((.*?)(:?\n\s*?```)|(.*)(:?\n```)?)$"; |
864 | @override | 878 | @override |
865 | Widget build( | 879 | Widget build( |
866 | BuildContext context, | 880 | BuildContext context, |
867 | String text, | 881 | String text, |
868 | final GptMarkdownConfig config, | 882 | final GptMarkdownConfig config, |
869 | ) { | 883 | ) { |
870 | - String codes = exp.firstMatch(text)?[2] ?? ""; | ||
871 | - String name = exp.firstMatch(text)?[1] ?? ""; | 884 | + String codes = this.exp.firstMatch(text)?[2] ?? ""; |
885 | + String name = this.exp.firstMatch(text)?[1] ?? ""; | ||
872 | codes = codes.replaceAll(r"```", "").trim(); | 886 | codes = codes.replaceAll(r"```", "").trim(); |
873 | return Padding( | 887 | return Padding( |
874 | padding: const EdgeInsets.all(16.0), | 888 | padding: const EdgeInsets.all(16.0), |
@@ -878,75 +892,3 @@ class CodeBlockMd extends BlockMd { | @@ -878,75 +892,3 @@ class CodeBlockMd extends BlockMd { | ||
878 | ); | 892 | ); |
879 | } | 893 | } |
880 | } | 894 | } |
881 | - | ||
882 | -class CodeField extends StatefulWidget { | ||
883 | - const CodeField({super.key, required this.name, required this.codes}); | ||
884 | - final String name; | ||
885 | - final String codes; | ||
886 | - | ||
887 | - @override | ||
888 | - State<CodeField> createState() => _CodeFieldState(); | ||
889 | -} | ||
890 | - | ||
891 | -class _CodeFieldState extends State<CodeField> { | ||
892 | - bool _copied = false; | ||
893 | - @override | ||
894 | - Widget build(BuildContext context) { | ||
895 | - return Material( | ||
896 | - color: Theme.of(context).colorScheme.onInverseSurface, | ||
897 | - shape: RoundedRectangleBorder( | ||
898 | - borderRadius: BorderRadius.circular(8), | ||
899 | - ), | ||
900 | - child: Column( | ||
901 | - crossAxisAlignment: CrossAxisAlignment.stretch, | ||
902 | - children: [ | ||
903 | - Row( | ||
904 | - children: [ | ||
905 | - Padding( | ||
906 | - padding: | ||
907 | - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), | ||
908 | - child: Text(widget.name), | ||
909 | - ), | ||
910 | - const Spacer(), | ||
911 | - TextButton.icon( | ||
912 | - style: TextButton.styleFrom( | ||
913 | - foregroundColor: Theme.of(context).colorScheme.onSurface, | ||
914 | - textStyle: const TextStyle( | ||
915 | - fontWeight: FontWeight.normal, | ||
916 | - ), | ||
917 | - ), | ||
918 | - onPressed: () async { | ||
919 | - await Clipboard.setData(ClipboardData(text: widget.codes)) | ||
920 | - .then((value) { | ||
921 | - setState(() { | ||
922 | - _copied = true; | ||
923 | - }); | ||
924 | - }); | ||
925 | - await Future.delayed(const Duration(seconds: 2)); | ||
926 | - setState(() { | ||
927 | - _copied = false; | ||
928 | - }); | ||
929 | - }, | ||
930 | - icon: Icon( | ||
931 | - (_copied) ? Icons.done : Icons.content_paste, | ||
932 | - size: 15, | ||
933 | - ), | ||
934 | - label: Text((_copied) ? "Copied!" : "Copy code"), | ||
935 | - ), | ||
936 | - ], | ||
937 | - ), | ||
938 | - const Divider( | ||
939 | - height: 1, | ||
940 | - ), | ||
941 | - SingleChildScrollView( | ||
942 | - scrollDirection: Axis.horizontal, | ||
943 | - padding: const EdgeInsets.all(16), | ||
944 | - child: Text( | ||
945 | - widget.codes, | ||
946 | - ), | ||
947 | - ), | ||
948 | - ], | ||
949 | - ), | ||
950 | - ); | ||
951 | - } | ||
952 | -} |
@@ -16,16 +16,17 @@ class MdWidget extends StatelessWidget { | @@ -16,16 +16,17 @@ class MdWidget extends StatelessWidget { | ||
16 | list.addAll( | 16 | list.addAll( |
17 | MarkdownComponent.generate( | 17 | MarkdownComponent.generate( |
18 | context, | 18 | context, |
19 | - exp.replaceAllMapped( | ||
20 | - RegExp( | ||
21 | - r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})", | ||
22 | - multiLine: true, | ||
23 | - dotAll: true, | ||
24 | - ), (match) { | ||
25 | - // | ||
26 | - String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? ""; | ||
27 | - return "\\[$body\\]"; | ||
28 | - }), | 19 | + exp, |
20 | + // .replaceAllMapped( | ||
21 | + // RegExp( | ||
22 | + // r"\\\[(.*?)\\\]|(\\begin.*?\\end{.*?})", | ||
23 | + // multiLine: true, | ||
24 | + // dotAll: true, | ||
25 | + // ), (match) { | ||
26 | + // // | ||
27 | + // String body = (match[1] ?? match[2])?.replaceAll("\n", " ") ?? ""; | ||
28 | + // return "\\[$body\\]"; | ||
29 | + // }), | ||
29 | config, | 30 | config, |
30 | ), | 31 | ), |
31 | ); | 32 | ); |
1 | part of 'gpt_markdown.dart'; | 1 | part of 'gpt_markdown.dart'; |
2 | 2 | ||
3 | -/// Theme defined for `TexMarkdown` widget | 3 | +/// Theme defined for `GptMarkdown` widget |
4 | class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> { | 4 | class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> { |
5 | - GptMarkdownThemeData({ | 5 | + GptMarkdownThemeData._({ |
6 | required this.highlightColor, | 6 | required this.highlightColor, |
7 | + required this.h1, | ||
8 | + required this.h2, | ||
9 | + required this.h3, | ||
10 | + required this.h4, | ||
11 | + required this.h5, | ||
12 | + required this.h6, | ||
13 | + required this.hrLineThickness, | ||
14 | + required this.hrLineColor, | ||
15 | + required this.linkColor, | ||
16 | + required this.linkHoverColor, | ||
7 | }); | 17 | }); |
8 | 18 | ||
9 | - /// Define default attributes. | ||
10 | - factory GptMarkdownThemeData.from(BuildContext context) { | ||
11 | - return GptMarkdownThemeData( | ||
12 | - highlightColor: | ||
13 | - Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(50), | 19 | + factory GptMarkdownThemeData({ |
20 | + required Brightness brightness, | ||
21 | + Color? highlightColor, | ||
22 | + TextStyle? h1, | ||
23 | + TextStyle? h2, | ||
24 | + TextStyle? h3, | ||
25 | + TextStyle? h4, | ||
26 | + TextStyle? h5, | ||
27 | + TextStyle? h6, | ||
28 | + double? hrLineThickness, | ||
29 | + Color? hrLineColor, | ||
30 | + Color? linkColor, | ||
31 | + Color? linkHoverColor, | ||
32 | + }) { | ||
33 | + ThemeData themeData = switch (brightness) { | ||
34 | + Brightness.light => ThemeData.light(), | ||
35 | + Brightness.dark => ThemeData.dark(), | ||
36 | + }; | ||
37 | + final typography = Typography.tall2021.copyWith( | ||
38 | + displayLarge: Typography.tall2021.displayLarge?.copyWith(inherit: true), | ||
39 | + displayMedium: Typography.tall2021.displayMedium?.copyWith(inherit: true), | ||
40 | + displaySmall: Typography.tall2021.displaySmall?.copyWith(inherit: true), | ||
41 | + headlineLarge: Typography.tall2021.headlineLarge?.copyWith(inherit: true), | ||
42 | + headlineMedium: | ||
43 | + Typography.tall2021.headlineMedium?.copyWith(inherit: true), | ||
44 | + headlineSmall: Typography.tall2021.headlineSmall?.copyWith(inherit: true), | ||
45 | + titleLarge: Typography.tall2021.titleLarge?.copyWith(inherit: true), | ||
46 | + titleMedium: Typography.tall2021.titleMedium?.copyWith(inherit: true), | ||
47 | + titleSmall: Typography.tall2021.titleSmall?.copyWith(inherit: true), | ||
48 | + bodyLarge: Typography.tall2021.bodyLarge?.copyWith(inherit: true), | ||
49 | + bodyMedium: Typography.tall2021.bodyMedium?.copyWith(inherit: true), | ||
50 | + bodySmall: Typography.tall2021.bodySmall?.copyWith(inherit: true), | ||
51 | + labelLarge: Typography.tall2021.labelLarge?.copyWith(inherit: true), | ||
52 | + labelMedium: Typography.tall2021.labelMedium?.copyWith(inherit: true), | ||
53 | + labelSmall: Typography.tall2021.labelSmall?.copyWith(inherit: true), | ||
54 | + ); | ||
55 | + themeData = themeData.copyWith( | ||
56 | + textTheme: typography, | ||
57 | + ); | ||
58 | + TextTheme textTheme = themeData.textTheme; | ||
59 | + return GptMarkdownThemeData._fromTheme(themeData, textTheme).copyWith( | ||
60 | + highlightColor: highlightColor, | ||
61 | + h1: h1, | ||
62 | + h2: h2, | ||
63 | + h3: h3, | ||
64 | + h4: h4, | ||
65 | + h5: h5, | ||
66 | + h6: h6, | ||
67 | + hrLineThickness: hrLineThickness, | ||
68 | + hrLineColor: hrLineColor, | ||
69 | + linkColor: linkColor, | ||
70 | + linkHoverColor: linkHoverColor, | ||
71 | + ); | ||
72 | + } | ||
73 | + | ||
74 | + factory GptMarkdownThemeData._fromTheme( | ||
75 | + ThemeData theme, TextTheme textTheme) { | ||
76 | + return GptMarkdownThemeData._( | ||
77 | + highlightColor: theme.colorScheme.onSurfaceVariant.withAlpha(50), | ||
78 | + h1: textTheme.headlineLarge, | ||
79 | + h2: textTheme.headlineMedium, | ||
80 | + h3: textTheme.headlineSmall, | ||
81 | + h4: textTheme.titleLarge, | ||
82 | + h5: textTheme.titleMedium, | ||
83 | + h6: textTheme.titleSmall, | ||
84 | + hrLineThickness: 1, | ||
85 | + hrLineColor: theme.colorScheme.outline, | ||
86 | + linkColor: Colors.blue, | ||
87 | + linkHoverColor: Colors.red, | ||
14 | ); | 88 | ); |
15 | } | 89 | } |
16 | 90 | ||
17 | Color highlightColor; | 91 | Color highlightColor; |
92 | + TextStyle? h1; | ||
93 | + TextStyle? h2; | ||
94 | + TextStyle? h3; | ||
95 | + TextStyle? h4; | ||
96 | + TextStyle? h5; | ||
97 | + TextStyle? h6; | ||
98 | + double hrLineThickness; | ||
99 | + Color hrLineColor; | ||
100 | + Color linkColor; | ||
101 | + Color linkHoverColor; | ||
18 | 102 | ||
103 | + /// Define default attributes. | ||
19 | @override | 104 | @override |
20 | - GptMarkdownThemeData copyWith({Color? highlightColor}) { | ||
21 | - return GptMarkdownThemeData( | 105 | + GptMarkdownThemeData copyWith({ |
106 | + Color? highlightColor, | ||
107 | + TextStyle? h1, | ||
108 | + TextStyle? h2, | ||
109 | + TextStyle? h3, | ||
110 | + TextStyle? h4, | ||
111 | + TextStyle? h5, | ||
112 | + TextStyle? h6, | ||
113 | + double? hrLineThickness, | ||
114 | + Color? hrLineColor, | ||
115 | + Color? linkColor, | ||
116 | + Color? linkHoverColor, | ||
117 | + }) { | ||
118 | + return GptMarkdownThemeData._( | ||
22 | highlightColor: highlightColor ?? this.highlightColor, | 119 | highlightColor: highlightColor ?? this.highlightColor, |
120 | + h1: h1 ?? this.h1, | ||
121 | + h2: h2 ?? this.h2, | ||
122 | + h3: h3 ?? this.h3, | ||
123 | + h4: h4 ?? this.h4, | ||
124 | + h5: h5 ?? this.h5, | ||
125 | + h6: h6 ?? this.h6, | ||
126 | + hrLineThickness: hrLineThickness ?? this.hrLineThickness, | ||
127 | + hrLineColor: hrLineColor ?? this.hrLineColor, | ||
128 | + linkColor: linkColor ?? this.linkColor, | ||
129 | + linkHoverColor: linkHoverColor ?? this.linkHoverColor, | ||
23 | ); | 130 | ); |
24 | } | 131 | } |
25 | 132 | ||
@@ -28,9 +135,21 @@ class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> { | @@ -28,9 +135,21 @@ class GptMarkdownThemeData extends ThemeExtension<GptMarkdownThemeData> { | ||
28 | if (other == null) { | 135 | if (other == null) { |
29 | return this; | 136 | return this; |
30 | } | 137 | } |
31 | - return GptMarkdownThemeData( | 138 | + return GptMarkdownThemeData._( |
32 | highlightColor: | 139 | highlightColor: |
33 | Color.lerp(highlightColor, other.highlightColor, t) ?? highlightColor, | 140 | Color.lerp(highlightColor, other.highlightColor, t) ?? highlightColor, |
141 | + h1: TextStyle.lerp(h1, other.h1, t) ?? h1, | ||
142 | + h2: TextStyle.lerp(h2, other.h2, t) ?? h2, | ||
143 | + h3: TextStyle.lerp(h3, other.h3, t) ?? h3, | ||
144 | + h4: TextStyle.lerp(h4, other.h4, t) ?? h4, | ||
145 | + h5: TextStyle.lerp(h5, other.h5, t) ?? h5, | ||
146 | + h6: TextStyle.lerp(h6, other.h6, t) ?? h6, | ||
147 | + hrLineThickness: Tween(begin: hrLineThickness, end: other.hrLineThickness) | ||
148 | + .transform(t), | ||
149 | + hrLineColor: Color.lerp(hrLineColor, other.hrLineColor, t) ?? hrLineColor, | ||
150 | + linkColor: Color.lerp(linkColor, other.linkColor, t) ?? linkColor, | ||
151 | + linkHoverColor: | ||
152 | + Color.lerp(linkHoverColor, other.linkHoverColor, t) ?? linkHoverColor, | ||
34 | ); | 153 | ); |
35 | } | 154 | } |
36 | } | 155 | } |
@@ -45,16 +164,17 @@ class GptMarkdownTheme extends InheritedWidget { | @@ -45,16 +164,17 @@ class GptMarkdownTheme extends InheritedWidget { | ||
45 | final GptMarkdownThemeData gptThemeData; | 164 | final GptMarkdownThemeData gptThemeData; |
46 | 165 | ||
47 | static GptMarkdownThemeData of(BuildContext context) { | 166 | static GptMarkdownThemeData of(BuildContext context) { |
167 | + var theme = Theme.of(context); | ||
48 | final provider = | 168 | final provider = |
49 | context.dependOnInheritedWidgetOfExactType<GptMarkdownTheme>(); | 169 | context.dependOnInheritedWidgetOfExactType<GptMarkdownTheme>(); |
50 | if (provider != null) { | 170 | if (provider != null) { |
51 | return provider.gptThemeData; | 171 | return provider.gptThemeData; |
52 | } | 172 | } |
53 | - final themeData = Theme.of(context).extension<GptMarkdownThemeData>(); | 173 | + final themeData = theme.extension<GptMarkdownThemeData>(); |
54 | if (themeData != null) { | 174 | if (themeData != null) { |
55 | return themeData; | 175 | return themeData; |
56 | } | 176 | } |
57 | - return GptMarkdownThemeData.from(context); | 177 | + return GptMarkdownThemeData._fromTheme(theme, theme.textTheme); |
58 | } | 178 | } |
59 | 179 | ||
60 | @override | 180 | @override |
1 | name: gpt_markdown | 1 | name: gpt_markdown |
2 | -description: "The purpose of this package is to render the response of ChatGPT into a Flutter app." | ||
3 | -version: 0.1.14 | 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.0 | ||
4 | homepage: https://github.com/Infinitix-LLC/gpt_markdown | 4 | homepage: https://github.com/Infinitix-LLC/gpt_markdown |
5 | 5 | ||
6 | environment: | 6 | environment: |
@@ -10,46 +10,21 @@ environment: | @@ -10,46 +10,21 @@ environment: | ||
10 | dependencies: | 10 | dependencies: |
11 | flutter: | 11 | flutter: |
12 | sdk: flutter | 12 | sdk: flutter |
13 | - flutter_math_fork: ^0.7.2 | 13 | + flutter_math_fork: ^0.7.3 |
14 | 14 | ||
15 | dev_dependencies: | 15 | dev_dependencies: |
16 | flutter_test: | 16 | flutter_test: |
17 | sdk: flutter | 17 | sdk: flutter |
18 | flutter_lints: ^4.0.0 | 18 | flutter_lints: ^4.0.0 |
19 | 19 | ||
20 | -# For information on the generic Dart part of this file, see the | ||
21 | -# following page: https://dart.dev/tools/pub/pubspec | ||
22 | - | ||
23 | -# The following section is specific to Flutter packages. | ||
24 | flutter: | 20 | flutter: |
25 | 21 | ||
26 | - # To add assets to your package, add an assets section, like this: | ||
27 | - # assets: | ||
28 | - # - images/a_dot_burr.jpeg | ||
29 | - # - images/a_dot_ham.jpeg | ||
30 | - # | ||
31 | - # For details regarding assets in packages, see | ||
32 | - # https://flutter.dev/assets-and-images/#from-packages | ||
33 | - # | ||
34 | - # An image asset can refer to one or more resolution-specific "variants", see | ||
35 | - # https://flutter.dev/assets-and-images/#resolution-aware | ||
36 | - | ||
37 | - # To add custom fonts to your package, add a fonts section here, | ||
38 | - # in this "flutter" section. Each entry in this list should have a | ||
39 | - # "family" key with the font family name, and a "fonts" key with a | ||
40 | - # list giving the asset and other descriptors for the font. For | ||
41 | - # example: | ||
42 | - # fonts: | ||
43 | - # - family: Schyler | ||
44 | - # fonts: | ||
45 | - # - asset: fonts/Schyler-Regular.ttf | ||
46 | - # - asset: fonts/Schyler-Italic.ttf | ||
47 | - # style: italic | ||
48 | - # - family: Trajan Pro | ||
49 | - # fonts: | ||
50 | - # - asset: fonts/TrajanPro.ttf | ||
51 | - # - asset: fonts/TrajanPro_Bold.ttf | ||
52 | - # weight: 700 | ||
53 | - # | ||
54 | - # For details regarding fonts in packages, see | ||
55 | - # https://flutter.dev/custom-fonts/#from-packages | 22 | +topics: |
23 | + - markdown | ||
24 | + - latex | ||
25 | + - selectable | ||
26 | + - math | ||
27 | + - chatgpt | ||
28 | + - gemini | ||
29 | + - ai | ||
30 | + - gpt |
-
Please register or login to post a comment