document_page.dart 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import 'package:app_flowy/plugins/document/presentation/plugins/board/board_menu_item.dart';
  2. import 'package:app_flowy/plugins/document/presentation/plugins/board/board_node_widget.dart';
  3. import 'package:app_flowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart';
  4. import 'package:app_flowy/plugins/document/presentation/plugins/grid/grid_node_widget.dart';
  5. import 'package:app_flowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart';
  6. import 'package:app_flowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_plugins.dart';
  7. import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
  8. import 'package:appflowy_editor/appflowy_editor.dart';
  9. import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
  10. import 'package:flowy_infra_ui/widget/error_page.dart';
  11. import 'package:flutter/material.dart';
  12. import 'package:flutter_bloc/flutter_bloc.dart';
  13. import 'package:intl/intl.dart';
  14. import '../../startup/startup.dart';
  15. import 'application/doc_bloc.dart';
  16. import 'editor_styles.dart';
  17. import 'presentation/banner.dart';
  18. class DocumentPage extends StatefulWidget {
  19. final VoidCallback onDeleted;
  20. final ViewPB view;
  21. DocumentPage({
  22. required this.view,
  23. required this.onDeleted,
  24. Key? key,
  25. }) : super(key: ValueKey(view.id));
  26. @override
  27. State<DocumentPage> createState() => _DocumentPageState();
  28. }
  29. class _DocumentPageState extends State<DocumentPage> {
  30. late DocumentBloc documentBloc;
  31. @override
  32. void initState() {
  33. // The appflowy editor use Intl as localization, set the default language as fallback.
  34. Intl.defaultLocale = 'en_US';
  35. documentBloc = getIt<DocumentBloc>(param1: super.widget.view)
  36. ..add(const DocumentEvent.initial());
  37. super.initState();
  38. }
  39. @override
  40. void dispose() {
  41. documentBloc.close();
  42. super.dispose();
  43. }
  44. @override
  45. Widget build(BuildContext context) {
  46. return MultiBlocProvider(
  47. providers: [
  48. BlocProvider<DocumentBloc>.value(value: documentBloc),
  49. ],
  50. child:
  51. BlocBuilder<DocumentBloc, DocumentState>(builder: (context, state) {
  52. return state.loadingState.map(
  53. loading: (_) => SizedBox.expand(
  54. child: Container(color: Colors.transparent),
  55. ),
  56. finish: (result) => result.successOrFail.fold(
  57. (_) {
  58. if (state.forceClose) {
  59. widget.onDeleted();
  60. return const SizedBox();
  61. } else if (documentBloc.editorState == null) {
  62. return const SizedBox();
  63. } else {
  64. return _renderDocument(context, state);
  65. }
  66. },
  67. (err) => FlowyErrorPage(err.toString()),
  68. ),
  69. );
  70. }),
  71. );
  72. }
  73. Widget _renderDocument(BuildContext context, DocumentState state) {
  74. return Column(
  75. children: [
  76. if (state.isDeleted) _renderBanner(context),
  77. // AppFlowy Editor
  78. const _AppFlowyEditorPage(),
  79. ],
  80. );
  81. }
  82. Widget _renderBanner(BuildContext context) {
  83. return DocumentBanner(
  84. onRestore: () =>
  85. context.read<DocumentBloc>().add(const DocumentEvent.restorePage()),
  86. onDelete: () => context
  87. .read<DocumentBloc>()
  88. .add(const DocumentEvent.deletePermanently()),
  89. );
  90. }
  91. }
  92. class _AppFlowyEditorPage extends StatefulWidget {
  93. const _AppFlowyEditorPage({
  94. Key? key,
  95. }) : super(key: key);
  96. @override
  97. State<_AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
  98. }
  99. class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
  100. late DocumentBloc documentBloc;
  101. late EditorState editorState;
  102. String? get openAIKey => documentBloc.state.userProfilePB?.openaiKey;
  103. @override
  104. void initState() {
  105. super.initState();
  106. documentBloc = context.read<DocumentBloc>();
  107. editorState = documentBloc.editorState ?? EditorState.empty();
  108. }
  109. @override
  110. Widget build(BuildContext context) {
  111. final theme = Theme.of(context);
  112. final editor = AppFlowyEditor(
  113. editorState: editorState,
  114. autoFocus: editorState.document.isEmpty,
  115. customBuilders: {
  116. // Divider
  117. kDividerType: DividerWidgetBuilder(),
  118. // Math Equation
  119. kMathEquationType: MathEquationNodeWidgetBuidler(),
  120. // Code Block
  121. kCodeBlockType: CodeBlockNodeWidgetBuilder(),
  122. // Board
  123. kBoardType: BoardNodeWidgetBuilder(),
  124. // Grid
  125. kGridType: GridNodeWidgetBuilder(),
  126. // Card
  127. kCalloutType: CalloutNodeWidgetBuilder(),
  128. // Auto Generator,
  129. kAutoCompletionInputType: AutoCompletionInputBuilder(),
  130. },
  131. shortcutEvents: [
  132. // Divider
  133. insertDividerEvent,
  134. // Code Block
  135. enterInCodeBlock,
  136. ignoreKeysInCodeBlock,
  137. pasteInCodeBlock,
  138. ],
  139. selectionMenuItems: [
  140. // Divider
  141. dividerMenuItem,
  142. // Math Equation
  143. mathEquationMenuItem,
  144. // Code Block
  145. codeBlockMenuItem,
  146. // Emoji
  147. emojiMenuItem,
  148. // Board
  149. boardMenuItem,
  150. // Grid
  151. gridMenuItem,
  152. // Callout
  153. calloutMenuItem,
  154. // AI
  155. // enable open ai features if needed.
  156. if (openAIKey != null && openAIKey!.isNotEmpty) ...[
  157. autoGeneratorMenuItem,
  158. ]
  159. ],
  160. themeData: theme.copyWith(extensions: [
  161. ...theme.extensions.values,
  162. customEditorTheme(context),
  163. ...customPluginTheme(context),
  164. ]),
  165. );
  166. return Expanded(
  167. child: Center(
  168. child: Container(
  169. constraints: const BoxConstraints(
  170. maxWidth: double.infinity,
  171. ),
  172. child: editor,
  173. ),
  174. ),
  175. );
  176. }
  177. @override
  178. void dispose() {
  179. _clearTemporaryNodes();
  180. super.dispose();
  181. }
  182. Future<void> _clearTemporaryNodes() async {
  183. final document = editorState.document;
  184. if (document.root.children.isEmpty) {
  185. return;
  186. }
  187. final temporaryNodeTypes = [
  188. kAutoCompletionInputType,
  189. ];
  190. final iterator = NodeIterator(
  191. document: document,
  192. startNode: document.root.children.first,
  193. );
  194. final transaction = editorState.transaction;
  195. while (iterator.moveNext()) {
  196. final node = iterator.current;
  197. if (temporaryNodeTypes.contains(node.type)) {
  198. transaction.deleteNode(node);
  199. }
  200. }
  201. if (transaction.operations.isNotEmpty) {
  202. await editorState.apply(transaction, withUpdateCursor: false);
  203. }
  204. }
  205. }