mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support text align and block align (#3292)
* feat: support text align and block align * test: add test
This commit is contained in:
parent
897d7980f6
commit
f73d59fb57
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import '../util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('document alignment', () {
|
||||||
|
testWidgets('edit alignment in toolbar', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
final selection = Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: 0,
|
||||||
|
endOffset: 1,
|
||||||
|
);
|
||||||
|
// click the first line of the readme
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
await tester.editor.updateSelection(selection);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// click the align center
|
||||||
|
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s);
|
||||||
|
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s);
|
||||||
|
|
||||||
|
// expect to see the align center
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final first = editorState.getNodeAtPath([0])!;
|
||||||
|
expect(first.attributes[blockComponentAlign], 'center');
|
||||||
|
|
||||||
|
// click the align right
|
||||||
|
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s);
|
||||||
|
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s);
|
||||||
|
expect(first.attributes[blockComponentAlign], 'right');
|
||||||
|
|
||||||
|
// click the align left
|
||||||
|
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s);
|
||||||
|
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s);
|
||||||
|
expect(first.attributes[blockComponentAlign], 'left');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'document_alignment_test.dart' as document_alignment_test;
|
||||||
|
import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
|
||||||
|
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
||||||
import 'document_create_and_delete_test.dart'
|
import 'document_create_and_delete_test.dart'
|
||||||
as document_create_and_delete_test;
|
as document_create_and_delete_test;
|
||||||
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
||||||
@ -7,11 +10,9 @@ 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_outline_block_test.dart' as document_with_outline_block;
|
||||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_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;
|
||||||
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
|
||||||
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
|
||||||
import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
|
|
||||||
|
|
||||||
void startTesting() {
|
void startTesting() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -27,4 +28,5 @@ void startTesting() {
|
|||||||
document_with_toggle_list_test.main();
|
document_with_toggle_list_test.main();
|
||||||
document_copy_and_paste_test.main();
|
document_copy_and_paste_test.main();
|
||||||
document_codeblock_paste_test.main();
|
document_codeblock_paste_test.main();
|
||||||
|
document_alignment_test.main();
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||||
|
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
|
||||||
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||||
@ -409,6 +409,14 @@ extension CommonOperations on WidgetTester {
|
|||||||
await gesture.up();
|
await gesture.up();
|
||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tap the button with [FlowySvgData]
|
||||||
|
Future<void> tapButtonWithFlowySvgData(FlowySvgData svg) async {
|
||||||
|
final button = find.byWidgetPredicate(
|
||||||
|
(widget) => widget is FlowySvg && widget.svg.path == svg.path,
|
||||||
|
);
|
||||||
|
await tapButton(button);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ViewLayoutPBTest on ViewLayoutPB {
|
extension ViewLayoutPBTest on ViewLayoutPB {
|
||||||
|
@ -102,7 +102,8 @@ class _DocumentPageState extends State<DocumentPage> {
|
|||||||
editorState: editorState!,
|
editorState: editorState!,
|
||||||
styleCustomizer: EditorStyleCustomizer(
|
styleCustomizer: EditorStyleCustomizer(
|
||||||
context: context,
|
context: context,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 50),
|
// the 44 is the width of the left action list
|
||||||
|
padding: const EdgeInsets.only(left: 40, right: 40 + 44),
|
||||||
),
|
),
|
||||||
header: _buildCoverAndIcon(context),
|
header: _buildCoverAndIcon(context),
|
||||||
);
|
);
|
||||||
|
@ -66,6 +66,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
numberedListItem,
|
numberedListItem,
|
||||||
inlineMathEquationItem,
|
inlineMathEquationItem,
|
||||||
linkItem,
|
linkItem,
|
||||||
|
alignToolbarItem,
|
||||||
buildTextColorItem(),
|
buildTextColorItem(),
|
||||||
buildHighlightColorItem(),
|
buildHighlightColorItem(),
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,153 @@
|
|||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final alignToolbarItem = ToolbarItem(
|
||||||
|
id: 'editor.align',
|
||||||
|
group: 4,
|
||||||
|
isActive: onlyShowInTextType,
|
||||||
|
builder: (context, editorState, highlightColor) {
|
||||||
|
final selection = editorState.selection!;
|
||||||
|
final nodes = editorState.getNodesInSelection(selection);
|
||||||
|
|
||||||
|
bool isSatisfyCondition(bool Function(Object? value) test) {
|
||||||
|
return nodes.every(
|
||||||
|
(n) => test(n.attributes[blockComponentAlign]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHighlight = false;
|
||||||
|
FlowySvgData data = FlowySvgs.toolbar_align_left_s;
|
||||||
|
if (isSatisfyCondition((value) => value == 'left')) {
|
||||||
|
isHighlight = true;
|
||||||
|
data = FlowySvgs.toolbar_align_left_s;
|
||||||
|
} else if (isSatisfyCondition((value) => value == 'center')) {
|
||||||
|
isHighlight = true;
|
||||||
|
data = FlowySvgs.toolbar_align_center_s;
|
||||||
|
} else if (isSatisfyCondition((value) => value == 'right')) {
|
||||||
|
isHighlight = true;
|
||||||
|
data = FlowySvgs.toolbar_align_right_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
final child = FlowySvg(
|
||||||
|
data,
|
||||||
|
size: const Size.square(16),
|
||||||
|
color: isHighlight ? highlightColor : Colors.white,
|
||||||
|
);
|
||||||
|
return _AlignmentButtons(
|
||||||
|
child: child,
|
||||||
|
onAlignChanged: (align) async {
|
||||||
|
await editorState.updateNode(
|
||||||
|
selection,
|
||||||
|
(node) => node.copyWith(
|
||||||
|
attributes: {
|
||||||
|
...node.attributes,
|
||||||
|
blockComponentAlign: align,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
class _AlignmentButtons extends StatefulWidget {
|
||||||
|
const _AlignmentButtons({
|
||||||
|
required this.child,
|
||||||
|
required this.onAlignChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final Function(String align) onAlignChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_AlignmentButtons> createState() => _AlignmentButtonsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlignmentButtonsState extends State<_AlignmentButtons> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppFlowyPopover(
|
||||||
|
windowPadding: const EdgeInsets.all(0),
|
||||||
|
margin: const EdgeInsets.all(0),
|
||||||
|
direction: PopoverDirection.bottomWithCenterAligned,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
child: widget.child,
|
||||||
|
popupBuilder: (_) => _AlignButtons(onAlignChanged: widget.onAlignChanged),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlignButtons extends StatelessWidget {
|
||||||
|
const _AlignButtons({
|
||||||
|
required this.onAlignChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Function(String align) onAlignChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 32,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const HSpace(4),
|
||||||
|
_AlignButton(
|
||||||
|
icon: FlowySvgs.toolbar_align_left_s,
|
||||||
|
onTap: () => onAlignChanged('left'),
|
||||||
|
),
|
||||||
|
const _Divider(),
|
||||||
|
_AlignButton(
|
||||||
|
icon: FlowySvgs.toolbar_align_center_s,
|
||||||
|
onTap: () => onAlignChanged('center'),
|
||||||
|
),
|
||||||
|
const _Divider(),
|
||||||
|
_AlignButton(
|
||||||
|
icon: FlowySvgs.toolbar_align_right_s,
|
||||||
|
onTap: () => onAlignChanged('right'),
|
||||||
|
),
|
||||||
|
const HSpace(4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlignButton extends StatelessWidget {
|
||||||
|
const _AlignButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final FlowySvgData icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: FlowySvg(
|
||||||
|
icon,
|
||||||
|
size: const Size.square(16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Divider extends StatelessWidget {
|
||||||
|
const _Divider();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Container(
|
||||||
|
width: 1,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -19,6 +19,7 @@ export 'image/image_menu.dart';
|
|||||||
export 'image/image_selection_menu.dart';
|
export 'image/image_selection_menu.dart';
|
||||||
export 'inline_math_equation/inline_math_equation.dart';
|
export 'inline_math_equation/inline_math_equation.dart';
|
||||||
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
|
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
|
||||||
|
export 'align_toolbar_item/align_toolbar_item.dart';
|
||||||
export 'math_equation/math_equation_block_component.dart';
|
export 'math_equation/math_equation_block_component.dart';
|
||||||
export 'openai/widgets/auto_completion_node_widget.dart';
|
export 'openai/widgets/auto_completion_node_widget.dart';
|
||||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||||
|
@ -132,7 +132,7 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
EdgeInsets get indentPadding => configuration.indentPadding(
|
EdgeInsets get indentPadding => configuration.indentPadding(
|
||||||
node,
|
node,
|
||||||
calculateTextDirection(
|
calculateTextDirection(
|
||||||
defaultTextDirection: Directionality.maybeOf(context),
|
layoutDirection: Directionality.maybeOf(context),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
@override
|
@override
|
||||||
Widget buildComponent(BuildContext context) {
|
Widget buildComponent(BuildContext context) {
|
||||||
final textDirection = calculateTextDirection(
|
final textDirection = calculateTextDirection(
|
||||||
defaultTextDirection: Directionality.maybeOf(context),
|
layoutDirection: Directionality.maybeOf(context),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget child = Container(
|
Widget child = Container(
|
||||||
|
@ -54,8 +54,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: a9af2bb
|
ref: a912c1c
|
||||||
resolved-ref: a9af2bbd373a6a478f1bd63d6037817e81d23de2
|
resolved-ref: a912c1c96532ec561ea68d5138aee415fdecede2
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.2.4"
|
version: "1.2.4"
|
||||||
|
@ -48,7 +48,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: a9af2bb
|
ref: a912c1c
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 4L12 4" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5 8H11" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 12L12 12" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 355 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 4L12 4" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 8H10" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 12L12 12" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 355 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 4L12 4" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 8H12" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 12L12 12" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 355 B |
Loading…
Reference in New Issue
Block a user