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">
|
<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>
|
||||||
<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>
|
|
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": {
|
"mathEquation": {
|
||||||
"addMathEquation": "Add Math Equation",
|
"addMathEquation": "Add Math Equation",
|
||||||
"editMathEquation": "Edit 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) {
|
Widget _buildEditorPage(BuildContext context, DocumentState state) {
|
||||||
final appflowyEditorPage = AppFlowyEditorPage(
|
final appflowyEditorPage = AppFlowyEditorPage(
|
||||||
editorState: editorState!,
|
editorState: editorState!,
|
||||||
|
header: _buildCoverAndIcon(context),
|
||||||
);
|
);
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
if (state.isDeleted) _buildBanner(context),
|
if (state.isDeleted) _buildBanner(context),
|
||||||
_buildCoverAndIcon(context),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: appflowyEditorPage,
|
child: appflowyEditorPage,
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
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.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_plugins/plugins.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
@ -13,9 +13,11 @@ class AppFlowyEditorPage extends StatefulWidget {
|
|||||||
const AppFlowyEditorPage({
|
const AppFlowyEditorPage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.editorState,
|
required this.editorState,
|
||||||
|
this.header,
|
||||||
});
|
});
|
||||||
|
|
||||||
final EditorState editorState;
|
final EditorState editorState;
|
||||||
|
final Widget? header;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
|
State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
|
||||||
@ -41,16 +43,12 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
|
|
||||||
final List<ToolbarItem> toolbarItems = [
|
final List<ToolbarItem> toolbarItems = [
|
||||||
smartEditItem,
|
smartEditItem,
|
||||||
placeholderItem,
|
|
||||||
paragraphItem,
|
paragraphItem,
|
||||||
...headingItems,
|
...headingItems,
|
||||||
placeholderItem,
|
|
||||||
...markdownFormatItems,
|
...markdownFormatItems,
|
||||||
placeholderItem,
|
|
||||||
quoteItem,
|
quoteItem,
|
||||||
bulletedListItem,
|
bulletedListItem,
|
||||||
numberedListItem,
|
numberedListItem,
|
||||||
placeholderItem,
|
|
||||||
linkItem,
|
linkItem,
|
||||||
textColorItem,
|
textColorItem,
|
||||||
highlightColorItem,
|
highlightColorItem,
|
||||||
@ -70,8 +68,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
(element) => element == slashCommand,
|
(element) => element == slashCommand,
|
||||||
), // remove the default slash command.
|
), // remove the default slash command.
|
||||||
customSlashCommand(slashMenuItems),
|
customSlashCommand(slashMenuItems),
|
||||||
|
|
||||||
|
// formatGreaterToToggleList,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
late final showSlashMenu = customSlashCommand(
|
||||||
|
slashMenuItems,
|
||||||
|
shouldInsertSlash: false,
|
||||||
|
).handler;
|
||||||
|
|
||||||
late final styleCustomizer = EditorStyleCustomizer(context: context);
|
late final styleCustomizer = EditorStyleCustomizer(context: context);
|
||||||
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
||||||
|
|
||||||
@ -92,6 +97,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
// customize the shortcuts
|
// customize the shortcuts
|
||||||
characterShortcutEvents: characterShortcutEvents,
|
characterShortcutEvents: characterShortcutEvents,
|
||||||
commandShortcutEvents: commandShortcutEvents,
|
commandShortcutEvents: commandShortcutEvents,
|
||||||
|
header: widget.header,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
@ -183,6 +189,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
),
|
),
|
||||||
AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(),
|
AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(),
|
||||||
SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(),
|
SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(),
|
||||||
|
// ToggleListBlockKeys.type: ToggleListBlockComponentBuilder(),
|
||||||
};
|
};
|
||||||
|
|
||||||
final builders = {
|
final builders = {
|
||||||
@ -207,24 +214,25 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
TodoListBlockKeys.type,
|
TodoListBlockKeys.type,
|
||||||
CalloutBlockKeys.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 = [
|
final colorAction = [
|
||||||
OptionAction.divider,
|
OptionAction.divider,
|
||||||
OptionAction.color,
|
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,
|
blockComponentContext: context,
|
||||||
blockComponentState: state,
|
blockComponentState: state,
|
||||||
editorState: widget.editorState,
|
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,
|
node: widget.node,
|
||||||
editorState: editorState,
|
editorState: editorState,
|
||||||
placeholderText: placeholderText,
|
placeholderText: placeholderText,
|
||||||
|
lineHeight: 1.5,
|
||||||
textSpanDecorator: (textSpan) => TextSpan(
|
textSpanDecorator: (textSpan) => TextSpan(
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
children: codeTextSpans,
|
children: codeTextSpans,
|
||||||
@ -218,10 +219,12 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSwitchLanguageButton(BuildContext context) {
|
Widget _buildSwitchLanguageButton(BuildContext context) {
|
||||||
|
const maxWidth = 100.0;
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
controller: popoverController,
|
controller: popoverController,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 100,
|
width: maxWidth,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: FlowyTextButton(
|
child: FlowyTextButton(
|
||||||
'${language?.capitalize() ?? 'auto'} ',
|
'${language?.capitalize() ?? 'auto'} ',
|
||||||
@ -229,8 +232,10 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
|||||||
horizontal: 12.0,
|
horizontal: 12.0,
|
||||||
vertical: 4.0,
|
vertical: 4.0,
|
||||||
),
|
),
|
||||||
|
constraints: const BoxConstraints(maxWidth: maxWidth),
|
||||||
fontColor: Theme.of(context).colorScheme.onBackground,
|
fontColor: Theme.of(context).colorScheme.onBackground,
|
||||||
fillColor: Colors.transparent,
|
fillColor: Colors.transparent,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -12,6 +12,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
|
|
||||||
final ToolbarItem smartEditItem = ToolbarItem(
|
final ToolbarItem smartEditItem = ToolbarItem(
|
||||||
id: 'appflowy.editor.smart_edit',
|
id: 'appflowy.editor.smart_edit',
|
||||||
|
group: 0,
|
||||||
isActive: (editorState) {
|
isActive: (editorState) {
|
||||||
final selection = editorState.selection;
|
final selection = editorState.selection;
|
||||||
if (selection == null) {
|
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/auto_completion_node_widget.dart';
|
||||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||||
export 'openai/widgets/smart_edit_toolbar_item.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 theme = Theme.of(context);
|
||||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||||
return EditorStyle.desktop(
|
return EditorStyle.desktop(
|
||||||
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
|
padding: EdgeInsets.only(
|
||||||
|
left: horizontalPadding / 2.0,
|
||||||
|
right: horizontalPadding,
|
||||||
|
),
|
||||||
backgroundColor: theme.colorScheme.surface,
|
backgroundColor: theme.colorScheme.surface,
|
||||||
cursorColor: theme.colorScheme.primary,
|
cursorColor: theme.colorScheme.primary,
|
||||||
textStyleConfiguration: TextStyleConfiguration(
|
textStyleConfiguration: TextStyleConfiguration(
|
||||||
@ -115,11 +118,13 @@ class EditorStyleCustomizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextStyle codeBlockStyleBuilder() {
|
TextStyle codeBlockStyleBuilder() {
|
||||||
|
final theme = Theme.of(context);
|
||||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||||
return TextStyle(
|
return TextStyle(
|
||||||
fontFamily: 'poppins',
|
fontFamily: 'poppins',
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
height: 1.5,
|
height: 1.5,
|
||||||
|
color: theme.colorScheme.onBackground,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "4f66f7"
|
ref: b1a1b14
|
||||||
resolved-ref: "4f66f77debabbc35cf4a56c816f9432a831a40e2"
|
resolved-ref: b1a1b14f35114a7becdb3e2de909d546d7328a59
|
||||||
url: "https://github.com/LucasXu0/appflowy-editor.git"
|
url: "https://github.com/LucasXu0/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.12"
|
version: "0.1.12"
|
||||||
|
@ -47,7 +47,7 @@ dependencies:
|
|||||||
# path: /Users/lucas.xu/Desktop/appflowy-editor
|
# path: /Users/lucas.xu/Desktop/appflowy-editor
|
||||||
git:
|
git:
|
||||||
url: https://github.com/LucasXu0/appflowy-editor.git
|
url: https://github.com/LucasXu0/appflowy-editor.git
|
||||||
ref: 4f66f7
|
ref: b1a1b14
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user