mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support toggle list (#3016)
This commit is contained in:
@ -7,6 +7,7 @@ import 'document_with_database_test.dart' as document_with_database_test;
|
|||||||
import 'document_with_inline_math_equation_test.dart'
|
import 'document_with_inline_math_equation_test.dart'
|
||||||
as document_with_inline_math_equation_test;
|
as document_with_inline_math_equation_test;
|
||||||
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
||||||
|
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||||
import 'edit_document_test.dart' as document_edit_test;
|
import 'edit_document_test.dart' as document_edit_test;
|
||||||
|
|
||||||
void startTesting() {
|
void startTesting() {
|
||||||
@ -19,4 +20,5 @@ void startTesting() {
|
|||||||
document_with_inline_page_test.main();
|
document_with_inline_page_test.main();
|
||||||
document_with_inline_math_equation_test.main();
|
document_with_inline_math_equation_test.main();
|
||||||
document_with_cover_image_test.main();
|
document_with_cover_image_test.main();
|
||||||
|
document_with_toggle_list_test.main();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,237 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import '../util/ime.dart';
|
||||||
|
import '../util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('toggle list in document', () {
|
||||||
|
void expectToggleListOpened() {
|
||||||
|
expect(find.byIcon(Icons.arrow_drop_down), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.arrow_right), findsNothing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectToggleListClosed() {
|
||||||
|
expect(find.byIcon(Icons.arrow_drop_down), findsNothing);
|
||||||
|
expect(find.byIcon(Icons.arrow_right), findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('convert > to toggle list, and click the icon to close it',
|
||||||
|
(tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// create a new document
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the first line of the document
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
// insert a toggle list
|
||||||
|
const text = 'This is a toggle list sample';
|
||||||
|
await tester.ime.insertText('> $text');
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: 0,
|
||||||
|
endOffset: text.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final toggleList = editorState.document.nodeAtPath([0])!;
|
||||||
|
expect(
|
||||||
|
toggleList.type,
|
||||||
|
ToggleListBlockKeys.type,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
toggleList.attributes[ToggleListBlockKeys.collapsed],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
toggleList.delta!.toPlainText(),
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Press the arrow down key to move the cursor to the next line
|
||||||
|
await tester.simulateKeyEvent(
|
||||||
|
LogicalKeyboardKey.arrowDown,
|
||||||
|
);
|
||||||
|
const text2 = 'This is a child node';
|
||||||
|
await tester.ime.insertText(text2);
|
||||||
|
expect(find.text(text2, findRichText: true), findsOneWidget);
|
||||||
|
|
||||||
|
// Click the toggle list icon to close it
|
||||||
|
final toggleListIcon = find.byIcon(Icons.arrow_drop_down);
|
||||||
|
await tester.tapButton(toggleListIcon);
|
||||||
|
|
||||||
|
// expect the toggle list to be closed
|
||||||
|
expect(find.text(text2, findRichText: true), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('press enter key when the toggle list is closed',
|
||||||
|
(tester) async {
|
||||||
|
// if the toggle list is closed, press enter key will insert a new toggle list after it
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// create a new document
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the first line of the document
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
// insert a toggle list
|
||||||
|
const text = 'Hello AppFlowy';
|
||||||
|
await tester.ime.insertText('> $text');
|
||||||
|
|
||||||
|
// Click the toggle list icon to close it
|
||||||
|
final toggleListIcon = find.byIcon(Icons.arrow_drop_down);
|
||||||
|
await tester.tapButton(toggleListIcon);
|
||||||
|
|
||||||
|
// Press the enter key
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.collapse(
|
||||||
|
[0],
|
||||||
|
'Hello '.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.ime.insertCharacter('\n');
|
||||||
|
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final node0 = editorState.getNodeAtPath([0])!;
|
||||||
|
final node1 = editorState.getNodeAtPath([1])!;
|
||||||
|
|
||||||
|
expect(node0.type, ToggleListBlockKeys.type);
|
||||||
|
expect(node0.attributes[ToggleListBlockKeys.collapsed], true);
|
||||||
|
expect(node0.delta!.toPlainText(), 'Hello ');
|
||||||
|
expect(node1.type, ToggleListBlockKeys.type);
|
||||||
|
expect(node1.delta!.toPlainText(), 'AppFlowy');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('press enter key when the toggle list is open', (tester) async {
|
||||||
|
// if the toggle list is open, press enter key will insert a new paragraph inside it
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// create a new document
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the first line of the document
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
// insert a toggle list
|
||||||
|
const text = 'Hello AppFlowy';
|
||||||
|
await tester.ime.insertText('> $text');
|
||||||
|
|
||||||
|
// Press the enter key
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.collapse(
|
||||||
|
[0],
|
||||||
|
'Hello '.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.ime.insertCharacter('\n');
|
||||||
|
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final node0 = editorState.getNodeAtPath([0])!;
|
||||||
|
final node00 = editorState.getNodeAtPath([0, 0])!;
|
||||||
|
final node1 = editorState.getNodeAtPath([1]);
|
||||||
|
|
||||||
|
expect(node0.type, ToggleListBlockKeys.type);
|
||||||
|
expect(node0.attributes[ToggleListBlockKeys.collapsed], false);
|
||||||
|
expect(node0.delta!.toPlainText(), 'Hello ');
|
||||||
|
expect(node00.type, ParagraphBlockKeys.type);
|
||||||
|
expect(node00.delta!.toPlainText(), 'AppFlowy');
|
||||||
|
expect(node1, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('clear the format if toggle list if empty', (tester) async {
|
||||||
|
// if the toggle list is open, press enter key will insert a new paragraph inside it
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// create a new document
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the first line of the document
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
// insert a toggle list
|
||||||
|
await tester.ime.insertText('> ');
|
||||||
|
|
||||||
|
// Press the enter key
|
||||||
|
// Click the toggle list icon to close it
|
||||||
|
final toggleListIcon = find.byIcon(Icons.arrow_drop_down);
|
||||||
|
await tester.tapButton(toggleListIcon);
|
||||||
|
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.collapse(
|
||||||
|
[0],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.ime.insertCharacter('\n');
|
||||||
|
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final node0 = editorState.getNodeAtPath([0])!;
|
||||||
|
|
||||||
|
expect(node0.type, ParagraphBlockKeys.type);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('use cmd/ctrl + enter to open/close the toggle list',
|
||||||
|
(tester) async {
|
||||||
|
// if the toggle list is open, press enter key will insert a new paragraph inside it
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// create a new document
|
||||||
|
await tester.createNewPageWithName(
|
||||||
|
ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the first line of the document
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
// insert a toggle list
|
||||||
|
await tester.ime.insertText('> Hello');
|
||||||
|
|
||||||
|
expectToggleListOpened();
|
||||||
|
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection.collapse(
|
||||||
|
[0],
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.simulateKeyEvent(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
isMetaPressed: Platform.isMacOS,
|
||||||
|
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectToggleListClosed();
|
||||||
|
|
||||||
|
await tester.simulateKeyEvent(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
isMetaPressed: Platform.isMacOS,
|
||||||
|
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||||
|
);
|
||||||
|
|
||||||
|
expectToggleListOpened();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -37,6 +37,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
final inlinePageReferenceService = InlinePageReferenceService();
|
final inlinePageReferenceService = InlinePageReferenceService();
|
||||||
|
|
||||||
final List<CommandShortcutEvent> commandShortcutEvents = [
|
final List<CommandShortcutEvent> commandShortcutEvents = [
|
||||||
|
toggleToggleListCommand,
|
||||||
...codeBlockCommands,
|
...codeBlockCommands,
|
||||||
...standardCommandShortcutEvents,
|
...standardCommandShortcutEvents,
|
||||||
];
|
];
|
||||||
@ -68,7 +69,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
...codeBlockCharacterEvents,
|
...codeBlockCharacterEvents,
|
||||||
|
|
||||||
// toggle list
|
// toggle list
|
||||||
// formatGreaterToToggleList,
|
formatGreaterToToggleList,
|
||||||
|
insertChildNodeInsideToggleList,
|
||||||
|
|
||||||
// customize the slash menu command
|
// customize the slash menu command
|
||||||
customSlashCommand(
|
customSlashCommand(
|
||||||
@ -107,6 +109,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
indentableBlockTypes.add(ToggleListBlockKeys.type);
|
||||||
slashMenuItems = _customSlashMenuItems();
|
slashMenuItems = _customSlashMenuItems();
|
||||||
|
|
||||||
effectiveScrollController = widget.scrollController ?? ScrollController();
|
effectiveScrollController = widget.scrollController ?? ScrollController();
|
||||||
@ -286,6 +289,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
TodoListBlockKeys.type,
|
TodoListBlockKeys.type,
|
||||||
CalloutBlockKeys.type,
|
CalloutBlockKeys.type,
|
||||||
OutlineBlockKeys.type,
|
OutlineBlockKeys.type,
|
||||||
|
ToggleListBlockKeys.type,
|
||||||
];
|
];
|
||||||
|
|
||||||
final supportAlignBuilderType = [
|
final supportAlignBuilderType = [
|
||||||
@ -313,7 +317,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
final top = builder.configuration.padding(context.node).top;
|
final top = builder.configuration.padding(context.node).top;
|
||||||
final padding = context.node.type == HeadingBlockKeys.type
|
final padding = context.node.type == HeadingBlockKeys.type
|
||||||
? EdgeInsets.only(top: top + 8.0)
|
? EdgeInsets.only(top: top + 8.0)
|
||||||
: EdgeInsets.only(top: top);
|
: EdgeInsets.only(top: top + 2.0);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: BlockActionList(
|
child: BlockActionList(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class ToggleListBlockKeys {
|
class ToggleListBlockKeys {
|
||||||
const ToggleListBlockKeys._();
|
const ToggleListBlockKeys._();
|
||||||
@ -11,24 +10,35 @@ class ToggleListBlockKeys {
|
|||||||
/// The content of a code block.
|
/// The content of a code block.
|
||||||
///
|
///
|
||||||
/// The value is a String.
|
/// 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.
|
/// The value is a bool.
|
||||||
static const String collapsed = 'collapsed';
|
static const String collapsed = 'collapsed';
|
||||||
}
|
}
|
||||||
|
|
||||||
Node toggleListBlockNode({
|
Node toggleListBlockNode({
|
||||||
|
String? text,
|
||||||
Delta? delta,
|
Delta? delta,
|
||||||
bool collapsed = false,
|
bool collapsed = false,
|
||||||
|
String? textDirection,
|
||||||
|
Attributes? attributes,
|
||||||
|
Iterable<Node>? children,
|
||||||
}) {
|
}) {
|
||||||
final attributes = {
|
|
||||||
ToggleListBlockKeys.delta: (delta ?? Delta()).toJson(),
|
|
||||||
ToggleListBlockKeys.collapsed: collapsed,
|
|
||||||
};
|
|
||||||
return Node(
|
return Node(
|
||||||
type: ToggleListBlockKeys.type,
|
type: ToggleListBlockKeys.type,
|
||||||
attributes: attributes,
|
attributes: {
|
||||||
children: [paragraphNode()],
|
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,
|
SelectableMixin,
|
||||||
DefaultSelectableMixin,
|
DefaultSelectableMixin,
|
||||||
BlockComponentConfigurable,
|
BlockComponentConfigurable,
|
||||||
BlockComponentBackgroundColorMixin {
|
BlockComponentBackgroundColorMixin,
|
||||||
|
NestedBlockComponentStatefulWidgetMixin,
|
||||||
|
BlockComponentTextDirectionMixin {
|
||||||
// the key used to forward focus to the richtext child
|
// the key used to forward focus to the richtext child
|
||||||
@override
|
@override
|
||||||
final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text');
|
final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text');
|
||||||
@ -105,63 +117,65 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
@override
|
@override
|
||||||
Node get node => widget.node;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return collapsed
|
return collapsed
|
||||||
? buildToggleListBlockComponent(context)
|
? buildComponent(context)
|
||||||
: buildToggleListBlockComponentWithChildren(context);
|
: buildComponentWithChildren(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildToggleListBlockComponentWithChildren(BuildContext context) {
|
@override
|
||||||
return Container(
|
Widget buildComponent(BuildContext context) {
|
||||||
color: backgroundColor,
|
final textDirection = calculateTextDirection(
|
||||||
child: NestedListWidget(
|
defaultTextDirection: Directionality.maybeOf(context),
|
||||||
children: editorState.renderer.buildList(
|
|
||||||
context,
|
|
||||||
widget.node.children,
|
|
||||||
),
|
|
||||||
child: buildToggleListBlockComponent(context),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// build the richtext child
|
Widget child = Container(
|
||||||
Widget buildToggleListBlockComponent(BuildContext context) {
|
color: backgroundColor,
|
||||||
Widget child = Row(
|
width: double.infinity,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Row(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
// the emoji picker button for the note
|
children: [
|
||||||
FlowyIconButton(
|
// the emoji picker button for the note
|
||||||
width: 24.0,
|
FlowyIconButton(
|
||||||
icon: Icon(
|
width: 24.0,
|
||||||
collapsed ? Icons.arrow_right : Icons.arrow_drop_down,
|
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,
|
|
||||||
),
|
),
|
||||||
placeholderTextSpanDecorator: (textSpan) =>
|
onPressed: onCollapsed,
|
||||||
textSpan.updateTextStyle(
|
),
|
||||||
placeholderTextStyle,
|
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(
|
child = Padding(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
const _greater = '>';
|
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;
|
||||||
|
};
|
||||||
|
@ -45,7 +45,7 @@ dependencies:
|
|||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
ref: 33b18d9
|
ref: 023f3c8
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
|
|
||||||
|
7
frontend/flowy-server-config/Cargo.lock
generated
Normal file
7
frontend/flowy-server-config/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flowy-server-config"
|
||||||
|
version = "0.1.0"
|
Reference in New Issue
Block a user