Skip to content

Commit

Permalink
feat: support customizing error block (#524)
Browse files Browse the repository at this point in the history
* feat: support customzing error block

* feat: support slash menu in toggle list

* fix: enter in heading block
  • Loading branch information
LucasXu0 authored Oct 10, 2023
1 parent 0abcf7f commit adb05d4
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Future<bool> insertNewLineInType(

if (selection.startIndex == 0 && delta.isEmpty) {
// clear the style

return KeyEventResult.ignored !=
convertToParagraphCommand.execute(editorState);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,34 @@ CharacterShortcutEvent formatSignToHeading = CharacterShortcutEvent(
},
),
);

/// Insert a new block after the heading block.
///
/// - support
/// - desktop
/// - web
/// - mobile
///
CharacterShortcutEvent insertNewLineAfterHeading = CharacterShortcutEvent(
key: 'insert new block after heading',
character: '\n',
handler: (editorState) async {
final selection = editorState.selection;
if (selection == null ||
!selection.isCollapsed ||
selection.startIndex != 0) {
return false;
}
final node = editorState.getNodeAtPath(selection.end.path);
if (node == null || node.type != HeadingBlockKeys.type) {
return false;
}
final transaction = editorState.transaction;
transaction.insertNode(selection.start.path, paragraphNode());
transaction.afterSelection = Selection.collapsed(
Position(path: selection.start.path.next, offset: 0),
);
await editorState.apply(transaction);
return true;
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ final List<CharacterShortcutEvent> standardCharacterShortcutEvents = [
insertNewLineAfterBulletedList,
insertNewLineAfterTodoList,
insertNewLineAfterNumberedList,
insertNewLineAfterHeading,
insertNewLine,

// bulleted list
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';

const errorBlockComponentBuilderKey = 'errorBlockComponentBuilderKey';

typedef BlockActionBuilder = Widget Function(
BlockComponentContext blockComponentContext,
BlockComponentActionState state,
Expand Down Expand Up @@ -121,17 +123,18 @@ class BlockComponentRenderer extends BlockComponentRendererService {
header: header,
footer: footer,
);
final errorBuilder = _builders[errorBlockComponentBuilderKey];
final builder = blockComponentBuilder(node.type);
if (builder == null) {
assert(false, 'no builder for node type(${node.type})');
return _buildPlaceHolderWidget(blockComponentContext);
}
if (!builder.validate(node)) {
assert(
false,
'node(${node.type}) is invalid, attributes: ${node.attributes}, children: ${node.children}',
);
return _buildPlaceHolderWidget(blockComponentContext);
if (builder == null || !builder.validate(node)) {
if (errorBuilder != null) {
return BlockComponentContainer(
node: node,
configuration: errorBuilder.configuration,
builder: (_) => errorBuilder.build(blockComponentContext),
);
} else {
return _buildPlaceHolderWidget(blockComponentContext);
}
}

return BlockComponentContainer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ CharacterShortcutEvent customSlashCommand(
);
}

final supportSlashMenuNodeWhiteList = [
final Set<String> supportSlashMenuNodeWhiteList = {
ParagraphBlockKeys.type,
HeadingBlockKeys.type,
TodoListBlockKeys.type,
BulletedListBlockKeys.type,
NumberedListBlockKeys.type,
QuoteBlockKeys.type,
];
};

SelectionMenuService? _selectionMenuService;
Future<bool> _showSlashMenu(
Expand Down
130 changes: 130 additions & 0 deletions test/customer/custom_error_block_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() async {
await AppFlowyEditorLocalizations.load(
const Locale.fromSubtags(languageCode: 'en'),
);

testWidgets('custom error block', (tester) async {
final editorState = EditorState.blank(withInitialText: false);
editorState.document.insert(
[0],
[
Node(
type: 'not_exist',
attributes: {
'text': 'line 1',
},
),
Node(
type: 'heading',
attributes: {},
),
],
);
final widget = ErrorEditor(
editorState: editorState,
);
await tester.pumpWidget(widget);
await tester.pumpAndSettle();

expect(find.byType(ErrorBlockComponentWidget), findsNWidgets(2));
});
}

class ErrorEditor extends StatelessWidget {
const ErrorEditor({
super.key,
required this.editorState,
});

final EditorState editorState;

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Container(
width: 1000,
decoration: BoxDecoration(
border: Border.all(color: Colors.blue),
),
child: AppFlowyEditor(
editorState: editorState,
blockComponentBuilders: {
...standardBlockComponentBuilderMap,
errorBlockComponentBuilderKey:
ErrorBlockComponentBuilder(),
},
),
),
),
],
),
),
),
);
}
}

class ErrorBlockComponentBuilder extends BlockComponentBuilder {
ErrorBlockComponentBuilder({
super.configuration,
});

@override
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
final node = blockComponentContext.node;
return ErrorBlockComponentWidget(
key: node.key,
node: node,
configuration: configuration,
showActions: showActions(node),
actionBuilder: (context, state) => actionBuilder(
blockComponentContext,
state,
),
);
}

@override
bool validate(Node node) => true;
}

class ErrorBlockComponentWidget extends BlockComponentStatefulWidget {
const ErrorBlockComponentWidget({
super.key,
required super.node,
super.showActions,
super.actionBuilder,
super.configuration = const BlockComponentConfiguration(),
});

@override
State<ErrorBlockComponentWidget> createState() =>
_DividerBlockComponentWidgetState();
}

class _DividerBlockComponentWidgetState extends State<ErrorBlockComponentWidget>
with BlockComponentConfigurable {
@override
BlockComponentConfiguration get configuration => widget.configuration;

@override
Node get node => widget.node;

@override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: const Text('error'),
);
}
}

0 comments on commit adb05d4

Please sign in to comment.