Переглянути джерело

feat: move code block plugin to appflowy editor plugins directory

Lucas.Xu 2 роки тому
батько
коміт
e476337a6a

+ 7 - 0
frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart

@@ -41,16 +41,23 @@ class SimpleEditor extends StatelessWidget {
               kDividerType: DividerWidgetBuilder(),
               kDividerType: DividerWidgetBuilder(),
               // Math Equation
               // Math Equation
               kMathEquationType: MathEquationNodeWidgetBuidler(),
               kMathEquationType: MathEquationNodeWidgetBuidler(),
+              // Code Block
+              kCodeBlockType: CodeBlockNodeWidgetBuilder(),
             },
             },
             shortcutEvents: [
             shortcutEvents: [
               // Divider
               // Divider
               insertDividerEvent,
               insertDividerEvent,
+              // Code Block
+              enterInCodeBlock,
+              ignoreKeysInCodeBlock,
             ],
             ],
             selectionMenuItems: [
             selectionMenuItems: [
               // Divider
               // Divider
               dividerMenuItem,
               dividerMenuItem,
               // Math Equation
               // Math Equation
               mathEquationMenuItem,
               mathEquationMenuItem,
+              // Code Block
+              codeBlockMenuItem,
             ],
             ],
           );
           );
         } else {
         } else {

+ 0 - 193
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart

@@ -1,193 +0,0 @@
-import 'dart:collection';
-
-import 'package:appflowy_editor/appflowy_editor.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_math_fork/flutter_math.dart';
-
-SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
-  name: () => 'Tex',
-  icon: (_, __) => const Icon(
-    Icons.text_fields_rounded,
-    color: Colors.black,
-    size: 18.0,
-  ),
-  keywords: ['tex, latex, katex'],
-  handler: (editorState, _, __) {
-    final selection =
-        editorState.service.selectionService.currentSelection.value;
-    final textNodes = editorState.service.selectionService.currentSelectedNodes
-        .whereType<TextNode>();
-    if (selection == null || !selection.isCollapsed || textNodes.isEmpty) {
-      return;
-    }
-    final Path texNodePath;
-    if (textNodes.first.toPlainText().isEmpty) {
-      texNodePath = selection.end.path;
-      final transaction = editorState.transaction
-        ..insertNode(
-          selection.end.path,
-          Node(
-            type: 'tex',
-            children: LinkedList(),
-            attributes: {'tex': ''},
-          ),
-        )
-        ..deleteNode(textNodes.first)
-        ..afterSelection = selection;
-      editorState.apply(transaction);
-    } else {
-      texNodePath = selection.end.path.next;
-      final transaction = editorState.transaction
-        ..insertNode(
-          selection.end.path.next,
-          Node(
-            type: 'tex',
-            children: LinkedList(),
-            attributes: {'tex': ''},
-          ),
-        )
-        ..afterSelection = selection;
-      editorState.apply(transaction);
-    }
-    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
-      final texState =
-          editorState.document.nodeAtPath(texNodePath)?.key?.currentState;
-      if (texState != null && texState is __TeXBlockNodeWidgetState) {
-        texState.showEditingDialog();
-      }
-    });
-  },
-);
-
-class TeXBlockNodeWidgetBuidler extends NodeWidgetBuilder<Node> {
-  @override
-  Widget build(NodeWidgetContext<Node> context) {
-    return _TeXBlockNodeWidget(
-      key: context.node.key,
-      node: context.node,
-      editorState: context.editorState,
-    );
-  }
-
-  @override
-  NodeValidator<Node> get nodeValidator => (node) {
-        return node.attributes['tex'] is String;
-      };
-}
-
-class _TeXBlockNodeWidget extends StatefulWidget {
-  const _TeXBlockNodeWidget({
-    Key? key,
-    required this.node,
-    required this.editorState,
-  }) : super(key: key);
-
-  final Node node;
-  final EditorState editorState;
-
-  @override
-  State<_TeXBlockNodeWidget> createState() => __TeXBlockNodeWidgetState();
-}
-
-class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> {
-  String get _tex => widget.node.attributes['tex'] as String;
-  bool _isHover = false;
-
-  @override
-  Widget build(BuildContext context) {
-    return InkWell(
-      onHover: (value) {
-        setState(() {
-          _isHover = value;
-        });
-      },
-      onTap: () {
-        showEditingDialog();
-      },
-      child: Stack(
-        children: [
-          _buildTex(context),
-          if (_isHover) _buildDeleteButton(context),
-        ],
-      ),
-    );
-  }
-
-  Widget _buildTex(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(
-          _tex,
-          textStyle: const TextStyle(fontSize: 20),
-          mathStyle: MathStyle.display,
-        ),
-      ),
-    );
-  }
-
-  Widget _buildDeleteButton(BuildContext context) {
-    return Positioned(
-      top: -5,
-      right: -5,
-      child: IconButton(
-        icon: Icon(
-          Icons.delete_outline,
-          color: Colors.blue[400],
-          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: _tex);
-        return AlertDialog(
-          title: const Text('Edit Katex'),
-          content: TextField(
-            controller: controller,
-            maxLines: null,
-            decoration: const InputDecoration(
-              border: OutlineInputBorder(),
-            ),
-          ),
-          actions: [
-            TextButton(
-              onPressed: () {
-                Navigator.of(context).pop();
-              },
-              child: const Text('Cancel'),
-            ),
-            TextButton(
-              onPressed: () {
-                Navigator.of(context).pop();
-                if (controller.text != _tex) {
-                  final transaction = widget.editorState.transaction
-                    ..updateNode(
-                      widget.node,
-                      {'tex': controller.text},
-                    );
-                  widget.editorState.apply(transaction);
-                }
-              },
-              child: const Text('OK'),
-            ),
-          ],
-        );
-      },
-    );
-  }
-}

+ 4 - 0
frontend/app_flowy/packages/appflowy_editor_plugins/lib/appflowy_editor_plugins.dart

@@ -6,3 +6,7 @@ export 'src/divider/divider_shortcut_event.dart';
 
 
 // Math Equation
 // Math Equation
 export 'src/math_ equation/math_equation_node_widget.dart';
 export 'src/math_ equation/math_equation_node_widget.dart';
+
+// Code Block
+export 'src/code_block/code_block_node_widget.dart';
+export 'src/code_block/code_block_shortcut_event.dart';

+ 52 - 105
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart → frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/code_block/code_block_node_widget.dart

@@ -1,93 +1,12 @@
-import 'dart:collection';
-
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:highlight/highlight.dart' as highlight;
 import 'package:highlight/highlight.dart' as highlight;
 import 'package:highlight/languages/all.dart';
 import 'package:highlight/languages/all.dart';
 
 
-ShortcutEvent enterInCodeBlock = ShortcutEvent(
-  key: 'Enter in code block',
-  command: 'enter',
-  handler: _enterInCodeBlockHandler,
-);
-
-ShortcutEvent ignoreKeysInCodeBlock = ShortcutEvent(
-  key: 'White space in code block',
-  command: 'space,slash,shift+underscore',
-  handler: _ignorekHandler,
-);
-
-ShortcutEventHandler _enterInCodeBlockHandler = (editorState, event) {
-  final selection = editorState.service.selectionService.currentSelection.value;
-  final nodes = editorState.service.selectionService.currentSelectedNodes;
-  final codeBlockNode =
-      nodes.whereType<TextNode>().where((node) => node.id == 'text/code_block');
-  if (codeBlockNode.length != 1 || selection == null) {
-    return KeyEventResult.ignored;
-  }
-  if (selection.isCollapsed) {
-    final transaction = editorState.transaction
-      ..insertText(codeBlockNode.first, selection.end.offset, '\n');
-    editorState.apply(transaction);
-    return KeyEventResult.handled;
-  }
-  return KeyEventResult.ignored;
-};
-
-ShortcutEventHandler _ignorekHandler = (editorState, event) {
-  final nodes = editorState.service.selectionService.currentSelectedNodes;
-  final codeBlockNodes =
-      nodes.whereType<TextNode>().where((node) => node.id == 'text/code_block');
-  if (codeBlockNodes.length == 1) {
-    return KeyEventResult.skipRemainingHandlers;
-  }
-  return KeyEventResult.ignored;
-};
-
-SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
-  name: () => 'Code Block',
-  icon: (_, __) => const Icon(
-    Icons.abc,
-    color: Colors.black,
-    size: 18.0,
-  ),
-  keywords: ['code block'],
-  handler: (editorState, _, __) {
-    final selection =
-        editorState.service.selectionService.currentSelection.value;
-    final textNodes = editorState.service.selectionService.currentSelectedNodes
-        .whereType<TextNode>();
-    if (selection == null || textNodes.isEmpty) {
-      return;
-    }
-    if (textNodes.first.toPlainText().isEmpty) {
-      final transaction = editorState.transaction
-        ..updateNode(textNodes.first, {
-          'subtype': 'code_block',
-          'theme': 'vs',
-          'language': null,
-        })
-        ..afterSelection = selection;
-      editorState.apply(transaction);
-    } else {
-      final transaction = editorState.transaction
-        ..insertNode(
-          selection.end.path.next,
-          TextNode(
-            children: LinkedList(),
-            attributes: {
-              'subtype': 'code_block',
-              'theme': 'vs',
-              'language': null,
-            },
-            delta: Delta()..insert('\n'),
-          ),
-        )
-        ..afterSelection = selection;
-      editorState.apply(transaction);
-    }
-  },
-);
+const String kCodeBlockType = 'text/$kCodeBlockSubType';
+const String kCodeBlockSubType = 'code_block';
+const String kCodeBlockAttrTheme = 'theme';
+const String kCodeBlockAttrLanguage = 'language';
 
 
 class CodeBlockNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
 class CodeBlockNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
   @override
   @override
@@ -101,7 +20,8 @@ class CodeBlockNodeWidgetBuilder extends NodeWidgetBuilder<TextNode> {
 
 
   @override
   @override
   NodeValidator<Node> get nodeValidator => (node) {
   NodeValidator<Node> get nodeValidator => (node) {
-        return node is TextNode && node.attributes['theme'] is String;
+        return node is TextNode &&
+            node.attributes[kCodeBlockAttrTheme] is String;
       };
       };
 }
 }
 
 
@@ -121,9 +41,10 @@ class _CodeBlockNodeWidge extends StatefulWidget {
 
 
 class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge>
 class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge>
     with SelectableMixin, DefaultSelectable {
     with SelectableMixin, DefaultSelectable {
-  final _richTextKey = GlobalKey(debugLabel: 'code_block_text');
-  final _padding = const EdgeInsets.only(left: 20, top: 20, bottom: 20);
-  String? get _language => widget.textNode.attributes['language'] as String?;
+  final _richTextKey = GlobalKey(debugLabel: kCodeBlockType);
+  final _padding = const EdgeInsets.only(left: 20, top: 30, bottom: 30);
+  String? get _language =>
+      widget.textNode.attributes[kCodeBlockAttrLanguage] as String?;
   String? _detectLanguage;
   String? _detectLanguage;
 
 
   @override
   @override
@@ -142,11 +63,13 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge>
       children: [
       children: [
         _buildCodeBlock(context),
         _buildCodeBlock(context),
         _buildSwitchCodeButton(context),
         _buildSwitchCodeButton(context),
+        _buildDeleteButton(context),
       ],
       ],
     );
     );
   }
   }
 
 
   Widget _buildCodeBlock(BuildContext context) {
   Widget _buildCodeBlock(BuildContext context) {
+    final plainText = widget.textNode.toPlainText();
     final result = highlight.highlight.parse(
     final result = highlight.highlight.parse(
       widget.textNode.toPlainText(),
       widget.textNode.toPlainText(),
       language: _language,
       language: _language,
@@ -177,25 +100,49 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge>
   Widget _buildSwitchCodeButton(BuildContext context) {
   Widget _buildSwitchCodeButton(BuildContext context) {
     return Positioned(
     return Positioned(
       top: -5,
       top: -5,
-      right: 0,
-      child: DropdownButton<String>(
-        value: _detectLanguage,
-        onChanged: (value) {
+      left: 10,
+      child: SizedBox(
+        height: 35,
+        child: DropdownButton<String>(
+          value: _detectLanguage,
+          iconSize: 14.0,
+          onChanged: (value) {
+            final transaction = widget.editorState.transaction
+              ..updateNode(widget.textNode, {
+                kCodeBlockAttrLanguage: value,
+              });
+            widget.editorState.apply(transaction);
+          },
+          items:
+              allLanguages.keys.map<DropdownMenuItem<String>>((String value) {
+            return DropdownMenuItem<String>(
+              value: value,
+              child: Text(
+                value,
+                style: const TextStyle(fontSize: 12.0),
+              ),
+            );
+          }).toList(growable: false),
+        ),
+      ),
+    );
+  }
+
+  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
           final transaction = widget.editorState.transaction
-            ..updateNode(widget.textNode, {
-              'language': value,
-            });
+            ..deleteNode(widget.textNode);
           widget.editorState.apply(transaction);
           widget.editorState.apply(transaction);
         },
         },
-        items: allLanguages.keys.map<DropdownMenuItem<String>>((String value) {
-          return DropdownMenuItem<String>(
-            value: value,
-            child: Text(
-              value,
-              style: const TextStyle(fontSize: 12.0),
-            ),
-          );
-        }).toList(growable: false),
       ),
       ),
     );
     );
   }
   }

+ 91 - 0
frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart

@@ -0,0 +1,91 @@
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor_plugins/src/code_block/code_block_node_widget.dart';
+import 'package:flutter/material.dart';
+
+ShortcutEvent enterInCodeBlock = ShortcutEvent(
+  key: 'Press Enter In Code Block',
+  command: 'enter',
+  handler: _enterInCodeBlockHandler,
+);
+
+ShortcutEvent ignoreKeysInCodeBlock = ShortcutEvent(
+  key: 'White space in code block',
+  command: 'space, slash, shift+underscore',
+  handler: _ignorekHandler,
+);
+
+ShortcutEventHandler _enterInCodeBlockHandler = (editorState, event) {
+  final selection = editorState.service.selectionService.currentSelection.value;
+  final nodes = editorState.service.selectionService.currentSelectedNodes;
+  final codeBlockNode =
+      nodes.whereType<TextNode>().where((node) => node.id == kCodeBlockType);
+  if (codeBlockNode.length != 1 ||
+      selection == null ||
+      !selection.isCollapsed) {
+    return KeyEventResult.ignored;
+  }
+
+  final transaction = editorState.transaction
+    ..insertText(
+      codeBlockNode.first,
+      selection.end.offset,
+      '\n',
+    );
+  editorState.apply(transaction);
+  return KeyEventResult.handled;
+};
+
+ShortcutEventHandler _ignorekHandler = (editorState, event) {
+  final nodes = editorState.service.selectionService.currentSelectedNodes;
+  final codeBlockNodes =
+      nodes.whereType<TextNode>().where((node) => node.id == kCodeBlockType);
+  if (codeBlockNodes.length == 1) {
+    return KeyEventResult.skipRemainingHandlers;
+  }
+  return KeyEventResult.ignored;
+};
+
+SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
+  name: () => 'Code Block',
+  icon: (editorState, onSelected) => Icon(
+    Icons.abc,
+    color: onSelected
+        ? editorState.editorStyle.selectionMenuItemSelectedIconColor
+        : editorState.editorStyle.selectionMenuItemIconColor,
+    size: 18.0,
+  ),
+  keywords: ['code block', 'code snippet'],
+  handler: (editorState, _, __) {
+    final selection =
+        editorState.service.selectionService.currentSelection.value;
+    final textNodes = editorState.service.selectionService.currentSelectedNodes
+        .whereType<TextNode>();
+    if (selection == null || textNodes.isEmpty) {
+      return;
+    }
+    final transaction = editorState.transaction;
+    if (textNodes.first.toPlainText().isEmpty) {
+      transaction.updateNode(textNodes.first, {
+        BuiltInAttributeKey.subtype: kCodeBlockSubType,
+        kCodeBlockAttrTheme: 'vs',
+        kCodeBlockAttrLanguage: null,
+      });
+      transaction.afterSelection = selection;
+      editorState.apply(transaction);
+    } else {
+      transaction.insertNode(
+        selection.end.path,
+        TextNode(
+          attributes: {
+            BuiltInAttributeKey.subtype: kCodeBlockSubType,
+            kCodeBlockAttrTheme: 'vs',
+            kCodeBlockAttrLanguage: null,
+          },
+          delta: Delta()..insert('\n'),
+        ),
+      );
+      transaction.afterSelection = selection;
+    }
+    editorState.apply(transaction);
+  },
+);

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor_plugins/pubspec.yaml

@@ -15,6 +15,7 @@ dependencies:
   appflowy_editor: 
   appflowy_editor: 
     path: ../appflowy_editor
     path: ../appflowy_editor
   flutter_math_fork: ^0.6.3+1
   flutter_math_fork: ^0.6.3+1
+  highlight: ^0.7.0
 
 
 dev_dependencies:
 dev_dependencies:
   flutter_test:
   flutter_test: