Skip to content

Commit 7ed7d36

Browse files
Undo/Redo (with snapshots + one-way commands) (#1900)
No snapshot support yet. All changes are accumulated for every editable.
1 parent 7fdd33c commit 7ed7d36

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1911
-326
lines changed

super_editor/clones/quill/lib/app.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ This is a code block.
122122
/// current block. This is especially important for a code block, in which pressing
123123
/// Enter inserts a newline inside the code block - it doesn't insert a new paragraph
124124
/// below the code block.
125-
class _AlwaysTrailingParagraphReaction implements EditReaction {
125+
class _AlwaysTrailingParagraphReaction extends EditReaction {
126126
@override
127-
void react(EditContext editorContext, RequestDispatcher requestDispatcher, List<EditEvent> changeList) {
127+
void modifyContent(EditContext editorContext, RequestDispatcher requestDispatcher, List<EditEvent> changeList) {
128128
final document = editorContext.find<MutableDocument>(Editor.documentKey);
129129
final lastNode = document.nodes.lastOrNull;
130130

super_editor/clones/quill/lib/editor/editor.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class ClearSelectedStylesRequest implements EditRequest {
126126
const ClearSelectedStylesRequest();
127127
}
128128

129-
class ClearSelectedStylesCommand implements EditCommand {
129+
class ClearSelectedStylesCommand extends EditCommand {
130130
const ClearSelectedStylesCommand();
131131

132132
@override
@@ -181,7 +181,7 @@ class ClearTextAttributionsRequest implements EditRequest {
181181
int get hashCode => documentRange.hashCode;
182182
}
183183

184-
class ClearTextAttributionsCommand implements EditCommand {
184+
class ClearTextAttributionsCommand extends EditCommand {
185185
const ClearTextAttributionsCommand(this.documentRange);
186186

187187
final DocumentRange documentRange;
@@ -288,7 +288,7 @@ class ToggleInlineFormatRequest implements EditRequest {
288288
int get hashCode => inlineFormat.hashCode;
289289
}
290290

291-
class ToggleInlineFormatCommand implements EditCommand {
291+
class ToggleInlineFormatCommand extends EditCommand {
292292
const ToggleInlineFormatCommand(this.inlineFormat);
293293

294294
final Attribution inlineFormat;

super_editor/clones/quill/lib/editor/toolbar.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
185185
]);
186186

187187
// Clear the field and hide the URL bar
188-
_urlController!.clear();
188+
_urlController!.clearTextAndSelection();
189189
_urlFocusNode.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
190190
_linkPopoverController.close();
191191
setState(() {});
@@ -220,7 +220,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
220220
}
221221

222222
// Clear the field and hide the URL bar
223-
_imageController!.clear();
223+
_imageController!.clearTextAndSelection();
224224
_imageFocusNode.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
225225
_imagePopoverController.close();
226226
setState(() {});
@@ -535,7 +535,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
535535
onPressed: () {
536536
setState(() {
537537
_urlFocusNode.unfocus();
538-
_urlController!.clear();
538+
_urlController!.clearTextAndSelection();
539539
});
540540
},
541541
),
@@ -605,7 +605,7 @@ class _FormattingToolbarState extends State<FormattingToolbar> {
605605
onPressed: () {
606606
setState(() {
607607
_imageFocusNode.unfocus();
608-
_imageController!.clear();
608+
_imageController!.clearTextAndSelection();
609609
});
610610
},
611611
),

super_editor/example/lib/demos/in_the_lab/feature_action_tags.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ class ConvertSelectedTextNodeRequest implements EditRequest {
298298
int get hashCode => newType.hashCode;
299299
}
300300

301-
class ConvertSelectedTextNodeCommand implements EditCommand {
301+
class ConvertSelectedTextNodeCommand extends EditCommand {
302302
ConvertSelectedTextNodeCommand(this.newType);
303303

304304
final TextNodeType newType;

super_editor/example/lib/main_super_editor.dart

Lines changed: 170 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:example/demos/example_editor/_example_document.dart';
2-
import 'package:example/demos/example_editor/example_editor.dart';
32
import 'package:flutter/material.dart';
43
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
54
import 'package:flutter_localizations/flutter_localizations.dart';
@@ -31,7 +30,7 @@ void main() {
3130
runApp(
3231
MaterialApp(
3332
home: Scaffold(
34-
body: ExampleEditor(),
33+
body: _Demo(),
3534
),
3635
supportedLocales: const [
3736
Locale('en', ''),
@@ -48,8 +47,164 @@ void main() {
4847
);
4948
}
5049

50+
class _Demo extends StatefulWidget {
51+
const _Demo();
52+
53+
@override
54+
State<_Demo> createState() => _DemoState();
55+
}
56+
57+
class _DemoState extends State<_Demo> {
58+
late MutableDocument _document;
59+
late MutableDocumentComposer _composer;
60+
late Editor _docEditor;
61+
62+
@override
63+
void initState() {
64+
super.initState();
65+
_document = createInitialDocument();
66+
_composer = MutableDocumentComposer();
67+
_docEditor = createDefaultDocumentEditor(document: _document, composer: _composer);
68+
}
69+
70+
@override
71+
void dispose() {
72+
_composer.dispose();
73+
super.dispose();
74+
}
75+
76+
@override
77+
Widget build(BuildContext context) {
78+
return Row(
79+
children: [
80+
Expanded(
81+
child: _StandardEditor(
82+
document: _document,
83+
composer: _composer,
84+
editor: _docEditor,
85+
),
86+
),
87+
_buildToolbar(),
88+
],
89+
);
90+
}
91+
92+
Widget _buildToolbar() {
93+
return Row(
94+
mainAxisSize: MainAxisSize.min,
95+
children: [
96+
_EditorHistoryPanel(editor: _docEditor),
97+
Container(
98+
width: 24,
99+
height: double.infinity,
100+
color: const Color(0xFF2F2F2F),
101+
child: Column(),
102+
),
103+
],
104+
);
105+
}
106+
}
107+
108+
class _EditorHistoryPanel extends StatefulWidget {
109+
const _EditorHistoryPanel({
110+
required this.editor,
111+
});
112+
113+
final Editor editor;
114+
115+
@override
116+
State<_EditorHistoryPanel> createState() => _EditorHistoryPanelState();
117+
}
118+
119+
class _EditorHistoryPanelState extends State<_EditorHistoryPanel> {
120+
final _scrollController = ScrollController();
121+
late EditListener _editListener;
122+
123+
@override
124+
void initState() {
125+
super.initState();
126+
127+
_editListener = FunctionalEditListener(_onEditorChange);
128+
widget.editor.addListener(_editListener);
129+
}
130+
131+
@override
132+
void didUpdateWidget(_EditorHistoryPanel oldWidget) {
133+
super.didUpdateWidget(oldWidget);
134+
135+
if (widget.editor != oldWidget.editor) {
136+
oldWidget.editor.removeListener(_editListener);
137+
widget.editor.addListener(_editListener);
138+
}
139+
}
140+
141+
@override
142+
void dispose() {
143+
_scrollController.dispose();
144+
widget.editor.removeListener(_editListener);
145+
super.dispose();
146+
}
147+
148+
void _onEditorChange(changes) {
149+
setState(() {
150+
// Build the latest list of changes.
151+
});
152+
153+
// Always scroll to bottom of transaction list.
154+
WidgetsBinding.instance.addPostFrameCallback((_) {
155+
_scrollController.position.jumpTo(_scrollController.position.maxScrollExtent);
156+
});
157+
}
158+
159+
@override
160+
Widget build(BuildContext context) {
161+
return Theme(
162+
data: ThemeData(
163+
brightness: Brightness.dark,
164+
),
165+
child: Container(
166+
width: 300,
167+
height: double.infinity,
168+
color: const Color(0xFF333333),
169+
child: SingleChildScrollView(
170+
controller: _scrollController,
171+
child: Padding(
172+
padding: const EdgeInsets.symmetric(vertical: 24.0),
173+
child: Column(
174+
children: [
175+
for (final history in widget.editor.history)
176+
ListTile(
177+
title: Text("${history.changes.length} changes"),
178+
titleTextStyle: TextStyle(
179+
fontSize: 16,
180+
),
181+
subtitle: Text("${history.changes.map((event) => event.describe()).join("\n")}"),
182+
subtitleTextStyle: TextStyle(
183+
color: Colors.white.withOpacity(0.5),
184+
fontSize: 10,
185+
height: 1.4,
186+
),
187+
visualDensity: VisualDensity.compact,
188+
),
189+
],
190+
),
191+
),
192+
),
193+
),
194+
);
195+
}
196+
}
197+
51198
class _StandardEditor extends StatefulWidget {
52-
const _StandardEditor();
199+
const _StandardEditor({
200+
required this.document,
201+
required this.composer,
202+
required this.editor,
203+
});
204+
205+
final MutableDocument document;
206+
final MutableDocumentComposer composer;
207+
final Editor editor;
53208

54209
@override
55210
State<_StandardEditor> createState() => _StandardEditorState();
@@ -58,20 +213,13 @@ class _StandardEditor extends StatefulWidget {
58213
class _StandardEditorState extends State<_StandardEditor> {
59214
final GlobalKey _docLayoutKey = GlobalKey();
60215

61-
late MutableDocument _doc;
62-
late MutableDocumentComposer _composer;
63-
late Editor _docEditor;
64-
65216
late FocusNode _editorFocusNode;
66217

67218
late ScrollController _scrollController;
68219

69220
@override
70221
void initState() {
71222
super.initState();
72-
_doc = createInitialDocument();
73-
_composer = MutableDocumentComposer();
74-
_docEditor = createDefaultDocumentEditor(document: _doc, composer: _composer);
75223
_editorFocusNode = FocusNode();
76224
_scrollController = ScrollController();
77225
}
@@ -80,19 +228,27 @@ class _StandardEditorState extends State<_StandardEditor> {
80228
void dispose() {
81229
_scrollController.dispose();
82230
_editorFocusNode.dispose();
83-
_composer.dispose();
84231
super.dispose();
85232
}
86233

87234
@override
88235
Widget build(BuildContext context) {
89236
return SuperEditor(
90-
editor: _docEditor,
91-
document: _doc,
92-
composer: _composer,
237+
editor: widget.editor,
238+
document: widget.document,
239+
composer: widget.composer,
93240
focusNode: _editorFocusNode,
94241
scrollController: _scrollController,
95242
documentLayoutKey: _docLayoutKey,
243+
stylesheet: defaultStylesheet.copyWith(
244+
addRulesAfter: [
245+
taskStyles,
246+
],
247+
),
248+
componentBuilders: [
249+
TaskComponentBuilder(widget.editor),
250+
...defaultComponentBuilders,
251+
],
96252
);
97253
}
98254
}

super_editor/example_docs/lib/toolbar.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'dart:math';
22

3-
import 'package:example_docs/editor.dart';
43
import 'package:example_docs/infrastructure/icon_selector.dart';
54
import 'package:example_docs/infrastructure/color_selector.dart';
65
import 'package:example_docs/infrastructure/text_item_selector.dart';

0 commit comments

Comments
 (0)