mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: render ai text message with appflowy_editor (#5682)
* feat: render ai text message with appflowy_editor * chore: update appflowy_editor * fix: integration test * feat: support paste inAppJson format * chore: update appflowy_editor
This commit is contained in:
parent
0fe383e538
commit
079b9888a8
@ -82,11 +82,11 @@ void main() {
|
||||
HeadingBlockKeys.type,
|
||||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
importedPageEditorState.getNodeAtPath([1])!.type,
|
||||
HeadingBlockKeys.type,
|
||||
);
|
||||
expect(
|
||||
importedPageEditorState.getNodeAtPath([4])!.type,
|
||||
importedPageEditorState.getNodeAtPath([2])!.type,
|
||||
TableBlockKeys.type,
|
||||
);
|
||||
});
|
||||
|
@ -1,10 +1,16 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_input.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_popmenu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@ -176,9 +182,20 @@ class CopyButton extends StatelessWidget {
|
||||
size: const Size.square(14),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: textMessage.text));
|
||||
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||
onPressed: () async {
|
||||
final document = markdownToDocument(textMessage.text);
|
||||
await getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(
|
||||
plainText: textMessage.text,
|
||||
inAppJson: jsonEncode(document.toJson()),
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.grid_url_copiedNotification.tr(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1,10 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'chat_input.dart';
|
||||
|
||||
|
@ -0,0 +1,377 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:markdown_widget/markdown_widget.dart';
|
||||
|
||||
import 'selectable_highlight.dart';
|
||||
|
||||
enum AIMarkdownType {
|
||||
appflowyEditor,
|
||||
markdownWidget,
|
||||
}
|
||||
|
||||
// Wrap the appflowy_editor or markdown_widget as a chat text message widget
|
||||
class AIMarkdownText extends StatelessWidget {
|
||||
const AIMarkdownText({
|
||||
super.key,
|
||||
required this.markdown,
|
||||
this.type = AIMarkdownType.appflowyEditor,
|
||||
});
|
||||
|
||||
final String markdown;
|
||||
final AIMarkdownType type;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
switch (type) {
|
||||
case AIMarkdownType.appflowyEditor:
|
||||
return _AppFlowyEditorMarkdown(markdown: markdown);
|
||||
case AIMarkdownType.markdownWidget:
|
||||
return _ThirdPartyMarkdown(markdown: markdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _AppFlowyEditorMarkdown extends StatefulWidget {
|
||||
const _AppFlowyEditorMarkdown({
|
||||
required this.markdown,
|
||||
});
|
||||
|
||||
// the text should be the markdown format
|
||||
final String markdown;
|
||||
|
||||
@override
|
||||
State<_AppFlowyEditorMarkdown> createState() =>
|
||||
_AppFlowyEditorMarkdownState();
|
||||
}
|
||||
|
||||
class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> {
|
||||
late EditorState editorState;
|
||||
late final styleCustomizer = EditorStyleCustomizer(
|
||||
context: context,
|
||||
padding: EdgeInsets.zero,
|
||||
);
|
||||
late final editorStyle = styleCustomizer.style().copyWith(
|
||||
// hide the cursor
|
||||
cursorColor: Colors.transparent,
|
||||
cursorWidth: 0,
|
||||
);
|
||||
late EditorScrollController scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
editorState = _parseMarkdown(widget.markdown);
|
||||
scrollController = EditorScrollController(
|
||||
editorState: editorState,
|
||||
shrinkWrap: true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant _AppFlowyEditorMarkdown oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.markdown != widget.markdown) {
|
||||
editorState.dispose();
|
||||
editorState = _parseMarkdown(widget.markdown);
|
||||
scrollController.dispose();
|
||||
scrollController = EditorScrollController(
|
||||
editorState: editorState,
|
||||
shrinkWrap: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
editorState.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final blockBuilders = getEditorBuilderMap(
|
||||
context: context,
|
||||
editorState: editorState,
|
||||
styleCustomizer: styleCustomizer,
|
||||
// the editor is not editable in the chat
|
||||
editable: false,
|
||||
);
|
||||
return IntrinsicHeight(
|
||||
child: AppFlowyEditor(
|
||||
shrinkWrap: true,
|
||||
// the editor is not editable in the chat
|
||||
editable: false,
|
||||
editorStyle: editorStyle,
|
||||
editorScrollController: scrollController,
|
||||
blockComponentBuilders: blockBuilders,
|
||||
commandShortcutEvents: [customCopyCommand],
|
||||
editorState: editorState,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
EditorState _parseMarkdown(String markdown) {
|
||||
final document = markdownToDocument(
|
||||
markdown,
|
||||
markdownParsers: [
|
||||
const MarkdownCodeBlockParser(),
|
||||
],
|
||||
);
|
||||
final editorState = EditorState(document: document);
|
||||
return editorState;
|
||||
}
|
||||
}
|
||||
|
||||
class _ThirdPartyMarkdown extends StatelessWidget {
|
||||
const _ThirdPartyMarkdown({
|
||||
required this.markdown,
|
||||
});
|
||||
|
||||
final String markdown;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MarkdownWidget(
|
||||
data: markdown,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
config: configFromContext(context),
|
||||
);
|
||||
}
|
||||
|
||||
MarkdownConfig configFromContext(BuildContext context) {
|
||||
return MarkdownConfig(
|
||||
configs: [
|
||||
HrConfig(color: AFThemeExtension.of(context).textColor),
|
||||
_ChatH1Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
_ChatH2Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
_ChatH3Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
H4Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H5Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H6Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
PreConfig(
|
||||
builder: (code, language) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 800,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
|
||||
child: SelectableHighlightView(
|
||||
code,
|
||||
language: language,
|
||||
theme: getHighlightTheme(context),
|
||||
padding: const EdgeInsets.all(14),
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PConfig(
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
CodeConfig(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
BlockquoteConfig(
|
||||
sideColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
textColor: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, TextStyle> getHighlightTheme(BuildContext context) {
|
||||
return {
|
||||
'root': TextStyle(
|
||||
color: const Color(0xffabb2bf),
|
||||
backgroundColor:
|
||||
Theme.of(context).isLightMode ? Colors.white : Colors.black38,
|
||||
),
|
||||
'comment': const TextStyle(
|
||||
color: Color(0xff5c6370),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
'quote': const TextStyle(
|
||||
color: Color(0xff5c6370),
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
'doctag': const TextStyle(color: Color(0xffc678dd)),
|
||||
'keyword': const TextStyle(color: Color(0xffc678dd)),
|
||||
'formula': const TextStyle(color: Color(0xffc678dd)),
|
||||
'section': const TextStyle(color: Color(0xffe06c75)),
|
||||
'name': const TextStyle(color: Color(0xffe06c75)),
|
||||
'selector-tag': const TextStyle(color: Color(0xffe06c75)),
|
||||
'deletion': const TextStyle(color: Color(0xffe06c75)),
|
||||
'subst': const TextStyle(color: Color(0xffe06c75)),
|
||||
'literal': const TextStyle(color: Color(0xff56b6c2)),
|
||||
'string': const TextStyle(color: Color(0xff98c379)),
|
||||
'regexp': const TextStyle(color: Color(0xff98c379)),
|
||||
'addition': const TextStyle(color: Color(0xff98c379)),
|
||||
'attribute': const TextStyle(color: Color(0xff98c379)),
|
||||
'meta-string': const TextStyle(color: Color(0xff98c379)),
|
||||
'built_in': const TextStyle(color: Color(0xffe6c07b)),
|
||||
'attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'template-variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'type': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-class': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-pseudo': const TextStyle(color: Color(0xffd19a66)),
|
||||
'number': const TextStyle(color: Color(0xffd19a66)),
|
||||
'symbol': const TextStyle(color: Color(0xff61aeee)),
|
||||
'bullet': const TextStyle(color: Color(0xff61aeee)),
|
||||
'link': const TextStyle(color: Color(0xff61aeee)),
|
||||
'meta': const TextStyle(color: Color(0xff61aeee)),
|
||||
'selector-id': const TextStyle(color: Color(0xff61aeee)),
|
||||
'title': const TextStyle(color: Color(0xff61aeee)),
|
||||
'emphasis': const TextStyle(fontStyle: FontStyle.italic),
|
||||
'strong': const TextStyle(fontWeight: FontWeight.bold),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _ChatH1Config extends HeadingConfig {
|
||||
const _ChatH1Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 32,
|
||||
height: 40 / 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h1.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
///config class for h2
|
||||
class _ChatH2Config extends HeadingConfig {
|
||||
const _ChatH2Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h2.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
class _ChatH3Config extends HeadingConfig {
|
||||
const _ChatH3Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h3.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
@ -2,19 +2,15 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_loading.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:markdown_widget/markdown_widget.dart';
|
||||
|
||||
import 'selectable_highlight.dart';
|
||||
|
||||
class ChatAITextMessageWidget extends StatelessWidget {
|
||||
const ChatAITextMessageWidget({
|
||||
@ -59,248 +55,12 @@ class ChatAITextMessageWidget extends StatelessWidget {
|
||||
if (state.text.isEmpty) {
|
||||
return const ChatAILoading();
|
||||
} else {
|
||||
return _textWidgetBuilder(user, context, state.text);
|
||||
return AIMarkdownText(markdown: state.text);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _textWidgetBuilder(
|
||||
User user,
|
||||
BuildContext context,
|
||||
String text,
|
||||
) {
|
||||
return MarkdownWidget(
|
||||
data: text,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
config: configFromContext(context),
|
||||
);
|
||||
}
|
||||
|
||||
MarkdownConfig configFromContext(BuildContext context) {
|
||||
return MarkdownConfig(
|
||||
configs: [
|
||||
HrConfig(color: AFThemeExtension.of(context).textColor),
|
||||
ChatH1Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
ChatH2Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
ChatH3Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
dividerColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
),
|
||||
H4Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H5Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
H6Config(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
PreConfig(
|
||||
builder: (code, language) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 800,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
|
||||
child: SelectableHighlightView(
|
||||
code,
|
||||
language: language,
|
||||
theme: getHightlineTheme(context),
|
||||
padding: const EdgeInsets.all(14),
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
PConfig(
|
||||
textStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
CodeConfig(
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.5,
|
||||
),
|
||||
),
|
||||
BlockquoteConfig(
|
||||
sideColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
textColor: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, TextStyle> getHightlineTheme(BuildContext context) {
|
||||
return {
|
||||
'root': TextStyle(
|
||||
color: const Color(0xffabb2bf),
|
||||
backgroundColor:
|
||||
Theme.of(context).isLightMode ? Colors.white : Colors.black38,
|
||||
),
|
||||
'comment':
|
||||
const TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
||||
'quote':
|
||||
const TextStyle(color: Color(0xff5c6370), fontStyle: FontStyle.italic),
|
||||
'doctag': const TextStyle(color: Color(0xffc678dd)),
|
||||
'keyword': const TextStyle(color: Color(0xffc678dd)),
|
||||
'formula': const TextStyle(color: Color(0xffc678dd)),
|
||||
'section': const TextStyle(color: Color(0xffe06c75)),
|
||||
'name': const TextStyle(color: Color(0xffe06c75)),
|
||||
'selector-tag': const TextStyle(color: Color(0xffe06c75)),
|
||||
'deletion': const TextStyle(color: Color(0xffe06c75)),
|
||||
'subst': const TextStyle(color: Color(0xffe06c75)),
|
||||
'literal': const TextStyle(color: Color(0xff56b6c2)),
|
||||
'string': const TextStyle(color: Color(0xff98c379)),
|
||||
'regexp': const TextStyle(color: Color(0xff98c379)),
|
||||
'addition': const TextStyle(color: Color(0xff98c379)),
|
||||
'attribute': const TextStyle(color: Color(0xff98c379)),
|
||||
'meta-string': const TextStyle(color: Color(0xff98c379)),
|
||||
'built_in': const TextStyle(color: Color(0xffe6c07b)),
|
||||
'attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'template-variable': const TextStyle(color: Color(0xffd19a66)),
|
||||
'type': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-class': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-attr': const TextStyle(color: Color(0xffd19a66)),
|
||||
'selector-pseudo': const TextStyle(color: Color(0xffd19a66)),
|
||||
'number': const TextStyle(color: Color(0xffd19a66)),
|
||||
'symbol': const TextStyle(color: Color(0xff61aeee)),
|
||||
'bullet': const TextStyle(color: Color(0xff61aeee)),
|
||||
'link': const TextStyle(color: Color(0xff61aeee)),
|
||||
'meta': const TextStyle(color: Color(0xff61aeee)),
|
||||
'selector-id': const TextStyle(color: Color(0xff61aeee)),
|
||||
'title': const TextStyle(color: Color(0xff61aeee)),
|
||||
'emphasis': const TextStyle(fontStyle: FontStyle.italic),
|
||||
'strong': const TextStyle(fontWeight: FontWeight.bold),
|
||||
};
|
||||
}
|
||||
|
||||
class ChatH1Config extends HeadingConfig {
|
||||
const ChatH1Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 32,
|
||||
height: 40 / 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h1.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
///config class for h2
|
||||
class ChatH2Config extends HeadingConfig {
|
||||
const ChatH2Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h2.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
class ChatH3Config extends HeadingConfig {
|
||||
const ChatH3Config({
|
||||
this.style = const TextStyle(
|
||||
fontSize: 24,
|
||||
height: 30 / 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
required this.dividerColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final TextStyle style;
|
||||
final Color dividerColor;
|
||||
|
||||
@override
|
||||
String get tag => MarkdownTag.h3.name;
|
||||
|
||||
@override
|
||||
HeadingDivider? get divider => HeadingDivider(
|
||||
space: 10,
|
||||
color: dividerColor,
|
||||
height: 10,
|
||||
);
|
||||
}
|
||||
|
||||
class StreamingError extends StatelessWidget {
|
||||
|
@ -52,7 +52,6 @@ class TextMessageText extends StatelessWidget {
|
||||
text,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
lineHeight: 1.5,
|
||||
maxLines: null,
|
||||
selectable: true,
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
|
@ -0,0 +1,52 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
|
||||
import 'package:markdown/markdown.dart' as md;
|
||||
|
||||
class MarkdownCodeBlockParser extends CustomMarkdownParser {
|
||||
const MarkdownCodeBlockParser();
|
||||
|
||||
@override
|
||||
List<Node> transform(
|
||||
md.Node element,
|
||||
List<CustomMarkdownParser> parsers, {
|
||||
MarkdownListType listType = MarkdownListType.unknown,
|
||||
int? startNumber,
|
||||
}) {
|
||||
if (element is! md.Element) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (element.tag != 'pre') {
|
||||
return [];
|
||||
}
|
||||
|
||||
final ec = element.children;
|
||||
if (ec == null || ec.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final code = ec.first;
|
||||
if (code is! md.Element || code.tag != 'code') {
|
||||
return [];
|
||||
}
|
||||
|
||||
String? language;
|
||||
if (code.attributes.containsKey('class')) {
|
||||
final classes = code.attributes['class']!.split(' ');
|
||||
final languageClass = classes.firstWhere(
|
||||
(c) => c.startsWith('language-'),
|
||||
orElse: () => '',
|
||||
);
|
||||
language = languageClass.substring('language-'.length);
|
||||
}
|
||||
|
||||
final deltaDecoder = DeltaMarkdownDecoder();
|
||||
|
||||
return [
|
||||
codeBlockNode(
|
||||
language: language,
|
||||
delta: deltaDecoder.convertNodes(code.children),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export 'callout_node_parser.dart';
|
||||
export 'markdown_code_parser.dart';
|
||||
export 'math_equation_node_parser.dart';
|
||||
export 'toggle_list_node_parser.dart';
|
||||
|
@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/markdown_code_parser.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/share/import_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/import/import_type.dart';
|
||||
@ -220,7 +221,12 @@ class _ImportPanelState extends State<ImportPanel> {
|
||||
Uint8List? _documentDataFrom(ImportType importType, String data) {
|
||||
switch (importType) {
|
||||
case ImportType.markdownOrText:
|
||||
final document = markdownToDocument(data);
|
||||
final document = markdownToDocument(
|
||||
data,
|
||||
markdownParsers: [
|
||||
const MarkdownCodeBlockParser(),
|
||||
],
|
||||
);
|
||||
return DocumentDataPBFromTo.fromDocument(document)?.writeToBuffer();
|
||||
case ImportType.historyDocument:
|
||||
final document = EditorMigration.migrateDocument(data);
|
||||
|
@ -53,11 +53,11 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: e8ee051
|
||||
resolved-ref: e8ee051719eded6621ccdc2722f696411c020209
|
||||
ref: d2d9873
|
||||
resolved-ref: d2d987312d3a667336c7e12c36da7dbbb62d66db
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "3.0.0"
|
||||
version: "3.1.0"
|
||||
appflowy_editor_plugins:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -121,6 +121,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
barcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: barcode
|
||||
sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.8"
|
||||
bidi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bidi
|
||||
sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.10"
|
||||
bitsdojo_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -965,6 +981,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
image_gallery_saver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1219,7 +1243,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
markdown:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: markdown
|
||||
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
|
||||
@ -1434,6 +1458,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0+3"
|
||||
pdf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pdf
|
||||
sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.11.0"
|
||||
pdf_widget_wrapper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pdf_widget_wrapper
|
||||
sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
percent_indicator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1554,6 +1594,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
printing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: printing
|
||||
sha256: cc4b256a5a89d5345488e3318897b595867f5181b8c5ed6fc63bfa5f2044aec3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.13.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1594,6 +1642,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
realtime_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -141,6 +141,7 @@ dependencies:
|
||||
shimmer: ^3.0.0
|
||||
isolates: ^3.0.3+8
|
||||
markdown_widget: ^2.3.2+6
|
||||
markdown:
|
||||
|
||||
# Window Manager for MacOS and Linux
|
||||
window_manager: ^0.3.9
|
||||
@ -187,7 +188,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "e8ee051"
|
||||
ref: "d2d9873"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
Loading…
Reference in New Issue
Block a user