From 74447c4580334e1495e2ac28c1fa76d4a5deed3f Mon Sep 17 00:00:00 2001 From: Amir Panahandeh Date: Sat, 23 Sep 2023 11:49:19 +0330 Subject: [PATCH] Fix exception thrown when inserting new line with toggled inline styles (#161) --- .../fleather/lib/src/widgets/controller.dart | 50 +++++++++++++------ packages/fleather/lib/util.dart | 5 ++ packages/fleather/test/util_test.dart | 10 ++++ .../test/widgets/controller_test.dart | 16 ++++++ 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/packages/fleather/lib/src/widgets/controller.dart b/packages/fleather/lib/src/widgets/controller.dart index 37936d11..b603c6d1 100644 --- a/packages/fleather/lib/src/widgets/controller.dart +++ b/packages/fleather/lib/src/widgets/controller.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math' as math; +import 'package:collection/collection.dart'; import 'package:fleather/src/widgets/history.dart'; import 'package:fleather/util.dart'; import 'package:flutter/cupertino.dart'; @@ -69,14 +70,39 @@ class FleatherController extends ChangeNotifier { .mergeAll(toggledStyles); } - bool _shouldApplyToggledStyles(Delta delta) => - toggledStyles.isNotEmpty && - delta.isNotEmpty && - ((delta.length <= 2 && // covers single insert and a retain+insert - delta.last.isInsert) || - (delta.length <= 3 && - delta.last.isRetain // special case for AutoTextDirectionRule - )); + bool _shouldApplyToggledStyles(Delta delta) { + if (toggledStyles.isNotEmpty && delta.isNotEmpty) { + // covers single insert and a retain+insert + if (delta.length <= 2 && delta.last.isInsert) { + return true; + } + // special case for AutoTextDirectionRule + if (delta.length <= 3 && delta.last.isRetain) { + return delta.last.attributes != null && + delta.last.attributes! + .containsKey(ParchmentAttribute.direction.key) && + delta.last.attributes! + .containsKey(ParchmentAttribute.alignment.key); + } + } + return false; + } + + void _applyToggledStyles(int index, Object data) { + if (data is String && !isDataOnlyNewLines(data)) { + var retainDelta = Delta()..retain(index); + final segments = data.split('\n'); + segments.forEachIndexed((index, segment) { + if (segment.isNotEmpty) { + retainDelta.retain(segment.length, toggledStyles.toJson()); + } + if (index != segments.length - 1) { + retainDelta.retain(1); + } + }); + document.compose(retainDelta, ChangeSource.local); + } + } /// Replaces [length] characters in the document starting at [index] with /// provided [text]. @@ -96,14 +122,8 @@ class FleatherController extends ChangeNotifier { final isDataNotEmpty = data is String ? data.isNotEmpty : true; if (length > 0 || isDataNotEmpty) { delta = document.replace(index, length, data); - // If the delta is an insert operation and we have toggled - // some styles, then apply those styles to the inserted text. if (_shouldApplyToggledStyles(delta)) { - final dataLength = data is String ? data.length : 1; - final retainDelta = Delta() - ..retain(index) - ..retain(dataLength, toggledStyles.toJson()); - document.compose(retainDelta, ChangeSource.local); + _applyToggledStyles(index, data); } } diff --git a/packages/fleather/lib/util.dart b/packages/fleather/lib/util.dart index 8056c59f..0c548a6a 100644 --- a/packages/fleather/lib/util.dart +++ b/packages/fleather/lib/util.dart @@ -53,3 +53,8 @@ TextDirection getDirectionOfNode(StyledNode node) { } return TextDirection.ltr; } + +bool isDataOnlyNewLines(Object data) { + if (data is! String || data.isEmpty) return false; + return RegExp('^(\n)+\$').hasMatch(data); +} diff --git a/packages/fleather/test/util_test.dart b/packages/fleather/test/util_test.dart index e043ee74..e8c7108d 100644 --- a/packages/fleather/test/util_test.dart +++ b/packages/fleather/test/util_test.dart @@ -40,4 +40,14 @@ void main() { expect(result, 1); }); }); + + test('isDataOnlyNewLines', () { + expect(isDataOnlyNewLines(123), false); + expect(isDataOnlyNewLines(Object()), false); + expect(isDataOnlyNewLines(''), false); + expect(isDataOnlyNewLines('\nTest\nTest\n'), false); + expect(isDataOnlyNewLines('\n \n\n'), false); + expect(isDataOnlyNewLines('\n\t\n\n'), false); + expect(isDataOnlyNewLines('\n\n\n'), true); + }); } diff --git a/packages/fleather/test/widgets/controller_test.dart b/packages/fleather/test/widgets/controller_test.dart index 68b84255..56f9ff10 100644 --- a/packages/fleather/test/widgets/controller_test.dart +++ b/packages/fleather/test/widgets/controller_test.dart @@ -128,6 +128,22 @@ void main() { // expect(controller.lastChangeSource, ChangeSource.local); }); + test('replaceText only applies toggled styles to non new line parts', () { + controller.replaceText(0, 0, 'Words'); + controller.formatText(2, 0, ParchmentAttribute.bold); + controller.replaceText(2, 0, '\nTest\n'); + + expect( + controller.document.toDelta(), + Delta() + ..insert('Wo\n') + ..insert('Test', ParchmentAttribute.bold.toJson()) + ..insert('\n') + ..insert('rds') + ..insert('\n'), + ); + }); + test('insert text with toggled style unset', () { var notified = false; controller.addListener(() {