diff --git a/lib/page/component/chat/markdown.dart b/lib/page/component/chat/markdown.dart index dcccc96d..73d0272b 100644 --- a/lib/page/component/chat/markdown.dart +++ b/lib/page/component/chat/markdown.dart @@ -1,14 +1,18 @@ import 'dart:convert'; import 'package:askaide/helper/platform.dart'; +import 'package:askaide/page/component/chat/markdown/code.dart'; import 'package:askaide/page/component/chat/markdown/latex.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:flutter_highlight/themes/default.dart'; import 'package:flutter_highlight/themes/monokai.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as md; -import 'package:markdown/markdown.dart'; +import 'package:markdown/markdown.dart' as mm; import 'package:markdown_widget/config/all.dart'; import 'package:markdown_widget/widget/all.dart'; @@ -50,7 +54,7 @@ class Markdown extends StatelessWidget { backgroundColor: Colors.transparent, ), codeblockPadding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + const EdgeInsets.symmetric(horizontal: 10, vertical: 10), codeblockDecoration: BoxDecoration( color: customColors.markdownPreColor, borderRadius: BorderRadius.circular(5), @@ -87,8 +91,17 @@ class Markdown extends StatelessWidget { child: Image.network(uri.toString()), ); }, - extensionSet: ExtensionSet.gitHubFlavored, + extensionSet: mm.ExtensionSet( + mm.ExtensionSet.gitHubFlavored.blockSyntaxes, + [ + mm.EmojiSyntax(), + ...mm.ExtensionSet.gitHubFlavored.inlineSyntaxes, + ], + ), data: data, + builders: { + 'code': CodeElementBuilder(), + }, ); } } @@ -124,12 +137,39 @@ class MarkdownPlus extends StatelessWidget { ), // 代码块配置 PreConfig( - theme: monokaiTheme, + theme: defaultTheme, decoration: BoxDecoration( color: customColors.markdownPreColor, borderRadius: BorderRadius.circular(5), ), + margin: const EdgeInsets.symmetric(vertical: 0.0), + padding: + const EdgeInsets.only(top: 30, left: 10, right: 10, bottom: 10), textStyle: const TextStyle(fontSize: 14), + wrapper: (child, code, language) { + return Stack( + children: [ + child, + Positioned( + right: 0, + top: 0, + child: IconButton( + tooltip: '复制代码', + icon: Icon( + Icons.copy, + size: 12, + color: customColors.weakLinkColor, + ), + onPressed: () { + FlutterClipboard.copy(code).then((value) { + showSuccessMessage('已复制到剪贴板'); + }); + }, + ), + ), + ], + ); + }, ), // 代码配置 CodeConfig( diff --git a/lib/page/component/chat/markdown/code.dart b/lib/page/component/chat/markdown/code.dart new file mode 100644 index 00000000..3894ffed --- /dev/null +++ b/lib/page/component/chat/markdown/code.dart @@ -0,0 +1,77 @@ +import 'package:askaide/helper/ability.dart'; +import 'package:askaide/page/component/dialog.dart'; +import 'package:clipboard/clipboard.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_highlight/flutter_highlight.dart'; +import 'package:flutter_highlight/themes/default.dart'; +import 'package:flutter_highlight/themes/monokai.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:markdown/markdown.dart' as md; + +class CodeElementBuilder extends MarkdownElementBuilder { + @override + Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) { + var language = ''; + + if (element.attributes['class'] != null) { + String lg = element.attributes['class'] as String; + language = lg.substring(9); + } + + final multiLine = element.textContent.trim().split("\n").length > 1; + + final child = SizedBox( + child: HighlightView( + // The original code to be highlighted + element.textContent, + + // Specify language + // It is recommended to give it a value for performance + language: language, + + // Specify highlight theme + // All available themes are listed in `themes` folder + theme: Ability().themeMode != 'dark' ? defaultTheme : monokaiTheme, + + // Specify padding + padding: multiLine + ? const EdgeInsets.only( + top: 30, + bottom: 10, + left: 10, + right: 10, + ) + : const EdgeInsets.symmetric(horizontal: 5, vertical: 2), + + textStyle: const TextStyle( + fontSize: 14, + height: 1.5, + wordSpacing: 3, + ), + ), + ); + + if (multiLine) { + return Stack( + children: [ + child, + Positioned( + right: 0, + top: 0, + child: IconButton( + tooltip: '复制代码', + icon: const Icon(Icons.copy, size: 12), + onPressed: () { + FlutterClipboard.copy(element.textContent).then((value) { + showSuccessMessage('已复制到剪贴板'); + }); + }, + ), + ), + ], + ); + } + + return child; + } +} diff --git a/lib/page/component/theme/custom_theme.dart b/lib/page/component/theme/custom_theme.dart index 0fec2c72..0d8e5c9d 100644 --- a/lib/page/component/theme/custom_theme.dart +++ b/lib/page/component/theme/custom_theme.dart @@ -267,8 +267,8 @@ class CustomColors extends ThemeExtension { chatExampleItemText: Color.fromARGB(255, 66, 66, 66), chatExampleTitleText: Color.fromARGB(255, 66, 66, 66), markdownLinkColor: Colors.blue, - markdownPreColor: Color.fromARGB(134, 224, 224, 224), - markdownCodeColor: Color.fromARGB(255, 244, 54, 111), + markdownPreColor: Color.fromARGB(255, 247, 247, 247), + markdownCodeColor: Color.fromARGB(255, 136, 0, 0), boxShadowColor: Color.fromARGB(149, 232, 232, 232), backgroundColor: Colors.white, backgroundInvertedColor: Color.fromARGB(255, 72, 72, 72), diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 07f701f5..4f1695ca 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -115,19 +115,19 @@ SPEC CHECKSUMS: flutter_localization: 4035848ae2ed142875d5fd3dde328250a5e81f42 flutter_tts: 64651204e5d276ffea5a910f942d5e9785a96085 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - in_app_purchase_storekit: 0e4b3c2e43ba1e1281f4f46dd71b0593ce529892 + in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433 media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82 media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5 media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5 - package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 record_darwin: 1f6619f2abac4d1ca91d3eeab038c980d76f1517 screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 - shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sign_in_with_apple: a9e97e744e8edc36aefc2723111f652102a7a727 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 PODFILE CHECKSUM: 38f6b47b8cb10a0771c5d71f5d300e8d2bb9b8d7