feat(doc): transaction and deltas

This commit is contained in:
Vincent Chan 2022-08-12 13:20:07 +08:00
parent a71d9401d5
commit f2c624778e
2 changed files with 53 additions and 2 deletions

View File

@ -256,7 +256,12 @@ TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
return result;
}
// basically copy from: https://github.com/quilljs/delta
/// Deltas are a simple, yet expressive format that can be used to describe contents and changes.
/// The format is JSON based, and is human readable, yet easily parsible by machines.
/// Deltas can describe any rich text document, includes all text and formatting information, without the ambiguity and complexity of HTML.
///
/// Basically borrowed from: https://github.com/quilljs/delta
class Delta extends Iterable<TextOperation> {
final List<TextOperation> _operations;
String? _rawString;
@ -316,6 +321,9 @@ class Delta extends Iterable<TextOperation> {
_operations.add(textOp);
}
/// The slice() method does not change the original string.
/// The start and end parameters specifies the part of the string to extract.
/// The end position is optional.
Delta slice(int start, [int? end]) {
final result = Delta();
final iterator = _OpIterator(_operations);
@ -336,19 +344,29 @@ class Delta extends Iterable<TextOperation> {
return result;
}
/// Insert operations have an `insert` key defined.
/// A String value represents inserting text.
void insert(String content, [Attributes? attributes]) =>
add(TextInsert(content, attributes));
/// Retain operations have a Number `retain` key defined representing the number of characters to keep (other libraries might use the name keep or skip).
/// An optional `attributes` key can be defined with an Object to describe formatting changes to the character range.
/// A value of `null` in the `attributes` Object represents removal of that key.
///
/// *Note: It is not necessary to retain the last characters of a document as this is implied.*
void retain(int length, [Attributes? attributes]) =>
add(TextRetain(length, attributes));
/// Delete operations have a Number `delete` key defined representing the number of characters to delete.
void delete(int length) => add(TextDelete(length));
/// The length of the string fo the [Delta].
int get length {
return _operations.fold(
0, (previousValue, element) => previousValue + element.length);
}
/// Returns a Delta that is equivalent to applying the operations of own Delta, followed by another Delta.
Delta compose(Delta other) {
final thisIter = _OpIterator(_operations);
final otherIter = _OpIterator(other._operations);
@ -412,6 +430,7 @@ class Delta extends Iterable<TextOperation> {
return delta..chop();
}
/// This method joins two Delta together.
Delta operator +(Delta other) {
var ops = [..._operations];
if (other._operations.isNotEmpty) {
@ -445,6 +464,7 @@ class Delta extends Iterable<TextOperation> {
return hashList(_operations);
}
/// Returned an inverted delta that has the opposite effect of against a base document delta.
Delta invert(Delta base) {
final inverted = Delta();
_operations.fold(0, (int previousValue, op) {
@ -475,6 +495,13 @@ class Delta extends Iterable<TextOperation> {
return _operations.map((e) => e.toJson()).toList();
}
/// This method will return the position of the previous rune.
///
/// Since the encoding of the [String] in Dart is UTF-16.
/// If you want to find the previous character of a position,
/// you can' just use the `position - 1` simply.
///
/// This method can help you to compute the position of the previous character.
int prevRunePosition(int pos) {
if (pos == 0) {
return pos - 1;
@ -485,6 +512,13 @@ class Delta extends Iterable<TextOperation> {
return _runeIndexes![pos - 1];
}
/// This method will return the position of the next rune.
///
/// Since the encoding of the [String] in Dart is UTF-16.
/// If you want to find the previous character of a position,
/// you can' just use the `position + 1` simply.
///
/// This method can help you to compute the position of the next character.
int nextRunePosition(int pos) {
final stringContent = toRawString();
if (pos >= stringContent.length - 1) {

View File

@ -14,7 +14,6 @@ import 'package:flowy_editor/src/operation/transaction.dart';
/// A [TransactionBuilder] is used to build the transaction from the state.
/// It will save make a snapshot of the cursor selection state automatically.
/// The cursor can be resorted if the transaction is undo.
class TransactionBuilder {
final List<Operation> operations = [];
EditorState state;
@ -29,15 +28,18 @@ class TransactionBuilder {
state.apply(transaction);
}
/// Insert the nodes at the position of path.
insertNode(Path path, Node node) {
insertNodes(path, [node]);
}
/// Insert a sequence of nodes at the position of path.
insertNodes(Path path, List<Node> nodes) {
beforeSelection = state.cursorSelection;
add(InsertOperation(path, nodes));
}
/// Update the attributes of nodes.
updateNode(Node node, Attributes attributes) {
beforeSelection = state.cursorSelection;
@ -49,6 +51,7 @@ class TransactionBuilder {
));
}
/// Delete a node in the document.
deleteNode(Node node) {
deleteNodesAtPath(node.path);
}
@ -57,6 +60,9 @@ class TransactionBuilder {
nodes.forEach(deleteNode);
}
/// Delete a sequence of nodes at the path of the document.
/// The length specific the length of the following nodes to delete(
/// including the start one).
deleteNodesAtPath(Path path, [int length = 1]) {
if (path.isEmpty) {
return;
@ -106,6 +112,9 @@ class TransactionBuilder {
);
}
/// Insert content at a specified index.
/// Optionally, you may specify formatting attributes that are applied to the inserted string.
/// By default, the formatting attributes before the insert position will be used.
insertText(TextNode node, int index, String content,
[Attributes? attributes]) {
var newAttributes = attributes;
@ -126,6 +135,7 @@ class TransactionBuilder {
Position(path: node.path, offset: index + content.length));
}
/// Assign formatting attributes to a range of text.
formatText(TextNode node, int index, int length, Attributes attributes) {
textEdit(
node,
@ -135,6 +145,7 @@ class TransactionBuilder {
afterSelection = beforeSelection;
}
/// Delete length characters starting from index.
deleteText(TextNode node, int index, int length) {
textEdit(
node,
@ -169,6 +180,11 @@ class TransactionBuilder {
);
}
/// Add an operation to the transaction.
/// This method will merge operations if they are both TextEdits.
///
/// Also, this method will transform the path of the operations
/// to avoid conflicts.
add(Operation op) {
final Operation? last = operations.isEmpty ? null : operations.last;
if (last != null) {
@ -190,6 +206,7 @@ class TransactionBuilder {
operations.add(op);
}
/// Generate a immutable [Transaction] to apply or transmit.
Transaction finish() {
return Transaction(
operations: UnmodifiableListView(operations),