From a50918fa6a1e1c597c8b499170c7feb4e225ca9e Mon Sep 17 00:00:00 2001 From: Vashon <54029053+Lagrange1813@users.noreply.github.com> Date: Thu, 11 Apr 2024 20:01:36 +0800 Subject: [PATCH] feat: expand share option (#5021) * feat: expand share option * chore: optimize the code --------- Co-authored-by: Lucas.Xu --- .../application/document_share_bloc.dart | 95 ++++++++++++++----- .../presentation/share/share_button.dart | 45 ++++++++- .../application/export/document_exporter.dart | 6 ++ frontend/resources/translations/en.json | 2 + frontend/resources/translations/zh-CN.json | 2 + .../rust-lib/flowy-document/src/entities.rs | 2 + 6 files changed, 126 insertions(+), 26 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_share_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_share_bloc.dart index 81c44d30b6..af0d5081f1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/document_share_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_share_bloc.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:appflowy/workspace/application/export/document_exporter.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -11,45 +12,89 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'document_share_bloc.freezed.dart'; class DocumentShareBloc extends Bloc { - DocumentShareBloc({required this.view}) - : super(const DocumentShareState.initial()) { - on(_onShareMarkdown); + DocumentShareBloc({ + required this.view, + }) : super(const DocumentShareState.initial()) { + on((event, emit) async { + await event.when( + share: (type, path) async { + if (DocumentShareType.unimplemented.contains(type)) { + Log.error('DocumentShareType $type is not implemented'); + return; + } + + emit(const DocumentShareState.loading()); + + final exporter = DocumentExporter(view); + final FlowyResult result = + await exporter.export(type.exportType).then((value) { + return value.fold( + (s) { + if (path != null) { + switch (type) { + case DocumentShareType.markdown: + return FlowyResult.success(_saveMarkdownToPath(s, path)); + case DocumentShareType.html: + return FlowyResult.success(_saveHTMLToPath(s, path)); + default: + break; + } + } + return FlowyResult.failure(FlowyError()); + }, + (f) => FlowyResult.failure(f), + ); + }); + + emit(DocumentShareState.finish(result)); + }, + ); + }); } final ViewPB view; - Future _onShareMarkdown( - ShareMarkdown event, - Emitter emit, - ) async { - emit(const DocumentShareState.loading()); - - final documentExporter = DocumentExporter(view); - final result = await documentExporter.export(DocumentExportType.markdown); - emit( - DocumentShareState.finish( - result.fold( - (markdown) => - FlowyResult.success(_saveMarkdownToPath(markdown, event.path)), - (error) => FlowyResult.failure(error), - ), - ), - ); - } - ExportDataPB _saveMarkdownToPath(String markdown, String path) { File(path).writeAsStringSync(markdown); return ExportDataPB() ..data = markdown ..exportType = ExportType.Markdown; } + + ExportDataPB _saveHTMLToPath(String html, String path) { + File(path).writeAsStringSync(html); + return ExportDataPB() + ..data = html + ..exportType = ExportType.HTML; + } +} + +enum DocumentShareType { + markdown, + html, + text, + link; + + static List get unimplemented => [text, link]; + + DocumentExportType get exportType { + switch (this) { + case DocumentShareType.markdown: + return DocumentExportType.markdown; + case DocumentShareType.html: + return DocumentExportType.html; + case DocumentShareType.text: + return DocumentExportType.text; + case DocumentShareType.link: + throw UnsupportedError('DocumentShareType.link is not supported'); + } + } } @freezed class DocumentShareEvent with _$DocumentShareEvent { - const factory DocumentShareEvent.shareMarkdown(String path) = ShareMarkdown; - const factory DocumentShareEvent.shareText() = ShareText; - const factory DocumentShareEvent.shareLink() = ShareLink; + const factory DocumentShareEvent.share(DocumentShareType type, String? path) = + Share; } @freezed diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart index b0a07b831d..fa15bc10d5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/share/share_button.dart @@ -1,7 +1,9 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_share_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/string_extension.dart'; +import 'package:appflowy/workspace/application/export/document_exporter.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; @@ -62,6 +64,12 @@ class DocumentShareButton extends StatelessWidget { case ExportType.Link: case ExportType.Text: break; + case ExportType.HTML: + showSnackBarMessage( + context, + LocaleKeys.settings_files_exportFileSuccess.tr(), + ); + break; } } @@ -125,9 +133,38 @@ class ShareActionListState extends State { fileName: '${name.toFileName()}.md', ); if (exportPath != null) { - docShareBloc.add(DocumentShareEvent.shareMarkdown(exportPath)); + docShareBloc.add( + DocumentShareEvent.share( + DocumentShareType.markdown, + exportPath, + ), + ); } break; + case ShareAction.html: + final exportPath = await getIt().saveFile( + dialogTitle: '', + fileName: '${name.toFileName()}.html', + ); + if (exportPath != null) { + docShareBloc.add( + DocumentShareEvent.share( + DocumentShareType.html, + exportPath, + ), + ); + } + break; + case ShareAction.clipboard: + final documentExporter = DocumentExporter(widget.view); + final result = + await documentExporter.export(DocumentExportType.markdown); + result.fold( + (markdown) => getIt() + .setData(ClipboardServiceData(plainText: markdown)), + (error) => showMessageToast(error.msg), + ); + break; } controller.close(); }, @@ -146,6 +183,8 @@ class ShareActionListState extends State { enum ShareAction { markdown, + html, + clipboard, } class ShareActionWrapper extends ActionCell { @@ -160,6 +199,10 @@ class ShareActionWrapper extends ActionCell { switch (inner) { case ShareAction.markdown: return LocaleKeys.shareAction_markdown.tr(); + case ShareAction.html: + return LocaleKeys.shareAction_html.tr(); + case ShareAction.clipboard: + return LocaleKeys.shareAction_clipboard.tr(); } } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart b/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart index 2c89de5381..13d07c62b9 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart @@ -21,6 +21,7 @@ enum DocumentExportType { json, markdown, text, + html, } class DocumentExporter { @@ -56,6 +57,11 @@ class DocumentExporter { return FlowyResult.success(markdown); case DocumentExportType.text: throw UnimplementedError(); + case DocumentExportType.html: + final html = documentToHTML( + document, + ); + return FlowyResult.success(html); } }, (error) => FlowyResult.failure(error), diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 7867b5cf87..87d1ae7047 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -92,6 +92,8 @@ "buttonText": "Share", "workInProgress": "Coming soon", "markdown": "Markdown", + "html": "HTML", + "clipboard": "Copy to clipboard", "csv": "CSV", "copyLink": "Copy Link" }, diff --git a/frontend/resources/translations/zh-CN.json b/frontend/resources/translations/zh-CN.json index a9669470b3..b43d21f8a5 100644 --- a/frontend/resources/translations/zh-CN.json +++ b/frontend/resources/translations/zh-CN.json @@ -70,6 +70,8 @@ "buttonText": "分享", "workInProgress": "敬请期待", "markdown": "Markdown", + "html": "HTML", + "clipboard": "拷贝到剪贴板", "csv": "CSV", "copyLink": "复制链接" }, diff --git a/frontend/rust-lib/flowy-document/src/entities.rs b/frontend/rust-lib/flowy-document/src/entities.rs index 65e9dcf820..ad5912dfeb 100644 --- a/frontend/rust-lib/flowy-document/src/entities.rs +++ b/frontend/rust-lib/flowy-document/src/entities.rs @@ -339,6 +339,7 @@ pub enum ExportType { Text = 0, Markdown = 1, Link = 2, + HTML = 3, } impl From for ExportType { @@ -347,6 +348,7 @@ impl From for ExportType { 0 => ExportType::Text, 1 => ExportType::Markdown, 2 => ExportType::Link, + 3 => ExportType::HTML, _ => { tracing::error!("🔴Invalid export type: {}", val); ExportType::Text