feat: support markdown export and customize save path (#1339)

This commit is contained in:
Lucas.Xu 2022-10-23 15:05:51 +08:00 committed by GitHub
parent 87247ccd9d
commit aa58c79dbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 81 additions and 60 deletions

View File

@ -1,7 +1,5 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; 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/plugins/doc/application/share_service.dart';
import 'package:app_flowy/workspace/application/markdown/document_markdown.dart'; import 'package:app_flowy/workspace/application/markdown/document_markdown.dart';
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.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()) { : super(const DocShareState.initial()) {
on<DocShareEvent>((event, emit) async { on<DocShareEvent>((event, emit) async {
await event.map( await event.map(
shareMarkdown: (ShareMarkdown value) async { shareMarkdown: (ShareMarkdown shareMarkdown) async {
await service.exportMarkdown(view).then((result) { await service.exportMarkdown(view).then((result) {
result.fold( result.fold(
(value) => emit(DocShareState.finish( (value) => emit(
left(_convertDocumentToMarkdown(value)))), DocShareState.finish(
left(_saveMarkdown(value, shareMarkdown.path)),
),
),
(error) => emit(DocShareState.finish(right(error))), (error) => emit(DocShareState.finish(right(error))),
); );
}); });
@ -37,40 +38,23 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
}); });
} }
ExportDataPB _convertDocumentToMarkdown(ExportDataPB value) { ExportDataPB _saveMarkdown(ExportDataPB value, String path) {
final json = jsonDecode(value.data); final markdown = _convertDocumentToMarkdown(value);
final document = Document.fromJson(json); value.data = markdown;
final result = documentToMarkdown(document); File(path).writeAsStringSync(markdown);
value.data = result;
writeFile(result);
return value; return value;
} }
Future<Directory> get _exportDir async { String _convertDocumentToMarkdown(ExportDataPB value) {
Directory documentsDir = await appFlowyDocumentDirectory(); final json = jsonDecode(value.data);
final document = Document.fromJson(json);
return documentsDir; return documentToMarkdown(document);
}
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);
} }
} }
@freezed @freezed
class DocShareEvent with _$DocShareEvent { class DocShareEvent with _$DocShareEvent {
const factory DocShareEvent.shareMarkdown() = ShareMarkdown; const factory DocShareEvent.shareMarkdown(String path) = ShareMarkdown;
const factory DocShareEvent.shareText() = ShareText; const factory DocShareEvent.shareText() = ShareText;
const factory DocShareEvent.shareLink() = ShareLink; const factory DocShareEvent.shareLink() = ShareLink;
} }

View File

@ -14,6 +14,7 @@ import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:clipboard/clipboard.dart'; import 'package:clipboard/clipboard.dart';
import 'package:easy_localization/easy_localization.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/size.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart';
@ -138,7 +139,9 @@ class DocumentShareButton extends StatelessWidget {
height: 30, height: 30,
width: 100, width: 100,
), ),
child: const ShareActionList(), child: ShareActionList(
view: view,
),
), ),
), ),
); );
@ -165,11 +168,17 @@ class DocumentShareButton extends StatelessWidget {
} }
class ShareActionList 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
final docShareBloc = context.read<DocShareBloc>();
return PopoverActionList<ShareActionWrapper>( return PopoverActionList<ShareActionWrapper>(
direction: PopoverDirection.bottomWithCenterAligned, direction: PopoverDirection.bottomWithCenterAligned,
actions: ShareAction.values actions: ShareAction.values
@ -184,14 +193,17 @@ class ShareActionList extends StatelessWidget {
onPressed: () => controller.show(), onPressed: () => controller.show(),
); );
}, },
onSelected: (action, controller) { onSelected: (action, controller) async {
switch (action.inner) { switch (action.inner) {
case ShareAction.markdown: case ShareAction.markdown:
context final exportPath = await FilePicker.platform.saveFile(
.read<DocShareBloc>() dialogTitle: '',
.add(const DocShareEvent.shareMarkdown()); fileName: '${view.name}.md',
showMessageToast( );
'Exported to: ${LocaleKeys.notifications_export_path.tr()}'); if (exportPath != null) {
docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
showMessageToast('Exported to: $exportPath');
}
break; break;
case ShareAction.copyLink: case ShareAction.copyLink:
NavigatorAlertDialog( NavigatorAlertDialog(

View File

@ -65,7 +65,8 @@ class DeltaMarkdownEncoder extends Converter<String, String> {
// First close any current styles if needed // First close any current styles if needed
final markedForRemoval = <Attribute>[]; final markedForRemoval = <Attribute>[];
// Close the styles in reverse order, e.g. **_ for _**Test**_. // 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? // TODO(tillf): Is block correct?
if (value.scope == AttributeScope.BLOCK) { if (value.scope == AttributeScope.BLOCK) {
continue; continue;
@ -122,8 +123,10 @@ class DeltaMarkdownEncoder extends Converter<String, String> {
// Close any open inline styles. // Close any open inline styles.
_handleInline(lineBuffer, '', null); _handleInline(lineBuffer, '', null);
final lineBlock = final lineBlock = Style.fromJson(attributes)
Style.fromJson(attributes).attributes.values.singleWhereOrNull((a) => a.scope == AttributeScope.BLOCK); .attributes
.values
.singleWhereOrNull((a) => a.scope == AttributeScope.BLOCK);
if (lineBlock == currentBlockStyle) { if (lineBlock == currentBlockStyle) {
currentBlockLines.add(lineBuffer.toString()); currentBlockLines.add(lineBuffer.toString());
@ -228,7 +231,7 @@ class DeltaMarkdownEncoder extends Converter<String, String> {
} else if (attribute.key == Attribute.strikeThrough.key) { } else if (attribute.key == Attribute.strikeThrough.key) {
buffer.write(!close ? '~~' : '~~'); buffer.write(!close ? '~~' : '~~');
} else { } 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) { } else if (block.key == Attribute.list.key) {
buffer.write('* '); buffer.write('* ');
} else { } else {
throw ArgumentError('Cannot handle block $block'); // do nothing, just skip the unknown attribute.
} }
} }

View File

@ -8,6 +8,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
appflowy_popover:
dependency: transitive
description:
path: "../../appflowy_popover"
relative: true
source: path
version: "0.0.1"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -234,7 +241,7 @@ packages:
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
provider: provider:
dependency: transitive dependency: "direct main"
description: description:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

View File

@ -61,7 +61,7 @@ packages:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -73,7 +73,7 @@ packages:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -164,5 +164,5 @@ packages:
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
sdks: sdks:
dart: ">=2.17.0-0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"

View File

@ -68,7 +68,7 @@ packages:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -92,7 +92,7 @@ packages:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -183,5 +183,5 @@ packages:
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
sdks: sdks:
dart: ">=2.17.0-0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"

View File

@ -8,8 +8,15 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
appflowy_popover:
dependency: "direct main"
description:
path: "../appflowy_popover"
relative: true
source: path
version: "0.0.1"
async: async:
dependency: transitive dependency: "direct main"
description: description:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"

View File

@ -70,7 +70,7 @@ packages:
name: dartz name: dartz
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.10.0-nullsafety.2" version: "0.10.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -122,7 +122,7 @@ packages:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -165,7 +165,7 @@ packages:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.1"
logger: logger:
dependency: transitive dependency: transitive
description: description:
@ -305,5 +305,5 @@ packages:
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
sdks: sdks:
dart: ">=2.17.0-0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"

View File

@ -168,7 +168,7 @@ packages:
name: dartz name: dartz
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.10.0-nullsafety.2" version: "0.10.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -208,7 +208,7 @@ packages:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -290,7 +290,7 @@ packages:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.1"
logger: logger:
dependency: "direct main" dependency: "direct main"
description: description:
@ -500,5 +500,5 @@ packages:
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
sdks: sdks:
dart: ">=2.17.0-0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"

View File

@ -393,6 +393,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.2" 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: fixnum:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -92,6 +92,7 @@ dependencies:
textstyle_extensions: "2.0.0-nullsafety" textstyle_extensions: "2.0.0-nullsafety"
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
google_fonts: ^3.0.1 google_fonts: ^3.0.1
file_picker: <=5.0.0
dev_dependencies: dev_dependencies:
flutter_lints: ^2.0.1 flutter_lints: ^2.0.1