Browse Source

fix: convert markdown symbol to styled text fails (#1090)

* fix: convert markdown symbol to styled text fails

* docs: update customzing.md

* chore: disable macOS titlebar

* chore: specify the built-in language as en_US

* docs: update documentation

* docs: update documentation
Lucas.Xu 2 years ago
parent
commit
3e75b1ac92
17 changed files with 139 additions and 86 deletions
  1. 2 6
      frontend/app_flowy/packages/appflowy_editor/README.md
  2. 22 15
      frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md
  3. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif
  4. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4
  5. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_component.gif
  6. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_after.gif
  7. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_before.gif
  8. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_component.gif
  9. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_after.gif
  10. BIN
      frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_before.gif
  11. 4 3
      frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart
  12. 53 0
      frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart
  13. 0 45
      frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart
  14. 7 6
      frontend/app_flowy/packages/appflowy_editor/example/macos/Runner/Base.lproj/MainMenu.xib
  15. 1 0
      frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart
  16. 10 11
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart
  17. 40 0
      frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart

+ 2 - 6
frontend/app_flowy/packages/appflowy_editor/README.md

@@ -27,7 +27,7 @@ and the Flutter guide for
 </p>
 
 <div align="center">
-    <img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif?raw=true" width = "700" style = "padding: 100"/>
+    <img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4?raw=true" width = "700" style = "padding: 100"/>
 </div>
 
 ## Key Features
@@ -58,8 +58,6 @@ final editorStyle = EditorStyle.defaultStyle();
 final editorState = EditorState.empty(); // an empty state
 final editor = AppFlowyEditor(
     editorState: editorState,
-    shortcutEvents: const [],
-    customBuilders: const {},
     editorStyle: editorStyle,
 );
 ```
@@ -72,8 +70,6 @@ final editorStyle = EditorStyle.defaultStyle();
 final editorState = EditorState(StateTree.fromJson(data));
 final editor = AppFlowyEditor(
     editorState: editorState,
-    shortcutEvents: const [],
-    customBuilders: const {},
     editorStyle: editorStyle,
 );
 ```
@@ -113,7 +109,7 @@ Please refer to our documentation on customizing AppFlowy for a detailed discuss
 
 Below are some examples of shortcut event customizations:
 
- * [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
+ * [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
  * [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
  * Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)
 

+ 22 - 15
frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md

@@ -27,7 +27,7 @@ Widget build(BuildContext context) {
 
 At this point, nothing magic will happen after typing `_xxx_`.
 
-![Before](./images/customizing_a_shortcut_event_before.gif)
+![Before](./images/customize_a_shortcut_event_before.gif)
 
 To implement our shortcut event we will create a `ShortcutEvent` instance to handle an underscore input.
 
@@ -39,11 +39,10 @@ We need to define `key` and `command` in a ShortCutEvent object to customize hot
 ```dart
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
 
 ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
   key: 'Underscore to italic',
-  command: 'underscore',
+  command: 'shift+underscore',
   handler: _underscoreToItalicHandler,
 );
 
@@ -82,9 +81,12 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
 
   final textNode = textNodes.first;
   final text = textNode.toRawString();
-  // Determine if an 'underscore' already exists in the text node
-  final previousUnderscore = text.indexOf('_');
-  if (previousUnderscore == -1) {
+  // Determine if an 'underscore' already exists in the text node and only once.
+  final firstUnderscore = text.indexOf('_');
+  final lastUnderscore = text.lastIndexOf('_');
+  if (firstUnderscore == -1 ||
+      firstUnderscore != lastUnderscore ||
+      firstUnderscore == selection.start.offset - 1) {
     return KeyEventResult.ignored;
   }
 
@@ -92,15 +94,20 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
   // update the style of the text surrounded by the two underscores to 'italic',
   // and update the cursor position.
   TransactionBuilder(editorState)
-    ..deleteText(textNode, previousUnderscore, 1)
+    ..deleteText(textNode, firstUnderscore, 1)
     ..formatText(
       textNode,
-      previousUnderscore,
-      selection.end.offset - previousUnderscore - 1,
-      {'italic': true},
+      firstUnderscore,
+      selection.end.offset - firstUnderscore - 1,
+      {
+        BuiltInAttributeKey.italic: true,
+      },
     )
     ..afterSelection = Selection.collapsed(
-      Position(path: textNode.path, offset: selection.end.offset - 1),
+      Position(
+        path: textNode.path,
+        offset: selection.end.offset - 1,
+      ),
     )
     ..commit();
 
@@ -121,7 +128,7 @@ Widget build(BuildContext context) {
         editorStyle: EditorStyle.defaultStyle(),
         customBuilders: const {},
         shortcutEvents: [
-            _underscoreToItalicHandler,
+          underscoreToItalic,
         ],
       ),
     ),
@@ -129,9 +136,9 @@ Widget build(BuildContext context) {
 }
 ```
 
-![After](./images/customizing_a_shortcut_event_after.gif)
+![After](./images/customize_a_shortcut_event_after.gif)
 
-Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart) file of this example.
+Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart) file of this example.
 
 
 ## Customizing a Component
@@ -297,7 +304,7 @@ return AppFlowyEditor(
 );
 ```
 
-![Whew!](./images/customizing_a_component.gif)
+![Whew!](./images/customize_a_component.gif)
 
 Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example.
 

BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_component.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_after.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customize_a_shortcut_event_before.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_component.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_after.gif


BIN
frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_shortcut_event_before.gif


+ 4 - 3
frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart

@@ -1,7 +1,7 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:example/plugin/underscore_to_italic_key_event_handler.dart';
+import 'package:example/plugin/underscore_to_italic.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
@@ -29,7 +29,7 @@ class MyApp extends StatelessWidget {
         GlobalWidgetsLocalizations.delegate,
         AppFlowyEditorLocalizations.delegate,
       ],
-      supportedLocales: AppFlowyEditorLocalizations.delegate.supportedLocales,
+      supportedLocales: const [Locale('en', 'US')],
       debugShowCheckedModeBanner: false,
       theme: ThemeData(
         primarySwatch: Colors.blue,
@@ -113,7 +113,7 @@ class _MyHomePageState extends State<MyHomePage> {
               editorState: _editorState!,
               editorStyle: _editorStyle,
               shortcutEvents: [
-                underscoreToItalicEvent,
+                underscoreToItalic,
               ],
             ),
           );
@@ -186,6 +186,7 @@ class _MyHomePageState extends State<MyHomePage> {
     final path = directory.path;
     final file = File('$path/editor.json');
     setState(() {
+      _editorState = null;
       _jsonString = file.readAsString();
     });
   }

+ 53 - 0
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart

@@ -0,0 +1,53 @@
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flutter/material.dart';
+
+ShortcutEvent underscoreToItalic = ShortcutEvent(
+  key: 'Underscore to italic',
+  command: 'shift+underscore',
+  handler: _underscoreToItalicHandler,
+);
+
+ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
+  // Obtain the selection and selected nodes of the current document through the 'selectionService'
+  // to determine whether the selection is collapsed and whether the selected node is a text node.
+  final selectionService = editorState.service.selectionService;
+  final selection = selectionService.currentSelection.value;
+  final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
+  if (selection == null || !selection.isSingle || textNodes.length != 1) {
+    return KeyEventResult.ignored;
+  }
+
+  final textNode = textNodes.first;
+  final text = textNode.toRawString();
+  // Determine if an 'underscore' already exists in the text node and only once.
+  final firstUnderscore = text.indexOf('_');
+  final lastUnderscore = text.lastIndexOf('_');
+  if (firstUnderscore == -1 ||
+      firstUnderscore != lastUnderscore ||
+      firstUnderscore == selection.start.offset - 1) {
+    return KeyEventResult.ignored;
+  }
+
+  // Delete the previous 'underscore',
+  // update the style of the text surrounded by the two underscores to 'italic',
+  // and update the cursor position.
+  TransactionBuilder(editorState)
+    ..deleteText(textNode, firstUnderscore, 1)
+    ..formatText(
+      textNode,
+      firstUnderscore,
+      selection.end.offset - firstUnderscore - 1,
+      {
+        BuiltInAttributeKey.italic: true,
+      },
+    )
+    ..afterSelection = Selection.collapsed(
+      Position(
+        path: textNode.path,
+        offset: selection.end.offset - 1,
+      ),
+    )
+    ..commit();
+
+  return KeyEventResult.handled;
+};

+ 0 - 45
frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart

@@ -1,45 +0,0 @@
-import 'package:appflowy_editor/appflowy_editor.dart';
-import 'package:flutter/material.dart';
-
-ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
-  key: 'Underscore to italic',
-  command: 'underscore',
-  handler: _underscoreToItalicHandler,
-);
-
-ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
-  // Obtaining the selection and selected nodes of the current document through `selectionService`,
-  // and determine whether it is a single selection and whether the selected node is a text node.
-  final selectionService = editorState.service.selectionService;
-  final selection = selectionService.currentSelection.value;
-  final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
-  if (selection == null || !selection.isSingle || textNodes.length != 1) {
-    return KeyEventResult.ignored;
-  }
-
-  final textNode = textNodes.first;
-  final text = textNode.toRawString();
-  // Determine if `underscore` already exists in the text node
-  final previousUnderscore = text.indexOf('_');
-  if (previousUnderscore == -1) {
-    return KeyEventResult.ignored;
-  }
-
-  // Delete the previous `underscore`,
-  // update the style of the text surrounded by two underscores to `italic`,
-  // and update the cursor position.
-  TransactionBuilder(editorState)
-    ..deleteText(textNode, previousUnderscore, 1)
-    ..formatText(
-      textNode,
-      previousUnderscore,
-      selection.end.offset - previousUnderscore - 1,
-      {'italic': true},
-    )
-    ..afterSelection = Selection.collapsed(
-      Position(path: textNode.path, offset: selection.end.offset - 1),
-    )
-    ..commit();
-
-  return KeyEventResult.handled;
-};

+ 7 - 6
frontend/app_flowy/packages/appflowy_editor/example/macos/Runner/Base.lproj/MainMenu.xib

@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
     <dependencies>
         <deployment identifier="macosx"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -13,7 +13,7 @@
         </customObject>
         <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
         <customObject id="-3" userLabel="Application" customClass="NSObject"/>
-        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="example" customModuleProvider="target">
             <connections>
                 <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
                 <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
@@ -330,14 +330,15 @@
             </items>
             <point key="canvasLocation" x="142" y="-258"/>
         </menu>
-        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
-            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="example" customModuleProvider="target">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
             <rect key="contentRect" x="335" y="390" width="800" height="600"/>
-            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
             <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
                 <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
                 <autoresizingMask key="autoresizingMask"/>
             </view>
+            <point key="canvasLocation" x="126" y="-658"/>
         </window>
     </objects>
 </document>

+ 1 - 0
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -10,6 +10,7 @@ export 'src/document/selection.dart';
 export 'src/document/state_tree.dart';
 export 'src/document/text_delta.dart';
 export 'src/document/attributes.dart';
+export 'src/document/built_in_attribute_keys.dart';
 export 'src/editor_state.dart';
 export 'src/operation/operation.dart';
 export 'src/operation/transaction.dart';

+ 10 - 11
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart

@@ -44,23 +44,22 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
   }
 
   final textNode = textNodes.first;
-  final text = textNode.toRawString();
+  final text = textNode.toRawString().substring(0, selection.end.offset);
 
   final numberMatch = _numberRegex.firstMatch(text);
-  if (numberMatch != null) {
-    final matchText = numberMatch.group(0);
-    final numText = numberMatch.group(1);
-    if (matchText != null && numText != null) {
-      return _toNumberList(editorState, textNode, matchText, numText);
-    }
-  }
 
-  if ((_checkboxListSymbols + _unCheckboxListSymbols).any(text.startsWith)) {
+  if ((_checkboxListSymbols + _unCheckboxListSymbols).contains(text)) {
     return _toCheckboxList(editorState, textNode);
-  } else if (_bulletedListSymbols.any(text.startsWith)) {
+  } else if (_bulletedListSymbols.contains(text)) {
     return _toBulletedList(editorState, textNode);
   } else if (_countOfSign(text, selection) != 0) {
     return _toHeadingStyle(editorState, textNode, selection);
+  } else if (numberMatch != null) {
+    final matchText = numberMatch.group(0);
+    final numText = numberMatch.group(1);
+    if (matchText != null && numText != null) {
+      return _toNumberList(editorState, textNode, matchText, numText);
+    }
   }
 
   return KeyEventResult.ignored;
@@ -196,7 +195,7 @@ KeyEventResult _toHeadingStyle(
 
 int _countOfSign(String text, Selection selection) {
   for (var i = 6; i >= 0; i--) {
-    if (text.substring(0, selection.end.offset).startsWith('#' * i)) {
+    if (text.substring(0, selection.end.offset).contains('#' * i)) {
       return i;
     }
   }

+ 40 - 0
frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart

@@ -174,5 +174,45 @@ void main() async {
         expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
       }
     });
+
+    testWidgets('Presses whitespace key in edge cases', (tester) async {
+      const text = '';
+      final editor = tester.editor..insertTextNode(text);
+      await editor.startTesting();
+
+      final textNode = editor.nodeAtPath([0]) as TextNode;
+      await editor.updateSelection(
+        Selection.single(path: [0], startOffset: 0),
+      );
+
+      await editor.insertText(textNode, '*', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
+
+      await editor.insertText(textNode, '[]', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.checkbox);
+      expect(textNode.attributes.check, false);
+
+      await editor.insertText(textNode, '1.', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.numberList);
+
+      await editor.insertText(textNode, '#', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.heading);
+
+      await editor.insertText(textNode, '[x]', 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.checkbox);
+      expect(textNode.attributes.check, true);
+
+      const insertedText = '[]AppFlowy';
+      await editor.insertText(textNode, insertedText, 0);
+      await editor.pressLogicKey(LogicalKeyboardKey.space);
+      expect(textNode.subtype, BuiltInAttributeKey.checkbox);
+      expect(textNode.attributes.check, true);
+      expect(textNode.toRawString(), insertedText);
+    });
   });
 }