mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: add toggle list and integrate add block button (#2547)
This commit is contained in:
parent
bc66f43f47
commit
623f182bba
@ -1,4 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="7.5" y="4" width="1" height="8" rx="0.5" fill="#333333"/>
|
||||
<rect x="12" y="7.5" width="1" height="8" rx="0.5" transform="rotate(90 12 7.5)" fill="#333333"/>
|
||||
</svg>
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="plus" style="width: 16px; height: 100%; display: block; fill: inherit; flex-shrink: 0; backface-visibility: hidden;" width="16" height="16" ><path d="M7.977 14.963c.407 0 .747-.324.747-.723V8.72h5.362c.399 0 .74-.34.74-.747a.746.746 0 00-.74-.738H8.724V1.706c0-.398-.34-.722-.747-.722a.732.732 0 00-.739.722v5.529h-5.37a.746.746 0 00-.74.738c0 .407.341.747.74.747h5.37v5.52c0 .399.332.723.739.723z" fill-opacity="0.35" fill="#37352F"></path></svg>
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 558 B |
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path stroke="currentColor" stroke-linecap="round" stroke-width="2.6" d="M9.41 7.3H9.4M14.6 7.3h-.01M9.31 12H9.3M14.6 12h-.01M9.41 16.7H9.4M14.6 16.7h-.01"/></svg>
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" class="dragHandle" style="width: 14px; height: 14px; display: block; fill: inherit; flex-shrink: 0; backface-visibility: hidden;" width="14" height="14" ><path d="M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z" fill-opacity="0.35" fill="#37352F"></path></svg>
|
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 1.1 KiB |
@ -395,6 +395,10 @@
|
||||
"mathEquation": {
|
||||
"addMathEquation": "Add Math Equation",
|
||||
"editMathEquation": "Edit Math Equation"
|
||||
},
|
||||
"optionAction": {
|
||||
"click": "Click",
|
||||
"toOpenMenu": " to open menu"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -89,11 +89,11 @@ class _DocumentPageState extends State<DocumentPage> {
|
||||
Widget _buildEditorPage(BuildContext context, DocumentState state) {
|
||||
final appflowyEditorPage = AppFlowyEditorPage(
|
||||
editorState: editorState!,
|
||||
header: _buildCoverAndIcon(context),
|
||||
);
|
||||
return Column(
|
||||
children: [
|
||||
if (state.isDeleted) _buildBanner(context),
|
||||
_buildCoverAndIcon(context),
|
||||
Expanded(
|
||||
child: appflowyEditorPage,
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_list.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@ -13,9 +13,11 @@ class AppFlowyEditorPage extends StatefulWidget {
|
||||
const AppFlowyEditorPage({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
this.header,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final Widget? header;
|
||||
|
||||
@override
|
||||
State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
|
||||
@ -41,16 +43,12 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
|
||||
final List<ToolbarItem> toolbarItems = [
|
||||
smartEditItem,
|
||||
placeholderItem,
|
||||
paragraphItem,
|
||||
...headingItems,
|
||||
placeholderItem,
|
||||
...markdownFormatItems,
|
||||
placeholderItem,
|
||||
quoteItem,
|
||||
bulletedListItem,
|
||||
numberedListItem,
|
||||
placeholderItem,
|
||||
linkItem,
|
||||
textColorItem,
|
||||
highlightColorItem,
|
||||
@ -70,8 +68,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
(element) => element == slashCommand,
|
||||
), // remove the default slash command.
|
||||
customSlashCommand(slashMenuItems),
|
||||
|
||||
// formatGreaterToToggleList,
|
||||
];
|
||||
|
||||
late final showSlashMenu = customSlashCommand(
|
||||
slashMenuItems,
|
||||
shouldInsertSlash: false,
|
||||
).handler;
|
||||
|
||||
late final styleCustomizer = EditorStyleCustomizer(context: context);
|
||||
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
||||
|
||||
@ -92,6 +97,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
// customize the shortcuts
|
||||
characterShortcutEvents: characterShortcutEvents,
|
||||
commandShortcutEvents: commandShortcutEvents,
|
||||
header: widget.header,
|
||||
);
|
||||
|
||||
return Center(
|
||||
@ -183,6 +189,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
),
|
||||
AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(),
|
||||
SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(),
|
||||
// ToggleListBlockKeys.type: ToggleListBlockComponentBuilder(),
|
||||
};
|
||||
|
||||
final builders = {
|
||||
@ -207,24 +214,25 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
TodoListBlockKeys.type,
|
||||
CalloutBlockKeys.type
|
||||
];
|
||||
if (!supportColorBuilderTypes.contains(entry.key)) {
|
||||
builder.actionBuilder = (context, state) => OptionActionList(
|
||||
blockComponentContext: context,
|
||||
blockComponentState: state,
|
||||
editorState: widget.editorState,
|
||||
actions: standardActions,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
final colorAction = [
|
||||
OptionAction.divider,
|
||||
OptionAction.color,
|
||||
];
|
||||
builder.actionBuilder = (context, state) => OptionActionList(
|
||||
|
||||
final List<OptionAction> actions = [
|
||||
...standardActions,
|
||||
if (supportColorBuilderTypes.contains(entry.key)) ...colorAction,
|
||||
];
|
||||
|
||||
builder.actionBuilder = (context, state) => BlockActionList(
|
||||
blockComponentContext: context,
|
||||
blockComponentState: state,
|
||||
editorState: widget.editorState,
|
||||
actions: standardActions + colorAction,
|
||||
actions: actions,
|
||||
showSlashMenu: () => showSlashMenu(
|
||||
widget.editorState,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,52 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BlockAddButton extends StatelessWidget {
|
||||
const BlockAddButton({
|
||||
Key? key,
|
||||
required this.blockComponentContext,
|
||||
required this.blockComponentState,
|
||||
required this.editorState,
|
||||
required this.showSlashMenu,
|
||||
}) : super(key: key);
|
||||
|
||||
final BlockComponentContext blockComponentContext;
|
||||
final BlockComponentState blockComponentState;
|
||||
|
||||
final EditorState editorState;
|
||||
final VoidCallback showSlashMenu;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlockActionButton(
|
||||
svgName: 'editor/add',
|
||||
richMessage: const TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
// todo: l10n.
|
||||
text: 'Click to add below',
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final transaction = editorState.transaction;
|
||||
// if the current block is not a empty paragraph block, then insert a new block below the current block.
|
||||
final node = blockComponentContext.node;
|
||||
if (node.type != ParagraphBlockKeys.type ||
|
||||
(node.delta?.isNotEmpty ?? true)) {
|
||||
transaction.insertNode(node.path.next, paragraphNode());
|
||||
transaction.afterSelection = Selection.collapse(node.path.next, 0);
|
||||
} else {
|
||||
transaction.afterSelection = Selection.collapse(node.path, 0);
|
||||
}
|
||||
// show the slash menu.
|
||||
editorState.apply(transaction).then(
|
||||
(_) => WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => showSlashMenu(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BlockActionButton extends StatelessWidget {
|
||||
const BlockActionButton({
|
||||
super.key,
|
||||
required this.svgName,
|
||||
required this.richMessage,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String svgName;
|
||||
final InlineSpan richMessage;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: Tooltip(
|
||||
preferBelow: false,
|
||||
richMessage: richMessage,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.grab,
|
||||
child: IgnoreParentGestureWidget(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
behavior: HitTestBehavior.deferToChild,
|
||||
child: svgWidget(
|
||||
svgName,
|
||||
size: const Size.square(14.0),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
class BlockActionList extends StatelessWidget {
|
||||
const BlockActionList({
|
||||
super.key,
|
||||
required this.blockComponentContext,
|
||||
required this.blockComponentState,
|
||||
required this.editorState,
|
||||
required this.actions,
|
||||
required this.showSlashMenu,
|
||||
});
|
||||
|
||||
final BlockComponentContext blockComponentContext;
|
||||
final BlockComponentState blockComponentState;
|
||||
final List<OptionAction> actions;
|
||||
final VoidCallback showSlashMenu;
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
BlockAddButton(
|
||||
blockComponentContext: blockComponentContext,
|
||||
blockComponentState: blockComponentState,
|
||||
editorState: editorState,
|
||||
showSlashMenu: showSlashMenu,
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
BlockOptionButton(
|
||||
blockComponentContext: blockComponentContext,
|
||||
blockComponentState: blockComponentState,
|
||||
actions: actions,
|
||||
editorState: editorState,
|
||||
),
|
||||
const SizedBox(width: 6.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
class BlockOptionButton extends StatelessWidget {
|
||||
const BlockOptionButton({
|
||||
Key? key,
|
||||
required this.blockComponentContext,
|
||||
required this.blockComponentState,
|
||||
required this.actions,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
final BlockComponentContext blockComponentContext;
|
||||
final BlockComponentState blockComponentState;
|
||||
final List<OptionAction> actions;
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final popoverActions = actions.map((e) {
|
||||
if (e == OptionAction.divider) {
|
||||
return DividerOptionAction();
|
||||
} else if (e == OptionAction.color) {
|
||||
return ColorOptionAction(
|
||||
editorState: editorState,
|
||||
);
|
||||
} else {
|
||||
return OptionActionWrapper(e);
|
||||
}
|
||||
}).toList();
|
||||
|
||||
return PopoverActionList<PopoverAction>(
|
||||
direction: PopoverDirection.leftWithCenterAligned,
|
||||
actions: popoverActions,
|
||||
onPopupBuilder: () => blockComponentState.alwaysShowActions = true,
|
||||
onClosed: () {
|
||||
editorState.selectionType = null;
|
||||
editorState.selection = null;
|
||||
blockComponentState.alwaysShowActions = false;
|
||||
},
|
||||
onSelected: (action, controller) {
|
||||
if (action is OptionActionWrapper) {
|
||||
_onSelectAction(action.inner);
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
buildChild: (controller) => _buildOptionButton(controller),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOptionButton(PopoverController controller) {
|
||||
return BlockActionButton(
|
||||
svgName: 'editor/option',
|
||||
richMessage: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
// todo: customize the color to highlight the text.
|
||||
text: LocaleKeys.document_plugins_optionAction_click.tr(),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.document_plugins_optionAction_toOpenMenu.tr(),
|
||||
)
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
controller.show();
|
||||
|
||||
// update selection
|
||||
_updateBlockSelection();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _updateBlockSelection() {
|
||||
final startNode = blockComponentContext.node;
|
||||
var endNode = startNode;
|
||||
while (endNode.children.isNotEmpty) {
|
||||
endNode = endNode.children.last;
|
||||
}
|
||||
|
||||
final start = Position(path: startNode.path, offset: 0);
|
||||
final end = endNode.selectable?.end() ??
|
||||
Position(
|
||||
path: endNode.path,
|
||||
offset: endNode.delta?.length ?? 0,
|
||||
);
|
||||
|
||||
editorState.selectionType = SelectionType.block;
|
||||
editorState.selection = Selection(
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelectAction(OptionAction action) {
|
||||
final node = blockComponentContext.node;
|
||||
final transaction = editorState.transaction;
|
||||
switch (action) {
|
||||
case OptionAction.delete:
|
||||
transaction.deleteNode(node);
|
||||
break;
|
||||
case OptionAction.duplicate:
|
||||
transaction.insertNode(
|
||||
node.path.next,
|
||||
node.copyWith(),
|
||||
);
|
||||
break;
|
||||
case OptionAction.turnInto:
|
||||
break;
|
||||
case OptionAction.moveUp:
|
||||
transaction.moveNode(node.path.previous, node);
|
||||
break;
|
||||
case OptionAction.moveDown:
|
||||
transaction.moveNode(node.path.next.next, node);
|
||||
break;
|
||||
case OptionAction.color:
|
||||
// show the color picker
|
||||
|
||||
break;
|
||||
case OptionAction.divider:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
editorState.apply(transaction);
|
||||
}
|
||||
}
|
@ -206,6 +206,7 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
||||
node: widget.node,
|
||||
editorState: editorState,
|
||||
placeholderText: placeholderText,
|
||||
lineHeight: 1.5,
|
||||
textSpanDecorator: (textSpan) => TextSpan(
|
||||
style: textStyle,
|
||||
children: codeTextSpans,
|
||||
@ -218,10 +219,12 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
||||
}
|
||||
|
||||
Widget _buildSwitchLanguageButton(BuildContext context) {
|
||||
const maxWidth = 100.0;
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
child: Container(
|
||||
width: 100,
|
||||
width: maxWidth,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: FlowyTextButton(
|
||||
'${language?.capitalize() ?? 'auto'} ',
|
||||
@ -229,8 +232,10 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
||||
horizontal: 12.0,
|
||||
vertical: 4.0,
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: maxWidth),
|
||||
fontColor: Theme.of(context).colorScheme.onBackground,
|
||||
fillColor: Colors.transparent,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
|
@ -12,6 +12,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
final ToolbarItem smartEditItem = ToolbarItem(
|
||||
id: 'appflowy.editor.smart_edit',
|
||||
group: 0,
|
||||
isActive: (editorState) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
|
@ -18,3 +18,5 @@ export 'math_equation/math_equation_block_component.dart';
|
||||
export 'openai/widgets/auto_completion_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_toolbar_item.dart';
|
||||
export 'toggle/toggle_block_component.dart';
|
||||
export 'toggle/toggle_block_shortcut_event.dart';
|
||||
|
@ -0,0 +1,165 @@
|
||||
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._();
|
||||
|
||||
static const String type = 'toggle_list';
|
||||
|
||||
/// The content of a code block.
|
||||
///
|
||||
/// The value is a String.
|
||||
static const String delta = 'delta';
|
||||
|
||||
/// The value is a bool.
|
||||
static const String collapsed = 'collapsed';
|
||||
}
|
||||
|
||||
Node toggleListBlockNode({
|
||||
Delta? delta,
|
||||
bool collapsed = false,
|
||||
}) {
|
||||
final attributes = {
|
||||
ToggleListBlockKeys.delta: (delta ?? Delta()).toJson(),
|
||||
ToggleListBlockKeys.collapsed: collapsed,
|
||||
};
|
||||
return Node(
|
||||
type: ToggleListBlockKeys.type,
|
||||
attributes: attributes,
|
||||
children: [paragraphNode()],
|
||||
);
|
||||
}
|
||||
|
||||
class ToggleListBlockComponentBuilder extends BlockComponentBuilder {
|
||||
ToggleListBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
this.padding = const EdgeInsets.all(0),
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final EdgeInsets padding;
|
||||
|
||||
@override
|
||||
Widget build(BlockComponentContext blockComponentContext) {
|
||||
final node = blockComponentContext.node;
|
||||
return ToggleListBlockComponentWidget(
|
||||
key: node.key,
|
||||
node: node,
|
||||
configuration: configuration,
|
||||
padding: padding,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool validate(Node node) => node.delta != null;
|
||||
}
|
||||
|
||||
class ToggleListBlockComponentWidget extends StatefulWidget {
|
||||
const ToggleListBlockComponentWidget({
|
||||
Key? key,
|
||||
required this.node,
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
this.padding = const EdgeInsets.all(0),
|
||||
}) : super(key: key);
|
||||
|
||||
final Node node;
|
||||
final BlockComponentConfiguration configuration;
|
||||
final EdgeInsets padding;
|
||||
|
||||
@override
|
||||
State<ToggleListBlockComponentWidget> createState() =>
|
||||
_ToggleListBlockComponentWidgetState();
|
||||
}
|
||||
|
||||
class _ToggleListBlockComponentWidgetState
|
||||
extends State<ToggleListBlockComponentWidget>
|
||||
with
|
||||
SelectableMixin,
|
||||
DefaultSelectable,
|
||||
BlockComponentConfigurable,
|
||||
BackgroundColorMixin {
|
||||
// the key used to forward focus to the richtext child
|
||||
@override
|
||||
final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text');
|
||||
|
||||
@override
|
||||
BlockComponentConfiguration get configuration => widget.configuration;
|
||||
|
||||
@override
|
||||
GlobalKey<State<StatefulWidget>> get containerKey => node.key;
|
||||
|
||||
@override
|
||||
Node get node => widget.node;
|
||||
|
||||
bool get collapsed => node.attributes[ToggleListBlockKeys.collapsed] ?? false;
|
||||
|
||||
late final editorState = context.read<EditorState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return collapsed
|
||||
? buildToggleListBlockComponent(context)
|
||||
: buildToggleListBlockComponentWithChildren(context);
|
||||
}
|
||||
|
||||
Widget buildToggleListBlockComponentWithChildren(BuildContext context) {
|
||||
return Container(
|
||||
color: backgroundColor,
|
||||
child: NestedListWidget(
|
||||
children: editorState.renderer.buildList(
|
||||
context,
|
||||
widget.node.children,
|
||||
),
|
||||
child: buildToggleListBlockComponent(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// build the richtext child
|
||||
Widget buildToggleListBlockComponent(BuildContext context) {
|
||||
return 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: FlowyRichText(
|
||||
key: forwardKey,
|
||||
node: widget.node,
|
||||
editorState: editorState,
|
||||
placeholderText: placeholderText,
|
||||
lineHeight: 1.5,
|
||||
textSpanDecorator: (textSpan) => textSpan.updateTextStyle(
|
||||
textStyle,
|
||||
),
|
||||
placeholderTextSpanDecorator: (textSpan) =>
|
||||
textSpan.updateTextStyle(
|
||||
placeholderTextStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onCollapsed() async {
|
||||
final transaction = editorState.transaction
|
||||
..updateNode(node, {
|
||||
ToggleListBlockKeys.collapsed: !collapsed,
|
||||
});
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
const _greater = '>';
|
||||
|
||||
/// Convert '> ' to toggle list
|
||||
///
|
||||
/// - support
|
||||
/// - desktop
|
||||
/// - mobile
|
||||
/// - web
|
||||
///
|
||||
CharacterShortcutEvent formatGreaterToToggleList = CharacterShortcutEvent(
|
||||
key: 'format greater to quote',
|
||||
character: ' ',
|
||||
handler: (editorState) async => await formatMarkdownSymbol(
|
||||
editorState,
|
||||
(node) => node.type != ToggleListBlockKeys.type,
|
||||
(text, _) => text == _greater,
|
||||
(_, node, delta) => toggleListBlockNode(
|
||||
delta: delta.compose(Delta()..delete(_greater.length)),
|
||||
),
|
||||
),
|
||||
);
|
@ -28,7 +28,10 @@ class EditorStyleCustomizer {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
return EditorStyle.desktop(
|
||||
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
|
||||
padding: EdgeInsets.only(
|
||||
left: horizontalPadding / 2.0,
|
||||
right: horizontalPadding,
|
||||
),
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
cursorColor: theme.colorScheme.primary,
|
||||
textStyleConfiguration: TextStyleConfiguration(
|
||||
@ -115,11 +118,13 @@ class EditorStyleCustomizer {
|
||||
}
|
||||
|
||||
TextStyle codeBlockStyleBuilder() {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
return TextStyle(
|
||||
fontFamily: 'poppins',
|
||||
fontSize: fontSize,
|
||||
height: 1.5,
|
||||
color: theme.colorScheme.onBackground,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "4f66f7"
|
||||
resolved-ref: "4f66f77debabbc35cf4a56c816f9432a831a40e2"
|
||||
ref: b1a1b14
|
||||
resolved-ref: b1a1b14f35114a7becdb3e2de909d546d7328a59
|
||||
url: "https://github.com/LucasXu0/appflowy-editor.git"
|
||||
source: git
|
||||
version: "0.1.12"
|
||||
|
@ -47,7 +47,7 @@ dependencies:
|
||||
# path: /Users/lucas.xu/Desktop/appflowy-editor
|
||||
git:
|
||||
url: https://github.com/LucasXu0/appflowy-editor.git
|
||||
ref: 4f66f7
|
||||
ref: b1a1b14
|
||||
appflowy_popover:
|
||||
path: packages/appflowy_popover
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user