Skip to content

Commit 4bbd126

Browse files
committed
WIP refactoring for document updating by NodePath. HR reaction updated, so it works within CompositeNode
1 parent bddc428 commit 4bbd126

8 files changed

Lines changed: 249 additions & 69 deletions

File tree

super_editor/example/lib/main_components_in_components.dart

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -321,14 +321,7 @@ class _BannerNode extends CompositeNode {
321321
throw UnimplementedError('Copy more than one child node is not yet implemented');
322322
}
323323

324-
@override
325-
CompositeNode copyAndReplaceLeaf({required CompositeNodePosition position, required DocumentNode newLeaf}) {
326-
return internalCopyAndReplaceLeaf(
327-
position: position,
328-
newLeaf: newLeaf,
329-
compositeNodeBuilder: (old, newChildren) {
330-
return _BannerNode(id: old.id, metadata: metadata, children: newChildren);
331-
},
332-
);
324+
CompositeNode copyWithChildren(List<DocumentNode> newChildren) {
325+
return _BannerNode(id: id, metadata: metadata, children: newChildren);
333326
}
334327
}

super_editor/lib/src/core/document.dart

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ abstract class Document implements Iterable<DocumentNode> {
5353
/// has the given [nodeId], or `-1` if the node does not exist.
5454
int getNodeIndexById(String nodeId);
5555

56+
/// Returns the index of the `DocumentNode` in parent node. If this
57+
/// is a root node, then returns the index in this `Document`.
58+
int getNodeIndexInParent(NodePath path);
59+
5660
/// Returns the [DocumentNode] that appears immediately before the
5761
/// given [node] in this [Document], or null if the given [node]
5862
/// is the first node, or the given [node] does not exist in this
@@ -147,9 +151,9 @@ class NodePath {
147151

148152
bool get isRoot => _path.length == 1;
149153

150-
String get rootId => _path.first;
154+
String get rootNodeId => _path.first;
151155

152-
String get leafId => _path.last;
156+
String get leafNodeId => _path.last;
153157

154158
NodePath? toLeafParentPath() {
155159
if (length > 1) {
@@ -160,7 +164,7 @@ class NodePath {
160164

161165
@override
162166
String toString() {
163-
return isRoot ? rootId : _path.join('.');
167+
return isRoot ? rootNodeId : _path.join('.');
164168
}
165169

166170
@override
@@ -184,6 +188,10 @@ class NodePath {
184188

185189
@override
186190
int get hashCode => _path.hashCode;
191+
192+
NodePath append(String childId) {
193+
return NodePath([..._path, childId]);
194+
}
187195
}
188196

189197
/// Listener that's notified when a document changes.
@@ -225,9 +233,9 @@ abstract class NodeDocumentChange extends DocumentChange {
225233
const NodeDocumentChange();
226234

227235
@Deprecated('Use rootNodeId instead or nodePath')
228-
String get nodeId => nodePath.rootId;
236+
String get nodeId => nodePath.rootNodeId;
229237

230-
String get rootNodeId => nodePath.rootId;
238+
String get rootNodeId => nodePath.rootNodeId;
231239

232240
NodePath get nodePath;
233241
}
@@ -380,7 +388,7 @@ class DocumentPosition {
380388
for (var i = nodePath.length - 1; i > 0; i -= 1) {
381389
resultPosition = CompositeNodePosition(nodePath.getAt(i), resultPosition);
382390
}
383-
return DocumentPosition(nodeId: nodePath.rootId, nodePosition: resultPosition);
391+
return DocumentPosition(nodeId: nodePath.rootNodeId, nodePosition: resultPosition);
384392
}
385393

386394
/// ID of a [DocumentNode] within a [Document].

super_editor/lib/src/core/editor.dart

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,7 +1210,7 @@ class MutableDocument with Iterable<DocumentNode> implements Document, Editable
12101210

12111211
@override
12121212
DocumentNode? getNodeAtPath(NodePath path) {
1213-
var node = getNodeById(path.rootId);
1213+
var node = getNodeById(path.rootNodeId);
12141214
for (var i = 1; i < path.length; i += 1) {
12151215
final childId = path.getAt(i);
12161216
assert(
@@ -1222,14 +1222,27 @@ class MutableDocument with Iterable<DocumentNode> implements Document, Editable
12221222
return node;
12231223
}
12241224

1225+
@override
1226+
int getNodeIndexInParent(NodePath path) {
1227+
final parentPath = path.toLeafParentPath();
1228+
if (parentPath == null) {
1229+
return getNodeIndexById(path.rootNodeId);
1230+
}
1231+
final parent = getNodeAtPath(parentPath) as CompositeNode;
1232+
return parent.getChildIndexByNodeId(path.leafNodeId);
1233+
}
1234+
12251235
@override
12261236
DocumentNode? getLeafNode(DocumentPosition position) => getNodeAtPath(NodePath.withDocumentPosition(position));
12271237

12281238
@override
12291239
CompositeNode? getLeafNodeParent(DocumentPosition position) {
12301240
final path = NodePath.withDocumentPosition(position).toLeafParentPath();
1231-
final node = path != null ? getNodeAtPath(path) : null;
1232-
assert(node is CompositeNode, 'Unexpected Leaf Parent Node ${node.runtimeType}. CompositeNode expected');
1241+
if (path == null) {
1242+
return null;
1243+
}
1244+
final node = getNodeAtPath(path);
1245+
assert(node is CompositeNode, 'Unexpected Leaf Parent Node "${node.runtimeType}". CompositeNode expected');
12331246
return node as CompositeNode;
12341247
}
12351248

@@ -1383,20 +1396,56 @@ class MutableDocument with Iterable<DocumentNode> implements Document, Editable
13831396
}
13841397
}
13851398

1399+
void replaceNodeByPath(NodePath path, DocumentNode newNode) {
1400+
var replacement = newNode;
1401+
if (!path.isRoot) {
1402+
final node = getNodeById(path.rootNodeId);
1403+
assert(node is CompositeNode);
1404+
replacement = (node as CompositeNode).copyAndReplaceLeafChildren(
1405+
nodePath: path.toLeafParentPath()!,
1406+
childrenReplacer: (CompositeNode leafNode, List<DocumentNode> children) {
1407+
return children.map((c) => c.id == path.leafNodeId ? newNode : c).toList();
1408+
},
1409+
);
1410+
}
1411+
return replaceNodeById(path.rootNodeId, replacement);
1412+
}
1413+
13861414
/// Replaces leaf node based on [position]. All CompositeNodes are copied and
13871415
/// given newNode is replaced by its id
13881416
void replaceLeafNodeByPosition(DocumentPosition position, DocumentNode newNode) {
1389-
final nodePosition = position.nodePosition;
1390-
var replacement = newNode;
1391-
if (nodePosition is CompositeNodePosition) {
1392-
final node = getNodeById(position.nodeId);
1417+
return replaceNodeByPath(NodePath.withDocumentPosition(position), newNode);
1418+
}
1419+
1420+
/// Inserts [newNode] immediately after the given [existingNode].
1421+
void insertNodeAfterPath({
1422+
required NodePath existingNodePath,
1423+
required DocumentNode newNode,
1424+
}) {
1425+
if (existingNodePath.isRoot) {
1426+
return insertNodeAfter(
1427+
existingNodeId: existingNodePath.rootNodeId,
1428+
newNode: newNode,
1429+
);
1430+
} else {
1431+
final leafParentPath = existingNodePath.toLeafParentPath()!;
1432+
final leafParent = getNodeAtPath(leafParentPath) as CompositeNode;
1433+
final insertIndex = leafParent.getChildIndexByNodeId(existingNodePath.leafNodeId);
1434+
1435+
final node = getNodeById(existingNodePath.rootNodeId);
13931436
assert(node is CompositeNode);
1394-
replacement = (node as CompositeNode).copyAndReplaceLeaf(
1395-
position: nodePosition,
1396-
newLeaf: newNode,
1437+
1438+
final replacement = (node as CompositeNode).copyAndReplaceLeafChildren(
1439+
nodePath: leafParentPath,
1440+
childrenReplacer: (CompositeNode leafNode, List<DocumentNode> children) {
1441+
final newChildren = List.of(children);
1442+
newChildren.insert(insertIndex, newNode);
1443+
return newChildren;
1444+
},
13971445
);
1446+
1447+
replaceNodeById(existingNodePath.rootNodeId, replacement);
13981448
}
1399-
return replaceNodeById(position.nodeId, replacement);
14001449
}
14011450

14021451
/// Returns [true] if the content of the [other] [Document] is equivalent

super_editor/lib/src/default_editor/common_editor_operations.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -930,10 +930,6 @@ class CommonEditorOperations {
930930
return true;
931931
}
932932

933-
// TODO: Implement support for CompositeNode
934-
if (composer.selection!.extent.nodePosition is UpstreamDownstreamNodePosition) {
935-
final nodePosition = composer.selection!.extent.nodePosition as UpstreamDownstreamNodePosition;
936-
if (nodePosition.affinity == TextAffinity.upstream) {
937933
final extentLeafPosition = composer.selection!.extent.leafNodePosition;
938934
if (extentLeafPosition is UpstreamDownstreamNodePosition) {
939935
if (extentLeafPosition.affinity == TextAffinity.upstream) {

super_editor/lib/src/default_editor/default_document_editor.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,11 @@ final defaultRequestHandlers = List.unmodifiable(<EditRequestHandler>[
168168
? InsertNodeAtIndexCommand(nodeIndex: editor.document.length, newNode: request.newNode)
169169
: null,
170170
(editor, request) => request is InsertNodeAtIndexRequest
171-
? InsertNodeAtIndexCommand(nodeIndex: request.nodeIndex, newNode: request.newNode)
171+
? InsertNodeAtIndexCommand(
172+
parentPath: request.parentPath,
173+
nodeIndex: request.nodeIndex,
174+
newNode: request.newNode,
175+
)
172176
: null,
173177
(editor, request) => request is InsertNodeBeforeNodeRequest
174178
? InsertNodeBeforeNodeCommand(existingNodeId: request.existingNodeId, newNode: request.newNode)

super_editor/lib/src/default_editor/default_document_editor_reactions.dart

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:super_editor/src/core/editor.dart';
1313
import 'package:super_editor/src/default_editor/attributions.dart';
1414
import 'package:super_editor/src/default_editor/horizontal_rule.dart';
1515
import 'package:super_editor/src/default_editor/image.dart';
16+
import 'package:super_editor/src/default_editor/layout_single_column/composite_nodes.dart';
1617
import 'package:super_editor/src/default_editor/list_items.dart';
1718
import 'package:super_editor/src/default_editor/paragraph.dart';
1819
import 'package:super_editor/src/default_editor/tasks.dart';
@@ -314,7 +315,8 @@ class HorizontalRuleConversionReaction extends EditReaction {
314315
}
315316

316317
final textInsertionEvent = edit.change as TextInsertionEvent;
317-
final paragraph = document.getNodeById(textInsertionEvent.nodeId) as TextNode;
318+
final paragraphPath = textInsertionEvent.nodePath;
319+
final paragraph = document.getNodeAtPath(paragraphPath) as TextNode;
318320
final match = _hrPattern.firstMatch(paragraph.text.toPlainText())?.group(0);
319321
if (match == null) {
320322
return;
@@ -327,20 +329,21 @@ class HorizontalRuleConversionReaction extends EditReaction {
327329
requestDispatcher.execute([
328330
DeleteContentRequest(
329331
documentRange: DocumentRange(
330-
start: DocumentPosition(nodeId: paragraph.id, nodePosition: const TextNodePosition(offset: 0)),
331-
end: DocumentPosition(nodeId: paragraph.id, nodePosition: TextNodePosition(offset: match.length)),
332+
start: DocumentPosition.withPath(nodePath: paragraphPath, nodePosition: const TextNodePosition(offset: 0)),
333+
end: DocumentPosition.withPath(nodePath: paragraphPath, nodePosition: TextNodePosition(offset: match.length)),
332334
),
333335
),
334336
InsertNodeAtIndexRequest(
335-
nodeIndex: document.getNodeIndexById(paragraph.id),
337+
parentPath: paragraphPath.toLeafParentPath(),
338+
nodeIndex: document.getNodeIndexInParent(paragraphPath),
336339
newNode: HorizontalRuleNode(
337340
id: Editor.createNodeId(),
338341
),
339342
),
340343
ChangeSelectionRequest(
341344
DocumentSelection.collapsed(
342-
position: DocumentPosition(
343-
nodeId: paragraph.id,
345+
position: DocumentPosition.withPath(
346+
nodePath: paragraphPath,
344347
nodePosition: const TextNodePosition(offset: 0),
345348
),
346349
),
@@ -573,7 +576,7 @@ class LinkifyReaction extends EditReaction {
573576
final edit = edits[i];
574577
if (edit is DocumentEdit) {
575578
final change = edit.change;
576-
if (change is TextInsertionEvent && change.text.toPlainText() == " " && change.nodePath.isRoot) {
579+
if (change is TextInsertionEvent && change.text.toPlainText() == " ") {
577580
// Every space insertion might appear after a URL.
578581
linkifyCandidate = change;
579582
didInsertSpace = true;
@@ -597,14 +600,14 @@ class LinkifyReaction extends EditReaction {
597600
}
598601

599602
final caretPosition = selection.extent;
600-
if (caretPosition.nodeId != linkifyCandidate.nodeId) {
603+
if (caretPosition.leafNodeId != linkifyCandidate.nodePath.leafNodeId) {
601604
// The selection moved to some other node. Don't linkify.
602605
linkifyCandidate = null;
603606
continue;
604607
}
605608

606609
// +1 for the inserted space
607-
if ((caretPosition.nodePosition as TextNodePosition).offset != linkifyCandidate.offset + 1) {
610+
if ((caretPosition.leafNodePosition as TextNodePosition).offset != linkifyCandidate.offset + 1) {
608611
// The caret isn't sitting directly after the space. Whatever
609612
// these events represent, it doesn't represent the user typing
610613
// a URL and then press SPACE. Don't linkify.
@@ -614,7 +617,7 @@ class LinkifyReaction extends EditReaction {
614617

615618
// The caret sits directly after an inserted space. Get the word before
616619
// the space from the document, and linkify, if it fits a schema.
617-
final textNode = document.getNodeById(linkifyCandidate.nodeId) as TextNode;
620+
final textNode = document.getNodeAtPath(linkifyCandidate.nodePath) as TextNode;
618621
_extractUpstreamWordAndLinkify(textNode.text, linkifyCandidate.offset);
619622
} else if ((edit is SubmitParagraphIntention && edit.isStart) ||
620623
(edit is SplitParagraphIntention && edit.isStart) ||
@@ -631,7 +634,7 @@ class LinkifyReaction extends EditReaction {
631634

632635
final nextEdit = edits[i + 1];
633636
if (nextEdit is DocumentEdit && nextEdit.change is NodeChangeEvent) {
634-
final editedNode = document.getNodeById((nextEdit.change as NodeChangeEvent).nodeId);
637+
final editedNode = document.getNodeAtPath((nextEdit.change as NodeChangeEvent).nodePath);
635638
if (editedNode is TextNode) {
636639
_extractUpstreamWordAndLinkify(editedNode.text, editedNode.text.length);
637640
}
@@ -1117,11 +1120,11 @@ class EditInspector {
11171120
return false;
11181121
}
11191122

1120-
if (lastSelectionChangeEvent.newSelection!.extent.nodeId != textInsertionEvent.nodeId) {
1123+
if (lastSelectionChangeEvent.newSelection!.extent.leafNodeId != textInsertionEvent.nodePath.leafNodeId) {
11211124
return false;
11221125
}
11231126

1124-
final editedNode = document.getNodeById(textInsertionEvent.nodeId)!;
1127+
final editedNode = document.getNodeAtPath(textInsertionEvent.nodePath)!;
11251128
if (editedNode is! TextNode) {
11261129
return false;
11271130
}

0 commit comments

Comments
 (0)