mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: the share markdown feature doesn't work in 0.2.0 (#2756)
* chore: bump version 0.2.0 * fix: the share markdown feature doesn't work in 0.2.0 * test: add integration test for share button
This commit is contained in:
parent
2ecd0a67ad
commit
ed04e247ba
@ -0,0 +1,75 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'util/mock/mock_file_picker.dart';
|
||||||
|
import 'util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('share markdown in document page', () {
|
||||||
|
const location = 'markdown';
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
await TestFolder.cleanTestLocation(location);
|
||||||
|
await TestFolder.setTestLocation(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await TestFolder.cleanTestLocation(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await TestFolder.cleanTestLocation(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('click the share button in document page', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// expect to see a readme page
|
||||||
|
tester.expectToSeePageName(readme);
|
||||||
|
|
||||||
|
// mock the file picker
|
||||||
|
final path = await mockSaveFilePath(location, 'test.md');
|
||||||
|
// click the share button and select markdown
|
||||||
|
await tester.tapShareButton();
|
||||||
|
await tester.tapMarkdownButton();
|
||||||
|
|
||||||
|
// expect to see the success dialog
|
||||||
|
tester.expectToExportSuccess();
|
||||||
|
|
||||||
|
final file = File(path);
|
||||||
|
final isExist = file.existsSync();
|
||||||
|
expect(isExist, true);
|
||||||
|
final markdown = file.readAsStringSync();
|
||||||
|
expect(markdown, expectedMarkdown);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedMarkdown = r'''
|
||||||
|
# Welcome to AppFlowy!
|
||||||
|
## Here are the basics
|
||||||
|
- [ ] Click anywhere and just start typing.
|
||||||
|
- [ ] Highlight any text, and use the editing menu to _style_ **your** <u>writing</u> `however` you ~~like.~~
|
||||||
|
- [ ] As soon as you type `/` a menu will pop up. Select different types of content blocks you can add.
|
||||||
|
- [ ] Type `/` followed by `/bullet` or `/num` to create a list.
|
||||||
|
- [x] Click `+ New Page `button at the bottom of your sidebar to add a new page.
|
||||||
|
- [ ] Click `+` next to any page title in the sidebar to quickly add a new subpage, `Document`, `Grid`, or `Kanban Board`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keyboard shortcuts, markdown, and code block
|
||||||
|
1. Keyboard shortcuts [guide](https://appflowy.gitbook.io/docs/essential-documentation/shortcuts)
|
||||||
|
1. Markdown [reference](https://appflowy.gitbook.io/docs/essential-documentation/markdown)
|
||||||
|
1. Type `/code` to insert a code block
|
||||||
|
|
||||||
|
## Have a question❓
|
||||||
|
> Click `?` at the bottom right for help and support.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
''';
|
@ -2,11 +2,13 @@ import 'dart:ui';
|
|||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/banner.dart';
|
import 'package:appflowy/plugins/document/presentation/banner.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||||
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'base.dart';
|
import 'base.dart';
|
||||||
@ -97,4 +99,27 @@ extension AppFlowyLaunch on WidgetTester {
|
|||||||
);
|
);
|
||||||
await tapButton(restoreButton);
|
await tapButton(restoreButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapShareButton() async {
|
||||||
|
final shareButton = find.byWidgetPredicate(
|
||||||
|
(widget) => widget is DocumentShareButton,
|
||||||
|
);
|
||||||
|
await tapButton(shareButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapMarkdownButton() async {
|
||||||
|
final markdownButton = find.textContaining(
|
||||||
|
LocaleKeys.shareAction_markdown.tr(),
|
||||||
|
);
|
||||||
|
await tapButton(markdownButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectToExportSuccess() {
|
||||||
|
final exportSuccess = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is FlowyText &&
|
||||||
|
widget.title == LocaleKeys.settings_files_exportFileSuccess.tr(),
|
||||||
|
);
|
||||||
|
expect(exportSuccess, findsOneWidget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/util/file_picker/file_picker_impl.dart';
|
|
||||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||||
|
import 'package:file_picker/src/file_picker.dart' as fp;
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
import '../util.dart';
|
import '../util.dart';
|
||||||
|
|
||||||
class MockFilePicker extends FilePicker {
|
class MockFilePicker implements FilePickerService {
|
||||||
MockFilePicker({
|
MockFilePicker({
|
||||||
required this.mockPath,
|
required this.mockPath,
|
||||||
});
|
});
|
||||||
@ -15,6 +15,34 @@ class MockFilePicker extends FilePicker {
|
|||||||
Future<String?> getDirectoryPath({String? title}) {
|
Future<String?> getDirectoryPath({String? title}) {
|
||||||
return Future.value(mockPath);
|
return Future.value(mockPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> saveFile({
|
||||||
|
String? dialogTitle,
|
||||||
|
String? fileName,
|
||||||
|
String? initialDirectory,
|
||||||
|
fp.FileType type = fp.FileType.any,
|
||||||
|
List<String>? allowedExtensions,
|
||||||
|
bool lockParentWindow = false,
|
||||||
|
}) {
|
||||||
|
return Future.value(mockPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<FilePickerResult?> pickFiles({
|
||||||
|
String? dialogTitle,
|
||||||
|
String? initialDirectory,
|
||||||
|
fp.FileType type = fp.FileType.any,
|
||||||
|
List<String>? allowedExtensions,
|
||||||
|
Function(fp.FilePickerStatus p1)? onFileLoading,
|
||||||
|
bool allowCompression = true,
|
||||||
|
bool allowMultiple = false,
|
||||||
|
bool withData = false,
|
||||||
|
bool withReadStream = false,
|
||||||
|
bool lockParentWindow = false,
|
||||||
|
}) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> mockGetDirectoryPath(String? name) async {
|
Future<void> mockGetDirectoryPath(String? name) async {
|
||||||
@ -27,3 +55,15 @@ Future<void> mockGetDirectoryPath(String? name) async {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> mockSaveFilePath(String? name, String fileName) async {
|
||||||
|
final dir = await TestFolder.testLocation(name);
|
||||||
|
final path = p.join(dir.path, fileName);
|
||||||
|
getIt.unregister<FilePickerService>();
|
||||||
|
getIt.registerFactory<FilePickerService>(
|
||||||
|
() => MockFilePicker(
|
||||||
|
mockPath: path,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export 'doc_bloc.dart';
|
export 'doc_bloc.dart';
|
||||||
export 'doc_service.dart';
|
export 'doc_service.dart';
|
||||||
export 'share_bloc.dart';
|
export 'share_bloc.dart';
|
||||||
export 'share_service.dart';
|
|
||||||
|
@ -1,65 +1,46 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:appflowy/plugins/document/application/share_service.dart';
|
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/divider_node_parser.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/math_equation_node_parser.dart';
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/code_block_node_parser.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
|
||||||
show Document, documentToMarkdown;
|
|
||||||
part 'share_bloc.freezed.dart';
|
part 'share_bloc.freezed.dart';
|
||||||
|
|
||||||
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
||||||
ShareService service;
|
DocShareBloc({
|
||||||
ViewPB view;
|
required this.view,
|
||||||
DocShareBloc({required this.view, required this.service})
|
}) : super(const DocShareState.initial()) {
|
||||||
: super(const DocShareState.initial()) {
|
on<ShareMarkdown>(_onShareMarkdown);
|
||||||
on<DocShareEvent>((event, emit) async {
|
|
||||||
await event.map(
|
|
||||||
shareMarkdown: (ShareMarkdown shareMarkdown) async {
|
|
||||||
await service.exportMarkdown(view).then((result) {
|
|
||||||
result.fold(
|
|
||||||
(value) => emit(
|
|
||||||
DocShareState.finish(
|
|
||||||
left(_saveMarkdown(value, shareMarkdown.path)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(error) => emit(DocShareState.finish(right(error))),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
emit(const DocShareState.loading());
|
|
||||||
},
|
|
||||||
shareLink: (ShareLink value) {},
|
|
||||||
shareText: (ShareText value) {},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExportDataPB _saveMarkdown(ExportDataPB value, String path) {
|
final ViewPB view;
|
||||||
final markdown = _convertDocumentToMarkdown(value);
|
|
||||||
value.data = markdown;
|
|
||||||
File(path).writeAsStringSync(markdown);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _convertDocumentToMarkdown(ExportDataPB value) {
|
Future<void> _onShareMarkdown(
|
||||||
final json = jsonDecode(value.data);
|
ShareMarkdown event,
|
||||||
final document = Document.fromJson(json);
|
Emitter<DocShareState> emit,
|
||||||
return documentToMarkdown(
|
) async {
|
||||||
document,
|
emit(const DocShareState.loading());
|
||||||
customParsers: [
|
|
||||||
const DividerNodeParser(),
|
final documentExporter = DocumentExporter(view);
|
||||||
const MathEquationNodeParser(),
|
final result = await documentExporter.export(DocumentExportType.markdown);
|
||||||
const CodeBlockNodeParser(),
|
emit(
|
||||||
],
|
DocShareState.finish(
|
||||||
|
result.fold(
|
||||||
|
(error) => right(error),
|
||||||
|
(markdown) => left(_saveMarkdownToPath(markdown, event.path)),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExportDataPB _saveMarkdownToPath(String markdown, String path) {
|
||||||
|
File(path).writeAsStringSync(markdown);
|
||||||
|
return ExportDataPB()
|
||||||
|
..data = markdown
|
||||||
|
..exportType = ExportType.Markdown;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
|
||||||
|
|
||||||
class ShareService {
|
|
||||||
Future<Either<ExportDataPB, FlowyError>> export(
|
|
||||||
ViewPB view,
|
|
||||||
ExportType type,
|
|
||||||
) {
|
|
||||||
// var payload = ExportPayloadPB.create()
|
|
||||||
// ..viewId = view.id
|
|
||||||
// ..exportType = type
|
|
||||||
// ..documentVersion = DocumentVersionPB.V1;
|
|
||||||
|
|
||||||
// return DocumentEventExportDocument(payload).send();
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Either<ExportDataPB, FlowyError>> exportText(ViewPB view) {
|
|
||||||
return export(view, ExportType.Text);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Either<ExportDataPB, FlowyError>> exportMarkdown(ViewPB view) {
|
|
||||||
return export(view, ExportType.Markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Either<ExportDataPB, FlowyError>> exportURL(ViewPB view) {
|
|
||||||
return export(view, ExportType.Link);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,25 @@
|
|||||||
library document_plugin;
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/plugins/document/application/share_bloc.dart';
|
import 'package:appflowy/plugins/document/application/share_bloc.dart';
|
||||||
|
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.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_ui/widget/rounded_button.dart';
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class DocumentShareButton extends StatelessWidget {
|
class DocumentShareButton extends StatelessWidget {
|
||||||
|
const DocumentShareButton({
|
||||||
|
super.key,
|
||||||
|
required this.view,
|
||||||
|
});
|
||||||
|
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
DocumentShareButton({Key? key, required this.view})
|
|
||||||
: super(key: ValueKey(view.hashCode));
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -28,12 +27,10 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
create: (context) => getIt<DocShareBloc>(param1: view),
|
create: (context) => getIt<DocShareBloc>(param1: view),
|
||||||
child: BlocListener<DocShareBloc, DocShareState>(
|
child: BlocListener<DocShareBloc, DocShareState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
state.map(
|
state.mapOrNull(
|
||||||
initial: (_) {},
|
|
||||||
loading: (_) {},
|
|
||||||
finish: (state) {
|
finish: (state) {
|
||||||
state.successOrFail.fold(
|
state.successOrFail.fold(
|
||||||
_handleExportData,
|
(data) => _handleExportData(context, data),
|
||||||
_handleExportError,
|
_handleExportError,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -52,27 +49,30 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleExportData(ExportDataPB exportData) {
|
void _handleExportData(BuildContext context, ExportDataPB exportData) {
|
||||||
switch (exportData.exportType) {
|
switch (exportData.exportType) {
|
||||||
case ExportType.Link:
|
|
||||||
break;
|
|
||||||
case ExportType.Markdown:
|
case ExportType.Markdown:
|
||||||
FlutterClipboard.copy(exportData.data)
|
showSnackBarMessage(
|
||||||
.then((value) => Log.info('copied to clipboard'));
|
context,
|
||||||
|
LocaleKeys.settings_files_exportFileSuccess.tr(),
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
case ExportType.Link:
|
||||||
case ExportType.Text:
|
case ExportType.Text:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleExportError(FlowyError error) {}
|
void _handleExportError(FlowyError error) {
|
||||||
|
showMessageToast(error.msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShareActionList extends StatelessWidget {
|
class ShareActionList extends StatelessWidget {
|
||||||
const ShareActionList({
|
const ShareActionList({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.view,
|
required this.view,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
|
|
||||||
@ -94,20 +94,14 @@ class ShareActionList extends StatelessWidget {
|
|||||||
onSelected: (action, controller) async {
|
onSelected: (action, controller) async {
|
||||||
switch (action.inner) {
|
switch (action.inner) {
|
||||||
case ShareAction.markdown:
|
case ShareAction.markdown:
|
||||||
final exportPath = await FilePicker.platform.saveFile(
|
final exportPath = await getIt<FilePickerService>().saveFile(
|
||||||
dialogTitle: '',
|
dialogTitle: '',
|
||||||
fileName: '${view.name}.md',
|
fileName: '${view.name}.md',
|
||||||
);
|
);
|
||||||
if (exportPath != null) {
|
if (exportPath != null) {
|
||||||
docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
|
docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
|
||||||
showMessageToast('Exported to: $exportPath');
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// case ShareAction.copyLink:
|
|
||||||
// NavigatorAlertDialog(
|
|
||||||
// title: LocaleKeys.shareAction_workInProgress.tr(),
|
|
||||||
// ).show(context);
|
|
||||||
// break;
|
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
},
|
},
|
||||||
@ -117,7 +111,6 @@ class ShareActionList extends StatelessWidget {
|
|||||||
|
|
||||||
enum ShareAction {
|
enum ShareAction {
|
||||||
markdown,
|
markdown,
|
||||||
// copyLink,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShareActionWrapper extends ActionCell {
|
class ShareActionWrapper extends ActionCell {
|
||||||
@ -132,8 +125,6 @@ class ShareActionWrapper extends ActionCell {
|
|||||||
switch (inner) {
|
switch (inner) {
|
||||||
case ShareAction.markdown:
|
case ShareAction.markdown:
|
||||||
return LocaleKeys.shareAction_markdown.tr();
|
return LocaleKeys.shareAction_markdown.tr();
|
||||||
// case ShareAction.copyLink:
|
|
||||||
// return LocaleKeys.shareAction_copyLink.tr();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,9 +110,8 @@ void _resolveHomeDeps(GetIt getIt) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// share
|
// share
|
||||||
getIt.registerLazySingleton<ShareService>(() => ShareService());
|
|
||||||
getIt.registerFactoryParam<DocShareBloc, ViewPB, void>(
|
getIt.registerFactoryParam<DocShareBloc, ViewPB, void>(
|
||||||
(view, _) => DocShareBloc(view: view, service: getIt<ShareService>()),
|
(view, _) => DocShareBloc(view: view),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,4 +34,23 @@ class FilePicker implements FilePickerService {
|
|||||||
);
|
);
|
||||||
return FilePickerResult(result?.files ?? []);
|
return FilePickerResult(result?.files ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> saveFile({
|
||||||
|
String? dialogTitle,
|
||||||
|
String? fileName,
|
||||||
|
String? initialDirectory,
|
||||||
|
fp.FileType type = fp.FileType.any,
|
||||||
|
List<String>? allowedExtensions,
|
||||||
|
bool lockParentWindow = false,
|
||||||
|
}) {
|
||||||
|
return fp.FilePicker.platform.saveFile(
|
||||||
|
dialogTitle: dialogTitle,
|
||||||
|
fileName: fileName,
|
||||||
|
initialDirectory: initialDirectory,
|
||||||
|
type: type,
|
||||||
|
allowedExtensions: allowedExtensions,
|
||||||
|
lockParentWindow: lockParentWindow,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,14 @@ abstract class FilePickerService {
|
|||||||
bool lockParentWindow = false,
|
bool lockParentWindow = false,
|
||||||
}) async =>
|
}) async =>
|
||||||
throw UnimplementedError('pickFiles() has not been implemented.');
|
throw UnimplementedError('pickFiles() has not been implemented.');
|
||||||
|
|
||||||
|
Future<String?> saveFile({
|
||||||
|
String? dialogTitle,
|
||||||
|
String? fileName,
|
||||||
|
String? initialDirectory,
|
||||||
|
FileType type = FileType.any,
|
||||||
|
List<String>? allowedExtensions,
|
||||||
|
bool lockParentWindow = false,
|
||||||
|
}) async =>
|
||||||
|
throw UnimplementedError('saveFile() has not been implemented.');
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||||
|
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/code_block_node_parser.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/divider_node_parser.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/math_equation_node_parser.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
enum DocumentExportType {
|
||||||
|
json,
|
||||||
|
markdown,
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
|
||||||
|
class DocumentExporter {
|
||||||
|
const DocumentExporter(
|
||||||
|
this.view,
|
||||||
|
);
|
||||||
|
|
||||||
|
final ViewPB view;
|
||||||
|
|
||||||
|
Future<Either<FlowyError, String>> export(DocumentExportType type) async {
|
||||||
|
final documentService = DocumentService();
|
||||||
|
final result = await documentService.openDocument(view: view);
|
||||||
|
return result.fold((error) => left(error), (r) {
|
||||||
|
final document = r.toDocument();
|
||||||
|
if (document == null) {
|
||||||
|
return left(
|
||||||
|
FlowyError(
|
||||||
|
msg: LocaleKeys.settings_files_exportFileFail.tr(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case DocumentExportType.json:
|
||||||
|
return right(jsonEncode(document));
|
||||||
|
case DocumentExportType.markdown:
|
||||||
|
final markdown = documentToMarkdown(
|
||||||
|
document,
|
||||||
|
customParsers: [
|
||||||
|
const DividerNodeParser(),
|
||||||
|
const MathEquationNodeParser(),
|
||||||
|
const CodeBlockNodeParser(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return right(markdown);
|
||||||
|
case DocumentExportType.text:
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -39,3 +39,14 @@ void showMessageToast(String message) {
|
|||||||
toastDuration: const Duration(seconds: 3),
|
toastDuration: const Duration(seconds: 3),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showSnackBarMessage(BuildContext context, String message) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: FlowyText(
|
||||||
|
message,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
|
||||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/settings_file_exporter_cubit.dart';
|
import 'package:appflowy/workspace/application/settings/settings_file_exporter_cubit.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/share/export_service.dart';
|
import 'package:appflowy/workspace/application/settings/share/export_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -248,18 +246,17 @@ class _AppFlowyFileExporter {
|
|||||||
) async {
|
) async {
|
||||||
final failedFileNames = <String>[];
|
final failedFileNames = <String>[];
|
||||||
final Map<String, int> names = {};
|
final Map<String, int> names = {};
|
||||||
final documentService = DocumentService();
|
|
||||||
for (final view in views) {
|
for (final view in views) {
|
||||||
String? content;
|
String? content;
|
||||||
String? fileExtension;
|
String? fileExtension;
|
||||||
switch (view.layout) {
|
switch (view.layout) {
|
||||||
case ViewLayoutPB.Document:
|
case ViewLayoutPB.Document:
|
||||||
final document = await documentService.openDocument(view: view);
|
final documentExporter = DocumentExporter(view);
|
||||||
document.fold((l) => Log.error(l), (r) {
|
final result = await documentExporter.export(
|
||||||
final json = r.toDocument()?.toJson();
|
DocumentExportType.json,
|
||||||
if (json != null) {
|
);
|
||||||
content = jsonEncode(json);
|
result.fold((l) => Log.error(l), (json) {
|
||||||
}
|
content = json;
|
||||||
});
|
});
|
||||||
fileExtension = 'afdocument';
|
fileExtension = 'afdocument';
|
||||||
break;
|
break;
|
||||||
|
@ -169,7 +169,7 @@ script = ["""
|
|||||||
cd appflowy_flutter/
|
cd appflowy_flutter/
|
||||||
flutter clean
|
flutter clean
|
||||||
flutter pub get
|
flutter pub get
|
||||||
flutter build ${TARGET_OS} --${BUILD_FLAG}
|
flutter build ${TARGET_OS} --${BUILD_FLAG} --verbose
|
||||||
"""]
|
"""]
|
||||||
script_runner = "@shell"
|
script_runner = "@shell"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user