فهرست منبع

feat: move math equation plugin to appflowy editor plugins directory

Lucas.Xu 2 سال پیش
والد
کامیت
2b27fe85aa

+ 7 - 0
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 {

+ 4 - 0
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';

+ 211 - 0
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<TextNode>();
+    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<Node> {
+  @override
+  Widget build(NodeWidgetContext<Node> context) {
+    return _MathEquationNodeWidget(
+      key: context.node.key,
+      node: context.node,
+      editorState: context.editorState,
+    );
+  }
+
+  @override
+  NodeValidator<Node> 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();
+  }
+}

+ 1 - 0
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: