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 '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'
|
||||
as document_create_and_delete_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'
|
||||
as document_with_inline_math_equation_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 '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() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -27,4 +28,5 @@ void startTesting() {
|
||||
document_with_toggle_list_test.main();
|
||||
document_copy_and_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/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_add_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_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||
@ -409,6 +409,14 @@ extension CommonOperations on WidgetTester {
|
||||
await gesture.up();
|
||||
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 {
|
||||
|
@ -102,7 +102,8 @@ class _DocumentPageState extends State<DocumentPage> {
|
||||
editorState: editorState!,
|
||||
styleCustomizer: EditorStyleCustomizer(
|
||||
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),
|
||||
);
|
||||
|
@ -66,6 +66,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
numberedListItem,
|
||||
inlineMathEquationItem,
|
||||
linkItem,
|
||||
alignToolbarItem,
|
||||
buildTextColorItem(),
|
||||
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_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -19,6 +19,7 @@ export 'image/image_menu.dart';
|
||||
export 'image/image_selection_menu.dart';
|
||||
export 'inline_math_equation/inline_math_equation.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 'openai/widgets/auto_completion_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||
|
@ -132,7 +132,7 @@ class _ToggleListBlockComponentWidgetState
|
||||
EdgeInsets get indentPadding => configuration.indentPadding(
|
||||
node,
|
||||
calculateTextDirection(
|
||||
defaultTextDirection: Directionality.maybeOf(context),
|
||||
layoutDirection: Directionality.maybeOf(context),
|
||||
),
|
||||
);
|
||||
|
||||
@ -148,7 +148,7 @@ class _ToggleListBlockComponentWidgetState
|
||||
@override
|
||||
Widget buildComponent(BuildContext context) {
|
||||
final textDirection = calculateTextDirection(
|
||||
defaultTextDirection: Directionality.maybeOf(context),
|
||||
layoutDirection: Directionality.maybeOf(context),
|
||||
);
|
||||
|
||||
Widget child = Container(
|
||||
|
@ -54,8 +54,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: a9af2bb
|
||||
resolved-ref: a9af2bbd373a6a478f1bd63d6037817e81d23de2
|
||||
ref: a912c1c
|
||||
resolved-ref: a912c1c96532ec561ea68d5138aee415fdecede2
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "1.2.4"
|
||||
|
@ -48,7 +48,7 @@ dependencies:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: a9af2bb
|
||||
ref: a912c1c
|
||||
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