diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d597df9..0907936e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.12] +- Add support for copy/cut select image and text together. + # [7.2.11] - Add affinity for localPosition. diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart index 13c98017..5f3b82fc 100644 --- a/lib/src/models/documents/document.dart +++ b/lib/src/models/documents/document.dart @@ -159,12 +159,7 @@ class Document { return (res.node as Line).collectStyle(res.offset, len); } - /// Returns all styles for each node within selection - List> collectAllIndividualStyles(int index, int len) { - final res = queryChild(index); - return (res.node as Line).collectAllIndividualStyles(res.offset, len); - } - + /// Returns all styles and Embed for each node within selection List collectAllIndividualStyleAndEmbed(int index, int len) { final res = queryChild(index); return (res.node as Line) diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index 653be164..bb06bbf9 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/line.dart @@ -395,41 +395,7 @@ class Line extends Container { } /// Returns each node segment's offset in selection - /// with its corresponding style as a list - List> collectAllIndividualStyles(int offset, int len, - {int beg = 0}) { - final local = math.min(length - offset, len); - final result = >[]; - - final data = queryChild(offset, true); - var node = data.node as Leaf?; - if (node != null) { - var pos = 0; - if (node is Text) { - pos = node.length - data.offset; - result.add(OffsetValue(beg, node.style)); - } - while (!node!.isLast && pos < local) { - node = node.next as Leaf; - if (node is Text) { - result.add(OffsetValue(pos + beg, node.style)); - pos += node.length; - } - } - } - - // TODO: add line style and parent's block style - - final remaining = len - local; - if (remaining > 0 && nextLine != null) { - final rest = - nextLine!.collectAllIndividualStyles(0, remaining, beg: local); - result.addAll(rest); - } - - return result; - } - + /// with its corresponding style or embed as a list List collectAllIndividualStylesAndEmbed(int offset, int len, {int beg = 0}) { final local = math.min(length - offset, len); @@ -439,20 +405,16 @@ class Line extends Container { var node = data.node as Leaf?; if (node != null) { var pos = 0; - if (node is Text) { + if (node is Text || node.value is Embeddable) { pos = node.length - data.offset; - result.add(OffsetValue(beg, node.style)); - } else if (node.value is Embeddable) { - pos = node.length - data.offset; - result.add(OffsetValue(beg, node.value as Embeddable)); + result.add(OffsetValue( + beg, node is Text ? node.style : node.value as Embeddable)); } while (!node!.isLast && pos < local) { node = node.next as Leaf; - if (node is Text) { - result.add(OffsetValue(pos + beg, node.style)); - pos += node.length; - } else if (node.value is Embeddable) { - result.add(OffsetValue(pos + beg, node.value as Embeddable)); + if (node is Text || node.value is Embeddable) { + result.add(OffsetValue( + pos + beg, node is Text ? node.style : node.value as Embeddable)); pos += node.length; } } @@ -460,8 +422,8 @@ class Line extends Container { final remaining = len - local; if (remaining > 0 && nextLine != null) { - final rest = - nextLine!.collectAllIndividualStylesAndEmbed(0, remaining, beg: local); + final rest = nextLine! + .collectAllIndividualStylesAndEmbed(0, remaining, beg: local); result.addAll(rest); } diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index 91932a0c..a3732449 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -39,7 +39,9 @@ class QuillController extends ChangeNotifier { /// Document managed by this controller. Document _document; + Document get document => _document; + set document(doc) { _document = doc; @@ -159,13 +161,7 @@ class QuillController extends ChangeNotifier { notifyListeners(); } - /// Returns all styles for each node within selection - List> getAllIndividualSelectionStyles() { - final styles = document.collectAllIndividualStyles( - selection.start, selection.end - selection.start); - return styles; - } - + /// Returns all styles and Embed for each node within selection List getAllIndividualSelectionStylesAndEmbed() { final stylesAndEmbed = document.collectAllIndividualStyleAndEmbed( selection.start, selection.end - selection.start); diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 5fe89f8f..03db3ac6 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; + // ignore: unnecessary_import import 'dart:typed_data'; @@ -13,7 +14,6 @@ import 'package:i18n_extension/i18n_widget.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/container.dart' as container_node; import '../models/documents/nodes/leaf.dart'; -import '../models/documents/style.dart'; import '../models/structs/offset_value.dart'; import '../models/themes/quill_dialog_theme.dart'; import '../utils/platform.dart'; @@ -369,6 +369,7 @@ class QuillEditor extends StatefulWidget { // Returns whether gesture is handled final bool Function(LongPressMoveUpdateDetails details, TextPosition Function(Offset offset))? onSingleLongTapMoveUpdate; + // Returns whether gesture is handled final bool Function( LongPressEndDetails details, TextPosition Function(Offset offset))? @@ -994,6 +995,7 @@ class RenderEditor extends RenderEditableContainerBox } double? _maxContentWidth; + set maxContentWidth(double? value) { if (_maxContentWidth == value) return; _maxContentWidth = value; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index ff6a4140..1753c87d 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -20,7 +20,6 @@ import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/nodes/line.dart'; import '../models/documents/nodes/node.dart'; -import '../models/documents/style.dart'; import '../models/structs/offset_value.dart'; import '../models/structs/vertical_spacing.dart'; import '../models/themes/quill_dialog_theme.dart'; diff --git a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart index aefa0b85..ac7c274d 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart @@ -3,17 +3,14 @@ import 'dart:math' as math; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; -import '../../../flutter_quill.dart'; import '../../models/documents/document.dart'; +import '../../models/documents/nodes/embeddable.dart'; import '../../models/documents/nodes/leaf.dart'; import '../../utils/delta.dart'; import '../editor.dart'; mixin RawEditorStateSelectionDelegateMixin on EditorState implements TextSelectionDelegate { - - bool isContainEmbed = false; - @override TextEditingValue get textEditingValue { return widget.controller.plainTextEditingValue; @@ -30,17 +27,22 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState return; } - isContainEmbed = false; - final insertedText = _adjustInsertedText(diff.inserted); + var insertedText = diff.inserted; + final containsEmbed = + insertedText.codeUnits.contains(Embed.kObjectReplacementInt); + insertedText = + containsEmbed ? _adjustInsertedText(diff.inserted) : diff.inserted; widget.controller.replaceText( diff.start, diff.deleted.length, insertedText, value.selection); - _applyPasteStyle(insertedText, diff.start); + _applyPasteStyleAndEmbed(insertedText, diff.start, containsEmbed); } - void _applyPasteStyle(String insertedText, int start) { - if (insertedText == pastePlainText && pastePlainText != '') { + void _applyPasteStyleAndEmbed( + String insertedText, int start, bool containsEmbed) { + if (insertedText == pastePlainText && pastePlainText != '' || + containsEmbed) { final pos = start; for (var i = 0; i < pasteStyleAndEmbed.length; i++) { final offset = pasteStyleAndEmbed[i].offset; @@ -57,30 +59,13 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState styleAndEmbed); } } - }else if(isContainEmbed){ - final pos = start; - for (var i = 0; i < pasteStyleAndEmbed.length; i++) { - final offset = pasteStyleAndEmbed[i].offset; - final style = pasteStyleAndEmbed[i].value; - if (style is Embeddable) { - widget.controller.replaceText(pos + offset, 0, style, null); - } - } } } String _adjustInsertedText(String text) { - // For clip from editor, it may contain image, a.k.a 65532 or '\uFFFC'. - // For clip from browser, image is directly ignore. - // Here we skip image when pasting. - if (!text.codeUnits.contains(Embed.kObjectReplacementInt)) { - return text; - } - final sb = StringBuffer(); for (var i = 0; i < text.length; i++) { if (text.codeUnitAt(i) == Embed.kObjectReplacementInt) { - isContainEmbed = true; continue; } sb.write(text[i]); diff --git a/pubspec.yaml b/pubspec.yaml index f2337b51..a9442559 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.11 +version: 7.2.12 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart index 047dfcae..415c51f6 100644 --- a/test/widgets/controller_test.dart +++ b/test/widgets/controller_test.dart @@ -101,12 +101,17 @@ void main() { expect(controller.getAllSelectionStyles(), everyElement(Style())); }); - test('getAllIndividualSelectionStyles', () { - controller.formatText(0, 2, Attribute.bold); - final result = controller.getAllIndividualSelectionStyles(); - expect(result.length, 1); + test('getAllIndividualSelectionStylesAndEmbed', () { + controller + ..formatText(0, 2, Attribute.bold) + ..replaceText(2, 2, BlockEmbed.image('/test'),null) + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), + ChangeSource.REMOTE); + final result = controller.getAllIndividualSelectionStylesAndEmbed(); + expect(result.length, 2); expect(result[0].offset, 0); expect(result[0].value, Style().put(Attribute.bold)); + expect((result[1].value as Embeddable).type, BlockEmbed.imageType); }); test('getPlainText', () {