| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 | import 'package:appflowy/plugins/document/presentation/plugins/board/board_menu_item.dart';import 'package:appflowy/plugins/document/presentation/plugins/board/board_node_widget.dart';import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart';import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_menu_item.dart';import 'package:appflowy/plugins/document/presentation/plugins/grid/grid_node_widget.dart';import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_node_widget.dart';import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/auto_completion_plugins.dart';import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart';import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_toolbar_item.dart';import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';import 'package:appflowy_editor/appflowy_editor.dart';import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';import 'package:dartz/dartz.dart' as dartz;import 'package:flowy_infra_ui/widget/error_page.dart';import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:intl/intl.dart';import '../../startup/startup.dart';import 'application/doc_bloc.dart';import 'editor_styles.dart';import 'presentation/banner.dart';class DocumentPage extends StatefulWidget {  final VoidCallback onDeleted;  final ViewPB view;  DocumentPage({    required this.view,    required this.onDeleted,    Key? key,  }) : super(key: ValueKey(view.id));  @override  State<DocumentPage> createState() => _DocumentPageState();}class _DocumentPageState extends State<DocumentPage> {  late DocumentBloc documentBloc;  @override  void initState() {    // The appflowy editor use Intl as localization, set the default language as fallback.    Intl.defaultLocale = 'en_US';    documentBloc = getIt<DocumentBloc>(param1: super.widget.view)      ..add(const DocumentEvent.initial());    super.initState();  }  @override  void dispose() {    documentBloc.close();    super.dispose();  }  @override  Widget build(BuildContext context) {    return MultiBlocProvider(      providers: [        BlocProvider<DocumentBloc>.value(value: documentBloc),      ],      child:          BlocBuilder<DocumentBloc, DocumentState>(builder: (context, state) {        return state.loadingState.map(          loading: (_) => SizedBox.expand(            child: Container(color: Colors.transparent),          ),          finish: (result) => result.successOrFail.fold(            (_) {              if (state.forceClose) {                widget.onDeleted();                return const SizedBox();              } else if (documentBloc.editorState == null) {                return const SizedBox();              } else {                return _renderDocument(context, state);              }            },            (err) => FlowyErrorPage(err.toString()),          ),        );      }),    );  }  Widget _renderDocument(BuildContext context, DocumentState state) {    return Column(      children: [        if (state.isDeleted) _renderBanner(context),        // AppFlowy Editor        const _AppFlowyEditorPage(),      ],    );  }  Widget _renderBanner(BuildContext context) {    return DocumentBanner(      onRestore: () =>          context.read<DocumentBloc>().add(const DocumentEvent.restorePage()),      onDelete: () => context          .read<DocumentBloc>()          .add(const DocumentEvent.deletePermanently()),    );  }}class _AppFlowyEditorPage extends StatefulWidget {  const _AppFlowyEditorPage({    Key? key,  }) : super(key: key);  @override  State<_AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();}class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {  late DocumentBloc documentBloc;  late EditorState editorState;  String? get openAIKey => documentBloc.state.userProfilePB?.openaiKey;  @override  void initState() {    super.initState();    documentBloc = context.read<DocumentBloc>();    editorState = documentBloc.editorState ?? EditorState.empty();  }  @override  Widget build(BuildContext context) {    final theme = Theme.of(context);    final autoFocusParamters = _autoFocusParamters();    final editor = AppFlowyEditor(      editorState: editorState,      autoFocus: autoFocusParamters.value1,      focusedSelection: autoFocusParamters.value2,      customBuilders: {        // Divider        kDividerType: DividerWidgetBuilder(),        // Math Equation        kMathEquationType: MathEquationNodeWidgetBuidler(),        // Code Block        kCodeBlockType: CodeBlockNodeWidgetBuilder(),        // Board        kBoardType: BoardNodeWidgetBuilder(),        // Grid        kGridType: GridNodeWidgetBuilder(),        // Card        kCalloutType: CalloutNodeWidgetBuilder(),        // Auto Generator,        kAutoCompletionInputType: AutoCompletionInputBuilder(),        // Cover        kCoverType: CoverNodeWidgetBuilder(),        // Smart Edit,        kSmartEditType: SmartEditInputBuilder(),      },      shortcutEvents: [        // Divider        insertDividerEvent,        // Code Block        enterInCodeBlock,        ignoreKeysInCodeBlock,        pasteInCodeBlock,      ],      selectionMenuItems: [        // Divider        dividerMenuItem,        // Math Equation        mathEquationMenuItem,        // Code Block        codeBlockMenuItem,        // Emoji        emojiMenuItem,        // Board        boardMenuItem,        // Grid        gridMenuItem,        // Callout        calloutMenuItem,        // AI        // enable open ai features if needed.        if (openAIKey != null && openAIKey!.isNotEmpty) ...[          autoGeneratorMenuItem,        ],      ],      toolbarItems: [        if (openAIKey != null && openAIKey!.isNotEmpty) ...[          smartEditItem,        ]      ],      themeData: theme.copyWith(extensions: [        ...theme.extensions.values,        customEditorTheme(context),        ...customPluginTheme(context),      ]),    );    return Expanded(      child: Center(        child: Container(          constraints: const BoxConstraints(            maxWidth: double.infinity,          ),          child: editor,        ),      ),    );  }  @override  void dispose() {    _clearTemporaryNodes();    super.dispose();  }  Future<void> _clearTemporaryNodes() async {    final document = editorState.document;    if (document.root.children.isEmpty) {      return;    }    final temporaryNodeTypes = [      kAutoCompletionInputType,      kSmartEditType,    ];    final iterator = NodeIterator(      document: document,      startNode: document.root.children.first,    );    final transaction = editorState.transaction;    while (iterator.moveNext()) {      final node = iterator.current;      if (temporaryNodeTypes.contains(node.type)) {        transaction.deleteNode(node);      }    }    if (transaction.operations.isNotEmpty) {      await editorState.apply(transaction, withUpdateCursor: false);    }  }  dartz.Tuple2<bool, Selection?> _autoFocusParamters() {    if (editorState.document.isEmpty) {      return dartz.Tuple2(true, Selection.single(path: [0], startOffset: 0));    }    final texts = editorState.document.root.children.whereType<TextNode>();    if (texts.every((element) => element.toPlainText().isEmpty)) {      return dartz.Tuple2(        true,        Selection.single(path: texts.first.path, startOffset: 0),      );    }    return const dartz.Tuple2(false, null);  }}
 |