feat: support toggle list (#3016)

This commit is contained in:
Lucas.Xu
2023-07-18 09:45:20 +07:00
committed by GitHub
parent a00dd5498e
commit 2da37122e4
7 changed files with 426 additions and 57 deletions

View File

@ -37,6 +37,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
final inlinePageReferenceService = InlinePageReferenceService();
final List<CommandShortcutEvent> commandShortcutEvents = [
toggleToggleListCommand,
...codeBlockCommands,
...standardCommandShortcutEvents,
];
@ -68,7 +69,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
...codeBlockCharacterEvents,
// toggle list
// formatGreaterToToggleList,
formatGreaterToToggleList,
insertChildNodeInsideToggleList,
// customize the slash menu command
customSlashCommand(
@ -107,6 +109,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
void initState() {
super.initState();
indentableBlockTypes.add(ToggleListBlockKeys.type);
slashMenuItems = _customSlashMenuItems();
effectiveScrollController = widget.scrollController ?? ScrollController();
@ -286,6 +289,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
TodoListBlockKeys.type,
CalloutBlockKeys.type,
OutlineBlockKeys.type,
ToggleListBlockKeys.type,
];
final supportAlignBuilderType = [
@ -313,7 +317,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
final top = builder.configuration.padding(context.node).top;
final padding = context.node.type == HeadingBlockKeys.type
? EdgeInsets.only(top: top + 8.0)
: EdgeInsets.only(top: top);
: EdgeInsets.only(top: top + 2.0);
return Padding(
padding: padding,
child: BlockActionList(

View File

@ -1,7 +1,6 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ToggleListBlockKeys {
const ToggleListBlockKeys._();
@ -11,24 +10,35 @@ class ToggleListBlockKeys {
/// The content of a code block.
///
/// The value is a String.
static const String delta = 'delta';
static const String delta = blockComponentDelta;
static const String backgroundColor = blockComponentBackgroundColor;
static const String textDirection = blockComponentTextDirection;
/// The value is a bool.
static const String collapsed = 'collapsed';
}
Node toggleListBlockNode({
String? text,
Delta? delta,
bool collapsed = false,
String? textDirection,
Attributes? attributes,
Iterable<Node>? children,
}) {
final attributes = {
ToggleListBlockKeys.delta: (delta ?? Delta()).toJson(),
ToggleListBlockKeys.collapsed: collapsed,
};
return Node(
type: ToggleListBlockKeys.type,
attributes: attributes,
children: [paragraphNode()],
attributes: {
ToggleListBlockKeys.collapsed: collapsed,
ToggleListBlockKeys.delta:
(delta ?? (Delta()..insert(text ?? ''))).toJson(),
if (attributes != null) ...attributes,
if (textDirection != null)
ToggleListBlockKeys.textDirection: textDirection,
},
children: children ?? [paragraphNode()],
);
}
@ -86,7 +96,9 @@ class _ToggleListBlockComponentWidgetState
SelectableMixin,
DefaultSelectableMixin,
BlockComponentConfigurable,
BlockComponentBackgroundColorMixin {
BlockComponentBackgroundColorMixin,
NestedBlockComponentStatefulWidgetMixin,
BlockComponentTextDirectionMixin {
// the key used to forward focus to the richtext child
@override
final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text');
@ -105,63 +117,65 @@ class _ToggleListBlockComponentWidgetState
@override
Node get node => widget.node;
bool get collapsed => node.attributes[ToggleListBlockKeys.collapsed] ?? false;
@override
EdgeInsets get indentPadding => configuration.indentPadding(
node,
calculateTextDirection(
defaultTextDirection: Directionality.maybeOf(context),
),
);
late final editorState = context.read<EditorState>();
bool get collapsed => node.attributes[ToggleListBlockKeys.collapsed] ?? false;
@override
Widget build(BuildContext context) {
return collapsed
? buildToggleListBlockComponent(context)
: buildToggleListBlockComponentWithChildren(context);
? buildComponent(context)
: buildComponentWithChildren(context);
}
Widget buildToggleListBlockComponentWithChildren(BuildContext context) {
return Container(
color: backgroundColor,
child: NestedListWidget(
children: editorState.renderer.buildList(
context,
widget.node.children,
),
child: buildToggleListBlockComponent(context),
),
@override
Widget buildComponent(BuildContext context) {
final textDirection = calculateTextDirection(
defaultTextDirection: Directionality.maybeOf(context),
);
}
// build the richtext child
Widget buildToggleListBlockComponent(BuildContext context) {
Widget child = Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// the emoji picker button for the note
FlowyIconButton(
width: 24.0,
icon: Icon(
collapsed ? Icons.arrow_right : Icons.arrow_drop_down,
),
onPressed: onCollapsed,
),
const SizedBox(
width: 4.0,
),
Expanded(
child: AppFlowyRichText(
key: forwardKey,
node: widget.node,
editorState: editorState,
placeholderText: placeholderText,
lineHeight: 1.5,
textSpanDecorator: (textSpan) => textSpan.updateTextStyle(
textStyle,
Widget child = Container(
color: backgroundColor,
width: double.infinity,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// the emoji picker button for the note
FlowyIconButton(
width: 24.0,
icon: Icon(
collapsed ? Icons.arrow_right : Icons.arrow_drop_down,
),
placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(
placeholderTextStyle,
onPressed: onCollapsed,
),
const SizedBox(
width: 4.0,
),
Expanded(
child: AppFlowyRichText(
key: forwardKey,
node: widget.node,
editorState: editorState,
placeholderText: placeholderText,
lineHeight: 1.5,
textSpanDecorator: (textSpan) => textSpan.updateTextStyle(
textStyle,
),
placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(
placeholderTextStyle,
),
textDirection: textDirection,
),
),
),
],
],
),
);
child = Padding(

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
const _greater = '>';
@ -22,3 +23,107 @@ CharacterShortcutEvent formatGreaterToToggleList = CharacterShortcutEvent(
),
),
);
/// Press enter key to insert child node inside the toggle list
///
/// - support
/// - desktop
/// - mobile
/// - web
CharacterShortcutEvent insertChildNodeInsideToggleList = CharacterShortcutEvent(
key: 'insert child node inside toggle list',
character: '\n',
handler: (editorState) async {
final selection = editorState.selection;
if (selection == null || !selection.isCollapsed) {
return false;
}
final node = editorState.getNodeAtPath(selection.start.path);
final delta = node?.delta;
if (node == null ||
node.type != ToggleListBlockKeys.type ||
delta == null) {
return false;
}
final slicedDelta = delta.slice(selection.start.offset);
final transaction = editorState.transaction;
final collapsed = node.attributes[ToggleListBlockKeys.collapsed] as bool;
if (collapsed) {
// if the delta is empty, clear the format
if (delta.isEmpty) {
transaction
..insertNode(
selection.start.path.next,
paragraphNode(),
)
..deleteNode(node)
..afterSelection = Selection.collapse(selection.start.path, 0);
} else {
// insert a toggle list block below the current toggle list block
transaction
..deleteText(node, selection.startIndex, slicedDelta.length)
..insertNode(
selection.start.path.next,
toggleListBlockNode(collapsed: true, delta: slicedDelta),
)
..afterSelection = Selection.collapse(selection.start.path.next, 0);
}
} else {
// insert a paragraph block inside the current toggle list block
transaction
..deleteText(node, selection.startIndex, slicedDelta.length)
..insertNode(
selection.start.path + [0],
paragraphNode(delta: slicedDelta),
)
..afterSelection = Selection.collapse(selection.start.path + [0], 0);
}
await editorState.apply(transaction);
return true;
},
);
/// cmd/ctrl + enter to close or open the toggle list
///
/// - support
/// - desktop
/// - web
///
// toggle the todo list
final CommandShortcutEvent toggleToggleListCommand = CommandShortcutEvent(
key: 'toggle the toggle list',
command: 'ctrl+enter',
macOSCommand: 'cmd+enter',
handler: _toggleToggleListCommandHandler,
);
CommandShortcutEventHandler _toggleToggleListCommandHandler = (editorState) {
if (PlatformExtension.isMobile) {
assert(false, 'enter key is not supported on mobile platform.');
return KeyEventResult.ignored;
}
final selection = editorState.selection;
if (selection == null) {
return KeyEventResult.ignored;
}
final nodes = editorState.getNodesInSelection(selection);
if (nodes.isEmpty || nodes.length > 1) {
return KeyEventResult.ignored;
}
final node = nodes.first;
if (node.type != ToggleListBlockKeys.type) {
return KeyEventResult.ignored;
}
final collapsed = node.attributes[ToggleListBlockKeys.collapsed] as bool;
final transaction = editorState.transaction;
transaction.updateNode(node, {
ToggleListBlockKeys.collapsed: !collapsed,
});
transaction.afterSelection = selection;
editorState.apply(transaction);
return KeyEventResult.handled;
};