123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- import 'package:appflowy/plugins/document/application/doc_bloc.dart';
- import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
- import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action_button.dart';
- import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
- import 'package:appflowy/plugins/document/presentation/editor_style.dart';
- import 'package:appflowy_editor/appflowy_editor.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter_bloc/flutter_bloc.dart';
- import 'package:tuple/tuple.dart';
- /// Wrapper for the appflowy editor.
- class AppFlowyEditorPage extends StatefulWidget {
- const AppFlowyEditorPage({
- super.key,
- required this.editorState,
- });
- final EditorState editorState;
- @override
- State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
- }
- class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
- final scrollController = ScrollController();
- final slashMenuItems = [
- boardMenuItem,
- gridMenuItem,
- calloutItem,
- dividerMenuItem,
- mathEquationItem,
- codeBlockItem,
- emojiMenuItem,
- autoGeneratorMenuItem,
- ];
- final List<CommandShortcutEvent> commandShortcutEvents = [
- ...codeBlockCommands,
- ...standardCommandShortcutEvents,
- ];
- final List<ToolbarItem> toolbarItems = [
- smartEditItem,
- placeholderItem,
- paragraphItem,
- ...headingItems,
- placeholderItem,
- ...markdownFormatItems,
- placeholderItem,
- quoteItem,
- bulletedListItem,
- numberedListItem,
- placeholderItem,
- linkItem,
- textColorItem,
- highlightColorItem,
- ];
- late final Map<String, BlockComponentBuilder> blockComponentBuilders =
- _customAppFlowyBlockComponentBuilders();
- late final List<CharacterShortcutEvent> characterShortcutEvents = [
- // divider
- convertMinusesToDivider,
- // code block
- ...codeBlockCharacterEvents,
- ...standardCharacterShortcutEvents
- ..removeWhere(
- (element) => element == slashCommand,
- ), // remove the default slash command.
- customSlashCommand(slashMenuItems),
- ];
- late final styleCustomizer = EditorStyleCustomizer(context: context);
- DocumentBloc get documentBloc => context.read<DocumentBloc>();
- @override
- Widget build(BuildContext context) {
- final autoFocusParameters = _computeAutoFocusParameters();
- final editor = AppFlowyEditor.custom(
- editorState: widget.editorState,
- editable: true,
- scrollController: scrollController,
- // setup the auto focus parameters
- autoFocus: autoFocusParameters.item1,
- focusedSelection: autoFocusParameters.item2,
- // setup the theme
- editorStyle: styleCustomizer.style(),
- // customize the block builder
- blockComponentBuilders: blockComponentBuilders,
- // customize the shortcuts
- characterShortcutEvents: characterShortcutEvents,
- commandShortcutEvents: commandShortcutEvents,
- );
- return Center(
- child: Container(
- constraints: const BoxConstraints(
- maxWidth: double.infinity,
- ),
- child: FloatingToolbar(
- items: toolbarItems,
- editorState: widget.editorState,
- scrollController: scrollController,
- child: editor,
- ),
- ),
- );
- }
- Map<String, BlockComponentBuilder> _customAppFlowyBlockComponentBuilders() {
- final standardActions = [
- OptionAction.delete,
- OptionAction.duplicate,
- OptionAction.divider,
- OptionAction.moveUp,
- OptionAction.moveDown,
- ];
- final configuration = BlockComponentConfiguration(
- padding: (_) => const EdgeInsets.symmetric(vertical: 4.0),
- );
- final customBlockComponentBuilderMap = {
- 'document': DocumentComponentBuilder(),
- ParagraphBlockKeys.type: TextBlockComponentBuilder(
- configuration: configuration,
- ),
- TodoListBlockKeys.type: TodoListBlockComponentBuilder(
- configuration: configuration.copyWith(
- placeholderText: (_) => 'To-do',
- ),
- ),
- BulletedListBlockKeys.type: BulletedListBlockComponentBuilder(
- configuration: configuration.copyWith(
- placeholderText: (_) => 'List',
- ),
- ),
- NumberedListBlockKeys.type: NumberedListBlockComponentBuilder(
- configuration: configuration.copyWith(
- placeholderText: (_) => 'List',
- ),
- ),
- QuoteBlockKeys.type: QuoteBlockComponentBuilder(
- configuration: configuration.copyWith(
- placeholderText: (_) => 'Quote',
- ),
- ),
- HeadingBlockKeys.type: HeadingBlockComponentBuilder(
- configuration: configuration.copyWith(
- padding: (_) => const EdgeInsets.only(top: 12.0, bottom: 4.0),
- placeholderText: (node) =>
- 'Heading ${node.attributes[HeadingBlockKeys.level]}',
- ),
- textStyleBuilder: (level) => styleCustomizer.headingStyleBuilder(level),
- ),
- ImageBlockKeys.type: ImageBlockComponentBuilder(),
- BoardBlockKeys.type: BoardBlockComponentBuilder(
- configuration: configuration,
- ),
- GridBlockKeys.type: GridBlockComponentBuilder(
- configuration: configuration,
- ),
- CalloutBlockKeys.type: CalloutBlockComponentBuilder(
- configuration: configuration,
- ),
- DividerBlockKeys.type: DividerBlockComponentBuilder(),
- MathEquationBlockKeys.type: MathEquationBlockComponentBuilder(
- configuration: configuration.copyWith(
- padding: (_) => const EdgeInsets.symmetric(vertical: 20),
- ),
- ),
- CodeBlockKeys.type: CodeBlockComponentBuilder(
- configuration: configuration.copyWith(
- textStyle: (_) => styleCustomizer.codeBlockStyleBuilder(),
- placeholderTextStyle: (_) => styleCustomizer.codeBlockStyleBuilder(),
- ),
- padding: const EdgeInsets.only(
- left: 30,
- right: 30,
- bottom: 36,
- ),
- ),
- AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(),
- SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(),
- };
- final builders = {
- ...standardBlockComponentBuilderMap,
- ...customBlockComponentBuilderMap,
- };
- // customize the action builder. actually, we can customize them in their own builder. Put them here just for convenience.
- for (final entry in builders.entries) {
- if (entry.key == 'document') {
- continue;
- }
- final builder = entry.value;
- // customize the action builder.
- final supportColorBuilderTypes = [
- ParagraphBlockKeys.type,
- HeadingBlockKeys.type,
- BulletedListBlockKeys.type,
- NumberedListBlockKeys.type,
- QuoteBlockKeys.type,
- TodoListBlockKeys.type,
- CalloutBlockKeys.type
- ];
- if (!supportColorBuilderTypes.contains(entry.key)) {
- builder.actionBuilder = (context, state) => OptionActionList(
- blockComponentContext: context,
- blockComponentState: state,
- editorState: widget.editorState,
- actions: standardActions,
- );
- continue;
- }
- final colorAction = [
- OptionAction.divider,
- OptionAction.color,
- ];
- builder.actionBuilder = (context, state) => OptionActionList(
- blockComponentContext: context,
- blockComponentState: state,
- editorState: widget.editorState,
- actions: standardActions + colorAction,
- );
- }
- return builders;
- }
- Tuple2<bool, Selection?> _computeAutoFocusParameters() {
- if (widget.editorState.document.isEmpty) {
- return Tuple2(true, Selection.collapse([0], 0));
- }
- final nodes = widget.editorState.document.root.children
- .where((element) => element.delta != null);
- final isAllEmpty =
- nodes.isNotEmpty && nodes.every((element) => element.delta!.isEmpty);
- if (isAllEmpty) {
- return Tuple2(true, Selection.collapse(nodes.first.path, 0));
- }
- return const Tuple2(false, null);
- }
- }
|