diff --git a/frontend/app_flowy/packages/appflowy_editor/assets/images/delete.svg b/frontend/app_flowy/packages/appflowy_editor/assets/images/delete.svg new file mode 100644 index 0000000000..b7f242542d --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/assets/images/delete.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_editor/assets/images/link.svg b/frontend/app_flowy/packages/appflowy_editor/assets/images/link.svg new file mode 100644 index 0000000000..5fbcc8d787 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/assets/images/link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/app_flowy/packages/appflowy_editor/assets/images/toolbar/link.svg b/frontend/app_flowy/packages/appflowy_editor/assets/images/toolbar/link.svg index 612e8377b6..279e7ac471 100644 --- a/frontend/app_flowy/packages/appflowy_editor/assets/images/toolbar/link.svg +++ b/frontend/app_flowy/packages/appflowy_editor/assets/images/toolbar/link.svg @@ -1,4 +1,4 @@ - - + + diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart index 1d7c68ab80..8416485a5a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart @@ -6,6 +6,31 @@ import 'package:appflowy_editor/src/document/text_delta.dart'; import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart'; extension TextNodeExtension on TextNode { + dynamic getAttributeInSelection(Selection selection, String styleKey) { + final ops = delta.whereType(); + final startOffset = + selection.isBackward ? selection.start.offset : selection.end.offset; + final endOffset = + selection.isBackward ? selection.end.offset : selection.start.offset; + var start = 0; + for (final op in ops) { + if (start >= endOffset) { + break; + } + final length = op.length; + if (start < endOffset && start + length > startOffset) { + if (op.attributes?.containsKey(styleKey) == true) { + return op.attributes![styleKey]; + } + } + start += length; + } + return null; + } + + bool allSatisfyLinkInSelection(Selection selection) => + allSatisfyInSelection(StyleKey.href, null, selection); + bool allSatisfyBoldInSelection(Selection selection) => allSatisfyInSelection(StyleKey.bold, true, selection); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart new file mode 100644 index 0000000000..3ebf87f4fa --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart @@ -0,0 +1,137 @@ +import 'package:appflowy_editor/src/infra/flowy_svg.dart'; +import 'package:flutter/material.dart'; + +class LinkMenu extends StatefulWidget { + const LinkMenu({ + Key? key, + this.linkText, + required this.onSubmitted, + required this.onCopyLink, + required this.onRemoveLink, + }) : super(key: key); + + final String? linkText; + final void Function(String text) onSubmitted; + final VoidCallback onCopyLink; + final VoidCallback onRemoveLink; + + @override + State createState() => _LinkMenuState(); +} + +class _LinkMenuState extends State { + final _textEditingController = TextEditingController(); + + @override + void initState() { + super.initState(); + + _textEditingController.text = widget.linkText ?? ''; + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 350, + height: 200, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + blurRadius: 5, + spreadRadius: 1, + color: Colors.black.withOpacity(0.1), + ), + ], + borderRadius: BorderRadius.circular(6.0), + ), + child: SizedBox( + width: 350, + height: 200, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + const SizedBox(height: 16.0), + _buildInput(), + const SizedBox(height: 16.0), + _buildIconButton( + iconName: 'link', + text: 'Copy link', + onPressed: widget.onCopyLink, + ), + _buildIconButton( + iconName: 'delete', + text: 'Remove link', + onPressed: widget.onRemoveLink, + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildHeader() { + return const Text( + 'Add your link', + style: TextStyle( + color: Colors.grey, + fontWeight: FontWeight.bold, + ), + ); + } + + Widget _buildInput() { + return TextField( + autofocus: true, + style: const TextStyle(fontSize: 14.0), + textAlign: TextAlign.left, + controller: _textEditingController, + onSubmitted: widget.onSubmitted, + decoration: const InputDecoration( + hintText: 'URL', + hintStyle: TextStyle(fontSize: 14.0), + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12.0)), + borderSide: BorderSide(color: Color(0xFFBDBDBD)), + ), + contentPadding: EdgeInsets.all(16.0), + isDense: true, + ), + ); + } + + Widget _buildIconButton({ + required String iconName, + required String text, + required VoidCallback onPressed, + }) { + return TextButton.icon( + icon: FlowySvg( + name: iconName, + width: 20.0, + height: 20.0, + ), + style: TextButton.styleFrom( + minimumSize: const Size.fromHeight(40), + padding: EdgeInsets.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + alignment: Alignment.centerLeft, + ), + label: Text( + text, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.black, + fontSize: 14.0, + ), + ), + onPressed: onPressed, + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart deleted file mode 100644 index 4c2b621795..0000000000 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/toolbar_widget.dart +++ /dev/null @@ -1,217 +0,0 @@ -import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart'; -import 'package:flutter/material.dart'; - -import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/infra/flowy_svg.dart'; -import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart'; - -typedef ToolbarEventHandler = void Function(EditorState editorState); - -typedef ToolbarEventHandlers = Map; - -ToolbarEventHandlers defaultToolbarEventHandlers = { - 'bold': (editorState) => formatBold(editorState), - 'italic': (editorState) => formatItalic(editorState), - 'strikethrough': (editorState) => formatStrikethrough(editorState), - 'underline': (editorState) => formatUnderline(editorState), - 'quote': (editorState) => formatQuote(editorState), - 'bulleted_list': (editorState) => formatBulletedList(editorState), - 'highlight': (editorState) => formatHighlight(editorState), - 'Text': (editorState) => formatText(editorState), - 'h1': (editorState) => formatHeading(editorState, StyleKey.h1), - 'h2': (editorState) => formatHeading(editorState, StyleKey.h2), - 'h3': (editorState) => formatHeading(editorState, StyleKey.h3), -}; - -List defaultListToolbarEventNames = [ - 'Text', - 'H1', - 'H2', - 'H3', -]; - -mixin ToolbarMixin on State { - void hide(); -} - -class ToolbarWidget extends StatefulWidget { - const ToolbarWidget({ - Key? key, - required this.editorState, - required this.layerLink, - required this.offset, - required this.handlers, - }) : super(key: key); - - final EditorState editorState; - final LayerLink layerLink; - final Offset offset; - final ToolbarEventHandlers handlers; - - @override - State createState() => _ToolbarWidgetState(); -} - -class _ToolbarWidgetState extends State with ToolbarMixin { - // final GlobalKey _listToolbarKey = GlobalKey(); - - final toolbarHeight = 32.0; - final topPadding = 5.0; - - final listToolbarWidth = 60.0; - final listToolbarHeight = 120.0; - - final cornerRadius = 8.0; - - OverlayEntry? _listToolbarOverlay; - - @override - Widget build(BuildContext context) { - return Positioned( - top: widget.offset.dx, - left: widget.offset.dy, - child: CompositedTransformFollower( - link: widget.layerLink, - showWhenUnlinked: true, - offset: widget.offset, - child: _buildToolbar(context), - ), - ); - } - - @override - void hide() { - _listToolbarOverlay?.remove(); - _listToolbarOverlay = null; - } - - Widget _buildToolbar(BuildContext context) { - return Material( - borderRadius: BorderRadius.circular(cornerRadius), - color: const Color(0xFF333333), - child: SizedBox( - height: toolbarHeight, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // _listToolbar(context), - _centerToolbarIcon('h1', tooltipMessage: 'Heading 1'), - _centerToolbarIcon('h2', tooltipMessage: 'Heading 2'), - _centerToolbarIcon('h3', tooltipMessage: 'Heading 3'), - _centerToolbarIcon('divider', width: 2), - _centerToolbarIcon('bold', tooltipMessage: 'Bold'), - _centerToolbarIcon('italic', tooltipMessage: 'Italic'), - _centerToolbarIcon('strikethrough', - tooltipMessage: 'Strikethrough'), - _centerToolbarIcon('underline', tooltipMessage: 'Underline'), - _centerToolbarIcon('divider', width: 2), - _centerToolbarIcon('quote', tooltipMessage: 'Quote'), - // _centerToolbarIcon('number_list'), - _centerToolbarIcon('bulleted_list', - tooltipMessage: 'Bulleted List'), - _centerToolbarIcon('divider', width: 2), - _centerToolbarIcon('highlight', tooltipMessage: 'Highlight'), - ], - ), - ), - ); - } - - // Widget _listToolbar(BuildContext context) { - // return _centerToolbarIcon( - // 'quote', - // key: _listToolbarKey, - // width: listToolbarWidth, - // onTap: () => _onTapListToolbar(context), - // ); - // } - - Widget _centerToolbarIcon(String name, - {Key? key, String? tooltipMessage, double? width, VoidCallback? onTap}) { - return Tooltip( - key: key, - preferBelow: false, - message: tooltipMessage ?? '', - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onTap ?? () => _onTap(name), - child: SizedBox.fromSize( - size: - Size(toolbarHeight - (width != null ? 20 : 0), toolbarHeight), - child: Center( - child: FlowySvg( - width: width ?? 20, - name: 'toolbar/$name', - ), - ), - ), - ), - )); - } - - // void _onTapListToolbar(BuildContext context) { - // // TODO: implement more detailed UI. - // final items = defaultListToolbarEventNames; - // final renderBox = - // _listToolbarKey.currentContext?.findRenderObject() as RenderBox; - // final offset = renderBox - // .localToGlobal(Offset.zero) - // .translate(0, toolbarHeight - cornerRadius); - // final rect = offset & Size(listToolbarWidth, listToolbarHeight); - - // _listToolbarOverlay?.remove(); - // _listToolbarOverlay = OverlayEntry(builder: (context) { - // return Positioned.fromRect( - // rect: rect, - // child: Material( - // borderRadius: BorderRadius.only( - // bottomLeft: Radius.circular(cornerRadius), - // bottomRight: Radius.circular(cornerRadius), - // ), - // color: const Color(0xFF333333), - // child: SingleChildScrollView( - // child: ListView.builder( - // itemExtent: toolbarHeight, - // padding: const EdgeInsets.only(bottom: 10.0), - // shrinkWrap: true, - // itemCount: items.length, - // itemBuilder: ((context, index) { - // return ListTile( - // contentPadding: const EdgeInsets.only( - // left: 3.0, - // right: 3.0, - // ), - // minVerticalPadding: 0.0, - // title: FittedBox( - // fit: BoxFit.scaleDown, - // child: Text( - // items[index], - // textAlign: TextAlign.center, - // style: const TextStyle( - // color: Colors.white, - // ), - // ), - // ), - // onTap: () { - // _onTap(items[index]); - // }, - // ); - // }), - // ), - // ), - // ), - // ); - // }); - // // TODO: disable scrolling. - // Overlay.of(context)?.insert(_listToolbarOverlay!); - // } - - void _onTap(String eventName) { - if (defaultToolbarEventHandlers.containsKey(eventName)) { - defaultToolbarEventHandlers[eventName]!(widget.editorState); - return; - } - assert(false, 'Could not find the event handler for $eventName'); - } -} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart new file mode 100644 index 0000000000..49413311d2 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart @@ -0,0 +1,192 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/infra/flowy_svg.dart'; +import 'package:appflowy_editor/src/render/link_menu/link_menu.dart'; +import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart'; +import 'package:appflowy_editor/src/extensions/text_node_extensions.dart'; +import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart'; +import 'package:flutter/material.dart'; +import 'package:rich_clipboard/rich_clipboard.dart'; + +typedef ToolbarEventHandler = void Function( + EditorState editorState, BuildContext context); +typedef ToolbarShowValidator = bool Function(EditorState editorState); + +class ToolbarItem { + ToolbarItem({ + required this.icon, + this.tooltipsMessage = '', + required this.validator, + required this.handler, + }); + + final Widget icon; + final String tooltipsMessage; + final ToolbarShowValidator validator; + final ToolbarEventHandler handler; + + factory ToolbarItem.divider() { + return ToolbarItem( + icon: const FlowySvg(name: 'toolbar/divider'), + validator: (editorState) => true, + handler: (editorState, context) {}, + ); + } +} + +List defaultToolbarItems = [ + ToolbarItem( + tooltipsMessage: 'Heading 1', + icon: const FlowySvg(name: 'toolbar/h1'), + validator: _onlyShowInSingleTextSelection, + handler: (editorState, context) => formatHeading(editorState, StyleKey.h1), + ), + ToolbarItem( + tooltipsMessage: 'Heading 2', + icon: const FlowySvg(name: 'toolbar/h2'), + validator: _onlyShowInSingleTextSelection, + handler: (editorState, context) => formatHeading(editorState, StyleKey.h2), + ), + ToolbarItem( + tooltipsMessage: 'Heading 3', + icon: const FlowySvg(name: 'toolbar/h3'), + validator: _onlyShowInSingleTextSelection, + handler: (editorState, context) => formatHeading(editorState, StyleKey.h3), + ), + ToolbarItem.divider(), + ToolbarItem( + tooltipsMessage: 'Bold', + icon: const FlowySvg(name: 'toolbar/bold'), + validator: _showInTextSelection, + handler: (editorState, context) => formatBold(editorState), + ), + ToolbarItem( + tooltipsMessage: 'Italic', + icon: const FlowySvg(name: 'toolbar/italic'), + validator: _showInTextSelection, + handler: (editorState, context) => formatItalic(editorState), + ), + ToolbarItem( + tooltipsMessage: 'Underline', + icon: const FlowySvg(name: 'toolbar/underline'), + validator: _showInTextSelection, + handler: (editorState, context) => formatUnderline(editorState), + ), + ToolbarItem( + tooltipsMessage: 'Strikethrough', + icon: const FlowySvg(name: 'toolbar/strikethrough'), + validator: _showInTextSelection, + handler: (editorState, context) => formatStrikethrough(editorState), + ), + ToolbarItem.divider(), + ToolbarItem( + tooltipsMessage: 'Quote', + icon: const FlowySvg(name: 'toolbar/quote'), + validator: _onlyShowInSingleTextSelection, + handler: (editorState, context) => formatQuote(editorState), + ), + ToolbarItem( + tooltipsMessage: 'Bulleted list', + icon: const FlowySvg(name: 'toolbar/bulleted_list'), + validator: _onlyShowInSingleTextSelection, + handler: (editorState, context) => formatBulletedList(editorState), + ), + ToolbarItem.divider(), + ToolbarItem( + tooltipsMessage: 'Link', + icon: const FlowySvg(name: 'toolbar/link'), + validator: _onlyShowInSingleTextSelection, + handler: (editorState, context) => _showLinkMenu(editorState, context), + ), + ToolbarItem( + tooltipsMessage: 'Highlight', + icon: const FlowySvg(name: 'toolbar/highlight'), + validator: _showInTextSelection, + handler: (editorState, context) => formatHighlight(editorState), + ), +]; + +ToolbarShowValidator _onlyShowInSingleTextSelection = (editorState) { + final nodes = editorState.service.selectionService.currentSelectedNodes; + return (nodes.length == 1 && nodes.first is TextNode); +}; + +ToolbarShowValidator _showInTextSelection = (editorState) { + final nodes = editorState.service.selectionService.currentSelectedNodes + .whereType(); + return nodes.isNotEmpty; +}; + +OverlayEntry? _linkMenuOverlay; +EditorState? _editorState; +void _showLinkMenu(EditorState editorState, BuildContext context) { + _editorState = editorState; + + final rects = editorState.service.selectionService.selectionRects; + var maxBottom = 0.0; + late Rect matchRect; + for (final rect in rects) { + if (rect.bottom > maxBottom) { + maxBottom = rect.bottom; + matchRect = rect; + } + } + + _dismissLinkMenu(); + + // Since the link menu will only show in single text selection, + // We get the text node directly instead of judging details again. + final selection = + editorState.service.selectionService.currentSelection.value!; + final index = + selection.isBackward ? selection.start.offset : selection.end.offset; + final length = (selection.start.offset - selection.end.offset).abs(); + final node = editorState.service.selectionService.currentSelectedNodes.first + as TextNode; + final linkText = node.getAttributeInSelection(selection, StyleKey.href); + _linkMenuOverlay = OverlayEntry(builder: (context) { + return Positioned( + top: matchRect.bottom, + left: matchRect.left, + child: Material( + child: LinkMenu( + linkText: linkText, + onSubmitted: (text) { + TransactionBuilder(editorState) + ..formatText(node, index, length, { + StyleKey.href: text, + }) + ..commit(); + _dismissLinkMenu(); + }, + onCopyLink: () { + RichClipboard.setData(RichClipboardData(text: linkText)); + _dismissLinkMenu(); + }, + onRemoveLink: () { + TransactionBuilder(editorState) + ..formatText(node, index, length, { + StyleKey.href: null, + }) + ..commit(); + _dismissLinkMenu(); + }, + ), + ), + ); + }); + Overlay.of(context)?.insert(_linkMenuOverlay!); + + editorState.service.scrollService?.disable(); + editorState.service.selectionService.currentSelection + .addListener(_dismissLinkMenu); +} + +void _dismissLinkMenu() { + _linkMenuOverlay?.remove(); + _linkMenuOverlay = null; + + _editorState?.service.scrollService?.enable(); + _editorState?.service.selectionService.currentSelection + .removeListener(_dismissLinkMenu); + _editorState = null; +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart new file mode 100644 index 0000000000..ce89eef126 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import 'toolbar_item.dart'; + +class ToolbarItemWidget extends StatelessWidget { + const ToolbarItemWidget({ + Key? key, + required this.item, + required this.onPressed, + }) : super(key: key); + + final ToolbarItem item; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 28, + height: 28, + child: Tooltip( + preferBelow: false, + message: item.tooltipsMessage, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: IconButton( + padding: EdgeInsets.zero, + icon: item.icon, + iconSize: 28, + onPressed: onPressed, + ), + ), + ), + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_widget.dart new file mode 100644 index 0000000000..167f8e79ba --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_widget.dart @@ -0,0 +1,82 @@ +import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart'; +import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart'; +import 'package:flutter/material.dart'; + +import 'package:appflowy_editor/src/editor_state.dart'; + +mixin ToolbarMixin on State { + void hide(); +} + +class ToolbarWidget extends StatefulWidget { + const ToolbarWidget({ + Key? key, + required this.editorState, + required this.layerLink, + required this.offset, + required this.items, + }) : super(key: key); + + final EditorState editorState; + final LayerLink layerLink; + final Offset offset; + final List items; + + @override + State createState() => _ToolbarWidgetState(); +} + +class _ToolbarWidgetState extends State with ToolbarMixin { + OverlayEntry? _listToolbarOverlay; + + @override + Widget build(BuildContext context) { + return Positioned( + top: widget.offset.dx, + left: widget.offset.dy, + child: CompositedTransformFollower( + link: widget.layerLink, + showWhenUnlinked: true, + offset: widget.offset, + child: _buildToolbar(context), + ), + ); + } + + @override + void hide() { + _listToolbarOverlay?.remove(); + _listToolbarOverlay = null; + } + + Widget _buildToolbar(BuildContext context) { + final items = widget.items.where( + (item) => item.validator(widget.editorState), + ); + return Material( + borderRadius: BorderRadius.circular(8.0), + color: const Color(0xFF333333), + child: Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0), + child: SizedBox( + height: 32.0, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: items + .map( + (item) => Center( + child: ToolbarItemWidget( + item: item, + onPressed: () { + item.handler(widget.editorState, context); + }, + ), + ), + ) + .toList(growable: false), + ), + ), + ), + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart index 9aae2b5fcb..a92fae1b95 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart @@ -87,15 +87,18 @@ class _AppFlowyInputState extends State @override void attach(TextEditingValue textEditingValue) { - _textInputConnection ??= TextInput.attach( - this, - const TextInputConfiguration( - // TODO: customize - enableDeltaModel: true, - inputType: TextInputType.multiline, - textCapitalization: TextCapitalization.sentences, - ), - ); + if (_textInputConnection == null || + _textInputConnection!.attached == false) { + _textInputConnection = TextInput.attach( + this, + const TextInputConfiguration( + // TODO: customize + enableDeltaModel: true, + inputType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + ), + ); + } _textInputConnection! ..setEditingState(textEditingValue) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart index bf380290f9..fe4a2beace 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart @@ -1,7 +1,8 @@ +import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/render/selection/toolbar_widget.dart'; +import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart'; import 'package:appflowy_editor/src/extensions/object_extensions.dart'; abstract class FlowyToolbarService { @@ -41,7 +42,7 @@ class _FlowyToolbarState extends State editorState: widget.editorState, layerLink: layerLink, offset: offset.translate(0, -37.0), - handlers: const {}, + items: defaultToolbarItems, ), ); Overlay.of(context)?.insert(_toolbarOverlay!);