Browse Source

Inline toggle style feature (#169)

jolancornevin 5 years ago
parent
commit
7f102b9943

+ 41
- 1
packages/zefyr/lib/src/widgets/controller.dart View File

@@ -44,6 +44,12 @@ class ZefyrController extends ChangeNotifier {
44 44
   /// Source of the last text or selection change.
45 45
   ChangeSource get lastChangeSource => _lastChangeSource;
46 46
 
47
+  /// Store any styles attribute that got toggled by the tap of a button
48
+  /// and that has not been applied yet.
49
+  /// It gets reseted after each format action within the [document].
50
+  NotusStyle get toggledStyles => _toggledStyles;
51
+  NotusStyle _toggledStyles = new NotusStyle();
52
+
47 53
   /// Updates selection with specified [value].
48 54
   ///
49 55
   /// [value] and [source] cannot be `null`.
@@ -104,6 +110,9 @@ class ZefyrController extends ChangeNotifier {
104 110
   /// Resulting change is registered as produced by user action, e.g.
105 111
   /// using [ChangeSource.local].
106 112
   ///
113
+  /// It also applies the toggledStyle if needed. And then it resets it
114
+  /// in any cases as we don't want to keep it except on inserts.
115
+  ///
107 116
   /// Optionally updates selection if provided.
108 117
   void replaceText(int index, int length, String text,
109 118
       {TextSelection selection}) {
@@ -111,8 +120,23 @@ class ZefyrController extends ChangeNotifier {
111 120
 
112 121
     if (length > 0 || text.isNotEmpty) {
113 122
       delta = document.replace(index, length, text);
123
+      // If the delta is a classical insert operation and we have toggled
124
+      // some style, then we apply it to our document.
125
+      if (delta != null &&
126
+          toggledStyles.isNotEmpty &&
127
+          delta.length == 2 &&
128
+          delta[1].isInsert) {
129
+        // Apply it.
130
+        Delta retainDelta = new Delta()
131
+          ..retain(index)
132
+          ..retain(1, toggledStyles.toJson());
133
+        document.compose(retainDelta, ChangeSource.local);
134
+      }
114 135
     }
115 136
 
137
+    // Always reset it after any user action, even if it has not been applied.
138
+    _toggledStyles = new NotusStyle();
139
+
116 140
     if (selection != null) {
117 141
       if (delta == null) {
118 142
         _updateSelectionSilent(selection, source: ChangeSource.local);
@@ -140,6 +164,14 @@ class ZefyrController extends ChangeNotifier {
140 164
   void formatText(int index, int length, NotusAttribute attribute) {
141 165
     final change = document.format(index, length, attribute);
142 166
     _lastChangeSource = ChangeSource.local;
167
+
168
+    if (length == 0 &&
169
+        (attribute.key == NotusAttribute.bold.key ||
170
+            attribute.key == NotusAttribute.italic.key)) {
171
+      // Add the attribute to our toggledStyle. It will be used later upon insertion.
172
+      _toggledStyles = toggledStyles.put(attribute);
173
+    }
174
+
143 175
     // Transform selection against the composed change and give priority to
144 176
     // the change. This is needed in cases when format operation actually
145 177
     // inserts data into the document (e.g. embeds).
@@ -160,10 +192,18 @@ class ZefyrController extends ChangeNotifier {
160 192
     formatText(index, length, attribute);
161 193
   }
162 194
 
195
+  /// Returns style of specified text range.
196
+  ///
197
+  /// If nothing is selected but we've toggled an attribute,
198
+  ///  we also merge those in our style before returning.
163 199
   NotusStyle getSelectionStyle() {
164 200
     int start = _selection.start;
165 201
     int length = _selection.end - start;
166
-    return _document.collectStyle(start, length);
202
+    var lineStyle = _document.collectStyle(start, length);
203
+
204
+    lineStyle = lineStyle.mergeAll(toggledStyles);
205
+
206
+    return lineStyle;
167 207
   }
168 208
 
169 209
   TextEditingValue get plainTextEditingValue {

+ 57
- 0
packages/zefyr/test/widgets/controller_test.dart View File

@@ -90,6 +90,54 @@ void main() {
90 90
       expect(controller.lastChangeSource, ChangeSource.local);
91 91
     });
92 92
 
93
+    test('formatText with toggled style enabled', () {
94
+      bool notified = false;
95
+      controller.addListener(() {
96
+        notified = true;
97
+      });
98
+      controller.replaceText(0, 0, 'Words');
99
+      controller.formatText(2, 0, NotusAttribute.bold);
100
+      // Test that doing nothing does reset the toggledStyle.
101
+      controller.replaceText(2, 0, '');
102
+      controller.replaceText(2, 0, 'n');
103
+      controller.formatText(3, 0, NotusAttribute.bold);
104
+      controller.replaceText(3, 0, 'B');
105
+      expect(notified, isTrue);
106
+
107
+      expect(
108
+        controller.document.toDelta(),
109
+        new Delta()
110
+          ..insert('Won')
111
+          ..insert('B', NotusAttribute.bold.toJson())
112
+          ..insert('rds')
113
+          ..insert('\n'),
114
+      );
115
+      expect(controller.lastChangeSource, ChangeSource.local);
116
+    });
117
+
118
+    test('insert text with toggled style unset', () {
119
+      bool notified = false;
120
+      controller.addListener(() {
121
+        notified = true;
122
+      });
123
+      controller.replaceText(0, 0, 'Words');
124
+      controller.formatText(1, 0, NotusAttribute.bold);
125
+      controller.replaceText(1, 0, 'B');
126
+      controller.formatText(2, 0, NotusAttribute.bold.unset);
127
+      controller.replaceText(2, 0, 'u');
128
+
129
+      expect(notified, isTrue);
130
+      expect(
131
+        controller.document.toDelta(),
132
+        new Delta()
133
+          ..insert('W')
134
+          ..insert('B', NotusAttribute.bold.toJson())
135
+          ..insert('uords')
136
+          ..insert('\n'),
137
+      );
138
+      expect(controller.lastChangeSource, ChangeSource.local);
139
+    });
140
+
93 141
     test('formatSelection', () {
94 142
       bool notified = false;
95 143
       controller.addListener(() {
@@ -113,5 +161,14 @@ void main() {
113 161
       var result = controller.getSelectionStyle();
114 162
       expect(result.values, [NotusAttribute.bold]);
115 163
     });
164
+
165
+    test('getSelectionStyle with toggled style', () {
166
+      var selection = new TextSelection.collapsed(offset: 3);
167
+      controller.replaceText(0, 0, 'Words', selection: selection);
168
+      controller.formatText(3, 0, NotusAttribute.bold);
169
+
170
+      var result = controller.getSelectionStyle();
171
+      expect(result.values, [NotusAttribute.bold]);
172
+    });
116 173
   });
117 174
 }