2022-09-13 17:33:26 +08:00
|
|
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
2022-10-13 11:55:16 +08:00
|
|
|
import 'package:appflowy_editor/src/history/undo_manager.dart';
|
2022-09-13 17:33:26 +08:00
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
|
|
|
|
|
void main() async {
|
|
|
|
|
setUpAll(() {
|
|
|
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
|
});
|
|
|
|
|
|
2023-07-12 12:21:54 +07:00
|
|
|
test('HistoryItem #1', () {
|
|
|
|
|
final editorState = EditorState.blank();
|
2022-09-13 17:33:26 +08:00
|
|
|
|
|
|
|
|
final historyItem = HistoryItem();
|
2023-07-12 12:21:54 +07:00
|
|
|
historyItem.add(DeleteOperation([0], [paragraphNode(text: '0')]));
|
|
|
|
|
historyItem.add(DeleteOperation([0], [paragraphNode(text: '1')]));
|
|
|
|
|
historyItem.add(DeleteOperation([0], [paragraphNode(text: '2')]));
|
2022-09-13 17:33:26 +08:00
|
|
|
|
|
|
|
|
final transaction = historyItem.toTransaction(editorState);
|
|
|
|
|
assert(isInsertAndPathEqual(transaction.operations[0], [0], '2'));
|
|
|
|
|
assert(isInsertAndPathEqual(transaction.operations[1], [0], '1'));
|
|
|
|
|
assert(isInsertAndPathEqual(transaction.operations[2], [0], '0'));
|
|
|
|
|
});
|
|
|
|
|
|
2023-07-12 12:21:54 +07:00
|
|
|
test('HistoryItem #2', () {
|
|
|
|
|
final editorState = EditorState.blank();
|
2022-09-13 17:33:26 +08:00
|
|
|
|
|
|
|
|
final historyItem = HistoryItem();
|
2023-07-12 12:21:54 +07:00
|
|
|
historyItem.add(DeleteOperation([0], [paragraphNode(text: '0')]));
|
2023-08-07 19:19:14 +02:00
|
|
|
historyItem.add(
|
|
|
|
|
const UpdateOperation([0], {'subType': 'number'}, {'subType': null}),
|
|
|
|
|
);
|
2023-07-12 12:21:54 +07:00
|
|
|
historyItem.add(DeleteOperation([0], [paragraphNode(), paragraphNode()]));
|
|
|
|
|
historyItem.add(DeleteOperation([0], [paragraphNode()]));
|
2022-09-13 17:33:26 +08:00
|
|
|
|
|
|
|
|
final transaction = historyItem.toTransaction(editorState);
|
|
|
|
|
assert(isInsertAndPathEqual(transaction.operations[0], [0]));
|
|
|
|
|
assert(isInsertAndPathEqual(transaction.operations[1], [0]));
|
|
|
|
|
assert(transaction.operations[2] is UpdateOperation);
|
|
|
|
|
assert(isInsertAndPathEqual(transaction.operations[3], [0], '0'));
|
|
|
|
|
});
|
2023-03-24 13:59:03 +01:00
|
|
|
|
2023-07-12 12:21:54 +07:00
|
|
|
test('HistoryItem sealed', () {
|
2023-03-24 13:59:03 +01:00
|
|
|
final historyItem = HistoryItem();
|
2023-07-12 12:21:54 +07:00
|
|
|
historyItem.add(DeleteOperation([0], [paragraphNode(text: '0')]));
|
2023-03-24 13:59:03 +01:00
|
|
|
historyItem.seal();
|
|
|
|
|
expect(historyItem.sealed, true);
|
|
|
|
|
});
|
2026-03-25 15:25:00 +08:00
|
|
|
|
|
|
|
|
test('Multi-step redo preserves redo stack', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Apply 3 separate transactions, sealing each one
|
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
|
|
|
final transaction = editorState.transaction;
|
|
|
|
|
transaction.insertText(
|
|
|
|
|
editorState.document.nodeAtPath([0])!,
|
|
|
|
|
i, // offset
|
|
|
|
|
String.fromCharCode(65 + i), // 'A', 'B', 'C'
|
|
|
|
|
);
|
|
|
|
|
editorState.apply(transaction);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Document should be "ABC"
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'ABC',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 3);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 0);
|
|
|
|
|
|
|
|
|
|
// Undo 3 times → back to empty
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'AB',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 2);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 1);
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'A',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 1);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 2);
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 0);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 3);
|
|
|
|
|
|
|
|
|
|
// Redo 3 times → should restore each step
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'A',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 1);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 2);
|
|
|
|
|
|
|
|
|
|
// Manually seal to simulate the 50ms debounce timer
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'AB',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 2);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 1);
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'ABC',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 3);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('New edit after undo still clears redo stack', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Insert "A"
|
|
|
|
|
final t1 = editorState.transaction;
|
|
|
|
|
t1.insertText(editorState.document.nodeAtPath([0])!, 0, 'A');
|
|
|
|
|
editorState.apply(t1);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
|
|
|
|
|
// Insert "B"
|
|
|
|
|
final t2 = editorState.transaction;
|
|
|
|
|
t2.insertText(editorState.document.nodeAtPath([0])!, 1, 'B');
|
|
|
|
|
editorState.apply(t2);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
|
|
|
|
|
// Undo once → "A", redo stack should have 1 item
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'A',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.redoStack.isNonEmpty, true);
|
|
|
|
|
|
|
|
|
|
// Make a NEW edit (not redo) → redo stack should be cleared
|
|
|
|
|
final t3 = editorState.transaction;
|
|
|
|
|
t3.insertText(editorState.document.nodeAtPath([0])!, 1, 'X');
|
|
|
|
|
editorState.apply(t3);
|
|
|
|
|
|
|
|
|
|
expect(editorState.undoManager.redoStack.isEmpty, true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Undo after redo restores previous state', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Insert A, B, C
|
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
|
|
|
final transaction = editorState.transaction;
|
|
|
|
|
transaction.insertText(
|
|
|
|
|
editorState.document.nodeAtPath([0])!,
|
|
|
|
|
i,
|
|
|
|
|
String.fromCharCode(65 + i),
|
|
|
|
|
);
|
|
|
|
|
editorState.apply(transaction);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
}
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'ABC');
|
|
|
|
|
|
|
|
|
|
// Undo all
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), '');
|
|
|
|
|
|
|
|
|
|
// Redo 2
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'AB');
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 2);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 1);
|
|
|
|
|
|
|
|
|
|
// Undo 1 — should undo the last redo
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'A');
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 1);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 2);
|
|
|
|
|
|
|
|
|
|
// Redo all remaining
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'ABC');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Partial undo then redo', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Insert A, B, C, D
|
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
|
|
|
final transaction = editorState.transaction;
|
|
|
|
|
transaction.insertText(
|
|
|
|
|
editorState.document.nodeAtPath([0])!,
|
|
|
|
|
i,
|
|
|
|
|
String.fromCharCode(65 + i),
|
|
|
|
|
);
|
|
|
|
|
editorState.apply(transaction);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
}
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'ABCD');
|
|
|
|
|
|
|
|
|
|
// Undo 2 of 4
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'AB');
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 2);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 2);
|
|
|
|
|
|
|
|
|
|
// Redo 2
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'ABC');
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'ABCD');
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 4);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Interleaved undo and redo', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Insert A, B, C
|
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
|
|
|
final transaction = editorState.transaction;
|
|
|
|
|
transaction.insertText(
|
|
|
|
|
editorState.document.nodeAtPath([0])!,
|
|
|
|
|
i,
|
|
|
|
|
String.fromCharCode(65 + i),
|
|
|
|
|
);
|
|
|
|
|
editorState.apply(transaction);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// undo → redo → undo → redo cycle
|
|
|
|
|
editorState.undoManager.undo(); // ABC → AB
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'AB');
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.redo(); // AB → ABC
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'ABC');
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.undo(); // ABC → AB
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'AB');
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.undo(); // AB → A
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'A');
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.redo(); // A → AB
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'AB');
|
|
|
|
|
|
|
|
|
|
editorState.undoManager.redo(); // AB → ABC
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'ABC');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('New edit mid-redo clears remaining redo stack', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Insert A, B, C
|
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
|
|
|
final transaction = editorState.transaction;
|
|
|
|
|
transaction.insertText(
|
|
|
|
|
editorState.document.nodeAtPath([0])!,
|
|
|
|
|
i,
|
|
|
|
|
String.fromCharCode(65 + i),
|
|
|
|
|
);
|
|
|
|
|
editorState.apply(transaction);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Undo all 3
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), '');
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 3);
|
|
|
|
|
|
|
|
|
|
// Redo 1
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'A');
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 2);
|
|
|
|
|
|
|
|
|
|
// Make a NEW edit → remaining redo should be cleared
|
|
|
|
|
final t = editorState.transaction;
|
|
|
|
|
t.insertText(editorState.document.nodeAtPath([0])!, 1, 'X');
|
|
|
|
|
editorState.apply(t);
|
|
|
|
|
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'AX');
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 0);
|
|
|
|
|
|
|
|
|
|
// Redo should be no-op now
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), 'AX');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Selection is preserved during multi-step redo', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Insert A at offset 0 with selection
|
|
|
|
|
final t1 = editorState.transaction;
|
|
|
|
|
t1.insertText(editorState.document.nodeAtPath([0])!, 0, 'A');
|
|
|
|
|
t1.afterSelection = Selection.collapsed(Position(path: [0], offset: 1));
|
|
|
|
|
editorState.apply(t1);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
|
|
|
|
|
// Insert B at offset 1 with selection
|
|
|
|
|
final t2 = editorState.transaction;
|
|
|
|
|
t2.insertText(editorState.document.nodeAtPath([0])!, 1, 'B');
|
|
|
|
|
t2.afterSelection = Selection.collapsed(Position(path: [0], offset: 2));
|
|
|
|
|
editorState.apply(t2);
|
|
|
|
|
editorState.undoManager.undoStack.last.seal();
|
|
|
|
|
|
|
|
|
|
// Undo both
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
editorState.undoManager.undo();
|
|
|
|
|
expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), '');
|
|
|
|
|
|
|
|
|
|
// Redo first — selection should be at offset 1
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.selection,
|
|
|
|
|
Selection.collapsed(Position(path: [0], offset: 1)),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Redo second — selection should be at offset 2
|
|
|
|
|
editorState.undoManager.redo();
|
|
|
|
|
expect(
|
|
|
|
|
editorState.selection,
|
|
|
|
|
Selection.collapsed(Position(path: [0], offset: 2)),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('TransactionSource.none does not record to either stack', () {
|
|
|
|
|
final editorState = EditorState(
|
|
|
|
|
document: Document(
|
|
|
|
|
root: pageNode(
|
|
|
|
|
children: [
|
|
|
|
|
paragraphNode(text: ''),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
editorState.disableSealTimer = true;
|
|
|
|
|
|
|
|
|
|
// Apply with source: none
|
|
|
|
|
final transaction = editorState.transaction;
|
|
|
|
|
transaction.insertText(
|
|
|
|
|
editorState.document.nodeAtPath([0])!,
|
|
|
|
|
0,
|
|
|
|
|
'A',
|
|
|
|
|
);
|
|
|
|
|
editorState.apply(
|
|
|
|
|
transaction,
|
|
|
|
|
options: const ApplyOptions(
|
|
|
|
|
recordUndo: false,
|
|
|
|
|
source: TransactionSource.none,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Document is modified but neither stack is affected
|
|
|
|
|
expect(
|
|
|
|
|
editorState.document.nodeAtPath([0])!.delta!.toPlainText(),
|
|
|
|
|
'A',
|
|
|
|
|
);
|
|
|
|
|
expect(editorState.undoManager.undoStack.length, 0);
|
|
|
|
|
expect(editorState.undoManager.redoStack.length, 0);
|
|
|
|
|
});
|
2022-09-13 17:33:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isInsertAndPathEqual(Operation operation, Path path, [String? content]) {
|
|
|
|
|
if (operation is! InsertOperation) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-09 23:53:18 +08:00
|
|
|
if (!operation.path.equals(path)) {
|
2022-09-13 17:33:26 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 12:21:54 +07:00
|
|
|
final delta = operation.nodes.first.delta;
|
|
|
|
|
if (delta == null) {
|
2022-09-13 17:33:26 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (content == null) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 12:21:54 +07:00
|
|
|
return delta.toPlainText() == content;
|
2022-09-13 17:33:26 +08:00
|
|
|
}
|