fix: workaround infinity formatting

This commit is contained in:
Andrei Dolgov 2022-09-26 17:24:29 -04:00
parent c0964fad5d
commit 6a902a2b21
5 changed files with 123 additions and 11 deletions

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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;
};

View File

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

View File

@ -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('');