Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ class DeltaTextInputService extends TextInputService with DeltaTextInputClient {
}
}
}

//new callback on flutter 3.44
@override
bool onFocusReceived() => attached;
}

const String _whitespace = ' ';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,16 @@ CommandShortcutEventHandler _moveCursorToLeftWordCommandHandler =
selectionRange: SelectionRange.word,
);
if (startOfWord == null) {
editorState.moveCursorForward(SelectionMoveRange.word);
return KeyEventResult.handled;
}

// if 0, it means we are at the beginning of the line, move to the previous node
if (selection.end.offset <= 0) {
editorState.moveCursorForward(SelectionMoveRange.word);
return KeyEventResult.handled;
}

final selectedWord = delta.toPlainText().substring(
startOfWord.offset,
selection.end.offset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ CommandShortcutEventHandler _moveCursorToRightWordCommandHandler =
selectionRange: SelectionRange.word,
);
if (endOfWord == null) {
editorState.moveCursorBackward(SelectionMoveRange.line);
return KeyEventResult.handled;
}
// if the end of the word is less than or equal to 0, it means we are at the end of the line, move to the next node
if (endOfWord.offset <= 0) {
editorState.moveCursorBackward(SelectionMoveRange.line);
return KeyEventResult.handled;
}
final selectedLine = delta.toPlainText();
Expand Down
18 changes: 11 additions & 7 deletions lib/src/extensions/node_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,20 @@ extension NodeExtensions on Node {
}
}

var next = this.next;
while (next != null) {
final nextDescendentMatch = next._thisOrDescendantMatching(test);
if (nextDescendentMatch != null) {
return nextDescendentMatch;
Node? current = this;
while (current != null) {
var next = current.next;
while (next != null) {
final nextDescendentMatch = next._thisOrDescendantMatching(test);
if (nextDescendentMatch != null) {
return nextDescendentMatch;
}
next = next.next;
}
next = next.next;
current = current.parent;
}

return parent?.next?._thisOrDescendantMatching(test);
return null;
}

Node? _thisOrDescendantMatching(bool Function(Node element) test) {
Expand Down
120 changes: 92 additions & 28 deletions lib/src/plugins/quill_delta/quill_delta_encoder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,56 @@ class QuillDeltaEncoder extends Converter<Delta, Document> {
node = _applyHeadingStyleIfNeeded(node, attributes);
node = _applyBlockquoteIfNeeded(node, attributes);
_applyIndentIfNeeded(node, attributes);
_applyAlignIfNeeded(node, attributes);
}
if (!_isListItem(attributes)) {
nestedLists.clear();
}
if (_isIndentBulletedList(attributes)) {
final level = _indentLevel(attributes);
final path = [
...nestedLists[level - 1]!.last.path,
nestedLists[level]!.length - 1,
];
document.insert(path, [node]);
final inserted = _tryInsertNestedListNode(
document: document,
node: node,
level: level,
);
if (!inserted) {
document.insert([index++], [node]);
}
} else {
document.insert([index++], [node]);
}
node = paragraphNode();
} else {
final texts = op.text.split('\n');
if (texts.length > 1) {
node.delta?.insert(texts[0]);
// First segment appends to current node, then commit it
if (texts[0].isNotEmpty) {
_applyStyle(node, texts[0], attributes);
}
document.insert([index++], [node]);
node = paragraphNode(delta: Delta()..insert(texts[1]));

// Middle segments: each becomes a full committed paragraph
for (int i = 1; i < texts.length - 1; i++) {
node = paragraphNode();
if (texts[i].isNotEmpty) {
_applyStyle(node, texts[i], attributes);
}
document.insert([index++], [node]);
}

// Last segment starts a new open node (accumulates more ops)
node = paragraphNode();
if (texts.last.isNotEmpty) {
_applyStyle(node, texts.last, attributes);
if (attributes != null) {
_applyAlignIfNeeded(node, attributes);
}
}
} else {
_applyStyle(node, op.text, attributes);
if (attributes != null) {
_applyAlignIfNeeded(node, attributes);
}
}
}
} else {
Expand Down Expand Up @@ -103,6 +133,13 @@ class QuillDeltaEncoder extends Converter<Delta, Document> {
});
}

void _applyAlignIfNeeded(Node node, Map<String, dynamic> attributes) {
final align = attributes['align'] as String?;
if (align != null) {
node.updateAttributes({'align': align});
}
}

void _applyIndentIfNeeded(Node node, Map<String, dynamic> attributes) {
final indent = attributes[_indent] as int?;
final list = attributes[_list] as String?;
Expand All @@ -122,9 +159,7 @@ class QuillDeltaEncoder extends Converter<Delta, Document> {
Node _applyBlockquoteIfNeeded(Node node, Map<String, dynamic> attributes) {
final blockquote = attributes[_blockquote] as bool?;
if (blockquote == true) {
return quoteNode(
delta: node.delta,
);
return quoteNode(delta: node.delta);
}

return node;
Expand All @@ -136,20 +171,15 @@ class QuillDeltaEncoder extends Converter<Delta, Document> {
return node;
}

return headingNode(
delta: node.delta,
level: header,
);
return headingNode(delta: node.delta, level: header);
}

// If the attributes contains the list style, then apply the list style to the node.
Node _applyListStyleIfNeeded(Node node, Map<String, dynamic> attributes) {
final list = attributes[_list] as String?;
switch (list) {
case _bulletedList:
final bulletedList = bulletedListNode(
delta: node.delta,
);
final bulletedList = bulletedListNode(delta: node.delta);
final indent = attributes[_indent] as int?;
if (indent != null) {
nestedLists[indent] ??= [];
Expand All @@ -162,9 +192,7 @@ class QuillDeltaEncoder extends Converter<Delta, Document> {
return bulletedList;

case _orderedList:
final numberedList = numberedListNode(
delta: node.delta,
);
final numberedList = numberedListNode(delta: node.delta);
final indent = attributes[_indent] as int?;
if (indent != null) {
nestedLists[indent] ??= [];
Expand All @@ -177,17 +205,11 @@ class QuillDeltaEncoder extends Converter<Delta, Document> {
return numberedList;

case _checkedList:
final checkedList = todoListNode(
delta: node.delta,
checked: true,
);
final checkedList = todoListNode(delta: node.delta, checked: true);
return checkedList;

case _uncheckedList:
final uncheckedList = todoListNode(
delta: node.delta,
checked: false,
);
final uncheckedList = todoListNode(delta: node.delta, checked: false);
return uncheckedList;

default:
Expand All @@ -208,6 +230,48 @@ class QuillDeltaEncoder extends Converter<Delta, Document> {
return [_bulletedList, _orderedList].contains(list) && indent != null;
}

bool _isListItem(Map<String, dynamic>? attributes) {
final list = attributes?[_list] as String?;

return [
_bulletedList,
_orderedList,
_checkedList,
_uncheckedList,
].contains(list);
}

bool _tryInsertNestedListNode({
required Document document,
required Node node,
required int level,
}) {
if (level <= 0) {
return false;
}

final parent = _findNearestParentNode(level);
if (parent == null) {
return false;
}

final path = [...parent.path, parent.children.length];
document.insert(path, [node]);

return true;
}

Node? _findNearestParentNode(int level) {
for (int parentLevel = level - 1; parentLevel >= 0; parentLevel--) {
final candidates = nestedLists[parentLevel];
if (candidates != null && candidates.isNotEmpty) {
return candidates.last;
}
}

return null;
}

bool _containsStyle(Map<String, dynamic>? attributes, String key) {
final value = attributes?[key] as bool?;

Expand Down