mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12: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…
Reference in New Issue
Block a user