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: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;
}

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: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(

View File

@ -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.
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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:

View File

@ -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