Browse Source

feat: add cover migration for document (#1929)

* feat: add cover migration for document

* fix: should not delete the cover when selecting all

* fix: chinese characters for openai
Lucas.Xu 2 years ago
parent
commit
7ff4cecd09

+ 62 - 17
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<DocumentEvent, DocumentState> {
   Future<void> _initial(Initial value, Emitter<DocumentState> 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<DocumentEvent, DocumentState> {
     );
   }
 
-  void _listenOnDocumentChange() {
-    _subscription = editorState?.transactionStream.listen((transaction) {
+  Future<void> _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<DocumentEvent, DocumentState> {
         );
       });
     });
+    // 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<void> 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);
+    }
+  }
+}

+ 14 - 2
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']));
     }

+ 2 - 14
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 {

+ 3 - 2
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<TextNode>().toList();
-  final List<Node> nonTextNodes =
-      nodes.where((node) => node is! TextNode).toList(growable: false);
+  final List<Node> nonTextNodes = nodes
+      .where((node) => node is! TextNode && node.selectable != null)
+      .toList(growable: false);
 
   final transaction = editorState.transaction;
   List<int>? cancelNumberListPath;