Skip to content

Commit bdd5fa7

Browse files
committed
fix: #10 Exception trhown after inserting '/' slash in middle of text
1 parent 1a467d3 commit bdd5fa7

File tree

2 files changed

+134
-21
lines changed

2 files changed

+134
-21
lines changed

lib/src/core/transform/transaction.dart

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -264,19 +264,19 @@ extension TextTransaction on Transaction {
264264
if (index != 0 && attributes == null) {
265265
newAttributes =
266266
textNode.delta.slice(max(index - 1, 0), index).first.attributes;
267-
if (newAttributes != null) {
268-
newAttributes = {...newAttributes}; // make a copy
269-
} else {
270-
newAttributes =
271-
textNode.delta.slice(index, index + length).first.attributes;
267+
if (newAttributes == null) {
268+
final slicedDelta = textNode.delta.slice(index, index + length);
269+
if (slicedDelta.isNotEmpty) {
270+
newAttributes = slicedDelta.first.attributes;
271+
}
272272
}
273273
}
274274
updateText(
275275
textNode,
276276
Delta()
277277
..retain(index)
278278
..delete(length)
279-
..insert(text, attributes: newAttributes),
279+
..insert(text, attributes: {...newAttributes ?? {}}),
280280
);
281281
afterSelection = Selection.collapsed(
282282
Position(
@@ -291,46 +291,125 @@ extension TextTransaction on Transaction {
291291
Selection selection,
292292
List<String> texts,
293293
) {
294-
if (textNodes.isEmpty) {
294+
if (textNodes.isEmpty || texts.isEmpty) {
295295
return;
296296
}
297297

298-
if (selection.isSingle) {
299-
assert(textNodes.length == 1 && texts.length == 1);
300-
replaceText(
301-
textNodes.first,
302-
selection.startIndex,
303-
selection.length,
304-
texts.first,
305-
);
306-
} else {
298+
if (textNodes.length == texts.length) {
307299
final length = textNodes.length;
308-
for (var i = 0; i < length; i++) {
300+
301+
if (length == 1) {
302+
replaceText(
303+
textNodes.first,
304+
selection.startIndex,
305+
selection.endIndex - selection.startIndex,
306+
texts.first,
307+
);
308+
return;
309+
}
310+
311+
for (var i = 0; i < textNodes.length; i++) {
309312
final textNode = textNodes[i];
310-
final text = texts[i];
311313
if (i == 0) {
312314
replaceText(
313315
textNode,
314316
selection.startIndex,
315317
textNode.toPlainText().length,
316-
text,
318+
texts.first,
317319
);
318320
} else if (i == length - 1) {
319321
replaceText(
320322
textNode,
321323
0,
322324
selection.endIndex,
323-
text,
325+
texts.last,
324326
);
325327
} else {
326328
replaceText(
327329
textNode,
328330
0,
329331
textNode.toPlainText().length,
332+
texts[i],
333+
);
334+
}
335+
}
336+
return;
337+
}
338+
339+
if (textNodes.length > texts.length) {
340+
final length = textNodes.length;
341+
for (var i = 0; i < textNodes.length; i++) {
342+
final textNode = textNodes[i];
343+
if (i == 0) {
344+
replaceText(
345+
textNode,
346+
selection.startIndex,
347+
textNode.toPlainText().length,
348+
texts.first,
349+
);
350+
} else if (i == length - 1) {
351+
replaceText(
352+
textNode,
353+
0,
354+
selection.endIndex,
355+
texts.last,
356+
);
357+
} else {
358+
if (i < texts.length - 1) {
359+
replaceText(
360+
textNode,
361+
0,
362+
textNode.toPlainText().length,
363+
texts[i],
364+
);
365+
} else {
366+
deleteNode(textNode);
367+
}
368+
}
369+
}
370+
afterSelection = null;
371+
return;
372+
}
373+
374+
if (textNodes.length < texts.length) {
375+
final length = texts.length;
376+
for (var i = 0; i < texts.length; i++) {
377+
final text = texts[i];
378+
if (i == 0) {
379+
replaceText(
380+
textNodes.first,
381+
selection.startIndex,
382+
textNodes.first.toPlainText().length,
383+
text,
384+
);
385+
} else if (i == length - 1) {
386+
replaceText(
387+
textNodes.last,
388+
0,
389+
selection.endIndex,
330390
text,
331391
);
392+
} else {
393+
if (i < textNodes.length - 1) {
394+
replaceText(
395+
textNodes[i],
396+
0,
397+
textNodes[i].toPlainText().length,
398+
text,
399+
);
400+
} else {
401+
var path = textNodes.first.path;
402+
var j = i - textNodes.length + length - 1;
403+
while (j > 0) {
404+
path = path.next;
405+
j--;
406+
}
407+
insertNode(path, TextNode(delta: Delta()..insert(text)));
408+
}
332409
}
333410
}
411+
afterSelection = null;
412+
return;
334413
}
335414
}
336415
}

test/service/internal_key_event_handlers/slash_handler_test.dart

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ void main() async {
1010
});
1111

1212
group('slash_handler.dart', () {
13-
testWidgets('Presses / to trigger selection menu', (tester) async {
13+
testWidgets('Presses / to trigger selection menu in 0 index',
14+
(tester) async {
1415
const text = 'Welcome to Appflowy 😁';
1516
const lines = 3;
1617
final editor = tester.editor;
@@ -41,5 +42,38 @@ void main() async {
4142
findsNothing,
4243
);
4344
});
45+
46+
testWidgets('Presses / to trigger selection menu in not 0 index',
47+
(tester) async {
48+
const text = 'Welcome to Appflowy 😁';
49+
const lines = 3;
50+
final editor = tester.editor;
51+
for (var i = 0; i < lines; i++) {
52+
editor.insertTextNode(text);
53+
}
54+
await editor.startTesting();
55+
await editor.updateSelection(Selection.single(path: [1], startOffset: 5));
56+
await editor.pressLogicKey(LogicalKeyboardKey.slash);
57+
58+
await tester.pumpAndSettle(const Duration(milliseconds: 1000));
59+
60+
expect(
61+
find.byType(SelectionMenuWidget, skipOffstage: false),
62+
findsOneWidget,
63+
);
64+
65+
for (final item in defaultSelectionMenuItems) {
66+
expect(find.text(item.name), findsOneWidget);
67+
}
68+
69+
await editor.updateSelection(Selection.single(path: [1], startOffset: 0));
70+
71+
await tester.pumpAndSettle(const Duration(milliseconds: 200));
72+
73+
expect(
74+
find.byType(SelectionMenuItemWidget, skipOffstage: false),
75+
findsNothing,
76+
);
77+
});
4478
});
4579
}

0 commit comments

Comments
 (0)