feat: support in memory transaction update (#972)

* feat: support in memory transaction update

* test: support in memory transaction update
This commit is contained in:
Lucas 2024-11-25 11:07:47 +08:00 • committed by GitHub
commit 5b3878dcc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 41 additions and 15 deletions

View file

@ -8,6 +8,12 @@ import 'package:appflowy_editor/src/history/undo_manager.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef EditorTransactionValue = (
TransactionTime time,
Transaction transaction,
ApplyOptions options,
);
/// the type of this value is bool. /// the type of this value is bool.
/// ///
/// set true to this key to prevent attaching the text service when selection is changed. /// set true to this key to prevent attaching the text service when selection is changed.
@ -15,15 +21,20 @@ const selectionExtraInfoDoNotAttachTextService =
'selectionExtraInfoDoNotAttachTextService'; 'selectionExtraInfoDoNotAttachTextService';
class ApplyOptions { class ApplyOptions {
const ApplyOptions({
this.recordUndo = true,
this.recordRedo = false,
this.inMemoryUpdate = false,
});
/// This flag indicates that /// This flag indicates that
/// whether the transaction should be recorded into /// whether the transaction should be recorded into
/// the undo stack /// the undo stack
final bool recordUndo; final bool recordUndo;
final bool recordRedo; final bool recordRedo;
const ApplyOptions({
this.recordUndo = true, /// This flag used to determine whether the transaction is in-memory update.
this.recordRedo = false, final bool inMemoryUpdate;
});
} }
@Deprecated('use SelectionUpdateReason instead') @Deprecated('use SelectionUpdateReason instead')
@ -172,9 +183,8 @@ class EditorState {
List<ToolbarItem> toolbarItems = []; List<ToolbarItem> toolbarItems = [];
/// listen to this stream to get notified when the transaction applies. /// listen to this stream to get notified when the transaction applies.
Stream<(TransactionTime, Transaction)> get transactionStream => Stream<EditorTransactionValue> get transactionStream => _observer.stream;
_observer.stream; final StreamController<EditorTransactionValue> _observer =
final StreamController<(TransactionTime, Transaction)> _observer =
StreamController.broadcast(sync: true); StreamController.broadcast(sync: true);
/// Store the toggled format style, like bold, italic, etc. /// Store the toggled format style, like bold, italic, etc.
@ -337,14 +347,14 @@ class EditorState {
} else { } else {
// broadcast to other users here, before applying the transaction // broadcast to other users here, before applying the transaction
if (!_observer.isClosed) { if (!_observer.isClosed) {
_observer.add((TransactionTime.before, transaction)); _observer.add((TransactionTime.before, transaction, options));
} }
_applyTransactionInLocal(transaction); _applyTransactionInLocal(transaction);
// broadcast to other users here, after applying the transaction // broadcast to other users here, after applying the transaction
if (!_observer.isClosed) { if (!_observer.isClosed) {
_observer.add((TransactionTime.after, transaction)); _observer.add((TransactionTime.after, transaction, options));
} }
_recordRedoOrUndo(options, transaction, skipHistoryDebounce); _recordRedoOrUndo(options, transaction, skipHistoryDebounce);

View file

@ -1,8 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/widgets.dart';
const _emptyCounters = Counters(); const _emptyCounters = Counters();
@ -99,7 +98,7 @@ class WordCountService with ChangeNotifier {
/// ///
bool isRunning = false; bool isRunning = false;
StreamSubscription<(TransactionTime, Transaction)>? _streamSubscription; StreamSubscription<EditorTransactionValue>? _streamSubscription;
/// This method can be used to get the word and character /// This method can be used to get the word and character
/// count of the [Document] of the [EditorState]. /// count of the [Document] of the [EditorState].
@ -221,9 +220,9 @@ class WordCountService with ChangeNotifier {
return Counters(wordCount: wordCount, charCount: charCount); return Counters(wordCount: wordCount, charCount: charCount);
} }
void _onDocUpdate((TransactionTime time, Transaction t) event) { void _onDocUpdate(EditorTransactionValue value) {
if (debounceDuration.inMilliseconds == 0) { if (debounceDuration.inMilliseconds == 0) {
return _recountOnTransactionUpdate(event.$1); return _recountOnTransactionUpdate(value.$1);
} }
if (_documentTimer?.isActive ?? false) { if (_documentTimer?.isActive ?? false) {
@ -232,7 +231,7 @@ class WordCountService with ChangeNotifier {
_documentTimer = Timer( _documentTimer = Timer(
debounceDuration, debounceDuration,
() => _recountOnTransactionUpdate(event.$1), () => _recountOnTransactionUpdate(value.$1),
); );
} }

View file

@ -28,5 +28,22 @@ void main() async {
await editorState.apply(transaction); await editorState.apply(transaction);
expect(count, 2); expect(count, 2);
}); });
test('transaction stream', () async {
final editorState = EditorState.blank(
withInitialText: false,
);
var isInMemoryUpdate = false;
editorState.transactionStream.listen((event) {
isInMemoryUpdate = event.$3.inMemoryUpdate;
});
final transaction = editorState.transaction;
transaction.insertNode([0], paragraphNode(text: 'Hello World!'));
await editorState.apply(
transaction,
options: const ApplyOptions(inMemoryUpdate: true),
);
expect(isInMemoryUpdate, true);
});
}); });
} }