Merge remote-tracking branch 'origin/master'

pull/2082/head
AtlasAutocode 9 months ago
commit 6978512b89
  1. 30
      CHANGELOG.md
  2. 2
      CHANGELOG_DATA.json
  3. 5
      README.md
  4. 30
      dart_quill_delta/CHANGELOG.md
  5. 2
      dart_quill_delta/pubspec.yaml
  6. 30
      flutter_quill_extensions/CHANGELOG.md
  7. 11
      flutter_quill_extensions/README.md
  8. 2
      flutter_quill_extensions/pubspec.yaml
  9. 30
      flutter_quill_test/CHANGELOG.md
  10. 2
      flutter_quill_test/pubspec.yaml
  11. 3
      lib/flutter_quill.dart
  12. 85
      lib/src/controller/quill_controller.dart
  13. 11
      lib/src/controller/quill_controller_configurations.dart
  14. 13
      lib/src/delta/delta_diff.dart
  15. 5
      lib/src/document/document.dart
  16. 45
      lib/src/document/nodes/line.dart
  17. 21
      lib/src/editor/editor.dart
  18. 13
      lib/src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service.dart
  19. 19
      lib/src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service_provider.dart
  20. 14
      lib/src/editor_toolbar_controller_shared/copy_cut_service/default_copy_cut_service.dart
  21. 494
      lib/src/toolbar/simple_toolbar.dart
  22. 2
      pubspec.yaml
  23. 190
      test/controller/controller_clipboard_test.dart
  24. 32
      test/controller/controller_test.dart

@ -4,6 +4,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 10.1.2
* Fix Multiline paste with attributes and embeds by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2074
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.1...v10.1.2
## 10.1.1
* Toolbar dividers fixes + Docs updates by @troyanskiy in https://github.com/singerdmx/flutter-quill/pull/2071
## New Contributors
* @troyanskiy made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2071
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.0...v10.1.1
## 10.1.0
* Feat: support for customize copy and cut Embeddables to Clipboard by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2067
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.10...v10.1.0
## 10.0.10
* fix: Hide selection toolbar if editor loses focus by @huandu in https://github.com/singerdmx/flutter-quill/pull/2066
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.9...v10.0.10
## 10.0.9 ## 10.0.9
* Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063 * Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063

File diff suppressed because one or more lines are too long

@ -94,8 +94,9 @@ dependencies:
```yaml ```yaml
dependencies: dependencies:
flutter_quill: flutter_quill:
git: https://github.com/singerdmx/flutter-quill.git git:
ref: v<latest-version-here> url: https://github.com/singerdmx/flutter-quill.git
ref: v<latest-version-here>
``` ```
> [!TIP] > [!TIP]

@ -4,6 +4,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 10.1.2
* Fix Multiline paste with attributes and embeds by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2074
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.1...v10.1.2
## 10.1.1
* Toolbar dividers fixes + Docs updates by @troyanskiy in https://github.com/singerdmx/flutter-quill/pull/2071
## New Contributors
* @troyanskiy made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2071
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.0...v10.1.1
## 10.1.0
* Feat: support for customize copy and cut Embeddables to Clipboard by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2067
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.10...v10.1.0
## 10.0.10
* fix: Hide selection toolbar if editor loses focus by @huandu in https://github.com/singerdmx/flutter-quill/pull/2066
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.9...v10.0.10
## 10.0.9 ## 10.0.9
* Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063 * Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063

@ -1,6 +1,6 @@
name: dart_quill_delta name: dart_quill_delta
description: A port of quill-js-delta from typescript to dart description: A port of quill-js-delta from typescript to dart
version: 10.0.9 version: 10.1.2
homepage: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ homepage: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/
repository: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ repository: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -4,6 +4,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 10.1.2
* Fix Multiline paste with attributes and embeds by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2074
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.1...v10.1.2
## 10.1.1
* Toolbar dividers fixes + Docs updates by @troyanskiy in https://github.com/singerdmx/flutter-quill/pull/2071
## New Contributors
* @troyanskiy made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2071
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.0...v10.1.1
## 10.1.0
* Feat: support for customize copy and cut Embeddables to Clipboard by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2067
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.10...v10.1.0
## 10.0.10
* fix: Hide selection toolbar if editor loses focus by @huandu in https://github.com/singerdmx/flutter-quill/pull/2066
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.9...v10.0.10
## 10.0.9 ## 10.0.9
* Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063 * Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063

@ -50,9 +50,10 @@ dependencies:
```yaml ```yaml
dependencies: dependencies:
flutter_quill_extensions: flutter_quill_extensions:
git: https://github.com/singerdmx/flutter-quill.git git:
path: flutter_quill_extensions url: https://github.com/singerdmx/flutter-quill.git
ref: v<latest-version-here> ref: v<latest-version-here>
path: flutter_quill_extensions
``` ```
## 🛠 Platform Specific Configurations ## 🛠 Platform Specific Configurations
@ -103,8 +104,8 @@ Set the `embedBuilders` and `embedToolbar` params in configurations of `QuillEdi
**Quill Toolbar**: **Quill Toolbar**:
```dart ```dart
QuillToolbar( QuillToolbar.simple(
configurations: QuillToolbarConfigurations( configurations: QuillSimpleToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.toolbarButtons(), embedButtons: FlutterQuillEmbeds.toolbarButtons(),
), ),
), ),

@ -1,6 +1,6 @@
name: flutter_quill_extensions name: flutter_quill_extensions
description: Embed extensions for flutter_quill including image, video, formula and etc. description: Embed extensions for flutter_quill including image, video, formula and etc.
version: 10.0.9 version: 10.1.2
homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/
repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -4,6 +4,36 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## 10.1.2
* Fix Multiline paste with attributes and embeds by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2074
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.1...v10.1.2
## 10.1.1
* Toolbar dividers fixes + Docs updates by @troyanskiy in https://github.com/singerdmx/flutter-quill/pull/2071
## New Contributors
* @troyanskiy made their first contribution in https://github.com/singerdmx/flutter-quill/pull/2071
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.0...v10.1.1
## 10.1.0
* Feat: support for customize copy and cut Embeddables to Clipboard by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2067
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.10...v10.1.0
## 10.0.10
* fix: Hide selection toolbar if editor loses focus by @huandu in https://github.com/singerdmx/flutter-quill/pull/2066
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.0.9...v10.0.10
## 10.0.9 ## 10.0.9
* Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063 * Fix: manual checking of directionality by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/2063

@ -1,6 +1,6 @@
name: flutter_quill_test name: flutter_quill_test
description: Test utilities for flutter_quill which includes methods to simplify interacting with the editor in test cases. description: Test utilities for flutter_quill which includes methods to simplify interacting with the editor in test cases.
version: 10.0.9 version: 10.1.2
homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/
repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -28,6 +28,9 @@ export 'src/editor/style_widgets/style_widgets.dart';
export 'src/editor/widgets/cursor.dart'; export 'src/editor/widgets/cursor.dart';
export 'src/editor/widgets/default_styles.dart'; export 'src/editor/widgets/default_styles.dart';
export 'src/editor/widgets/link.dart'; export 'src/editor/widgets/link.dart';
export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service.dart';
export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service_provider.dart';
export 'src/editor_toolbar_controller_shared/copy_cut_service/default_copy_cut_service.dart';
export 'src/editor_toolbar_controller_shared/quill_configurations.dart'; export 'src/editor_toolbar_controller_shared/quill_configurations.dart';
export 'src/editor_toolbar_shared/quill_configurations_ext.dart'; export 'src/editor_toolbar_shared/quill_configurations_ext.dart';
export 'src/toolbar/base_toolbar.dart'; export 'src/toolbar/base_toolbar.dart';

@ -17,6 +17,7 @@ import '../document/nodes/embeddable.dart';
import '../document/nodes/leaf.dart'; import '../document/nodes/leaf.dart';
import '../document/structs/doc_change.dart'; import '../document/structs/doc_change.dart';
import '../document/style.dart'; import '../document/style.dart';
import '../editor/config/editor_configurations.dart';
import '../editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart'; import '../editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart';
import 'quill_controller_configurations.dart'; import 'quill_controller_configurations.dart';
@ -39,19 +40,25 @@ class QuillController extends ChangeNotifier {
_selection = selection; _selection = selection;
factory QuillController.basic( factory QuillController.basic(
{QuillControllerConfigurations configurations = {QuillControllerConfigurations configurations =
const QuillControllerConfigurations(), const QuillControllerConfigurations(),
FocusNode? editorFocusNode}) { FocusNode? editorFocusNode}) =>
return QuillController( QuillController(
configurations: configurations, configurations: configurations,
editorFocusNode: editorFocusNode, editorFocusNode: editorFocusNode,
document: Document(), document: Document(),
selection: const TextSelection.collapsed(offset: 0), selection: const TextSelection.collapsed(offset: 0),
); );
}
final QuillControllerConfigurations configurations; final QuillControllerConfigurations configurations;
/// Local copy of editor configurations enables fail-safe setting from editor _initState method
QuillEditorConfigurations? _editorConfigurations;
QuillEditorConfigurations? get editorConfigurations =>
configurations.editorConfigurations ?? _editorConfigurations;
set editorConfigurations(QuillEditorConfigurations? value) =>
_editorConfigurations = value;
/// Document managed by this controller. /// Document managed by this controller.
Document _document; Document _document;
@ -476,10 +483,13 @@ class QuillController extends ChangeNotifier {
/// Clipboard caches last copy to allow paste with styles. Static to allow paste between multiple instances of editor. /// Clipboard caches last copy to allow paste with styles. Static to allow paste between multiple instances of editor.
static String _pastePlainText = ''; static String _pastePlainText = '';
static Delta _pasteDelta = Delta();
static List<OffsetValue> _pasteStyleAndEmbed = <OffsetValue>[]; static List<OffsetValue> _pasteStyleAndEmbed = <OffsetValue>[];
String get pastePlainText => _pastePlainText; String get pastePlainText => _pastePlainText;
Delta get pasteDelta => _pasteDelta;
List<OffsetValue> get pasteStyleAndEmbed => _pasteStyleAndEmbed; List<OffsetValue> get pasteStyleAndEmbed => _pasteStyleAndEmbed;
bool readOnly; bool readOnly;
/// Used to give focus to the editor following a toolbar action /// Used to give focus to the editor following a toolbar action
@ -495,9 +505,17 @@ class QuillController extends ChangeNotifier {
bool clipboardSelection(bool copy) { bool clipboardSelection(bool copy) {
copiedImageUrl = null; copiedImageUrl = null;
_pastePlainText = getPlainText();
/// Get the text for the selected region and expand the content of Embedded objects.
_pastePlainText = document.getPlainText(
selection.start, selection.end - selection.start, editorConfigurations);
/// Get the internal representation so it can be pasted into a QuillEditor with style retained.
_pasteStyleAndEmbed = getAllIndividualSelectionStylesAndEmbed(); _pasteStyleAndEmbed = getAllIndividualSelectionStylesAndEmbed();
/// Get the deltas for the selection so they can be pasted into a QuillEditor with styles and embeds retained.
_pasteDelta = document.toDelta().slice(selection.start, selection.end);
if (!selection.isCollapsed) { if (!selection.isCollapsed) {
Clipboard.setData(ClipboardData(text: _pastePlainText)); Clipboard.setData(ClipboardData(text: _pastePlainText));
if (!copy) { if (!copy) {
@ -538,28 +556,7 @@ class QuillController extends ChangeNotifier {
// See https://github.com/flutter/flutter/issues/11427 // See https://github.com/flutter/flutter/issues/11427
final plainTextClipboardData = final plainTextClipboardData =
await Clipboard.getData(Clipboard.kTextPlain); await Clipboard.getData(Clipboard.kTextPlain);
if (plainTextClipboardData != null) { if (pasteUsingPlainOrDelta(plainTextClipboardData?.text)) {
final lines = plainTextClipboardData.text!.split('\n');
for (var i = 0; i < lines.length; ++i) {
final line = lines[i];
if (line.isNotEmpty) {
replaceTextWithEmbeds(
selection.start,
selection.end - selection.start,
line,
TextSelection.collapsed(offset: selection.start + line.length),
);
}
if (i != lines.length - 1) {
document.insert(selection.extentOffset, '\n');
_updateSelection(
TextSelection.collapsed(
offset: selection.extentOffset + 1,
),
insertNewline: true,
);
}
}
updateEditor?.call(); updateEditor?.call();
return true; return true;
} }
@ -572,6 +569,28 @@ class QuillController extends ChangeNotifier {
return false; return false;
} }
/// Internal method to allow unit testing
bool pasteUsingPlainOrDelta(String? clipboardText) {
if (clipboardText != null) {
/// Internal copy-paste preserves styles and embeds
if (clipboardText == _pastePlainText &&
_pastePlainText.isNotEmpty &&
_pasteDelta.isNotEmpty) {
replaceText(selection.start, selection.end - selection.start,
_pasteDelta, TextSelection.collapsed(offset: selection.end));
} else {
replaceText(
selection.start,
selection.end - selection.start,
clipboardText,
TextSelection.collapsed(
offset: selection.end + clipboardText.length));
}
return true;
}
return false;
}
void _pasteUsingDelta(Delta deltaFromClipboard) { void _pasteUsingDelta(Delta deltaFromClipboard) {
replaceText( replaceText(
selection.start, selection.start,

@ -1,6 +1,15 @@
import '../editor/config/editor_configurations.dart';
class QuillControllerConfigurations { class QuillControllerConfigurations {
const QuillControllerConfigurations( const QuillControllerConfigurations(
{this.onClipboardPaste, this.requireScriptFontFeatures = false}); {this.editorConfigurations,
this.onClipboardPaste,
this.requireScriptFontFeatures = false});
/// Provides central access to editor configurations required for controller actions
///
/// Future: will be changed to 'required final'
final QuillEditorConfigurations? editorConfigurations;
/// Callback when the user pastes and data has not already been processed /// Callback when the user pastes and data has not already been processed
/// ///

@ -70,19 +70,18 @@ int getPositionDelta(Delta user, Delta actual) {
); );
} }
if (userOperation.key == actualOperation.key) { if (userOperation.key == actualOperation.key) {
/// Insertions must update diff allowing for type mismatch of Operation
if (userOperation.key == Operation.insertKey) {
if (userOperation.data is Delta && actualOperation.data is String) {
diff += actualOperation.length!;
}
}
continue; continue;
} else if (userOperation.isInsert && actualOperation.isRetain) { } else if (userOperation.isInsert && actualOperation.isRetain) {
diff -= userOperation.length!; diff -= userOperation.length!;
} else if (userOperation.isDelete && actualOperation.isRetain) { } else if (userOperation.isDelete && actualOperation.isRetain) {
diff += userOperation.length!; diff += userOperation.length!;
} else if (userOperation.isRetain && actualOperation.isInsert) { } else if (userOperation.isRetain && actualOperation.isInsert) {
String? operationTxt = '';
if (actualOperation.data is String) {
operationTxt = actualOperation.data as String?;
}
if (operationTxt!.startsWith('\n')) {
continue;
}
diff += actualOperation.length!; diff += actualOperation.length!;
} }
} }

@ -6,6 +6,7 @@ import '../../quill_delta.dart';
import '../common/structs/offset_value.dart'; import '../common/structs/offset_value.dart';
import '../common/structs/segment_leaf_node.dart'; import '../common/structs/segment_leaf_node.dart';
import '../delta/delta_x.dart'; import '../delta/delta_x.dart';
import '../editor/config/editor_configurations.dart';
import '../editor/embed/embed_editor_builder.dart'; import '../editor/embed/embed_editor_builder.dart';
import '../rules/rule.dart'; import '../rules/rule.dart';
import 'attribute.dart'; import 'attribute.dart';
@ -239,9 +240,9 @@ class Document {
} }
/// Returns plain text within the specified text range. /// Returns plain text within the specified text range.
String getPlainText(int index, int len) { String getPlainText(int index, int len, [QuillEditorConfigurations? config]) {
final res = queryChild(index); final res = queryChild(index);
return (res.node as Line).getPlainText(res.offset, len); return (res.node as Line).getPlainText(res.offset, len, config);
} }
/// Returns [Line] located at specified character [offset]. /// Returns [Line] located at specified character [offset].

@ -4,7 +4,9 @@ import 'package:collection/collection.dart';
import '../../../../quill_delta.dart'; import '../../../../quill_delta.dart';
import '../../common/structs/offset_value.dart'; import '../../common/structs/offset_value.dart';
import '../../editor/config/editor_configurations.dart';
import '../../editor/embed/embed_editor_builder.dart'; import '../../editor/embed/embed_editor_builder.dart';
import '../../editor_toolbar_controller_shared/copy_cut_service/copy_cut_service_provider.dart';
import '../attribute.dart'; import '../attribute.dart';
import '../style.dart'; import '../style.dart';
import 'block.dart'; import 'block.dart';
@ -511,25 +513,49 @@ base class Line extends QuillContainer<Leaf?> {
} }
/// Returns plain text within the specified text range. /// Returns plain text within the specified text range.
String getPlainText(int offset, int len) { String getPlainText(int offset, int len,
[QuillEditorConfigurations? config]) {
final plainText = StringBuffer(); final plainText = StringBuffer();
_getPlainText(offset, len, plainText); _getPlainText(offset, len, plainText, config);
return plainText.toString(); return plainText.toString();
} }
int _getNodeText(Leaf node, StringBuffer buffer, int offset, int remaining) { int _getNodeText(Leaf node, StringBuffer buffer, int offset, int remaining,
final text = node.toPlainText(); QuillEditorConfigurations? config) {
final text =
node.toPlainText(config?.embedBuilders, config?.unknownEmbedBuilder);
if (text == Embed.kObjectReplacementCharacter) { if (text == Embed.kObjectReplacementCharacter) {
buffer.write(Embed.kObjectReplacementCharacter); final embed = node.value as Embeddable;
final provider = CopyCutServiceProvider.instance;
// By default getCopyCutAction just return the same operation
// returning Embed.kObjectReplacementCharacter for the buffer
final action = provider.getCopyCutAction(embed.type);
final data = action.call(embed.data);
// This conditional avoid an issue where the plain data copied
// to the clipboard, when it is pasted on the editor
// the content has a unexpected behaviors
if (data != Embed.kObjectReplacementCharacter) {
buffer.write(data);
return remaining;
} else {
buffer.write(action.call(data));
}
return remaining - node.length; return remaining - node.length;
} }
/// Text for clipboard will expand the content of Embed nodes
if (node is Embed && config != null) {
buffer.write(text);
return remaining - 1;
}
final end = math.min(offset + remaining, text.length); final end = math.min(offset + remaining, text.length);
buffer.write(text.substring(offset, end)); buffer.write(text.substring(offset, end));
return remaining - (end - offset); return remaining - (end - offset);
} }
int _getPlainText(int offset, int len, StringBuffer plainText) { int _getPlainText(int offset, int len, StringBuffer plainText,
QuillEditorConfigurations? config) {
var len0 = len; var len0 = len;
final data = queryChild(offset, false); final data = queryChild(offset, false);
var node = data.node as Leaf?; var node = data.node as Leaf?;
@ -540,11 +566,12 @@ base class Line extends QuillContainer<Leaf?> {
plainText.write('\n'); plainText.write('\n');
len0 -= 1; len0 -= 1;
} else { } else {
len0 = _getNodeText(node, plainText, offset - node.offset, len0); len0 =
_getNodeText(node, plainText, offset - node.offset, len0, config);
while (!node!.isLast && len0 > 0) { while (!node!.isLast && len0 > 0) {
node = node.next as Leaf; node = node.next as Leaf;
len0 = _getNodeText(node, plainText, 0, len0); len0 = _getNodeText(node, plainText, 0, len0, config);
} }
if (len0 > 0) { if (len0 > 0) {
@ -555,7 +582,7 @@ base class Line extends QuillContainer<Leaf?> {
} }
if (len0 > 0 && nextLine != null) { if (len0 > 0 && nextLine != null) {
len0 = nextLine!._getPlainText(0, len0, plainText); len0 = nextLine!._getPlainText(0, len0, plainText, config);
} }
} }

@ -174,16 +174,29 @@ class QuillEditorState extends State<QuillEditor>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
widget.configurations.controller.editorFocusNode ??= widget.focusNode;
if (configurations.autoFocus) {
widget.configurations.controller.editorFocusNode?.requestFocus();
}
_editorKey = configurations.editorKey ?? GlobalKey<EditorState>(); _editorKey = configurations.editorKey ?? GlobalKey<EditorState>();
_selectionGestureDetectorBuilder = _selectionGestureDetectorBuilder =
_QuillEditorSelectionGestureDetectorBuilder( _QuillEditorSelectionGestureDetectorBuilder(
this, this,
configurations.detectWordBoundary, configurations.detectWordBoundary,
); );
widget.configurations.controller.editorConfigurations ??=
widget.configurations;
final focusNode =
widget.configurations.controller.editorFocusNode ??= widget.focusNode;
if (configurations.autoFocus) {
focusNode.requestFocus();
}
// Hide toolbar when the editor loses focus.
focusNode.addListener(() {
if (!focusNode.hasFocus) {
_editorKey.currentState?.hideToolbar();
}
});
} }
@override @override

@ -0,0 +1,13 @@
import 'package:flutter/foundation.dart';
typedef CopyCutAction = Object? Function(dynamic data);
/// An abstraction to make it easy to provide different implementations
/// For copy or cut actions from a Line (just for embeddable blocks)
@immutable
abstract class CopyCutService {
/// Get the CopyCutAction by the type
/// of the embeddable (this type is decided by
/// the property type of that class)
CopyCutAction getCopyCutAction(String type);
}

@ -0,0 +1,19 @@
import 'package:flutter/foundation.dart' show immutable;
import 'copy_cut_service.dart';
import 'default_copy_cut_service.dart';
@immutable
class CopyCutServiceProvider {
const CopyCutServiceProvider._();
static CopyCutService _instance = DefaultCopyCutService();
static CopyCutService get instance => _instance;
static void setInstance(CopyCutService service) {
_instance = service;
}
static void setInstanceToDefault() {
_instance = DefaultCopyCutService();
}
}

@ -0,0 +1,14 @@
import '../../document/nodes/leaf.dart';
import 'copy_cut_service.dart';
/// Default implementation for [CopyCutService]
///
/// This implementation always return the default embed character
/// replacemenet ([\uFFFC]) to work with the embeds from the internal
/// flutter quill plugins
class DefaultCopyCutService extends CopyCutService {
@override
CopyCutAction getCopyCutAction(String type) {
return (data) => Embed.kObjectReplacementCharacter;
}
}

@ -23,34 +23,6 @@ class QuillSimpleToolbar extends StatelessWidget
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theEmbedButtons = configurations.embedButtons; final theEmbedButtons = configurations.embedButtons;
final isButtonGroupShown = [
configurations.showFontFamily ||
configurations.showFontSize ||
configurations.showBoldButton ||
configurations.showItalicButton ||
configurations.showSmallButton ||
configurations.showUnderLineButton ||
configurations.showLineHeightButton ||
configurations.showStrikeThrough ||
configurations.showInlineCode ||
configurations.showColorButton ||
configurations.showBackgroundColorButton ||
configurations.showClearFormat ||
theEmbedButtons?.isNotEmpty == true,
configurations.showLeftAlignment ||
configurations.showCenterAlignment ||
configurations.showRightAlignment ||
configurations.showJustifyAlignment ||
configurations.showDirection,
configurations.showHeaderStyle,
configurations.showListNumbers ||
configurations.showListBullets ||
configurations.showListCheck ||
configurations.showCodeBlock,
configurations.showQuote || configurations.showIndent,
configurations.showLink || configurations.showSearchButton
];
List<Widget> childrenBuilder(BuildContext context) { List<Widget> childrenBuilder(BuildContext context) {
final toolbarConfigurations = final toolbarConfigurations =
context.requireQuillSimpleToolbarConfigurations; context.requireQuillSimpleToolbarConfigurations;
@ -68,251 +40,232 @@ class QuillSimpleToolbar extends StatelessWidget
space: configurations.sectionDividerSpace, space: configurations.sectionDividerSpace,
)); ));
return [ final groups = [
if (configurations.showUndo) [
QuillToolbarHistoryButton( if (configurations.showUndo)
isUndo: true, QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.undoHistory, isUndo: true,
controller: globalController, options: toolbarConfigurations.buttonOptions.undoHistory,
), controller: globalController,
if (configurations.showRedo) ),
QuillToolbarHistoryButton( if (configurations.showRedo)
isUndo: false, QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.redoHistory, isUndo: false,
controller: globalController, options: toolbarConfigurations.buttonOptions.redoHistory,
), controller: globalController,
if (configurations.showFontFamily) ),
QuillToolbarFontFamilyButton( if (configurations.showFontFamily)
options: toolbarConfigurations.buttonOptions.fontFamily, QuillToolbarFontFamilyButton(
controller: globalController, options: toolbarConfigurations.buttonOptions.fontFamily,
), controller: globalController,
if (configurations.showFontSize) ),
QuillToolbarFontSizeButton( if (configurations.showFontSize)
options: toolbarConfigurations.buttonOptions.fontSize, QuillToolbarFontSizeButton(
controller: globalController, options: toolbarConfigurations.buttonOptions.fontSize,
), controller: globalController,
if (configurations.showBoldButton) ),
QuillToolbarToggleStyleButton( if (configurations.showBoldButton)
attribute: Attribute.bold, QuillToolbarToggleStyleButton(
options: toolbarConfigurations.buttonOptions.bold, attribute: Attribute.bold,
controller: globalController, options: toolbarConfigurations.buttonOptions.bold,
), controller: globalController,
if (configurations.showItalicButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
options: toolbarConfigurations.buttonOptions.italic,
controller: globalController,
),
if (configurations.showUnderLineButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
options: toolbarConfigurations.buttonOptions.underLine,
controller: globalController,
),
if (configurations.showStrikeThrough)
QuillToolbarToggleStyleButton(
attribute: Attribute.strikeThrough,
options: toolbarConfigurations.buttonOptions.strikeThrough,
controller: globalController,
),
if (configurations.showInlineCode)
QuillToolbarToggleStyleButton(
attribute: Attribute.inlineCode,
options: toolbarConfigurations.buttonOptions.inlineCode,
controller: globalController,
),
if (configurations.showSubscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.subscript,
options: toolbarConfigurations.buttonOptions.subscript,
controller: globalController,
),
if (configurations.showSuperscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.superscript,
options: toolbarConfigurations.buttonOptions.superscript,
controller: globalController,
),
if (configurations.showSmallButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.small,
options: toolbarConfigurations.buttonOptions.small,
controller: globalController,
),
if (configurations.showColorButton)
QuillToolbarColorButton(
controller: globalController,
isBackground: false,
options: toolbarConfigurations.buttonOptions.color,
),
if (configurations.showBackgroundColorButton)
QuillToolbarColorButton(
options: toolbarConfigurations.buttonOptions.backgroundColor,
controller: globalController,
isBackground: true,
),
if (configurations.showClearFormat)
QuillToolbarClearFormatButton(
controller: globalController,
options: toolbarConfigurations.buttonOptions.clearFormat,
),
if (theEmbedButtons != null)
for (final builder in theEmbedButtons)
builder(
globalController,
globalIconSize ?? kDefaultIconSize,
context.quillToolbarBaseButtonOptions?.iconTheme,
configurations.dialogTheme),
if (configurations.showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
divider,
if (configurations.showAlignmentButtons)
QuillToolbarSelectAlignmentButtons(
controller: globalController,
options: toolbarConfigurations.buttonOptions.selectAlignmentButtons
.copyWith(
showLeftAlignment: configurations.showLeftAlignment,
showCenterAlignment: configurations.showCenterAlignment,
showRightAlignment: configurations.showRightAlignment,
showJustifyAlignment: configurations.showJustifyAlignment,
), ),
), if (configurations.showItalicButton)
if (configurations.showDirection) QuillToolbarToggleStyleButton(
QuillToolbarToggleStyleButton( attribute: Attribute.italic,
attribute: Attribute.rtl, options: toolbarConfigurations.buttonOptions.italic,
options: toolbarConfigurations.buttonOptions.direction, controller: globalController,
controller: globalController, ),
), if (configurations.showUnderLineButton)
if (configurations.showDividers && QuillToolbarToggleStyleButton(
isButtonGroupShown[1] && attribute: Attribute.underline,
(isButtonGroupShown[2] || options: toolbarConfigurations.buttonOptions.underLine,
isButtonGroupShown[3] || controller: globalController,
isButtonGroupShown[4] || ),
isButtonGroupShown[5])) if (configurations.showStrikeThrough)
divider, QuillToolbarToggleStyleButton(
if (configurations.showLineHeightButton) attribute: Attribute.strikeThrough,
QuillToolbarSelectLineHeightStyleDropdownButton( options: toolbarConfigurations.buttonOptions.strikeThrough,
controller: globalController, controller: globalController,
options: toolbarConfigurations ),
.buttonOptions.selectLineHeightStyleDropdownButton, if (configurations.showInlineCode)
), QuillToolbarToggleStyleButton(
if (configurations.showHeaderStyle) ...[ attribute: Attribute.inlineCode,
if (configurations.headerStyleType.isOriginal) options: toolbarConfigurations.buttonOptions.inlineCode,
QuillToolbarSelectHeaderStyleDropdownButton( controller: globalController,
),
if (configurations.showSubscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.subscript,
options: toolbarConfigurations.buttonOptions.subscript,
controller: globalController,
),
if (configurations.showSuperscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.superscript,
options: toolbarConfigurations.buttonOptions.superscript,
controller: globalController,
),
if (configurations.showSmallButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.small,
options: toolbarConfigurations.buttonOptions.small,
controller: globalController,
),
if (configurations.showColorButton)
QuillToolbarColorButton(
controller: globalController,
isBackground: false,
options: toolbarConfigurations.buttonOptions.color,
),
if (configurations.showBackgroundColorButton)
QuillToolbarColorButton(
options: toolbarConfigurations.buttonOptions.backgroundColor,
controller: globalController,
isBackground: true,
),
if (configurations.showClearFormat)
QuillToolbarClearFormatButton(
controller: globalController,
options: toolbarConfigurations.buttonOptions.clearFormat,
),
if (theEmbedButtons != null)
for (final builder in theEmbedButtons)
builder(
globalController,
globalIconSize ?? kDefaultIconSize,
context.quillToolbarBaseButtonOptions?.iconTheme,
configurations.dialogTheme),
],
[
if (configurations.showAlignmentButtons)
QuillToolbarSelectAlignmentButtons(
controller: globalController, controller: globalController,
options: toolbarConfigurations options: toolbarConfigurations
.buttonOptions.selectHeaderStyleDropdownButton, .buttonOptions.selectAlignmentButtons
) .copyWith(
else showLeftAlignment: configurations.showLeftAlignment,
QuillToolbarSelectHeaderStyleButtons( showCenterAlignment: configurations.showCenterAlignment,
showRightAlignment: configurations.showRightAlignment,
showJustifyAlignment: configurations.showJustifyAlignment,
),
),
if (configurations.showDirection)
QuillToolbarToggleStyleButton(
attribute: Attribute.rtl,
options: toolbarConfigurations.buttonOptions.direction,
controller: globalController, controller: globalController,
options:
toolbarConfigurations.buttonOptions.selectHeaderStyleButtons,
), ),
], ],
if (configurations.showDividers && [
configurations.showHeaderStyle && if (configurations.showLineHeightButton)
isButtonGroupShown[2] && QuillToolbarSelectLineHeightStyleDropdownButton(
(isButtonGroupShown[3] || controller: globalController,
isButtonGroupShown[4] || options: toolbarConfigurations
isButtonGroupShown[5])) .buttonOptions.selectLineHeightStyleDropdownButton,
divider, ),
if (configurations.showListNumbers) if (configurations.showHeaderStyle) ...[
QuillToolbarToggleStyleButton( if (configurations.headerStyleType.isOriginal)
attribute: Attribute.ol, QuillToolbarSelectHeaderStyleDropdownButton(
options: toolbarConfigurations.buttonOptions.listNumbers, controller: globalController,
controller: globalController, options: toolbarConfigurations
), .buttonOptions.selectHeaderStyleDropdownButton,
if (configurations.showListBullets) )
QuillToolbarToggleStyleButton( else
attribute: Attribute.ul, QuillToolbarSelectHeaderStyleButtons(
options: toolbarConfigurations.buttonOptions.listBullets, controller: globalController,
controller: globalController, options: toolbarConfigurations
), .buttonOptions.selectHeaderStyleButtons,
if (configurations.showListCheck) ),
QuillToolbarToggleCheckListButton( ],
options: toolbarConfigurations.buttonOptions.toggleCheckList, ],
controller: globalController, [
), if (configurations.showListNumbers)
if (configurations.showCodeBlock) QuillToolbarToggleStyleButton(
QuillToolbarToggleStyleButton( attribute: Attribute.ol,
attribute: Attribute.codeBlock, options: toolbarConfigurations.buttonOptions.listNumbers,
options: toolbarConfigurations.buttonOptions.codeBlock, controller: globalController,
controller: globalController, ),
), if (configurations.showListBullets)
if (configurations.showDividers && QuillToolbarToggleStyleButton(
isButtonGroupShown[3] && attribute: Attribute.ul,
(isButtonGroupShown[4] || isButtonGroupShown[5])) ...[ options: toolbarConfigurations.buttonOptions.listBullets,
divider, controller: globalController,
),
if (configurations.showListCheck)
QuillToolbarToggleCheckListButton(
options: toolbarConfigurations.buttonOptions.toggleCheckList,
controller: globalController,
),
if (configurations.showCodeBlock)
QuillToolbarToggleStyleButton(
attribute: Attribute.codeBlock,
options: toolbarConfigurations.buttonOptions.codeBlock,
controller: globalController,
),
],
[
if (configurations.showQuote)
QuillToolbarToggleStyleButton(
options: toolbarConfigurations.buttonOptions.quote,
controller: globalController,
attribute: Attribute.blockQuote,
),
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: globalController,
isIncrease: true,
options: toolbarConfigurations.buttonOptions.indentIncrease,
),
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: globalController,
isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease,
),
], ],
if (configurations.showQuote) [
QuillToolbarToggleStyleButton( if (configurations.showLink)
options: toolbarConfigurations.buttonOptions.quote, toolbarConfigurations.linkStyleType.isOriginal
controller: globalController, ? QuillToolbarLinkStyleButton(
attribute: Attribute.blockQuote, controller: globalController,
), options: toolbarConfigurations.buttonOptions.linkStyle,
if (configurations.showIndent) )
QuillToolbarIndentButton( : QuillToolbarLinkStyleButton2(
controller: globalController, controller: globalController,
isIncrease: true, options: toolbarConfigurations.buttonOptions.linkStyle2,
options: toolbarConfigurations.buttonOptions.indentIncrease, ),
), if (configurations.showSearchButton)
if (configurations.showIndent) switch (configurations.searchButtonType) {
QuillToolbarIndentButton( SearchButtonType.legacy => QuillToolbarLegacySearchButton(
controller: globalController,
isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease,
),
if (configurations.showDividers &&
isButtonGroupShown[4] &&
isButtonGroupShown[5])
divider,
if (configurations.showLink)
toolbarConfigurations.linkStyleType.isOriginal
? QuillToolbarLinkStyleButton(
controller: globalController, controller: globalController,
options: toolbarConfigurations.buttonOptions.linkStyle, options: toolbarConfigurations.buttonOptions.search,
) ),
: QuillToolbarLinkStyleButton2( SearchButtonType.modern => QuillToolbarSearchButton(
controller: globalController, controller: globalController,
options: toolbarConfigurations.buttonOptions.linkStyle2, options: toolbarConfigurations.buttonOptions.search,
), ),
if (configurations.showSearchButton) },
switch (configurations.searchButtonType) { if (configurations.showClipboardCut)
SearchButtonType.legacy => QuillToolbarLegacySearchButton( QuillToolbarClipboardButton(
controller: globalController, options: toolbarConfigurations.buttonOptions.clipboardCut,
options: toolbarConfigurations.buttonOptions.search, controller: globalController,
), clipboardAction: ClipboardAction.cut,
SearchButtonType.modern => QuillToolbarSearchButton( ),
controller: globalController, if (configurations.showClipboardCopy)
options: toolbarConfigurations.buttonOptions.search, QuillToolbarClipboardButton(
), options: toolbarConfigurations.buttonOptions.clipboardCopy,
}, controller: globalController,
if (configurations.showClipboardCut) clipboardAction: ClipboardAction.copy,
QuillToolbarClipboardButton( ),
options: toolbarConfigurations.buttonOptions.clipboardCut, if (configurations.showClipboardPaste)
controller: globalController, QuillToolbarClipboardButton(
clipboardAction: ClipboardAction.cut, options: toolbarConfigurations.buttonOptions.clipboardPaste,
), controller: globalController,
if (configurations.showClipboardCopy) clipboardAction: ClipboardAction.paste,
QuillToolbarClipboardButton( ),
options: toolbarConfigurations.buttonOptions.clipboardCopy, ],
controller: globalController, [
clipboardAction: ClipboardAction.copy,
),
if (configurations.showClipboardPaste)
QuillToolbarClipboardButton(
options: toolbarConfigurations.buttonOptions.clipboardPaste,
controller: globalController,
clipboardAction: ClipboardAction.paste,
),
if (configurations.customButtons.isNotEmpty) ...[
if (configurations.showDividers) divider,
for (final customButton in configurations.customButtons) for (final customButton in configurations.customButtons)
QuillToolbarCustomButton( QuillToolbarCustomButton(
options: customButton, options: customButton,
@ -320,6 +273,21 @@ class QuillSimpleToolbar extends StatelessWidget
), ),
], ],
]; ];
final buttonsAll = <Widget>[];
for (var i = 0; i < groups.length; i++) {
final buttons = groups[i];
if (buttons.isNotEmpty) {
if (buttonsAll.isNotEmpty) {
buttonsAll.add(divider);
}
buttonsAll.addAll(buttons);
}
}
return buttonsAll;
} }
return QuillSimpleToolbarProvider( return QuillSimpleToolbarProvider(

@ -1,6 +1,6 @@
name: flutter_quill name: flutter_quill
description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter. description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter.
version: 10.0.9 version: 10.1.2
homepage: https://1o24bbs.com/c/bulletjournal/108/ homepage: https://1o24bbs.com/c/bulletjournal/108/
repository: https://github.com/singerdmx/flutter-quill/ repository: https://github.com/singerdmx/flutter-quill/
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/quill_delta.dart';
import 'package:test/test.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
group('copy', () {
const testDocumentContents = 'data';
late QuillController controller;
setUp(() {
controller = QuillController.basic()
..compose(Delta()..insert(testDocumentContents),
const TextSelection.collapsed(offset: 0), ChangeSource.local);
});
test('clipboardSelection empty', () {
expect(controller.clipboardSelection(true), false,
reason: 'No effect when no selection');
expect(controller.clipboardSelection(false), false);
});
test('clipboardSelection', () {
controller
..replaceText(0, 4, 'bold plain italic', null)
..formatText(0, 4, Attribute.bold)
..formatText(11, 17, Attribute.italic)
..updateSelection(const TextSelection(baseOffset: 2, extentOffset: 14),
ChangeSource.local);
//
expect(controller.clipboardSelection(true), true);
expect(controller.document.length, 18,
reason: 'Copy does not change the document');
expect(controller.clipboardSelection(false), true);
expect(controller.document.length, 6, reason: 'Cut changes the document');
//
controller
..readOnly = true
..updateSelection(const TextSelection(baseOffset: 2, extentOffset: 4),
ChangeSource.local);
expect(controller.selection.isCollapsed, false);
expect(controller.clipboardSelection(true), true);
expect(controller.document.length, 6);
expect(controller.clipboardSelection(false), false);
expect(controller.document.length, 6,
reason: 'Cut not permitted on readOnly document');
});
});
group('paste', () {
test('Plain', () async {
final controller = QuillController.basic()
..compose(Delta()..insert('[]'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..updateSelection(
const TextSelection.collapsed(offset: 1), ChangeSource.local);
//
expect(controller.document.toPlainText(), '[]\n');
expect(controller.pasteUsingPlainOrDelta('insert'), true);
expect(controller.document.toPlainText(), '[insert]\n');
});
test('Plain lines', () async {
final controller = QuillController.basic()
..compose(Delta()..insert('[]'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..updateSelection(
const TextSelection.collapsed(offset: 1), ChangeSource.local);
//
expect(controller.document.toPlainText(), '[]\n');
expect(controller.pasteUsingPlainOrDelta('1\n2\n3\n'), true);
expect(controller.document.toPlainText(), '[1\n2\n3\n]\n');
});
test('Paste from external', () async {
final source = QuillController.basic()
..compose(Delta()..insert('Plain text'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..updateSelection(const TextSelection(baseOffset: 4, extentOffset: 8),
ChangeSource.local);
assert(source.clipboardSelection(true));
expect(source.pastePlainText, 'n te');
//
final controller = QuillController.basic()
..compose(Delta()..insert('[]'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..updateSelection(
const TextSelection.collapsed(offset: 1), ChangeSource.local);
//
expect(controller.pasteUsingPlainOrDelta('insert'), true,
reason: 'External paste');
expect(controller.document.toPlainText(), '[insert]\n');
});
test('Delta simple', () async {
final source = QuillController.basic()
..compose(Delta()..insert('Plain text'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..formatText(6, 8, Attribute.bold)
..updateSelection(const TextSelection(baseOffset: 4, extentOffset: 8),
ChangeSource.local);
assert(source.clipboardSelection(true));
expect(source.pastePlainText, 'n te');
expect(
source.pasteDelta,
Delta()
..insert('n ')
..insert('te', {'bold': true}));
//
final controller = QuillController.basic()
..compose(Delta()..insert('[]'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..updateSelection(
const TextSelection.collapsed(offset: 1), ChangeSource.local);
//
expect(controller.pasteUsingPlainOrDelta('n te'), true,
reason: 'Internal paste');
expect(controller.document.toPlainText(), '[n te]\n');
expect(
controller.document.toDelta(),
Delta()
..insert('[n ')
..insert('te', {'bold': true})
..insert(']\n'));
expect(controller.selection, const TextSelection.collapsed(offset: 5));
});
test('Delta multi line', () async {
const blockAttribute = Attribute.ol;
const plainSelection = 'BC\nDEF\nGHI\nJK';
final source = QuillController.basic()
..compose(Delta()..insert('ABC\nDEF\nGHI\nJKL'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..formatText(1, 1, Attribute.underline) // ABC with B underlined
..formatText(4, 0, blockAttribute) // 1. DEF with E in italic
..formatText(5, 1, Attribute.italic)
..formatText(8, 0, blockAttribute) // 2. GHI with H as inline code
..formatText(9, 1, Attribute.inlineCode)
..formatText(13, 1, Attribute.strikeThrough) // JKL with K strikethrough
..updateSelection(const TextSelection(baseOffset: 1, extentOffset: 14),
ChangeSource.local);
//
assert(source.clipboardSelection(true));
expect(source.pastePlainText, plainSelection);
expect(
source.pasteDelta,
Delta()
..insert('B', {'underline': true})
..insert('C\nD')
..insert('E', {'italic': true})
..insert('F')
..insert('\n', {'list': 'ordered'})
..insert('G')
..insert('H', {'code': true})
..insert('I')
..insert('\n', {'list': 'ordered'})
..insert('J')
..insert('K', {'strike': true}));
//
final controller = QuillController.basic()
..compose(Delta()..insert('[]'),
const TextSelection.collapsed(offset: 0), ChangeSource.local)
..updateSelection(
const TextSelection.collapsed(offset: 1), ChangeSource.local);
//
expect(controller.pasteUsingPlainOrDelta(plainSelection), true,
reason: 'Internal paste');
expect(controller.document.toPlainText(), '[$plainSelection]\n');
expect(
controller.document.toDelta(),
Delta()
..insert('[')
..insert('B', {'underline': true})
..insert('C\nD')
..insert('E', {'italic': true})
..insert('F')
..insert('\n', {'list': 'ordered'})
..insert('G')
..insert('H', {'code': true})
..insert('I')
..insert('\n', {'list': 'ordered'})
..insert('J')
..insert('K', {'strike': true})
..insert(']\n'));
expect(controller.selection, const TextSelection.collapsed(offset: 14));
});
});
}

@ -324,38 +324,6 @@ void main() {
Delta()..insert('test $originalContents')); Delta()..insert('test $originalContents'));
}); });
test('clipboardSelection empty', () {
expect(controller.clipboardSelection(true), false,
reason: 'No effect when no selection');
expect(controller.clipboardSelection(false), false);
});
test('clipboardSelection', () {
controller
..replaceText(0, 4, 'bold plain italic', null)
..formatText(0, 4, Attribute.bold)
..formatText(11, 17, Attribute.italic)
..updateSelection(const TextSelection(baseOffset: 2, extentOffset: 14),
ChangeSource.local);
//
expect(controller.clipboardSelection(true), true);
expect(controller.document.length, 18,
reason: 'Copy does not change the document');
expect(controller.clipboardSelection(false), true);
expect(controller.document.length, 6, reason: 'Cut changes the document');
//
controller
..readOnly = true
..updateSelection(const TextSelection(baseOffset: 2, extentOffset: 4),
ChangeSource.local);
expect(controller.selection.isCollapsed, false);
expect(controller.clipboardSelection(true), true);
expect(controller.document.length, 6);
expect(controller.clipboardSelection(false), false);
expect(controller.document.length, 6,
reason: 'Cut not permitted on readOnly document');
});
test('blockSelectionStyles', () { test('blockSelectionStyles', () {
Style select(int start, int end) { Style select(int start, int end) {
controller.updateSelection( controller.updateSelection(

Loading…
Cancel
Save