mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: add image entry into selection menu
This commit is contained in:
parent
3832af5fa8
commit
6af85fbe56
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="1.5" y="3" width="13" height="10" rx="1.5" stroke="#333333"/>
|
||||||
|
<circle cx="5.5" cy="6.5" r="1" stroke="#333333"/>
|
||||||
|
<path d="M5 13L10.112 8.45603C10.4211 8.18126 10.8674 8.12513 11.235 8.31482L14.5 10" stroke="#333333"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 330 B |
@ -62,7 +62,7 @@
|
|||||||
{
|
{
|
||||||
"type": "image",
|
"type": "image",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"image_src": "https://images.unsplash.com/photo-1471897488648-5eae4ac6686b?ixlib=rb-1.2.1&dl=sarah-dorweiler-QeVmJxZOv3k-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb",
|
"image_src": "https://s1.ax1x.com/2022/08/24/vgAJED.png",
|
||||||
"align": "center"
|
"align": "center"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11,9 +11,11 @@ class ImageNodeBuilder extends NodeWidgetBuilder<Node> {
|
|||||||
Widget build(NodeWidgetContext<Node> context) {
|
Widget build(NodeWidgetContext<Node> context) {
|
||||||
final src = context.node.attributes['image_src'];
|
final src = context.node.attributes['image_src'];
|
||||||
final align = context.node.attributes['align'];
|
final align = context.node.attributes['align'];
|
||||||
|
final width = context.node.attributes['width'];
|
||||||
return ImageNodeWidget(
|
return ImageNodeWidget(
|
||||||
key: context.node.key,
|
key: context.node.key,
|
||||||
src: src,
|
src: src,
|
||||||
|
width: width,
|
||||||
alignment: _textToAlignment(align),
|
alignment: _textToAlignment(align),
|
||||||
onCopy: () {
|
onCopy: () {
|
||||||
RichClipboard.setData(RichClipboardData(text: src));
|
RichClipboard.setData(RichClipboardData(text: src));
|
||||||
@ -30,6 +32,13 @@ class ImageNodeBuilder extends NodeWidgetBuilder<Node> {
|
|||||||
})
|
})
|
||||||
..commit();
|
..commit();
|
||||||
},
|
},
|
||||||
|
onResize: (width) {
|
||||||
|
TransactionBuilder(context.editorState)
|
||||||
|
..updateNode(context.node, {
|
||||||
|
'width': width,
|
||||||
|
})
|
||||||
|
..commit();
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,33 +1,57 @@
|
|||||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ImageNodeWidget extends StatefulWidget {
|
class ImageNodeWidget extends StatefulWidget {
|
||||||
const ImageNodeWidget({
|
const ImageNodeWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.src,
|
required this.src,
|
||||||
|
this.width,
|
||||||
required this.alignment,
|
required this.alignment,
|
||||||
required this.onCopy,
|
required this.onCopy,
|
||||||
required this.onDelete,
|
required this.onDelete,
|
||||||
required this.onAlign,
|
required this.onAlign,
|
||||||
|
required this.onResize,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String src;
|
final String src;
|
||||||
|
final double? width;
|
||||||
final Alignment alignment;
|
final Alignment alignment;
|
||||||
final VoidCallback onCopy;
|
final VoidCallback onCopy;
|
||||||
final VoidCallback onDelete;
|
final VoidCallback onDelete;
|
||||||
final void Function(Alignment alignment) onAlign;
|
final void Function(Alignment alignment) onAlign;
|
||||||
|
final void Function(double width) onResize;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ImageNodeWidget> createState() => _ImageNodeWidgetState();
|
State<ImageNodeWidget> createState() => _ImageNodeWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
||||||
double? imageWidth = defaultMaxTextNodeWidth;
|
double? _imageWidth;
|
||||||
double _initial = 0;
|
double _initial = 0;
|
||||||
double _distance = 0;
|
double _distance = 0;
|
||||||
bool _onFocus = false;
|
bool _onFocus = false;
|
||||||
|
|
||||||
|
ImageStream? _imageStream;
|
||||||
|
late ImageStreamListener _imageStreamListener;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_imageWidth = widget.width;
|
||||||
|
_imageStreamListener = ImageStreamListener(
|
||||||
|
(image, _) {
|
||||||
|
_imageWidth = image.image.width.toDouble();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_imageStream?.removeListener(_imageStreamListener);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// only support network image.
|
// only support network image.
|
||||||
@ -52,18 +76,13 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
|||||||
Widget _buildResizableImage(BuildContext context) {
|
Widget _buildResizableImage(BuildContext context) {
|
||||||
final networkImage = Image.network(
|
final networkImage = Image.network(
|
||||||
widget.src,
|
widget.src,
|
||||||
width: imageWidth == null ? null : imageWidth! - _distance,
|
width: _imageWidth == null ? null : _imageWidth! - _distance,
|
||||||
loadingBuilder: (context, child, loadingProgress) =>
|
loadingBuilder: (context, child, loadingProgress) =>
|
||||||
loadingProgress == null ? child : _buildLoading(context),
|
loadingProgress == null ? child : _buildLoading(context),
|
||||||
);
|
);
|
||||||
if (imageWidth == null) {
|
if (_imageWidth == null) {
|
||||||
networkImage.image.resolve(const ImageConfiguration()).addListener(
|
_imageStream = networkImage.image.resolve(const ImageConfiguration())
|
||||||
ImageStreamListener(
|
..addListener(_imageStreamListener);
|
||||||
(image, _) {
|
|
||||||
imageWidth = image.image.width.toDouble();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
@ -108,7 +127,7 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
|||||||
|
|
||||||
Widget _buildLoading(BuildContext context) {
|
Widget _buildLoading(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: imageWidth,
|
width: _imageWidth,
|
||||||
height: 300,
|
height: 300,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -151,9 +170,11 @@ class _ImageNodeWidgetState extends State<ImageNodeWidget> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onHorizontalDragEnd: (details) {
|
onHorizontalDragEnd: (details) {
|
||||||
imageWidth = imageWidth! - _distance;
|
_imageWidth = _imageWidth! - _distance;
|
||||||
_initial = 0;
|
_initial = 0;
|
||||||
_distance = 0;
|
_distance = 0;
|
||||||
|
|
||||||
|
widget.onResize(_imageWidth!);
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: SystemMouseCursors.resizeLeftRight,
|
cursor: SystemMouseCursors.resizeLeftRight,
|
||||||
|
@ -0,0 +1,194 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/src/document/node.dart';
|
||||||
|
import 'package:appflowy_editor/src/editor_state.dart';
|
||||||
|
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||||
|
import 'package:appflowy_editor/src/operation/transaction_builder.dart';
|
||||||
|
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
OverlayEntry? _imageUploadMenu;
|
||||||
|
void showImageUploadMenu(
|
||||||
|
EditorState editorState,
|
||||||
|
SelectionMenuService menuService,
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
menuService.dismiss();
|
||||||
|
|
||||||
|
_imageUploadMenu?.remove();
|
||||||
|
_imageUploadMenu = OverlayEntry(builder: (context) {
|
||||||
|
return Positioned(
|
||||||
|
top: menuService.topLeft.dy,
|
||||||
|
left: menuService.topLeft.dx,
|
||||||
|
child: Material(
|
||||||
|
child: ImageUploadMenu(
|
||||||
|
onSubmitted: (text) {
|
||||||
|
_dismissImageUploadMenu();
|
||||||
|
editorState.insertImageNode(text);
|
||||||
|
},
|
||||||
|
onUpload: (text) {
|
||||||
|
_dismissImageUploadMenu();
|
||||||
|
editorState.insertImageNode(text);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Overlay.of(context)?.insert(_imageUploadMenu!);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _dismissImageUploadMenu() {
|
||||||
|
_imageUploadMenu?.remove();
|
||||||
|
_imageUploadMenu = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageUploadMenu extends StatefulWidget {
|
||||||
|
const ImageUploadMenu({
|
||||||
|
Key? key,
|
||||||
|
required this.onSubmitted,
|
||||||
|
required this.onUpload,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final void Function(String text) onSubmitted;
|
||||||
|
final void Function(String text) onUpload;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ImageUploadMenu> createState() => _ImageUploadMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ImageUploadMenuState extends State<ImageUploadMenu> {
|
||||||
|
final _textEditingController = TextEditingController();
|
||||||
|
final _focusNode = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 300,
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: 5,
|
||||||
|
spreadRadius: 1,
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
borderRadius: BorderRadius.circular(6.0),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(context),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
_buildInput(),
|
||||||
|
const SizedBox(height: 18.0),
|
||||||
|
_buildUploadButton(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader(BuildContext context) {
|
||||||
|
return const Text(
|
||||||
|
'URL Image',
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14.0,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInput() {
|
||||||
|
return TextField(
|
||||||
|
focusNode: _focusNode,
|
||||||
|
style: const TextStyle(fontSize: 14.0),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
controller: _textEditingController,
|
||||||
|
onSubmitted: widget.onSubmitted,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'URL',
|
||||||
|
hintStyle: const TextStyle(fontSize: 14.0),
|
||||||
|
contentPadding: const EdgeInsets.all(16.0),
|
||||||
|
isDense: true,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
icon: const FlowySvg(
|
||||||
|
name: 'clear',
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_textEditingController.clear();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12.0)),
|
||||||
|
borderSide: BorderSide(color: Color(0xFFBDBDBD)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildUploadButton(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 170,
|
||||||
|
height: 48,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(const Color(0xFF00BCF0)),
|
||||||
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
widget.onUpload(_textEditingController.text);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'Upload',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 14.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on EditorState {
|
||||||
|
void insertImageNode(String src) {
|
||||||
|
final selection = service.selectionService.currentSelection.value;
|
||||||
|
if (selection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final imageNode = Node(
|
||||||
|
type: 'image',
|
||||||
|
children: LinkedList(),
|
||||||
|
attributes: {
|
||||||
|
'image_src': src,
|
||||||
|
'align': 'center',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
TransactionBuilder(this)
|
||||||
|
..insertNode(
|
||||||
|
selection.start.path,
|
||||||
|
imageNode,
|
||||||
|
)
|
||||||
|
..commit();
|
||||||
|
}
|
||||||
|
}
|
@ -45,7 +45,7 @@ class SelectionMenuItemWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
item.handler(editorState, menuService);
|
item.handler(editorState, menuService, context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy_editor/src/editor_state.dart';
|
import 'package:appflowy_editor/src/editor_state.dart';
|
||||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||||
|
import 'package:appflowy_editor/src/render/image/image_upload_widget.dart';
|
||||||
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
import 'package:appflowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
||||||
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||||
@ -23,6 +24,7 @@ class SelectionMenu implements SelectionMenuService {
|
|||||||
|
|
||||||
OverlayEntry? _selectionMenuEntry;
|
OverlayEntry? _selectionMenuEntry;
|
||||||
bool _selectionUpdateByInner = false;
|
bool _selectionUpdateByInner = false;
|
||||||
|
Offset? _topLeft;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dismiss() {
|
void dismiss() {
|
||||||
@ -53,6 +55,7 @@ class SelectionMenu implements SelectionMenuService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final offset = selectionRects.first.bottomRight + const Offset(10, 10);
|
final offset = selectionRects.first.bottomRight + const Offset(10, 10);
|
||||||
|
_topLeft = offset;
|
||||||
|
|
||||||
_selectionMenuEntry = OverlayEntry(builder: (context) {
|
_selectionMenuEntry = OverlayEntry(builder: (context) {
|
||||||
return Positioned(
|
return Positioned(
|
||||||
@ -84,8 +87,9 @@ class SelectionMenu implements SelectionMenuService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// TODO: implement topLeft
|
Offset get topLeft {
|
||||||
Offset get topLeft => throw UnimplementedError();
|
return _topLeft ?? Offset.zero;
|
||||||
|
}
|
||||||
|
|
||||||
void _onSelectionChange() {
|
void _onSelectionChange() {
|
||||||
// workaround: SelectionService has been released after hot reload.
|
// workaround: SelectionService has been released after hot reload.
|
||||||
@ -115,7 +119,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
name: 'Text',
|
name: 'Text',
|
||||||
icon: _selectionMenuIcon('text'),
|
icon: _selectionMenuIcon('text'),
|
||||||
keywords: ['text'],
|
keywords: ['text'],
|
||||||
handler: (editorState, menuService) {
|
handler: (editorState, _, __) {
|
||||||
insertTextNodeAfterSelection(editorState, {});
|
insertTextNodeAfterSelection(editorState, {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -123,7 +127,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
name: 'Heading 1',
|
name: 'Heading 1',
|
||||||
icon: _selectionMenuIcon('h1'),
|
icon: _selectionMenuIcon('h1'),
|
||||||
keywords: ['heading 1, h1'],
|
keywords: ['heading 1, h1'],
|
||||||
handler: (editorState, menuService) {
|
handler: (editorState, _, __) {
|
||||||
insertHeadingAfterSelection(editorState, StyleKey.h1);
|
insertHeadingAfterSelection(editorState, StyleKey.h1);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -131,7 +135,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
name: 'Heading 2',
|
name: 'Heading 2',
|
||||||
icon: _selectionMenuIcon('h2'),
|
icon: _selectionMenuIcon('h2'),
|
||||||
keywords: ['heading 2, h2'],
|
keywords: ['heading 2, h2'],
|
||||||
handler: (editorState, menuService) {
|
handler: (editorState, _, __) {
|
||||||
insertHeadingAfterSelection(editorState, StyleKey.h2);
|
insertHeadingAfterSelection(editorState, StyleKey.h2);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -139,15 +143,21 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
name: 'Heading 3',
|
name: 'Heading 3',
|
||||||
icon: _selectionMenuIcon('h3'),
|
icon: _selectionMenuIcon('h3'),
|
||||||
keywords: ['heading 3, h3'],
|
keywords: ['heading 3, h3'],
|
||||||
handler: (editorState, menuService) {
|
handler: (editorState, _, __) {
|
||||||
insertHeadingAfterSelection(editorState, StyleKey.h3);
|
insertHeadingAfterSelection(editorState, StyleKey.h3);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SelectionMenuItem(
|
||||||
|
name: 'Image',
|
||||||
|
icon: _selectionMenuIcon('image'),
|
||||||
|
keywords: ['image'],
|
||||||
|
handler: showImageUploadMenu,
|
||||||
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: 'Bulleted list',
|
name: 'Bulleted list',
|
||||||
icon: _selectionMenuIcon('bulleted_list'),
|
icon: _selectionMenuIcon('bulleted_list'),
|
||||||
keywords: ['bulleted list', 'list', 'unordered list'],
|
keywords: ['bulleted list', 'list', 'unordered list'],
|
||||||
handler: (editorState, menuService) {
|
handler: (editorState, _, __) {
|
||||||
insertBulletedListAfterSelection(editorState);
|
insertBulletedListAfterSelection(editorState);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -155,7 +165,7 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
name: 'Checkbox',
|
name: 'Checkbox',
|
||||||
icon: _selectionMenuIcon('checkbox'),
|
icon: _selectionMenuIcon('checkbox'),
|
||||||
keywords: ['todo list', 'list', 'checkbox list'],
|
keywords: ['todo list', 'list', 'checkbox list'],
|
||||||
handler: (editorState, menuService) {
|
handler: (editorState, _, __) {
|
||||||
insertCheckboxAfterSelection(editorState);
|
insertCheckboxAfterSelection(editorState);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -22,8 +22,11 @@ class SelectionMenuItem {
|
|||||||
///
|
///
|
||||||
/// The keywords are used to quickly retrieve items.
|
/// The keywords are used to quickly retrieve items.
|
||||||
final List<String> keywords;
|
final List<String> keywords;
|
||||||
final void Function(EditorState editorState, SelectionMenuService menuService)
|
final void Function(
|
||||||
handler;
|
EditorState editorState,
|
||||||
|
SelectionMenuService menuService,
|
||||||
|
BuildContext context,
|
||||||
|
) handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectionMenuWidget extends StatefulWidget {
|
class SelectionMenuWidget extends StatefulWidget {
|
||||||
@ -203,7 +206,7 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
|||||||
if (0 <= _selectedIndex && _selectedIndex < _showingItems.length) {
|
if (0 <= _selectedIndex && _selectedIndex < _showingItems.length) {
|
||||||
_deleteLastCharacters(length: keyword.length + 1);
|
_deleteLastCharacters(length: keyword.length + 1);
|
||||||
_showingItems[_selectedIndex]
|
_showingItems[_selectedIndex]
|
||||||
.handler(widget.editorState, widget.menuService);
|
.handler(widget.editorState, widget.menuService, context);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
|
@ -30,6 +30,7 @@ void main() async {
|
|||||||
onAlign: (alignment) {
|
onAlign: (alignment) {
|
||||||
onAlignHit = true;
|
onAlignHit = true;
|
||||||
},
|
},
|
||||||
|
onResize: (width) {},
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
@ -20,7 +20,7 @@ void main() async {
|
|||||||
name: 'example',
|
name: 'example',
|
||||||
icon: icon,
|
icon: icon,
|
||||||
keywords: ['example A', 'example B'],
|
keywords: ['example A', 'example B'],
|
||||||
handler: (editorState, menuService) {
|
handler: (editorState, menuService, context) {
|
||||||
flag = true;
|
flag = true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user