mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-10-25 23:24:39 +00:00 
			
		
		
		
	feat: support markdown export and customize save path (#1339)
This commit is contained in:
		
							parent
							
								
									87247ccd9d
								
							
						
					
					
						commit
						aa58c79dbb
					
				| @ -1,7 +1,5 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:io'; | ||||
| import 'package:app_flowy/startup/tasks/rust_sdk.dart'; | ||||
| import 'package:app_flowy/plugins/doc/application/share_service.dart'; | ||||
| import 'package:app_flowy/workspace/application/markdown/document_markdown.dart'; | ||||
| import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart'; | ||||
| @ -20,11 +18,14 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> { | ||||
|       : super(const DocShareState.initial()) { | ||||
|     on<DocShareEvent>((event, emit) async { | ||||
|       await event.map( | ||||
|         shareMarkdown: (ShareMarkdown value) async { | ||||
|         shareMarkdown: (ShareMarkdown shareMarkdown) async { | ||||
|           await service.exportMarkdown(view).then((result) { | ||||
|             result.fold( | ||||
|               (value) => emit(DocShareState.finish( | ||||
|                   left(_convertDocumentToMarkdown(value)))), | ||||
|               (value) => emit( | ||||
|                 DocShareState.finish( | ||||
|                   left(_saveMarkdown(value, shareMarkdown.path)), | ||||
|                 ), | ||||
|               ), | ||||
|               (error) => emit(DocShareState.finish(right(error))), | ||||
|             ); | ||||
|           }); | ||||
| @ -37,40 +38,23 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   ExportDataPB _convertDocumentToMarkdown(ExportDataPB value) { | ||||
|     final json = jsonDecode(value.data); | ||||
|     final document = Document.fromJson(json); | ||||
|     final result = documentToMarkdown(document); | ||||
|     value.data = result; | ||||
|     writeFile(result); | ||||
|   ExportDataPB _saveMarkdown(ExportDataPB value, String path) { | ||||
|     final markdown = _convertDocumentToMarkdown(value); | ||||
|     value.data = markdown; | ||||
|     File(path).writeAsStringSync(markdown); | ||||
|     return value; | ||||
|   } | ||||
| 
 | ||||
|   Future<Directory> get _exportDir async { | ||||
|     Directory documentsDir = await appFlowyDocumentDirectory(); | ||||
| 
 | ||||
|     return documentsDir; | ||||
|   } | ||||
| 
 | ||||
|   Future<String> get _localPath async { | ||||
|     final dir = await _exportDir; | ||||
|     return dir.path; | ||||
|   } | ||||
| 
 | ||||
|   Future<File> get _localFile async { | ||||
|     final path = await _localPath; | ||||
|     return File('$path/${view.name}.md'); | ||||
|   } | ||||
| 
 | ||||
|   Future<File> writeFile(String md) async { | ||||
|     final file = await _localFile; | ||||
|     return file.writeAsString(md); | ||||
|   String _convertDocumentToMarkdown(ExportDataPB value) { | ||||
|     final json = jsonDecode(value.data); | ||||
|     final document = Document.fromJson(json); | ||||
|     return documentToMarkdown(document); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @freezed | ||||
| class DocShareEvent with _$DocShareEvent { | ||||
|   const factory DocShareEvent.shareMarkdown() = ShareMarkdown; | ||||
|   const factory DocShareEvent.shareMarkdown(String path) = ShareMarkdown; | ||||
|   const factory DocShareEvent.shareText() = ShareText; | ||||
|   const factory DocShareEvent.shareLink() = ShareLink; | ||||
| } | ||||
|  | ||||
| @ -14,6 +14,7 @@ import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; | ||||
| import 'package:appflowy_popover/appflowy_popover.dart'; | ||||
| import 'package:clipboard/clipboard.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:file_picker/file_picker.dart'; | ||||
| import 'package:flowy_infra/size.dart'; | ||||
| import 'package:flowy_infra/theme.dart'; | ||||
| import 'package:flowy_infra_ui/widget/rounded_button.dart'; | ||||
| @ -138,7 +139,9 @@ class DocumentShareButton extends StatelessWidget { | ||||
|                     height: 30, | ||||
|                     width: 100, | ||||
|                   ), | ||||
|                   child: const ShareActionList(), | ||||
|                   child: ShareActionList( | ||||
|                     view: view, | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ); | ||||
| @ -165,11 +168,17 @@ class DocumentShareButton extends StatelessWidget { | ||||
| } | ||||
| 
 | ||||
| class ShareActionList extends StatelessWidget { | ||||
|   const ShareActionList({Key? key}) : super(key: key); | ||||
|   const ShareActionList({ | ||||
|     Key? key, | ||||
|     required this.view, | ||||
|   }) : super(key: key); | ||||
| 
 | ||||
|   final ViewPB view; | ||||
| 
 | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     final theme = context.watch<AppTheme>(); | ||||
|     final docShareBloc = context.read<DocShareBloc>(); | ||||
|     return PopoverActionList<ShareActionWrapper>( | ||||
|       direction: PopoverDirection.bottomWithCenterAligned, | ||||
|       actions: ShareAction.values | ||||
| @ -184,14 +193,17 @@ class ShareActionList extends StatelessWidget { | ||||
|           onPressed: () => controller.show(), | ||||
|         ); | ||||
|       }, | ||||
|       onSelected: (action, controller) { | ||||
|       onSelected: (action, controller) async { | ||||
|         switch (action.inner) { | ||||
|           case ShareAction.markdown: | ||||
|             context | ||||
|                 .read<DocShareBloc>() | ||||
|                 .add(const DocShareEvent.shareMarkdown()); | ||||
|             showMessageToast( | ||||
|                 'Exported to: ${LocaleKeys.notifications_export_path.tr()}'); | ||||
|             final exportPath = await FilePicker.platform.saveFile( | ||||
|               dialogTitle: '', | ||||
|               fileName: '${view.name}.md', | ||||
|             ); | ||||
|             if (exportPath != null) { | ||||
|               docShareBloc.add(DocShareEvent.shareMarkdown(exportPath)); | ||||
|               showMessageToast('Exported to: $exportPath'); | ||||
|             } | ||||
|             break; | ||||
|           case ShareAction.copyLink: | ||||
|             NavigatorAlertDialog( | ||||
|  | ||||
| @ -65,7 +65,8 @@ class DeltaMarkdownEncoder extends Converter<String, String> { | ||||
|     // First close any current styles if needed | ||||
|     final markedForRemoval = <Attribute>[]; | ||||
|     // Close the styles in reverse order, e.g. **_ for _**Test**_. | ||||
|     for (final value in currentInlineStyle.attributes.values.toList().reversed) { | ||||
|     for (final value | ||||
|         in currentInlineStyle.attributes.values.toList().reversed) { | ||||
|       // TODO(tillf): Is block correct? | ||||
|       if (value.scope == AttributeScope.BLOCK) { | ||||
|         continue; | ||||
| @ -122,8 +123,10 @@ class DeltaMarkdownEncoder extends Converter<String, String> { | ||||
|         // Close any open inline styles. | ||||
|         _handleInline(lineBuffer, '', null); | ||||
| 
 | ||||
|         final lineBlock = | ||||
|             Style.fromJson(attributes).attributes.values.singleWhereOrNull((a) => a.scope == AttributeScope.BLOCK); | ||||
|         final lineBlock = Style.fromJson(attributes) | ||||
|             .attributes | ||||
|             .values | ||||
|             .singleWhereOrNull((a) => a.scope == AttributeScope.BLOCK); | ||||
| 
 | ||||
|         if (lineBlock == currentBlockStyle) { | ||||
|           currentBlockLines.add(lineBuffer.toString()); | ||||
| @ -228,7 +231,7 @@ class DeltaMarkdownEncoder extends Converter<String, String> { | ||||
|     } else if (attribute.key == Attribute.strikeThrough.key) { | ||||
|       buffer.write(!close ? '~~' : '~~'); | ||||
|     } else { | ||||
|       throw ArgumentError('Cannot handle $attribute'); | ||||
|       // do nothing, just skip the unknown attribute. | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -256,7 +259,7 @@ class DeltaMarkdownEncoder extends Converter<String, String> { | ||||
|     } else if (block.key == Attribute.list.key) { | ||||
|       buffer.write('* '); | ||||
|     } else { | ||||
|       throw ArgumentError('Cannot handle block $block'); | ||||
|       // do nothing, just skip the unknown attribute. | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -8,6 +8,13 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.1" | ||||
|   appflowy_popover: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       path: "../../appflowy_popover" | ||||
|       relative: true | ||||
|     source: path | ||||
|     version: "0.0.1" | ||||
|   async: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -234,7 +241,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "2.0.1" | ||||
|   provider: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: provider | ||||
|       url: "https://pub.dartlang.org" | ||||
|  | ||||
| @ -61,7 +61,7 @@ packages: | ||||
|       name: flutter_lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.4" | ||||
|     version: "2.0.1" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @ -73,7 +73,7 @@ packages: | ||||
|       name: lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|     version: "2.0.1" | ||||
|   matcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -164,5 +164,5 @@ packages: | ||||
|     source: hosted | ||||
|     version: "2.1.2" | ||||
| sdks: | ||||
|   dart: ">=2.17.0-0 <3.0.0" | ||||
|   dart: ">=2.17.0 <3.0.0" | ||||
|   flutter: ">=1.17.0" | ||||
|  | ||||
| @ -68,7 +68,7 @@ packages: | ||||
|       name: flutter_lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.4" | ||||
|     version: "2.0.1" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @ -92,7 +92,7 @@ packages: | ||||
|       name: lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|     version: "2.0.1" | ||||
|   matcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -183,5 +183,5 @@ packages: | ||||
|     source: hosted | ||||
|     version: "2.1.2" | ||||
| sdks: | ||||
|   dart: ">=2.17.0-0 <3.0.0" | ||||
|   dart: ">=2.17.0 <3.0.0" | ||||
|   flutter: ">=1.17.0" | ||||
|  | ||||
| @ -8,8 +8,15 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.1" | ||||
|   appflowy_popover: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       path: "../appflowy_popover" | ||||
|       relative: true | ||||
|     source: path | ||||
|     version: "0.0.1" | ||||
|   async: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: async | ||||
|       url: "https://pub.dartlang.org" | ||||
|  | ||||
| @ -70,7 +70,7 @@ packages: | ||||
|       name: dartz | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.10.0-nullsafety.2" | ||||
|     version: "0.10.1" | ||||
|   fake_async: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -122,7 +122,7 @@ packages: | ||||
|       name: flutter_lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.3" | ||||
|     version: "2.0.1" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @ -165,7 +165,7 @@ packages: | ||||
|       name: lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|     version: "2.0.1" | ||||
|   logger: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -305,5 +305,5 @@ packages: | ||||
|     source: hosted | ||||
|     version: "3.0.0" | ||||
| sdks: | ||||
|   dart: ">=2.17.0-0 <3.0.0" | ||||
|   dart: ">=2.17.0 <3.0.0" | ||||
|   flutter: ">=1.17.0" | ||||
|  | ||||
| @ -168,7 +168,7 @@ packages: | ||||
|       name: dartz | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.10.0-nullsafety.2" | ||||
|     version: "0.10.1" | ||||
|   fake_async: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @ -208,7 +208,7 @@ packages: | ||||
|       name: flutter_lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.3" | ||||
|     version: "2.0.1" | ||||
|   flutter_test: | ||||
|     dependency: "direct dev" | ||||
|     description: flutter | ||||
| @ -290,7 +290,7 @@ packages: | ||||
|       name: lints | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|     version: "2.0.1" | ||||
|   logger: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @ -500,5 +500,5 @@ packages: | ||||
|     source: hosted | ||||
|     version: "3.1.0" | ||||
| sdks: | ||||
|   dart: ">=2.17.0-0 <3.0.0" | ||||
|   dart: ">=2.17.0 <3.0.0" | ||||
|   flutter: ">=1.17.0" | ||||
|  | ||||
| @ -393,6 +393,13 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "6.1.2" | ||||
|   file_picker: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: file_picker | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "4.6.1" | ||||
|   fixnum: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|  | ||||
| @ -92,6 +92,7 @@ dependencies: | ||||
|   textstyle_extensions: "2.0.0-nullsafety" | ||||
|   shared_preferences: ^2.0.15 | ||||
|   google_fonts: ^3.0.1 | ||||
|   file_picker: <=5.0.0 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   flutter_lints: ^2.0.1 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Lucas.Xu
						Lucas.Xu