diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart index 5d0c0cd0f9..ae47a120ca 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart @@ -1,15 +1,18 @@ import 'dart:convert'; +import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart'; import 'package:appflowy/plugins/trash/application/trash_service.dart'; import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy/plugins/document/application/doc_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart'; import 'package:appflowy_editor/appflowy_editor.dart' - show EditorState, Document, Transaction; + show EditorState, Document, Transaction, Node; import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:dartz/dartz.dart'; @@ -78,29 +81,27 @@ class DocumentBloc extends Bloc { Future _initial(Initial value, Emitter emit) async { final userProfile = await UserBackendService.getCurrentUserProfile(); if (userProfile.isRight()) { - emit( + return emit( state.copyWith( loadingState: DocumentLoadingState.finish( right(userProfile.asRight()), ), ), ); - return; } final result = await _documentService.openDocument(view: view); - result.fold( - (documentData) { - final document = Document.fromJson(jsonDecode(documentData.content)); - editorState = EditorState(document: document); - _listenOnDocumentChange(); - emit( - state.copyWith( - loadingState: DocumentLoadingState.finish(left(unit)), - userProfilePB: userProfile.asLeft(), - ), - ); + return result.fold( + (documentData) async { + await _initEditorState(documentData).whenComplete(() { + emit( + state.copyWith( + loadingState: DocumentLoadingState.finish(left(unit)), + userProfilePB: userProfile.asLeft(), + ), + ); + }); }, - (err) { + (err) async { emit( state.copyWith( loadingState: DocumentLoadingState.finish(right(err)), @@ -127,8 +128,13 @@ class DocumentBloc extends Bloc { ); } - void _listenOnDocumentChange() { - _subscription = editorState?.transactionStream.listen((transaction) { + Future _initEditorState(DocumentDataPB documentData) async { + final document = Document.fromJson(jsonDecode(documentData.content)); + final editorState = EditorState(document: document); + this.editorState = editorState; + + // listen on document change + _subscription = editorState.transactionStream.listen((transaction) { final json = jsonEncode(TransactionAdaptor(transaction).toJson()); _documentService .applyEdit(docId: view.id, operations: json) @@ -139,6 +145,15 @@ class DocumentBloc extends Bloc { ); }); }); + // log + if (kDebugMode) { + editorState.logConfiguration.handler = (log) { + Log.debug(log); + }; + } + // migration + final migration = DocumentMigration(editorState: editorState); + await migration.apply(); } } @@ -215,3 +230,33 @@ class TransactionAdaptor { return json; } } + +class DocumentMigration { + const DocumentMigration({ + required this.editorState, + }); + + final EditorState editorState; + + /// Migrate the document to the latest version. + Future apply() async { + final transaction = editorState.transaction; + + // A temporary solution to migrate the document to the latest version. + // Once the editor is stable, we can remove this. + + // cover plugin + if (editorState.document.nodeAtPath([0])?.type != kCoverType) { + transaction.insertNode( + [0], + Node(type: kCoverType), + ); + } + + transaction.afterSelection = null; + + if (transaction.operations.isNotEmpty) { + editorState.apply(transaction); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart index 3bd41e2d52..37a5279662 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart @@ -91,7 +91,13 @@ class HttpOpenAIRepository implements OpenAIRepository { ); if (response.statusCode == 200) { - return Right(TextCompletionResponse.fromJson(json.decode(response.body))); + return Right( + TextCompletionResponse.fromJson( + json.decode( + utf8.decode(response.bodyBytes), + ), + ), + ); } else { return Left(OpenAIError.fromJson(json.decode(response.body)['error'])); } @@ -119,7 +125,13 @@ class HttpOpenAIRepository implements OpenAIRepository { ); if (response.statusCode == 200) { - return Right(TextEditResponse.fromJson(json.decode(response.body))); + return Right( + TextEditResponse.fromJson( + json.decode( + utf8.decode(response.bodyBytes), + ), + ), + ); } else { return Left(OpenAIError.fromJson(json.decode(response.body)['error'])); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart index 5d1bfee6bc..642cc063da 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart @@ -1,10 +1,9 @@ import 'package:appflowy/plugins/document/document.dart'; -import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/presentation/home/menu/app/header/import/import_panel.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' show Document, Node; +import 'package:appflowy_editor/appflowy_editor.dart' show Document; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -61,12 +60,7 @@ class AddButton extends StatelessWidget { }, onSelected: (action, controller) { if (action is AddButtonActionWrapper) { - Document? document; - if (action.pluginType == PluginType.editor) { - // initialize the document if needed. - document = buildInitialDocument(); - } - onSelected(action.pluginBuilder, document); + onSelected(action.pluginBuilder, null); } if (action is ImportActionWrapper) { showImportPanel(context, (document) { @@ -80,12 +74,6 @@ class AddButton extends StatelessWidget { }, ); } - - Document buildInitialDocument() { - final document = Document.empty(); - document.insert([0], [Node(type: kCoverType)]); - return document; - } } class AddButtonActionWrapper extends ActionCell { diff --git a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart index 49f5da8798..2a1f9db324 100644 --- a/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart +++ b/frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart @@ -12,8 +12,9 @@ ShortcutEventHandler backspaceEventHandler = (editorState, event) { nodes = selection.isBackward ? nodes : nodes.reversed.toList(growable: false); selection = selection.isBackward ? selection : selection.reversed; final textNodes = nodes.whereType().toList(); - final List nonTextNodes = - nodes.where((node) => node is! TextNode).toList(growable: false); + final List nonTextNodes = nodes + .where((node) => node is! TextNode && node.selectable != null) + .toList(growable: false); final transaction = editorState.transaction; List? cancelNumberListPath;