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: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;
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user