mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 14:47:13 -04:00
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
This commit is contained in:
parent
675c833f07
commit
7ff4cecd09
4 changed files with 81 additions and 35 deletions
|
@ -1,15 +1,18 @@
|
||||||
import 'dart:convert';
|
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/plugins/trash/application/trash_service.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||||
import 'package:appflowy/plugins/document/application/doc_service.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_backend/protobuf/flowy-user/user_profile.pbserver.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.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-folder/trash.pb.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-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:dartz/dartz.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 {
|
Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
|
||||||
final userProfile = await UserBackendService.getCurrentUserProfile();
|
final userProfile = await UserBackendService.getCurrentUserProfile();
|
||||||
if (userProfile.isRight()) {
|
if (userProfile.isRight()) {
|
||||||
emit(
|
return emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
loadingState: DocumentLoadingState.finish(
|
loadingState: DocumentLoadingState.finish(
|
||||||
right(userProfile.asRight()),
|
right(userProfile.asRight()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
final result = await _documentService.openDocument(view: view);
|
final result = await _documentService.openDocument(view: view);
|
||||||
result.fold(
|
return result.fold(
|
||||||
(documentData) {
|
(documentData) async {
|
||||||
final document = Document.fromJson(jsonDecode(documentData.content));
|
await _initEditorState(documentData).whenComplete(() {
|
||||||
editorState = EditorState(document: document);
|
emit(
|
||||||
_listenOnDocumentChange();
|
state.copyWith(
|
||||||
emit(
|
loadingState: DocumentLoadingState.finish(left(unit)),
|
||||||
state.copyWith(
|
userProfilePB: userProfile.asLeft(),
|
||||||
loadingState: DocumentLoadingState.finish(left(unit)),
|
),
|
||||||
userProfilePB: userProfile.asLeft(),
|
);
|
||||||
),
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
(err) {
|
(err) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
loadingState: DocumentLoadingState.finish(right(err)),
|
loadingState: DocumentLoadingState.finish(right(err)),
|
||||||
|
@ -127,8 +128,13 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenOnDocumentChange() {
|
Future<void> _initEditorState(DocumentDataPB documentData) async {
|
||||||
_subscription = editorState?.transactionStream.listen((transaction) {
|
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());
|
final json = jsonEncode(TransactionAdaptor(transaction).toJson());
|
||||||
_documentService
|
_documentService
|
||||||
.applyEdit(docId: view.id, operations: json)
|
.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;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -91,7 +91,13 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return Right(TextCompletionResponse.fromJson(json.decode(response.body)));
|
return Right(
|
||||||
|
TextCompletionResponse.fromJson(
|
||||||
|
json.decode(
|
||||||
|
utf8.decode(response.bodyBytes),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Left(OpenAIError.fromJson(json.decode(response.body)['error']));
|
return Left(OpenAIError.fromJson(json.decode(response.body)['error']));
|
||||||
}
|
}
|
||||||
|
@ -119,7 +125,13 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return Right(TextEditResponse.fromJson(json.decode(response.body)));
|
return Right(
|
||||||
|
TextEditResponse.fromJson(
|
||||||
|
json.decode(
|
||||||
|
utf8.decode(response.bodyBytes),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return Left(OpenAIError.fromJson(json.decode(response.body)['error']));
|
return Left(OpenAIError.fromJson(json.decode(response.body)['error']));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import 'package:appflowy/plugins/document/document.dart';
|
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/plugin/plugin.dart';
|
||||||
import 'package:appflowy/startup/startup.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/home/menu/app/header/import/import_panel.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.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:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
|
@ -61,12 +60,7 @@ class AddButton extends StatelessWidget {
|
||||||
},
|
},
|
||||||
onSelected: (action, controller) {
|
onSelected: (action, controller) {
|
||||||
if (action is AddButtonActionWrapper) {
|
if (action is AddButtonActionWrapper) {
|
||||||
Document? document;
|
onSelected(action.pluginBuilder, null);
|
||||||
if (action.pluginType == PluginType.editor) {
|
|
||||||
// initialize the document if needed.
|
|
||||||
document = buildInitialDocument();
|
|
||||||
}
|
|
||||||
onSelected(action.pluginBuilder, document);
|
|
||||||
}
|
}
|
||||||
if (action is ImportActionWrapper) {
|
if (action is ImportActionWrapper) {
|
||||||
showImportPanel(context, (document) {
|
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 {
|
class AddButtonActionWrapper extends ActionCell {
|
||||||
|
|
|
@ -12,8 +12,9 @@ ShortcutEventHandler backspaceEventHandler = (editorState, event) {
|
||||||
nodes = selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
|
nodes = selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
|
||||||
selection = selection.isBackward ? selection : selection.reversed;
|
selection = selection.isBackward ? selection : selection.reversed;
|
||||||
final textNodes = nodes.whereType<TextNode>().toList();
|
final textNodes = nodes.whereType<TextNode>().toList();
|
||||||
final List<Node> nonTextNodes =
|
final List<Node> nonTextNodes = nodes
|
||||||
nodes.where((node) => node is! TextNode).toList(growable: false);
|
.where((node) => node is! TextNode && node.selectable != null)
|
||||||
|
.toList(growable: false);
|
||||||
|
|
||||||
final transaction = editorState.transaction;
|
final transaction = editorState.transaction;
|
||||||
List<int>? cancelNumberListPath;
|
List<int>? cancelNumberListPath;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue