Browse Source

fix: workaround infinity formatting

Andrei Dolgov 2 năm trước cách đây
mục cha
commit
6a902a2b21

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

@@ -37,6 +37,7 @@ class BuiltInAttributeKey {
   static String checkbox = 'checkbox';
   static String code = 'code';
   static String number = 'number';
+  static String defaultFormating = 'defaultFormating';
 
   static List<String> partialStyleKeys = [
     BuiltInAttributeKey.bold,

+ 40 - 9
frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart

@@ -1,6 +1,7 @@
 import 'dart:collection';
 import 'dart:math';
 
+import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/src/document/attributes.dart';
 import 'package:appflowy_editor/src/document/node.dart';
 import 'package:appflowy_editor/src/document/path.dart';
@@ -114,21 +115,17 @@ class TransactionBuilder {
 
   /// Inserts content at a specified index.
   /// Optionally, you may specify formatting attributes that are applied to the inserted string.
-  /// By default, the formatting attributes before the insert position will be used.
+  /// When no formatting attributes specified, the formating attributes before the insert position will be used if they don't have defaultFormatting flag set
+  /// When defaultFormatting flag is set before the insert position, it will be cleared.
+  /// When insert position is within a text having defaultFormatting flag set, the flag will be ignored and clear (formatting attributes of the text will be applied)
   insertText(
     TextNode node,
     int index,
     String content, {
     Attributes? attributes,
   }) {
-    var newAttributes = attributes;
-    if (index != 0 && attributes == null) {
-      newAttributes =
-          node.delta.slice(max(index - 1, 0), index).first.attributes;
-      if (newAttributes != null) {
-        newAttributes = Attributes.from(newAttributes);
-      }
-    }
+    final newAttributes = attributes ?? _getAttributesAt(node, index);
+
     textEdit(
       node,
       () => Delta()
@@ -227,4 +224,38 @@ class TransactionBuilder {
       afterSelection: afterSelection,
     );
   }
+
+  Attributes? _getAttributesAt(TextNode node, int index) {
+    if (index == 0) {
+      return null;
+    }
+
+    final previousAttributes =
+        node.delta.slice(index - 1, index).first.attributes;
+
+    final nextAttributes = node.delta.length > index
+        ? node.delta.slice(index, index + 1).first.attributes
+        : null;
+
+    if (previousAttributes == null) {
+      return null;
+    }
+
+    if (previousAttributes.containsKey(BuiltInAttributeKey.defaultFormating)) {
+      Attributes newAttributes = Map.from(previousAttributes)
+        ..removeWhere((key, _) => key == BuiltInAttributeKey.defaultFormating);
+
+      if (node.previous != null) {
+        updateNode(node.next!, newAttributes);
+        if (previousAttributes == nextAttributes) {
+          updateNode(node.next!, newAttributes);
+          return newAttributes;
+        }
+      }
+
+      return null;
+    }
+
+    return Attributes.from(previousAttributes);
+  }
 }

+ 3 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler.dart

@@ -1,4 +1,5 @@
 import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/src/extensions/path_extensions.dart';
 import 'package:flutter/material.dart';
 
 // convert **abc** to bold abc.
@@ -51,6 +52,7 @@ ShortcutEventHandler doubleAsterisksToBold = (editorState, event) {
       selection.end.offset - thirdToLastAsteriskIndex - 2,
       {
         BuiltInAttributeKey.bold: true,
+        BuiltInAttributeKey.defaultFormating: true,
       },
     )
     ..afterSelection = Selection.collapsed(
@@ -116,6 +118,7 @@ ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
       selection.end.offset - thirdToLastUnderscoreIndex - 2,
       {
         BuiltInAttributeKey.bold: true,
+        BuiltInAttributeKey.defaultFormating: true,
       },
     )
     ..afterSelection = Selection.collapsed(
@@ -125,7 +128,6 @@ ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
       ),
     )
     ..commit();
-  editorState.editorStyle == EditorStyle.defaultStyle();
 
   return KeyEventResult.handled;
 };

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart

@@ -143,7 +143,7 @@ extension on LogicalKeyboardKey {
       return PhysicalKeyboardKey.digit8;
     }
     if (this == LogicalKeyboardKey.underscore) {
-      return PhysicalKeyboardKey.minus gg;
+      return PhysicalKeyboardKey.minus;
     }
     throw UnimplementedError();
   }

+ 78 - 0
frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler_test.dart

@@ -92,6 +92,45 @@ void main() async {
         expect(textNode.toRawString(), '*AppFlowy');
       });
 
+      testWidgets('**AppFlowy** application to bold AppFlowy only',
+          (tester) async {
+        const boldText = '**AppFlowy*';
+        const normalText = ' application';
+        final editor = tester.editor..insertTextNode('');
+        await editor.startTesting();
+        await editor.updateSelection(
+          Selection.single(path: [0], startOffset: 0),
+        );
+        final textNode = editor.nodeAtPath([0]) as TextNode;
+
+        for (var i = 0; i < boldText.length; i++) {
+          await editor.insertText(textNode, boldText[i], i);
+        }
+        await insertAsterisk(editor);
+        for (var i = 0; i < normalText.length; i++) {
+          await editor.insertText(
+              textNode, normalText[i], i + boldText.length - 3);
+        }
+        final boldTextLength = boldText.replaceAll('*', '').length;
+        final appFlowyBold = textNode.allSatisfyBoldInSelection(
+          Selection.single(
+            path: [0],
+            startOffset: 0,
+            endOffset: boldTextLength,
+          ),
+        );
+        final applicationNormal = textNode.allSatisfyBoldInSelection(
+          Selection.single(
+            path: [0],
+            startOffset: boldTextLength,
+            endOffset: textNode.toRawString().length,
+          ),
+        );
+        expect(appFlowyBold, true);
+        expect(applicationNormal, false);
+        expect(textNode.toRawString(), 'AppFlowy application');
+      });
+
       testWidgets('**** nothing changes', (tester) async {
         const text = '***';
         final editor = tester.editor..insertTextNode('');
@@ -198,6 +237,45 @@ void main() async {
         expect(textNode.toRawString(), '_AppFlowy');
       });
 
+      testWidgets('__AppFlowy__ application to bold AppFlowy only',
+          (tester) async {
+        const boldText = '__AppFlowy_';
+        const normalText = ' application';
+        final editor = tester.editor..insertTextNode('');
+        await editor.startTesting();
+        await editor.updateSelection(
+          Selection.single(path: [0], startOffset: 0),
+        );
+        final textNode = editor.nodeAtPath([0]) as TextNode;
+
+        for (var i = 0; i < boldText.length; i++) {
+          await editor.insertText(textNode, boldText[i], i);
+        }
+        await insertUnderscore(editor);
+        for (var i = 0; i < normalText.length; i++) {
+          await editor.insertText(
+              textNode, normalText[i], i + boldText.length - 3);
+        }
+        final boldTextLength = boldText.replaceAll('_', '').length;
+        final appFlowyBold = textNode.allSatisfyBoldInSelection(
+          Selection.single(
+            path: [0],
+            startOffset: 0,
+            endOffset: boldTextLength,
+          ),
+        );
+        final applicationNormal = textNode.allSatisfyBoldInSelection(
+          Selection.single(
+            path: [0],
+            startOffset: boldTextLength,
+            endOffset: textNode.toRawString().length,
+          ),
+        );
+        expect(appFlowyBold, true);
+        expect(applicationNormal, false);
+        expect(textNode.toRawString(), 'AppFlowy application');
+      });
+
       testWidgets('____ nothing changes', (tester) async {
         const text = '___';
         final editor = tester.editor..insertTextNode('');