mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-11-20 12:27:50 +00:00
fix: toggle heading issues (#6714)
* fix: pages overflow when selecting homepage * fix: toggle heading issues * Revert "fix: pages overflow when selecting homepage" This reverts commit 156882a9a7e039a38cd206306e19b94eb391f948. * chore: optimize code logic * fix: assertion in toggle list * fix: make the turn into menu and color menu exclusive * test: add toggle heading test
This commit is contained in:
parent
54096b391f
commit
82effbf8e4
@ -1,6 +1,8 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
@ -60,6 +62,50 @@ void main() {
|
|||||||
findsNWidgets(6),
|
findsNWidgets(6),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('insert toggle heading and convert it to heading',
|
||||||
|
(tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapAnonymousSignInButton();
|
||||||
|
|
||||||
|
await tester.createNewPageWithNameUnderParent(
|
||||||
|
name: 'toggle heading block test',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.editor.tapLineOfEditorAt(0);
|
||||||
|
await tester.ime.insertText('# > $_heading1\n');
|
||||||
|
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||||
|
await tester.ime.insertText('item 1');
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.editor.updateSelection(
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [0]),
|
||||||
|
end: Position(path: [0], offset: _heading1.length),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tapButton(find.byType(HeadingPopup));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.byType(HeadingButton),
|
||||||
|
findsNWidgets(3),
|
||||||
|
);
|
||||||
|
|
||||||
|
// tap the H1 button
|
||||||
|
await tester.tapButton(find.byType(HeadingButton).at(0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final editorState = tester.editor.getCurrentEditorState();
|
||||||
|
final node1 = editorState.document.nodeAtPath([0])!;
|
||||||
|
expect(node1.type, HeadingBlockKeys.type);
|
||||||
|
expect(node1.attributes[HeadingBlockKeys.level], 1);
|
||||||
|
|
||||||
|
final node2 = editorState.document.nodeAtPath([1])!;
|
||||||
|
expect(node2.type, ParagraphBlockKeys.type);
|
||||||
|
expect(node2.delta!.toPlainText(), 'item 1');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -231,7 +231,6 @@ class _DocumentPageState extends State<DocumentPage>
|
|||||||
|
|
||||||
final Path? path = _getPathFromAction(action, editorState);
|
final Path? path = _getPathFromAction(action, editorState);
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
debugPrint('jump to block: $path');
|
|
||||||
editorState.updateSelectionWithReason(
|
editorState.updateSelectionWithReason(
|
||||||
Selection.collapsed(Position(path: path)),
|
Selection.collapsed(Position(path: path)),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -134,12 +134,19 @@ void _customBlockOptionActions(
|
|||||||
);
|
);
|
||||||
|
|
||||||
builder.actionBuilder = (context, state) {
|
builder.actionBuilder = (context, state) {
|
||||||
final top = builder.configuration.padding(context.node).top;
|
double top = builder.configuration.padding(context.node).top;
|
||||||
final padding = context.node.type == HeadingBlockKeys.type
|
final type = context.node.type;
|
||||||
? EdgeInsets.only(top: top + 8.0)
|
final level = context.node.attributes[HeadingBlockKeys.level] ?? 0;
|
||||||
: EdgeInsets.only(top: top + 2.0);
|
if ((type == HeadingBlockKeys.type ||
|
||||||
|
type == ToggleListBlockKeys.type) &&
|
||||||
|
level > 0) {
|
||||||
|
final offset = [14.0, 11.0, 8.0, 6.0, 4.0, 2.0];
|
||||||
|
top += offset[level - 1];
|
||||||
|
} else {
|
||||||
|
top += 2.0;
|
||||||
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: padding,
|
padding: EdgeInsets.only(top: top),
|
||||||
child: BlockActionList(
|
child: BlockActionList(
|
||||||
blockComponentContext: context,
|
blockComponentContext: context,
|
||||||
blockComponentState: state,
|
blockComponentState: state,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
|
|
||||||
import 'drag_to_reorder/draggable_option_button.dart';
|
import 'drag_to_reorder/draggable_option_button.dart';
|
||||||
|
|
||||||
class BlockOptionButton extends StatelessWidget {
|
class BlockOptionButton extends StatefulWidget {
|
||||||
const BlockOptionButton({
|
const BlockOptionButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.blockComponentContext,
|
required this.blockComponentContext,
|
||||||
@ -25,6 +25,16 @@ class BlockOptionButton extends StatelessWidget {
|
|||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BlockOptionButton> createState() => _BlockOptionButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BlockOptionButtonState extends State<BlockOptionButton> {
|
||||||
|
// the mutex is used to ensure that only one popover is open at a time
|
||||||
|
// for example, when the user is selecting the color, the turn into option
|
||||||
|
// should not be shown.
|
||||||
|
final mutex = PopoverMutex();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final direction =
|
final direction =
|
||||||
@ -34,8 +44,8 @@ class BlockOptionButton extends StatelessWidget {
|
|||||||
: PopoverDirection.leftWithCenterAligned;
|
: PopoverDirection.leftWithCenterAligned;
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => BlockActionOptionCubit(
|
create: (context) => BlockActionOptionCubit(
|
||||||
editorState: editorState,
|
editorState: widget.editorState,
|
||||||
blockComponentBuilder: blockComponentBuilder,
|
blockComponentBuilder: widget.blockComponentBuilder,
|
||||||
),
|
),
|
||||||
child: BlocBuilder<BlockActionOptionCubit, BlockActionOptionState>(
|
child: BlocBuilder<BlockActionOptionCubit, BlockActionOptionState>(
|
||||||
builder: (context, _) => PopoverActionList<PopoverAction>(
|
builder: (context, _) => PopoverActionList<PopoverAction>(
|
||||||
@ -55,30 +65,41 @@ class BlockOptionButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
buildChild: (controller) => DraggableOptionButton(
|
buildChild: (controller) => DraggableOptionButton(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
editorState: editorState,
|
editorState: widget.editorState,
|
||||||
blockComponentContext: blockComponentContext,
|
blockComponentContext: widget.blockComponentContext,
|
||||||
blockComponentBuilder: blockComponentBuilder,
|
blockComponentBuilder: widget.blockComponentBuilder,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
mutex.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
List<PopoverAction> _buildPopoverActions(BuildContext context) {
|
List<PopoverAction> _buildPopoverActions(BuildContext context) {
|
||||||
return actions.map((e) {
|
return widget.actions.map((e) {
|
||||||
switch (e) {
|
switch (e) {
|
||||||
case OptionAction.divider:
|
case OptionAction.divider:
|
||||||
return DividerOptionAction();
|
return DividerOptionAction();
|
||||||
case OptionAction.color:
|
case OptionAction.color:
|
||||||
return ColorOptionAction(editorState: editorState);
|
return ColorOptionAction(
|
||||||
|
editorState: widget.editorState,
|
||||||
|
mutex: mutex,
|
||||||
|
);
|
||||||
case OptionAction.align:
|
case OptionAction.align:
|
||||||
return AlignOptionAction(editorState: editorState);
|
return AlignOptionAction(editorState: widget.editorState);
|
||||||
case OptionAction.depth:
|
case OptionAction.depth:
|
||||||
return DepthOptionAction(editorState: editorState);
|
return DepthOptionAction(editorState: widget.editorState);
|
||||||
case OptionAction.turnInto:
|
case OptionAction.turnInto:
|
||||||
return TurnIntoOptionAction(
|
return TurnIntoOptionAction(
|
||||||
editorState: editorState,
|
editorState: widget.editorState,
|
||||||
blockComponentBuilder: blockComponentBuilder,
|
blockComponentBuilder: widget.blockComponentBuilder,
|
||||||
|
mutex: mutex,
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return OptionActionWrapper(e);
|
return OptionActionWrapper(e);
|
||||||
@ -88,15 +109,17 @@ class BlockOptionButton extends StatelessWidget {
|
|||||||
|
|
||||||
void _onPopoverBuilder() {
|
void _onPopoverBuilder() {
|
||||||
keepEditorFocusNotifier.increase();
|
keepEditorFocusNotifier.increase();
|
||||||
blockComponentState.alwaysShowActions = true;
|
widget.blockComponentState.alwaysShowActions = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPopoverClosed(BuildContext context) {
|
void _onPopoverClosed(BuildContext context) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
editorState.selectionType = null;
|
widget.editorState.selectionType = null;
|
||||||
editorState.selection = null;
|
widget.editorState.selection = null;
|
||||||
blockComponentState.alwaysShowActions = false;
|
widget.blockComponentState.alwaysShowActions = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PopoverContainer.maybeOf(context)?.closeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onActionSelected(
|
void _onActionSelected(
|
||||||
@ -110,7 +133,7 @@ class BlockOptionButton extends StatelessWidget {
|
|||||||
|
|
||||||
context.read<BlockActionOptionCubit>().handleAction(
|
context.read<BlockActionOptionCubit>().handleAction(
|
||||||
action.inner,
|
action.inner,
|
||||||
blockComponentContext.node,
|
widget.blockComponentContext.node,
|
||||||
);
|
);
|
||||||
controller.close();
|
controller.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -389,6 +389,14 @@ class BlockActionOptionCubit extends Cubit<BlockActionOptionState> {
|
|||||||
]);
|
]);
|
||||||
if (afterSelection != null) {
|
if (afterSelection != null) {
|
||||||
transaction.afterSelection = afterSelection;
|
transaction.afterSelection = afterSelection;
|
||||||
|
} else if (insertedNodes.isNotEmpty) {
|
||||||
|
// select the blocks
|
||||||
|
transaction.afterSelection = Selection(
|
||||||
|
start: Position(path: node.path.child(0)),
|
||||||
|
end: Position(path: node.path.child(insertedNodes.length - 1)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
transaction.afterSelection = transaction.beforeSelection;
|
||||||
}
|
}
|
||||||
await editorState.apply(transaction);
|
await editorState.apply(transaction);
|
||||||
|
|
||||||
|
|||||||
@ -13,10 +13,12 @@ const optionActionColorDefaultColor = 'appflowy_theme_default_color';
|
|||||||
class ColorOptionAction extends CustomActionCell {
|
class ColorOptionAction extends CustomActionCell {
|
||||||
ColorOptionAction({
|
ColorOptionAction({
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
|
required this.mutex,
|
||||||
});
|
});
|
||||||
|
|
||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
final PopoverController innerController = PopoverController();
|
final PopoverController innerController = PopoverController();
|
||||||
|
final PopoverMutex mutex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildWithContext(
|
Widget buildWithContext(
|
||||||
@ -24,16 +26,49 @@ class ColorOptionAction extends CustomActionCell {
|
|||||||
PopoverController controller,
|
PopoverController controller,
|
||||||
PopoverMutex? mutex,
|
PopoverMutex? mutex,
|
||||||
) {
|
) {
|
||||||
|
return ColorOptionButton(
|
||||||
|
editorState: editorState,
|
||||||
|
mutex: this.mutex,
|
||||||
|
controller: controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ColorOptionButton extends StatefulWidget {
|
||||||
|
const ColorOptionButton({
|
||||||
|
super.key,
|
||||||
|
required this.editorState,
|
||||||
|
required this.mutex,
|
||||||
|
required this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EditorState editorState;
|
||||||
|
final PopoverMutex mutex;
|
||||||
|
final PopoverController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ColorOptionButton> createState() => _ColorOptionButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ColorOptionButtonState extends State<ColorOptionButton> {
|
||||||
|
final PopoverController innerController = PopoverController();
|
||||||
|
bool isOpen = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
asBarrier: true,
|
asBarrier: true,
|
||||||
controller: innerController,
|
controller: innerController,
|
||||||
mutex: mutex,
|
mutex: widget.mutex,
|
||||||
popupBuilder: (context) => _buildColorOptionMenu(
|
popupBuilder: (context) {
|
||||||
context,
|
isOpen = true;
|
||||||
controller,
|
return _buildColorOptionMenu(
|
||||||
),
|
context,
|
||||||
|
widget.controller,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onClose: () => isOpen = false,
|
||||||
direction: PopoverDirection.rightWithCenterAligned,
|
direction: PopoverDirection.rightWithCenterAligned,
|
||||||
offset: const Offset(10, 0),
|
|
||||||
animationDuration: Durations.short3,
|
animationDuration: Durations.short3,
|
||||||
beginScaleFactor: 1.0,
|
beginScaleFactor: 1.0,
|
||||||
beginOpacity: 0.8,
|
beginOpacity: 0.8,
|
||||||
@ -45,7 +80,9 @@ class ColorOptionAction extends CustomActionCell {
|
|||||||
),
|
),
|
||||||
name: LocaleKeys.document_plugins_optionAction_color.tr(),
|
name: LocaleKeys.document_plugins_optionAction_color.tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
innerController.show();
|
if (!isOpen) {
|
||||||
|
innerController.show();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -55,12 +92,12 @@ class ColorOptionAction extends CustomActionCell {
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
PopoverController controller,
|
PopoverController controller,
|
||||||
) {
|
) {
|
||||||
final selection = editorState.selection?.normalized;
|
final selection = widget.editorState.selection?.normalized;
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final node = editorState.getNodeAtPath(selection.start.path);
|
final node = widget.editorState.getNodeAtPath(selection.start.path);
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@ -73,11 +110,11 @@ class ColorOptionAction extends CustomActionCell {
|
|||||||
Node node,
|
Node node,
|
||||||
PopoverController controller,
|
PopoverController controller,
|
||||||
) {
|
) {
|
||||||
final selection = editorState.selection?.normalized;
|
final selection = widget.editorState.selection?.normalized;
|
||||||
if (selection == null) {
|
if (selection == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
final node = editorState.getNodeAtPath(selection.start.path);
|
final node = widget.editorState.getNodeAtPath(selection.start.path);
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@ -110,11 +147,11 @@ class ColorOptionAction extends CustomActionCell {
|
|||||||
color: AFThemeExtension.of(context).onBackground,
|
color: AFThemeExtension.of(context).onBackground,
|
||||||
),
|
),
|
||||||
onTap: (option, index) async {
|
onTap: (option, index) async {
|
||||||
final transaction = editorState.transaction;
|
final transaction = widget.editorState.transaction;
|
||||||
transaction.updateNode(node, {
|
transaction.updateNode(node, {
|
||||||
blockComponentBackgroundColor: option.id,
|
blockComponentBackgroundColor: option.id,
|
||||||
});
|
});
|
||||||
await editorState.apply(transaction);
|
await widget.editorState.apply(transaction);
|
||||||
|
|
||||||
innerController.close();
|
innerController.close();
|
||||||
controller.close();
|
controller.close();
|
||||||
|
|||||||
@ -15,11 +15,13 @@ class TurnIntoOptionAction extends CustomActionCell {
|
|||||||
TurnIntoOptionAction({
|
TurnIntoOptionAction({
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
required this.blockComponentBuilder,
|
required this.blockComponentBuilder,
|
||||||
|
required this.mutex,
|
||||||
});
|
});
|
||||||
|
|
||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
||||||
final PopoverController innerController = PopoverController();
|
final PopoverController innerController = PopoverController();
|
||||||
|
final PopoverMutex mutex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildWithContext(
|
Widget buildWithContext(
|
||||||
@ -27,21 +29,54 @@ class TurnIntoOptionAction extends CustomActionCell {
|
|||||||
PopoverController controller,
|
PopoverController controller,
|
||||||
PopoverMutex? mutex,
|
PopoverMutex? mutex,
|
||||||
) {
|
) {
|
||||||
|
return TurnInfoButton(
|
||||||
|
editorState: editorState,
|
||||||
|
blockComponentBuilder: blockComponentBuilder,
|
||||||
|
mutex: this.mutex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TurnInfoButton extends StatefulWidget {
|
||||||
|
const TurnInfoButton({
|
||||||
|
super.key,
|
||||||
|
required this.editorState,
|
||||||
|
required this.blockComponentBuilder,
|
||||||
|
required this.mutex,
|
||||||
|
});
|
||||||
|
|
||||||
|
final EditorState editorState;
|
||||||
|
final Map<String, BlockComponentBuilder> blockComponentBuilder;
|
||||||
|
final PopoverMutex mutex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TurnInfoButton> createState() => _TurnInfoButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TurnInfoButtonState extends State<TurnInfoButton> {
|
||||||
|
final PopoverController innerController = PopoverController();
|
||||||
|
bool isOpen = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
asBarrier: true,
|
asBarrier: true,
|
||||||
controller: innerController,
|
controller: innerController,
|
||||||
mutex: mutex,
|
mutex: widget.mutex,
|
||||||
popupBuilder: (context) => BlocProvider<BlockActionOptionCubit>(
|
popupBuilder: (context) {
|
||||||
create: (_) => BlockActionOptionCubit(
|
isOpen = true;
|
||||||
editorState: editorState,
|
return BlocProvider<BlockActionOptionCubit>(
|
||||||
blockComponentBuilder: blockComponentBuilder,
|
create: (context) => BlockActionOptionCubit(
|
||||||
),
|
editorState: widget.editorState,
|
||||||
child: BlocBuilder<BlockActionOptionCubit, BlockActionOptionState>(
|
blockComponentBuilder: widget.blockComponentBuilder,
|
||||||
builder: (context, _) => _buildTurnIntoOptionMenu(context),
|
),
|
||||||
),
|
child: BlocBuilder<BlockActionOptionCubit, BlockActionOptionState>(
|
||||||
),
|
builder: (context, _) => _buildTurnIntoOptionMenu(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onClose: () => isOpen = false,
|
||||||
direction: PopoverDirection.rightWithCenterAligned,
|
direction: PopoverDirection.rightWithCenterAligned,
|
||||||
offset: const Offset(10, 0),
|
|
||||||
animationDuration: Durations.short3,
|
animationDuration: Durations.short3,
|
||||||
beginScaleFactor: 1.0,
|
beginScaleFactor: 1.0,
|
||||||
beginOpacity: 0.8,
|
beginOpacity: 0.8,
|
||||||
@ -50,13 +85,17 @@ class TurnIntoOptionAction extends CustomActionCell {
|
|||||||
// todo(lucas): replace the svg with the correct one
|
// todo(lucas): replace the svg with the correct one
|
||||||
leftIcon: const FlowySvg(FlowySvgs.turninto_s),
|
leftIcon: const FlowySvg(FlowySvgs.turninto_s),
|
||||||
name: LocaleKeys.document_plugins_optionAction_turnInto.tr(),
|
name: LocaleKeys.document_plugins_optionAction_turnInto.tr(),
|
||||||
onTap: innerController.show,
|
onTap: () {
|
||||||
|
if (!isOpen) {
|
||||||
|
innerController.show();
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTurnIntoOptionMenu(BuildContext context) {
|
Widget _buildTurnIntoOptionMenu(BuildContext context) {
|
||||||
final selection = editorState.selection?.normalized;
|
final selection = widget.editorState.selection?.normalized;
|
||||||
// the selection may not be collapsed, for example, if a block contains some children,
|
// the selection may not be collapsed, for example, if a block contains some children,
|
||||||
// the selection will be the start from the current block and end at the last child block.
|
// the selection will be the start from the current block and end at the last child block.
|
||||||
// we should take care of this case:
|
// we should take care of this case:
|
||||||
@ -66,7 +105,7 @@ class TurnIntoOptionAction extends CustomActionCell {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final node = editorState.getNodeAtPath(selection.start.path);
|
final node = widget.editorState.getNodeAtPath(selection.start.path);
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@ -78,7 +117,7 @@ class TurnIntoOptionAction extends CustomActionCell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _hasNonSupportedTypes(Selection selection) {
|
bool _hasNonSupportedTypes(Selection selection) {
|
||||||
final nodes = editorState.getNodesInSelection(selection);
|
final nodes = widget.editorState.getNodesInSelection(selection);
|
||||||
if (nodes.isEmpty) {
|
if (nodes.isEmpty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,7 @@ final headingsToolbarItem = ToolbarItem(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return _HeadingPopup(
|
return HeadingPopup(
|
||||||
currentLevel: isHighlight ? level : -1,
|
currentLevel: isHighlight ? level : -1,
|
||||||
highlightColor: highlightColor,
|
highlightColor: highlightColor,
|
||||||
child: child,
|
child: child,
|
||||||
@ -60,9 +60,9 @@ final headingsToolbarItem = ToolbarItem(
|
|||||||
? ParagraphBlockKeys.type
|
? ParagraphBlockKeys.type
|
||||||
: HeadingBlockKeys.type;
|
: HeadingBlockKeys.type;
|
||||||
|
|
||||||
await editorState.formatNode(
|
if (type == HeadingBlockKeys.type) {
|
||||||
selection,
|
// from paragraph to heading
|
||||||
(node) => node.copyWith(
|
final newNode = node.copyWith(
|
||||||
type: type,
|
type: type,
|
||||||
attributes: {
|
attributes: {
|
||||||
HeadingBlockKeys.level: newLevel,
|
HeadingBlockKeys.level: newLevel,
|
||||||
@ -72,15 +72,41 @@ final headingsToolbarItem = ToolbarItem(
|
|||||||
node.attributes[blockComponentTextDirection],
|
node.attributes[blockComponentTextDirection],
|
||||||
blockComponentDelta: delta,
|
blockComponentDelta: delta,
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
);
|
final children = node.children.map((child) => child.copyWith());
|
||||||
|
|
||||||
|
final transaction = editorState.transaction;
|
||||||
|
transaction.insertNodes(
|
||||||
|
selection.start.path.next,
|
||||||
|
[newNode, ...children],
|
||||||
|
);
|
||||||
|
transaction.deleteNode(node);
|
||||||
|
await editorState.apply(transaction);
|
||||||
|
} else {
|
||||||
|
// from heading to paragraph
|
||||||
|
await editorState.formatNode(
|
||||||
|
selection,
|
||||||
|
(node) => node.copyWith(
|
||||||
|
type: type,
|
||||||
|
attributes: {
|
||||||
|
HeadingBlockKeys.level: newLevel,
|
||||||
|
blockComponentBackgroundColor:
|
||||||
|
node.attributes[blockComponentBackgroundColor],
|
||||||
|
blockComponentTextDirection:
|
||||||
|
node.attributes[blockComponentTextDirection],
|
||||||
|
blockComponentDelta: delta,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
class _HeadingPopup extends StatelessWidget {
|
class HeadingPopup extends StatelessWidget {
|
||||||
const _HeadingPopup({
|
const HeadingPopup({
|
||||||
|
super.key,
|
||||||
required this.currentLevel,
|
required this.currentLevel,
|
||||||
required this.highlightColor,
|
required this.highlightColor,
|
||||||
required this.onLevelChanged,
|
required this.onLevelChanged,
|
||||||
@ -144,7 +170,7 @@ class _HeadingButtons extends StatelessWidget {
|
|||||||
final svg = data.$1;
|
final svg = data.$1;
|
||||||
final message = data.$2;
|
final message = data.$2;
|
||||||
return [
|
return [
|
||||||
_HeadingButton(
|
HeadingButton(
|
||||||
icon: svg,
|
icon: svg,
|
||||||
tooltip: message,
|
tooltip: message,
|
||||||
onTap: () => onLevelChanged(index + 1),
|
onTap: () => onLevelChanged(index + 1),
|
||||||
@ -163,8 +189,9 @@ class _HeadingButtons extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HeadingButton extends StatelessWidget {
|
class HeadingButton extends StatelessWidget {
|
||||||
const _HeadingButton({
|
const HeadingButton({
|
||||||
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.tooltip,
|
required this.tooltip,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.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';
|
||||||
|
|
||||||
/// Convert '# ' to bulleted list
|
/// Convert '# ' to bulleted list
|
||||||
@ -35,6 +36,7 @@ CharacterShortcutEvent customFormatSignToHeading = CharacterShortcutEvent(
|
|||||||
level: numberOfSign,
|
level: numberOfSign,
|
||||||
delta: delta.compose(Delta()..delete(numberOfSign)),
|
delta: delta.compose(Delta()..delete(numberOfSign)),
|
||||||
collapsed: collapsed ?? false,
|
collapsed: collapsed ?? false,
|
||||||
|
children: node.children.map((child) => child.copyWith()),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -180,6 +180,31 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
: buildComponentWithChildren(context);
|
: buildComponentWithChildren(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildComponentWithChildren(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
if (backgroundColor != Colors.transparent)
|
||||||
|
Positioned.fill(
|
||||||
|
left: cachedLeft,
|
||||||
|
top: padding.top,
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
color: backgroundColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
NestedListWidget(
|
||||||
|
indentPadding: indentPadding,
|
||||||
|
child: buildComponent(context),
|
||||||
|
children: editorState.renderer.buildList(
|
||||||
|
context,
|
||||||
|
widget.node.children,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildComponent(
|
Widget buildComponent(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@ -190,9 +215,6 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
);
|
);
|
||||||
|
|
||||||
Widget child = Container(
|
Widget child = Container(
|
||||||
color: withBackgroundColor || backgroundColor != Colors.transparent
|
|
||||||
? backgroundColor
|
|
||||||
: null,
|
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
child: Row(
|
child: Row(
|
||||||
@ -208,12 +230,6 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
child = Padding(
|
|
||||||
key: blockComponentKey,
|
|
||||||
padding: padding,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
child = BlockSelectionContainer(
|
child = BlockSelectionContainer(
|
||||||
node: node,
|
node: node,
|
||||||
delegate: this,
|
delegate: this,
|
||||||
@ -225,6 +241,18 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
child = Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Container(
|
||||||
|
key: blockComponentKey,
|
||||||
|
color: withBackgroundColor ||
|
||||||
|
(backgroundColor != Colors.transparent && collapsed)
|
||||||
|
? backgroundColor
|
||||||
|
: null,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (widget.showActions && widget.actionBuilder != null) {
|
if (widget.showActions && widget.actionBuilder != null) {
|
||||||
child = BlockComponentActionWrapper(
|
child = BlockComponentActionWrapper(
|
||||||
node: node,
|
node: node,
|
||||||
@ -293,16 +321,16 @@ class _ToggleListBlockComponentWidgetState
|
|||||||
minHeight: buttonHeight,
|
minHeight: buttonHeight,
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.only(top: top, right: 4.0),
|
padding: EdgeInsets.only(top: top, right: 4.0),
|
||||||
child: AnimatedRotation(
|
child: FlowyIconButton(
|
||||||
turns: collapsed ? 0.0 : 0.25,
|
width: 20.0,
|
||||||
duration: const Duration(milliseconds: 200),
|
onPressed: onCollapsed,
|
||||||
child: FlowyIconButton(
|
icon: AnimatedRotation(
|
||||||
width: 20.0,
|
turns: collapsed ? 0.0 : 0.25,
|
||||||
icon: const Icon(
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: const Icon(
|
||||||
Icons.arrow_right,
|
Icons.arrow_right,
|
||||||
size: 18.0,
|
size: 18.0,
|
||||||
),
|
),
|
||||||
onPressed: onCollapsed,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -61,8 +61,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: eccb244
|
ref: "2903792"
|
||||||
resolved-ref: eccb24452a451c734bc2aeae56c1fd81398d849c
|
resolved-ref: "2903792fa319e1b4077164eeb684f6e8d1c63e27"
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
|||||||
@ -172,7 +172,7 @@ dependency_overrides:
|
|||||||
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: "eccb244"
|
ref: "2903792"
|
||||||
|
|
||||||
appflowy_editor_plugins:
|
appflowy_editor_plugins:
|
||||||
git:
|
git:
|
||||||
|
|||||||
@ -2495,7 +2495,7 @@
|
|||||||
"spaceIcon": "Icon",
|
"spaceIcon": "Icon",
|
||||||
"dangerZone": "Danger Zone",
|
"dangerZone": "Danger Zone",
|
||||||
"unableToDeleteLastSpace": "Unable to delete the last Space",
|
"unableToDeleteLastSpace": "Unable to delete the last Space",
|
||||||
"unableToDeleteSpaceNotCreatedByYou": "Unable to delete Spaces created by others",
|
"unableToDeleteSpaceNotCreatedByYou": "Unable to delete spaces created by others",
|
||||||
"enableSpacesForYourWorkspace": "Enable Spaces for your workspace",
|
"enableSpacesForYourWorkspace": "Enable Spaces for your workspace",
|
||||||
"title": "Spaces",
|
"title": "Spaces",
|
||||||
"defaultSpaceName": "General",
|
"defaultSpaceName": "General",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user