From 2b27fe85aa4cc7ff5e9068cc6cee900919df7c9a Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 1 Dec 2022 14:44:06 +0800 Subject: [PATCH] feat: move math equation plugin to appflowy editor plugins directory --- .../example/lib/pages/simple_editor.dart | 7 + .../lib/appflowy_editor_plugins.dart | 4 + .../math_equation_node_widget.dart | 211 ++++++++++++++++++ .../appflowy_editor_plugins/pubspec.yaml | 1 + 4 files changed, 223 insertions(+) create mode 100644 frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart index b58b367ef3..109a027a89 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart @@ -37,13 +37,20 @@ class SimpleEditor extends StatelessWidget { themeData: themeData, autoFocus: editorState.document.isEmpty, customBuilders: { + // Divider kDividerType: DividerWidgetBuilder(), + // Math Equation + kMathEquationType: MathEquationNodeWidgetBuidler(), }, shortcutEvents: [ + // Divider insertDividerEvent, ], selectionMenuItems: [ + // Divider dividerMenuItem, + // Math Equation + mathEquationMenuItem, ], ); } else { diff --git a/frontend/app_flowy/packages/appflowy_editor_plugins/lib/appflowy_editor_plugins.dart b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/appflowy_editor_plugins.dart index 6fe8f0d01a..1d35d272e9 100644 --- a/frontend/app_flowy/packages/appflowy_editor_plugins/lib/appflowy_editor_plugins.dart +++ b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/appflowy_editor_plugins.dart @@ -1,4 +1,8 @@ library appflowy_editor_plugins; +// Divider export 'src/divider/divider_node_widget.dart'; export 'src/divider/divider_shortcut_event.dart'; + +// Math Equation +export 'src/math_ equation/math_equation_node_widget.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart new file mode 100644 index 0000000000..dbc741852c --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/math_ equation/math_equation_node_widget.dart @@ -0,0 +1,211 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_math_fork/flutter_math.dart'; + +const String kMathEquationType = 'math_equation'; +const String kMathEquationAttr = 'math_equation'; + +// TODO: l10n +SelectionMenuItem mathEquationMenuItem = SelectionMenuItem( + name: () => 'Math Equation', + icon: (editorState, onSelected) => Icon( + Icons.text_fields_rounded, + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, + size: 18.0, + ), + keywords: ['tex, latex, katex', 'math equation'], + handler: (editorState, _, __) { + final selection = + editorState.service.selectionService.currentSelection.value; + final textNodes = editorState.service.selectionService.currentSelectedNodes + .whereType(); + if (selection == null || textNodes.isEmpty) { + return; + } + final textNode = textNodes.first; + final Path mathEquationNodePath; + if (textNode.toPlainText().isEmpty) { + mathEquationNodePath = selection.end.path; + } else { + mathEquationNodePath = selection.end.path.next; + } + // insert the math equation node + final transaction = editorState.transaction + ..insertNode( + mathEquationNodePath, + Node(type: kMathEquationType, attributes: {kMathEquationAttr: ''}), + ) + ..afterSelection = selection; + editorState.apply(transaction); + + // tricy to show the editing dialog. + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final mathEquationState = editorState.document + .nodeAtPath(mathEquationNodePath) + ?.key + ?.currentState; + if (mathEquationState != null && + mathEquationState is _MathEquationNodeWidgetState) { + mathEquationState.showEditingDialog(); + } + }); + }, +); + +class MathEquationNodeWidgetBuidler extends NodeWidgetBuilder { + @override + Widget build(NodeWidgetContext context) { + return _MathEquationNodeWidget( + key: context.node.key, + node: context.node, + editorState: context.editorState, + ); + } + + @override + NodeValidator get nodeValidator => + (node) => node.attributes[kMathEquationAttr] is String; +} + +class _MathEquationNodeWidget extends StatefulWidget { + const _MathEquationNodeWidget({ + Key? key, + required this.node, + required this.editorState, + }) : super(key: key); + + final Node node; + final EditorState editorState; + + @override + State<_MathEquationNodeWidget> createState() => + _MathEquationNodeWidgetState(); +} + +class _MathEquationNodeWidgetState extends State<_MathEquationNodeWidget> { + String get _mathEquation => + widget.node.attributes[kMathEquationAttr] as String; + bool _isHover = false; + + @override + Widget build(BuildContext context) { + return InkWell( + onHover: (value) { + setState(() { + _isHover = value; + }); + }, + onTap: () { + showEditingDialog(); + }, + child: Stack( + children: [ + _buildMathEquation(context), + if (_isHover) _buildDeleteButton(context), + ], + ), + ); + } + + Widget _buildMathEquation(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + color: _isHover ? Colors.grey[200] : Colors.transparent, + ), + child: Center( + child: Math.tex( + _mathEquation, + textStyle: const TextStyle(fontSize: 20), + mathStyle: MathStyle.display, + ), + ), + ); + } + + Widget _buildDeleteButton(BuildContext context) { + return Positioned( + top: -5, + right: -5, + child: IconButton( + icon: Icon( + Icons.delete_forever_outlined, + color: widget.editorState.editorStyle.selectionMenuItemIconColor, + size: 16, + ), + onPressed: () { + final transaction = widget.editorState.transaction + ..deleteNode(widget.node); + widget.editorState.apply(transaction); + }, + ), + ); + } + + void showEditingDialog() { + showDialog( + context: context, + builder: (context) { + final controller = TextEditingController(text: _mathEquation); + return AlertDialog( + title: const Text('Edit Math Equation'), + content: RawKeyboardListener( + focusNode: FocusNode(), + onKey: (key) { + if (key is! RawKeyDownEvent) return; + if (key.logicalKey == LogicalKeyboardKey.enter && + !key.isShiftPressed) { + _updateMathEquation(controller.text); + } else if (key.logicalKey == LogicalKeyboardKey.escape) { + _dismiss(); + } + }, + child: TextField( + autofocus: true, + controller: controller, + maxLines: null, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'E = MC^2', + ), + ), + ), + actions: [ + TextButton( + onPressed: () => _dismiss(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => _updateMathEquation(controller.text), + child: const Text('Done'), + ), + ], + ); + }, + ); + } + + void _updateMathEquation(String mathEquation) { + _dismiss(); + if (mathEquation == _mathEquation) { + return; + } + final transaction = widget.editorState.transaction; + transaction.updateNode( + widget.node, + { + kMathEquationAttr: mathEquation, + }, + ); + widget.editorState.apply(transaction); + } + + void _dismiss() { + Navigator.of(context).pop(); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor_plugins/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor_plugins/pubspec.yaml index 74ee8176ca..ca1908a31a 100644 --- a/frontend/app_flowy/packages/appflowy_editor_plugins/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_editor_plugins/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: sdk: flutter appflowy_editor: path: ../appflowy_editor + flutter_math_fork: ^0.6.3+1 dev_dependencies: flutter_test: