|
@@ -1,23 +1,34 @@
|
|
-import 'package:flowy_editor/document/node.dart';
|
|
|
|
-import 'package:flowy_editor/operation/operation.dart';
|
|
|
|
-import 'package:flowy_editor/document/attributes.dart';
|
|
|
|
|
|
+import 'dart:async';
|
|
|
|
+import 'package:flowy_editor/flowy_editor.dart';
|
|
|
|
+import 'package:flowy_editor/undo_manager.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
-import './document/state_tree.dart';
|
|
|
|
import './document/selection.dart';
|
|
import './document/selection.dart';
|
|
-import './operation/operation.dart';
|
|
|
|
-import './operation/transaction.dart';
|
|
|
|
-import './render/render_plugins.dart';
|
|
|
|
|
|
+
|
|
|
|
+class ApplyOptions {
|
|
|
|
+ /// This flag indicates that
|
|
|
|
+ /// whether the transaction should be recorded into
|
|
|
|
+ /// the undo stack.
|
|
|
|
+ final bool recordUndo;
|
|
|
|
+ const ApplyOptions({
|
|
|
|
+ this.recordUndo = true,
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
|
|
class EditorState {
|
|
class EditorState {
|
|
final StateTree document;
|
|
final StateTree document;
|
|
final RenderPlugins renderPlugins;
|
|
final RenderPlugins renderPlugins;
|
|
|
|
+ final UndoManager undoManager = UndoManager();
|
|
Selection? cursorSelection;
|
|
Selection? cursorSelection;
|
|
|
|
|
|
|
|
+ Timer? _debouncedSealHistoryItemTimer;
|
|
|
|
+
|
|
EditorState({
|
|
EditorState({
|
|
required this.document,
|
|
required this.document,
|
|
required this.renderPlugins,
|
|
required this.renderPlugins,
|
|
- });
|
|
|
|
|
|
+ }) {
|
|
|
|
+ undoManager.state = this;
|
|
|
|
+ }
|
|
|
|
|
|
/// TODO: move to a better place.
|
|
/// TODO: move to a better place.
|
|
Widget build(BuildContext context) {
|
|
Widget build(BuildContext context) {
|
|
@@ -30,14 +41,38 @@ class EditorState {
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
- void apply(Transaction transaction) {
|
|
|
|
|
|
+ apply(Transaction transaction,
|
|
|
|
+ [ApplyOptions options = const ApplyOptions()]) {
|
|
for (final op in transaction.operations) {
|
|
for (final op in transaction.operations) {
|
|
_applyOperation(op);
|
|
_applyOperation(op);
|
|
}
|
|
}
|
|
cursorSelection = transaction.afterSelection;
|
|
cursorSelection = transaction.afterSelection;
|
|
|
|
+
|
|
|
|
+ if (options.recordUndo) {
|
|
|
|
+ final undoItem = undoManager.getUndoHistoryItem();
|
|
|
|
+ undoItem.addAll(transaction.operations);
|
|
|
|
+ if (undoItem.beforeSelection == null &&
|
|
|
|
+ transaction.beforeSelection != null) {
|
|
|
|
+ undoItem.beforeSelection = transaction.beforeSelection;
|
|
|
|
+ }
|
|
|
|
+ undoItem.afterSelection = transaction.afterSelection;
|
|
|
|
+ _debouncedSealHistoryItem();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _debouncedSealHistoryItem() {
|
|
|
|
+ _debouncedSealHistoryItemTimer?.cancel();
|
|
|
|
+ _debouncedSealHistoryItemTimer =
|
|
|
|
+ Timer(const Duration(milliseconds: 1000), () {
|
|
|
|
+ if (undoManager.undoStack.isNonEmpty) {
|
|
|
|
+ debugPrint('Seal history item');
|
|
|
|
+ final last = undoManager.undoStack.last;
|
|
|
|
+ last.seal();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
|
|
- void _applyOperation(Operation op) {
|
|
|
|
|
|
+ _applyOperation(Operation op) {
|
|
if (op is InsertOperation) {
|
|
if (op is InsertOperation) {
|
|
document.insert(op.path, op.value);
|
|
document.insert(op.path, op.value);
|
|
} else if (op is UpdateOperation) {
|
|
} else if (op is UpdateOperation) {
|