Merge branch 'singerdmx:master' into feature/allowShortcutOverride

pull/2089/head
DeveloperBiz 8 months ago committed by GitHub
commit 4665bfdfb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 44
      CHANGELOG.md
  2. 2
      CHANGELOG_DATA.json
  3. 15
      README.md
  4. 44
      dart_quill_delta/CHANGELOG.md
  5. 2
      dart_quill_delta/pubspec.yaml
  6. 2
      doc/attribute_introduction.md
  7. 1
      doc/configurations/custom_buttons.md
  8. 6
      doc/custom_embed_blocks.md
  9. 4
      doc/translation.md
  10. 7
      example/lib/screens/quill/my_quill_editor.dart
  11. 6
      example/lib/screens/quill/my_quill_toolbar.dart
  12. 3
      example/lib/screens/quill/quill_screen.dart
  13. 10
      example/lib/screens/simple/simple_screen.dart
  14. 44
      flutter_quill_extensions/CHANGELOG.md
  15. 11
      flutter_quill_extensions/README.md
  16. 2
      flutter_quill_extensions/pubspec.yaml
  17. 44
      flutter_quill_test/CHANGELOG.md
  18. 2
      flutter_quill_test/pubspec.yaml
  19. 2
      lib/src/common/structs/horizontal_spacing.dart
  20. 2
      lib/src/common/structs/vertical_spacing.dart
  21. 4
      lib/src/controller/provider.dart
  22. 97
      lib/src/controller/quill_controller.dart
  23. 13
      lib/src/controller/quill_controller_configurations.dart
  24. 13
      lib/src/delta/delta_diff.dart
  25. 20
      lib/src/document/document.dart
  26. 28
      lib/src/document/nodes/line.dart
  27. 14
      lib/src/editor/config/editor_configurations.dart
  28. 106
      lib/src/editor/editor.dart
  29. 26
      lib/src/editor/provider.dart
  30. 7
      lib/src/editor/raw_editor/config/raw_editor_configurations.dart
  31. 10
      lib/src/editor/raw_editor/raw_editor.dart
  32. 17
      lib/src/editor/raw_editor/raw_editor_state.dart
  33. 9
      lib/src/editor/raw_editor/raw_editor_state_selection_delegate_mixin.dart
  34. 5
      lib/src/editor/raw_editor/raw_editor_state_text_input_client_mixin.dart
  35. 46
      lib/src/editor/widgets/default_styles.dart
  36. 5
      lib/src/toolbar/base_toolbar.dart
  37. 7
      lib/src/toolbar/config/simple_toolbar_configurations.dart
  38. 537
      lib/src/toolbar/simple_toolbar.dart
  39. 2
      pubspec.yaml
  40. 14
      test/bug_fix_test.dart
  41. 190
      test/controller/controller_clipboard_test.dart
  42. 32
      test/controller/controller_test.dart
  43. 54
      test/document/document_test.dart
  44. 15
      test/editor/editor_test.dart

@ -4,6 +4,50 @@
All notable changes to this project will be documented in this file.
## 10.1.6
* fixed #1295 Double click to select text sometimes doesn't work. by @li8607 in https://github.com/singerdmx/flutter-quill/pull/2086
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.5...v10.1.6
## 10.1.5
* ref: add `VerticalSpacing.zero` and `HorizontalSpacing.zero` named constants by @adil192 in https://github.com/singerdmx/flutter-quill/pull/2083
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.4...v10.1.5
## 10.1.4
* Fix: collectStyles for lists and alignments by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2082
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.3...v10.1.4
## 10.1.3
* Move Controller outside of configurations data class by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2078
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.2...v10.1.3
## 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

File diff suppressed because one or more lines are too long

@ -94,8 +94,9 @@ dependencies:
```yaml
dependencies:
flutter_quill:
git: https://github.com/singerdmx/flutter-quill.git
ref: v<latest-version-here>
git:
url: https://github.com/singerdmx/flutter-quill.git
ref: v<latest-version-here>
```
> [!TIP]
@ -129,16 +130,18 @@ Instantiate a controller:
QuillController _controller = QuillController.basic();
```
Use the `QuillEditor`, and `QuillToolbar` widgets,
Use the `QuillEditor`, and `QuillSimpleToolbar` widgets,
and attach the `QuillController` to them:
```dart
QuillToolbar.simple(
configurations: QuillSimpleToolbarConfigurations(controller: _controller),
QuillSimpleToolbar(
controller: _controller,
configurations: QuillSimpleToolbarConfigurations(),
),
Expanded(
child: QuillEditor.basic(
configurations: QuillEditorConfigurations(controller: _controller),
controller: _controller,
configurations: const QuillEditorConfigurations(),
),
)
```

@ -4,6 +4,50 @@
All notable changes to this project will be documented in this file.
## 10.1.6
* fixed #1295 Double click to select text sometimes doesn't work. by @li8607 in https://github.com/singerdmx/flutter-quill/pull/2086
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.5...v10.1.6
## 10.1.5
* ref: add `VerticalSpacing.zero` and `HorizontalSpacing.zero` named constants by @adil192 in https://github.com/singerdmx/flutter-quill/pull/2083
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.4...v10.1.5
## 10.1.4
* Fix: collectStyles for lists and alignments by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2082
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.3...v10.1.4
## 10.1.3
* Move Controller outside of configurations data class by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2078
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.2...v10.1.3
## 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

@ -1,6 +1,6 @@
name: dart_quill_delta
description: A port of quill-js-delta from typescript to dart
version: 10.1.0
version: 10.1.6
homepage: 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/

@ -85,8 +85,8 @@ On `QuillEditor` or `QuillEditorConfigurations` **doesn't exist** a param that l
##### The editor
```dart
QuillEditor.basic(
controller: controller,
configurations: QuillEditorConfigurations(
controller: controller,
customStyleBuilder: (Attribute<dynamic> attribute) {
if (attribute.key.equals(highlightKey)) {
return TextStyle(color: Colors.black, backgroundColor: Colors.yellow);

@ -22,6 +22,7 @@ Each `QuillCustomButton` is used as part of the `customButtons` option as follow
```dart
QuillToolbar.simple(
controller: _controller,
configurations: QuillSimpleToolbarConfigurations(
customButtons: [
QuillToolbarCustomButtonOptions(

@ -98,10 +98,8 @@ Future<void> _addEditNote(BuildContext context, {Document? document}) async {
],
),
content: QuillEditor.basic(
configurations: const QuillEditorConfigurations(
controller: quillEditorController,
readOnly: false,
),
controller: quillEditorController,
configurations: const QuillEditorConfigurations(),
),
),
);

@ -6,8 +6,8 @@ with:
```dart
QuillToolbar.simple(
controller: _controller,
configurations: QuillSimpleToolbarConfigurations(
controller: _controller,
sharedConfigurations: const QuillSharedConfigurations(
locale: Locale('de'),
),
@ -15,8 +15,8 @@ QuillToolbar.simple(
),
Expanded(
child: QuillEditor.basic(
controller: _controller,
configurations: QuillEditorConfigurations(
controller: _controller,
sharedConfigurations: const QuillSharedConfigurations(
locale: Locale('de'),
),

@ -18,12 +18,14 @@ import 'embeds/timestamp_embed.dart';
class MyQuillEditor extends StatelessWidget {
const MyQuillEditor({
required this.controller,
required this.configurations,
required this.scrollController,
required this.focusNode,
super.key,
});
final QuillController controller;
final QuillEditorConfigurations configurations;
final ScrollController scrollController;
final FocusNode focusNode;
@ -34,6 +36,7 @@ class MyQuillEditor extends StatelessWidget {
return QuillEditor(
scrollController: scrollController,
focusNode: focusNode,
controller: controller,
configurations: configurations.copyWith(
elementOptions: const QuillEditorElementOptions(
codeBlock: QuillEditorCodeBlockElementOptions(
@ -51,9 +54,9 @@ class MyQuillEditor extends StatelessWidget {
height: 1.15,
fontWeight: FontWeight.w300,
),
const HorizontalSpacing(0, 0),
HorizontalSpacing.zero,
const VerticalSpacing(16, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
null,
),
sizeSmall: defaultTextStyle.style.copyWith(fontSize: 9),

@ -203,8 +203,12 @@ class MyQuillToolbar extends StatelessWidget {
);
}
return QuillToolbar.simple(
controller: controller,
/// configurations parameter:
/// Optional: if not provided will use the configuration set when the controller was instantiated.
/// Override: Provide parameter here to override the default configuration - useful if configuration will change.
configurations: QuillSimpleToolbarConfigurations(
controller: controller,
showAlignmentButtons: true,
multiRowsDisplay: true,
fontFamilyValues: {

@ -33,6 +33,7 @@ class QuillScreen extends StatefulWidget {
}
class _QuillScreenState extends State<QuillScreen> {
/// Instantiate the controller
final _controller = QuillController.basic();
final _editorFocusNode = FocusNode();
final _editorScrollController = ScrollController();
@ -101,9 +102,9 @@ class _QuillScreenState extends State<QuillScreen> {
builder: (context) {
return Expanded(
child: MyQuillEditor(
controller: _controller,
configurations: QuillEditorConfigurations(
sharedConfigurations: _sharedConfigurations,
controller: _controller,
),
scrollController: _editorScrollController,
focusNode: _editorFocusNode,

@ -18,14 +18,14 @@ class _SimpleScreenState extends State<SimpleScreen> {
body: Column(
children: [
QuillToolbar.simple(
configurations:
QuillSimpleToolbarConfigurations(controller: _controller),
controller: _controller,
configurations: const QuillSimpleToolbarConfigurations(),
),
Expanded(
child: QuillEditor.basic(
configurations: QuillEditorConfigurations(
controller: _controller,
padding: const EdgeInsets.all(16),
controller: _controller,
configurations: const QuillEditorConfigurations(
padding: EdgeInsets.all(16),
),
),
),

@ -4,6 +4,50 @@
All notable changes to this project will be documented in this file.
## 10.1.6
* fixed #1295 Double click to select text sometimes doesn't work. by @li8607 in https://github.com/singerdmx/flutter-quill/pull/2086
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.5...v10.1.6
## 10.1.5
* ref: add `VerticalSpacing.zero` and `HorizontalSpacing.zero` named constants by @adil192 in https://github.com/singerdmx/flutter-quill/pull/2083
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.4...v10.1.5
## 10.1.4
* Fix: collectStyles for lists and alignments by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2082
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.3...v10.1.4
## 10.1.3
* Move Controller outside of configurations data class by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2078
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.2...v10.1.3
## 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

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

@ -1,6 +1,6 @@
name: flutter_quill_extensions
description: Embed extensions for flutter_quill including image, video, formula and etc.
version: 10.1.0
version: 10.1.6
homepage: 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/

@ -4,6 +4,50 @@
All notable changes to this project will be documented in this file.
## 10.1.6
* fixed #1295 Double click to select text sometimes doesn't work. by @li8607 in https://github.com/singerdmx/flutter-quill/pull/2086
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.5...v10.1.6
## 10.1.5
* ref: add `VerticalSpacing.zero` and `HorizontalSpacing.zero` named constants by @adil192 in https://github.com/singerdmx/flutter-quill/pull/2083
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.4...v10.1.5
## 10.1.4
* Fix: collectStyles for lists and alignments by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2082
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.3...v10.1.4
## 10.1.3
* Move Controller outside of configurations data class by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/2078
**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v10.1.2...v10.1.3
## 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

@ -1,6 +1,6 @@
name: flutter_quill_test
description: Test utilities for flutter_quill which includes methods to simplify interacting with the editor in test cases.
version: 10.1.0
version: 10.1.6
homepage: 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/

@ -9,4 +9,6 @@ class HorizontalSpacing {
final double left;
final double right;
static const zero = HorizontalSpacing(0, 0);
}

@ -9,4 +9,6 @@ class VerticalSpacing {
final double top;
final double bottom;
static const zero = VerticalSpacing(0, 0);
}

@ -6,12 +6,16 @@ import 'quill_controller.dart';
extension QuillControllerExt on BuildContext {
QuillController? get quilController {
// ignore: deprecated_member_use_from_same_package
return quillSimpleToolbarConfigurations?.controller ??
// ignore: deprecated_member_use_from_same_package
quillEditorConfigurations?.controller;
}
QuillController get requireQuillController {
// ignore: deprecated_member_use_from_same_package
return quillSimpleToolbarConfigurations?.controller ??
// ignore: deprecated_member_use_from_same_package
quillEditorConfigurations?.controller ??
(throw ArgumentError(
'The quill provider is required, you must only call requireQuillController inside the QuillToolbar and QuillEditor'));

@ -17,7 +17,9 @@ import '../document/nodes/embeddable.dart';
import '../document/nodes/leaf.dart';
import '../document/structs/doc_change.dart';
import '../document/style.dart';
import '../editor/config/editor_configurations.dart';
import '../editor_toolbar_controller_shared/clipboard/clipboard_service_provider.dart';
import '../toolbar/config/simple_toolbar_configurations.dart';
import 'quill_controller_configurations.dart';
typedef ReplaceTextCallback = bool Function(int index, int len, Object? data);
@ -39,19 +41,36 @@ class QuillController extends ChangeNotifier {
_selection = selection;
factory QuillController.basic(
{QuillControllerConfigurations configurations =
const QuillControllerConfigurations(),
FocusNode? editorFocusNode}) {
return QuillController(
configurations: configurations,
editorFocusNode: editorFocusNode,
document: Document(),
selection: const TextSelection.collapsed(offset: 0),
);
}
{QuillControllerConfigurations configurations =
const QuillControllerConfigurations(),
FocusNode? editorFocusNode}) =>
QuillController(
configurations: configurations,
editorFocusNode: editorFocusNode,
document: Document(),
selection: const TextSelection.collapsed(offset: 0),
);
final QuillControllerConfigurations configurations;
/// Editor configurations
///
/// Caches configuration set in QuillEditor ctor.
QuillEditorConfigurations? _editorConfigurations;
QuillEditorConfigurations get editorConfigurations =>
_editorConfigurations ?? const QuillEditorConfigurations();
set editorConfigurations(QuillEditorConfigurations? value) =>
_editorConfigurations = value;
/// Toolbar configurations
///
/// Caches configuration set in QuillSimpleToolbar ctor.
QuillSimpleToolbarConfigurations? _toolbarConfigurations;
QuillSimpleToolbarConfigurations get toolbarConfigurations =>
_toolbarConfigurations ?? const QuillSimpleToolbarConfigurations();
set toolbarConfigurations(QuillSimpleToolbarConfigurations? value) =>
_toolbarConfigurations = value;
/// Document managed by this controller.
Document _document;
@ -476,10 +495,13 @@ class QuillController extends ChangeNotifier {
/// Clipboard caches last copy to allow paste with styles. Static to allow paste between multiple instances of editor.
static String _pastePlainText = '';
static Delta _pasteDelta = Delta();
static List<OffsetValue> _pasteStyleAndEmbed = <OffsetValue>[];
String get pastePlainText => _pastePlainText;
Delta get pasteDelta => _pasteDelta;
List<OffsetValue> get pasteStyleAndEmbed => _pasteStyleAndEmbed;
bool readOnly;
/// Used to give focus to the editor following a toolbar action
@ -495,9 +517,17 @@ class QuillController extends ChangeNotifier {
bool clipboardSelection(bool copy) {
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();
/// 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) {
Clipboard.setData(ClipboardData(text: _pastePlainText));
if (!copy) {
@ -538,28 +568,7 @@ class QuillController extends ChangeNotifier {
// See https://github.com/flutter/flutter/issues/11427
final plainTextClipboardData =
await Clipboard.getData(Clipboard.kTextPlain);
if (plainTextClipboardData != null) {
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,
);
}
}
if (pasteUsingPlainOrDelta(plainTextClipboardData?.text)) {
updateEditor?.call();
return true;
}
@ -572,6 +581,28 @@ class QuillController extends ChangeNotifier {
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) {
replaceText(
selection.start,

@ -1,6 +1,17 @@
import '../editor/config/editor_configurations.dart'
show QuillEditorConfigurations;
class QuillControllerConfigurations {
const QuillControllerConfigurations(
{this.onClipboardPaste, this.requireScriptFontFeatures = false});
{@Deprecated(
'This parameter is not used and will be removed in future versions.')
this.editorConfigurations,
this.onClipboardPaste,
this.requireScriptFontFeatures = false});
@Deprecated(
'This parameter is not used and will be removed in future versions.')
final QuillEditorConfigurations? editorConfigurations;
/// 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) {
/// 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;
} else if (userOperation.isInsert && actualOperation.isRetain) {
diff -= userOperation.length!;
} else if (userOperation.isDelete && actualOperation.isRetain) {
diff += userOperation.length!;
} 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!;
}
}

@ -6,6 +6,7 @@ import '../../quill_delta.dart';
import '../common/structs/offset_value.dart';
import '../common/structs/segment_leaf_node.dart';
import '../delta/delta_x.dart';
import '../editor/config/editor_configurations.dart';
import '../editor/embed/embed_editor_builder.dart';
import '../rules/rule.dart';
import 'attribute.dart';
@ -191,17 +192,28 @@ class Document {
res = queryChild(--index);
}
//
final style = (res.node as Line).collectStyle(res.offset, 0);
var style = (res.node as Line).collectStyle(res.offset, 0);
final remove = <Attribute>{};
final add = <String, Attribute>{};
for (final attr in style.attributes.values) {
if (!Attribute.inlineKeys.contains(attr.key)) {
if (!current.containsKey(attr.key)) {
remove.add(attr);
} else {
/// Trap for type of block attribute is changing
final curAttr = current.attributes[attr.key];
if (curAttr!.value != attr.value) {
remove.add(attr);
add[curAttr.key] = curAttr;
}
}
}
}
if (remove.isNotEmpty) {
return style.removeAll(remove);
style = style.removeAll(remove);
}
if (add.isNotEmpty) {
style.attributes.addAll(add);
}
return style;
}
@ -239,9 +251,9 @@ class Document {
}
/// 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);
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].

@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
import '../../../../quill_delta.dart';
import '../../common/structs/offset_value.dart';
import '../../editor/config/editor_configurations.dart';
import '../../editor/embed/embed_editor_builder.dart';
import '../../editor_toolbar_controller_shared/copy_cut_service/copy_cut_service_provider.dart';
import '../attribute.dart';
@ -512,14 +513,17 @@ base class Line extends QuillContainer<Leaf?> {
}
/// 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();
_getPlainText(offset, len, plainText);
_getPlainText(offset, len, plainText, config);
return plainText.toString();
}
int _getNodeText(Leaf node, StringBuffer buffer, int offset, int remaining) {
final text = node.toPlainText();
int _getNodeText(Leaf node, StringBuffer buffer, int offset, int remaining,
QuillEditorConfigurations? config) {
final text =
node.toPlainText(config?.embedBuilders, config?.unknownEmbedBuilder);
if (text == Embed.kObjectReplacementCharacter) {
final embed = node.value as Embeddable;
final provider = CopyCutServiceProvider.instance;
@ -539,12 +543,19 @@ base class Line extends QuillContainer<Leaf?> {
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);
buffer.write(text.substring(offset, end));
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;
final data = queryChild(offset, false);
var node = data.node as Leaf?;
@ -555,11 +566,12 @@ base class Line extends QuillContainer<Leaf?> {
plainText.write('\n');
len0 -= 1;
} else {
len0 = _getNodeText(node, plainText, offset - node.offset, len0);
len0 =
_getNodeText(node, plainText, offset - node.offset, len0, config);
while (!node!.isLast && len0 > 0) {
node = node.next as Leaf;
len0 = _getNodeText(node, plainText, 0, len0);
len0 = _getNodeText(node, plainText, 0, len0, config);
}
if (len0 > 0) {
@ -570,7 +582,7 @@ base class Line extends QuillContainer<Leaf?> {
}
if (len0 > 0 && nextLine != null) {
len0 = nextLine!._getPlainText(0, len0, plainText);
len0 = nextLine!._getPlainText(0, len0, plainText, config);
}
}

@ -24,7 +24,9 @@ class QuillEditorConfigurations extends Equatable {
/// Important note for the maintainers
/// When editing this class please update the [copyWith] function too.
const QuillEditorConfigurations({
required this.controller,
@Deprecated(
'controller should be passed directly to the editor - this parameter will be removed in future versions.')
this.controller,
this.sharedConfigurations = const QuillSharedConfigurations(),
this.scrollable = true,
this.padding = EdgeInsets.zero,
@ -86,7 +88,8 @@ class QuillEditorConfigurations extends Equatable {
final QuillSharedConfigurations sharedConfigurations;
final QuillController controller;
@Deprecated('controller will be removed in future versions.')
final QuillController? controller;
/// The text placeholder in the quill editor
final String? placeholder;
@ -97,7 +100,8 @@ class QuillEditorConfigurations extends Equatable {
/// by any shortcut or keyboard operation. The text is still selectable.
///
/// Defaults to `false`. Must not be `null`.
bool get readOnly => controller.readOnly;
// ignore: deprecated_member_use_from_same_package
bool get readOnly => controller?.readOnly != false;
/// Override [readOnly] for checkbox.
///
@ -381,7 +385,8 @@ class QuillEditorConfigurations extends Equatable {
@override
List<Object?> get props => [
placeholder,
controller.readOnly,
// ignore: deprecated_member_use_from_same_package
controller?.readOnly,
];
// We might use code generator like freezed but sometimes it can be limited
@ -445,6 +450,7 @@ class QuillEditorConfigurations extends Equatable {
}) {
return QuillEditorConfigurations(
sharedConfigurations: sharedConfigurations ?? this.sharedConfigurations,
// ignore: deprecated_member_use_from_same_package
controller: controller ?? this.controller,
placeholder: placeholder ?? this.placeholder,
checkBoxReadOnly: checkBoxReadOnly ?? this.checkBoxReadOnly,

@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import '../common/utils/platform.dart';
import '../controller/quill_controller.dart';
import '../document/attribute.dart';
import '../document/document.dart';
import '../document/nodes/container.dart' as container_node;
@ -120,36 +121,80 @@ abstract class RenderAbstractEditor implements TextLayoutMetrics {
}
class QuillEditor extends StatefulWidget {
const QuillEditor({
required this.configurations,
required this.focusNode,
required this.scrollController,
super.key,
});
/// Quick start guide:
///
/// Instantiate a controller:
/// QuillController _controller = QuillController.basic();
///
/// Connect the controller to the `QuillEditor` and `QuillSimpleToolbar` widgets.
/// QuillSimpleToolbar(
/// controller: _controller,
/// configurations: const QuillSimpleToolbarConfigurations(),
/// ),
/// Expanded(
/// child: QuillEditor.basic(
/// controller: _controller,
/// configurations: const QuillEditorConfigurations(),
/// ),
/// ),
///
factory QuillEditor({
required FocusNode focusNode,
required ScrollController scrollController,
/// Controller and configurations are required
///
/// Prefer: use controller and pass QuillEditorConfigurations in constructor for controller (using QuillControllerConfigurations).
/// Backward compatibility: use configurations and pass QuillController in constructor for configurations. (Will be removed in future versions.)
QuillController? controller,
QuillEditorConfigurations? configurations,
}) {
// ignore: deprecated_member_use_from_same_package
controller ??= configurations?.controller;
assert(controller != null,
'controller required. Provide controller directly (preferred) or indirectly through configurations (not recommended - will be removed in future versions).');
controller ??= QuillController(
document: Document(),
selection: const TextSelection.collapsed(offset: 0));
//
controller
..editorConfigurations = configurations
..editorFocusNode = focusNode;
//
return QuillEditor._(
focusNode: focusNode,
scrollController: scrollController,
controller: controller);
}
const QuillEditor._(
{required this.focusNode,
required this.scrollController,
required this.controller});
factory QuillEditor.basic({
/// The controller for the quill editor widget of flutter quill
QuillController? controller,
/// The configurations for the quill editor widget of flutter quill
required QuillEditorConfigurations configurations,
QuillEditorConfigurations? configurations,
FocusNode? focusNode,
ScrollController? scrollController,
}) {
return QuillEditor(
scrollController: scrollController ?? ScrollController(),
focusNode: focusNode ?? FocusNode(),
configurations: configurations.copyWith(
textSelectionThemeData: configurations.textSelectionThemeData,
autoFocus: configurations.autoFocus,
expands: configurations.expands,
padding: configurations.padding,
keyboardAppearance: configurations.keyboardAppearance,
embedBuilders: configurations.embedBuilders,
editorKey: configurations.editorKey,
),
controller: controller,
configurations: configurations?.copyWith(),
);
}
/// The controller for the quill editor widget of flutter quill
final QuillController controller;
/// The configurations for the quill editor widget of flutter quill
final QuillEditorConfigurations configurations;
QuillEditorConfigurations get configurations =>
controller.editorConfigurations;
/// Controls whether this editor has keyboard focus.
final FocusNode focusNode;
@ -167,14 +212,13 @@ class QuillEditorState extends State<QuillEditor>
late EditorTextSelectionGestureDetectorBuilder
_selectionGestureDetectorBuilder;
QuillEditorConfigurations get configurations {
return widget.configurations;
}
QuillController get controller => widget.controller;
QuillEditorConfigurations get configurations => widget.configurations;
@override
void initState() {
super.initState();
_editorKey = configurations.editorKey ?? GlobalKey<EditorState>();
_selectionGestureDetectorBuilder =
_QuillEditorSelectionGestureDetectorBuilder(
@ -182,9 +226,7 @@ class QuillEditorState extends State<QuillEditor>
configurations.detectWordBoundary,
);
final focusNode =
widget.configurations.controller.editorFocusNode ?? widget.focusNode;
widget.configurations.controller.editorFocusNode = focusNode;
final focusNode = widget.focusNode;
if (configurations.autoFocus) {
focusNode.requestFocus();
@ -240,13 +282,13 @@ class QuillEditorState extends State<QuillEditor>
final child = FlutterQuillLocalizationsWidget(
child: QuillEditorProvider(
editorConfigurations: configurations,
controller: controller,
child: QuillEditorBuilderWidget(
builder: configurations.builder,
child: QuillRawEditor(
key: _editorKey,
controller: controller,
configurations: QuillRawEditorConfigurations(
controller: configurations.controller,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
scrollable: configurations.scrollable,
@ -254,7 +296,7 @@ class QuillEditorState extends State<QuillEditor>
configurations.enableMarkdownStyleConversion,
scrollBottomInset: configurations.scrollBottomInset,
padding: configurations.padding,
readOnly: configurations.readOnly,
readOnly: controller.readOnly,
checkBoxReadOnly: configurations.checkBoxReadOnly,
disableClipboard: configurations.disableClipboard,
placeholder: configurations.placeholder,
@ -444,19 +486,19 @@ class _QuillEditorSelectionGestureDetectorBuilder
}
bool _isPositionSelected(TapUpDetails details) {
if (_state.configurations.controller.document.isEmpty()) {
if (_state.controller.document.isEmpty()) {
return false;
}
final pos = renderEditor!.getPositionForOffset(details.globalPosition);
final result = editor!.widget.configurations.controller.document
.querySegmentLeafNode(pos.offset);
final result =
editor!.widget.controller.document.querySegmentLeafNode(pos.offset);
final line = result.line;
if (line == null) {
return false;
}
final segmentLeaf = result.leaf;
if (segmentLeaf == null && line.length == 1) {
editor!.widget.configurations.controller.updateSelection(
editor!.widget.controller.updateSelection(
TextSelection.collapsed(offset: pos.offset),
ChangeSource.local,
);
@ -1060,7 +1102,7 @@ class RenderEditor extends RenderEditableContainerBox
TextSelection selectWordAtPosition(TextPosition position) {
final word = getWordBoundary(position);
// When long-pressing past the end of the text, we want a collapsed cursor.
if (position.offset >= word.end) {
if (position.offset > word.end) {
return TextSelection.fromPosition(position);
}
return TextSelection(baseOffset: word.start, extentOffset: word.end);

@ -2,15 +2,32 @@ import 'package:flutter/foundation.dart' show debugPrint, kDebugMode;
import 'package:flutter/widgets.dart'
show BuildContext, InheritedWidget, Widget;
import '../controller/quill_controller.dart';
import 'config/editor_configurations.dart';
class QuillEditorProvider extends InheritedWidget {
const QuillEditorProvider({
QuillEditorProvider({
required super.child,
required this.editorConfigurations,
/// Controller and configurations are required but should only be provided from one.
///
/// Passing the controller as part of configurations is being deprecated and will be removed in the future.
/// Prefer: use controller and set QuillEditorConfigurations in the controller.
/// Current: use configurations and pass QuillController in constructor for configurations.
QuillController? controller,
@Deprecated(
'editorConfigurations are no longer needed and will be removed in future versions. Set configurations in the controller')
QuillEditorConfigurations? editorConfigurations,
super.key,
});
}) : editorConfigurations = editorConfigurations ??
controller?.editorConfigurations ??
const QuillEditorConfigurations(),
controller = controller ??
// ignore: deprecated_member_use_from_same_package
editorConfigurations?.controller ??
QuillController.basic();
final QuillController controller;
final QuillEditorConfigurations editorConfigurations;
@override
@ -52,8 +69,9 @@ class QuillEditorProvider extends InheritedWidget {
required QuillEditorProvider value,
required Widget child,
}) {
value.controller.editorConfigurations = value.editorConfigurations;
return QuillEditorProvider(
editorConfigurations: value.editorConfigurations,
controller: value.controller,
child: child,
);
}

@ -40,7 +40,6 @@ import '../../../toolbar/theme/quill_dialog_theme.dart';
@immutable
class QuillRawEditorConfigurations extends Equatable {
const QuillRawEditorConfigurations({
required this.controller,
required this.focusNode,
required this.scrollController,
required this.scrollBottomInset,
@ -49,6 +48,9 @@ class QuillRawEditorConfigurations extends Equatable {
required this.selectionCtrls,
required this.embedBuilder,
required this.autoFocus,
@Deprecated(
'controller should be passed directly to the editor - this parameter will be removed in future versions.')
this.controller,
this.showCursor = true,
this.scrollable = true,
this.padding = EdgeInsets.zero,
@ -93,7 +95,8 @@ class QuillRawEditorConfigurations extends Equatable {
});
/// Controls the document being edited.
final QuillController controller;
@Deprecated('controller will be removed in future versions.')
final QuillController? controller;
/// Controls whether this editor has keyboard focus.
final FocusNode focusNode;

@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart'
immutable;
import '../../common/structs/offset_value.dart';
import '../../controller/quill_controller.dart';
import '../editor.dart';
import '../widgets/text/text_selection.dart';
import 'config/raw_editor_configurations.dart';
@ -20,8 +21,14 @@ import 'raw_editor_state.dart';
class QuillRawEditor extends StatefulWidget {
QuillRawEditor({
required this.configurations,
controller,
super.key,
}) : assert(
}) :
// ignore: deprecated_member_use_from_same_package
assert((controller ?? configurations.controller) != null),
// ignore: deprecated_member_use_from_same_package
controller = controller ?? configurations.controller,
assert(
configurations.maxHeight == null || configurations.maxHeight! > 0,
'maxHeight cannot be null'),
assert(
@ -33,6 +40,7 @@ class QuillRawEditor extends StatefulWidget {
configurations.maxHeight! >= configurations.minHeight!,
'maxHeight cannot be null');
final QuillController controller;
final QuillRawEditorConfigurations configurations;
@override

@ -77,7 +77,7 @@ class QuillRawEditorState extends EditorState
// Cursors
late CursorCont _cursorCont;
QuillController get controller => widget.configurations.controller;
QuillController get controller => widget.controller;
// Focus
bool _didAutoFocus = false;
@ -1150,7 +1150,7 @@ class QuillRawEditorState extends EditorState
} else if (attrs.containsKey(Attribute.align.key)) {
return defaultStyles!.align!.horizontalSpacing;
}
return const HorizontalSpacing(0, 0);
return HorizontalSpacing.zero;
}
VerticalSpacing _getVerticalSpacingForBlock(
@ -1167,7 +1167,7 @@ class QuillRawEditorState extends EditorState
} else if (attrs.containsKey(Attribute.align.key)) {
return defaultStyles!.align!.verticalSpacing;
}
return const VerticalSpacing(0, 0);
return VerticalSpacing.zero;
}
void _didChangeTextEditingValueListener() {
@ -1279,9 +1279,8 @@ class QuillRawEditorState extends EditorState
_cursorCont.show.value = widget.configurations.showCursor;
_cursorCont.style = widget.configurations.cursorStyle;
if (controller != oldWidget.configurations.controller) {
oldWidget.configurations.controller
.removeListener(_didChangeTextEditingValue);
if (controller != oldWidget.controller) {
oldWidget.controller.removeListener(_didChangeTextEditingValue);
controller.addListener(_didChangeTextEditingValue);
updateRemoteValueIfNeeded();
}
@ -1298,7 +1297,7 @@ class QuillRawEditorState extends EditorState
updateKeepAlive();
}
if (controller.selection != oldWidget.configurations.controller.selection) {
if (controller.selection != oldWidget.controller.selection) {
_selectionOverlay?.update(textEditingValue);
}
@ -1353,7 +1352,7 @@ class QuillRawEditorState extends EditorState
/// operating on stale data.
void _markNeedsBuild() {
if (_dirty) {
// No need to rebuilt if it already darty
// No need to rebuilt if it already dirty
return;
}
setState(() {
@ -1631,7 +1630,7 @@ class QuillRawEditorState extends EditorState
final QuillEditorTextBoundary boundary;
// final TextEditingValue textEditingValue =
// _textEditingValueforTextLayoutMetrics;
// _textEditingValueForTextLayoutMetrics;
atomicTextBoundary = QuillEditorCharacterBoundary(textEditingValue);
// This isn't enough. Newline characters.
boundary = QuillEditorExpandedTextBoundary(

@ -11,22 +11,21 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
implements TextSelectionDelegate {
@override
TextEditingValue get textEditingValue {
return widget.configurations.controller.plainTextEditingValue;
return widget.controller.plainTextEditingValue;
}
set textEditingValue(TextEditingValue value) {
final cursorPosition = value.selection.extentOffset;
final oldText = widget.configurations.controller.document.toPlainText();
final oldText = widget.controller.document.toPlainText();
final newText = value.text;
final diff = getDiff(oldText, newText, cursorPosition);
if (diff.deleted == '' && diff.inserted == '') {
// Only changing selection range
widget.configurations.controller
.updateSelection(value.selection, ChangeSource.local);
widget.controller.updateSelection(value.selection, ChangeSource.local);
return;
}
widget.configurations.controller.replaceTextWithEmbeds(
widget.controller.replaceTextWithEmbeds(
diff.start, diff.deleted.length, diff.inserted, value.selection);
}

@ -198,10 +198,9 @@ mixin RawEditorStateTextInputClientMixin on EditorState
final cursorPosition = value.selection.extentOffset;
final diff = getDiff(oldText, text, cursorPosition);
if (diff.deleted.isEmpty && diff.inserted.isEmpty) {
widget.configurations.controller
.updateSelection(value.selection, ChangeSource.local);
widget.controller.updateSelection(value.selection, ChangeSource.local);
} else {
widget.configurations.controller.replaceText(
widget.controller.replaceText(
diff.start,
diff.deleted.length,
diff.inserted,

@ -271,7 +271,7 @@ class DefaultStyles {
),
baseHorizontalSpacing,
const VerticalSpacing(16, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
null),
h2: DefaultTextBlockStyle(
defaultTextStyle.style.copyWith(
@ -284,7 +284,7 @@ class DefaultStyles {
),
baseHorizontalSpacing,
const VerticalSpacing(8, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
null),
h3: DefaultTextBlockStyle(
defaultTextStyle.style.copyWith(
@ -297,7 +297,7 @@ class DefaultStyles {
),
baseHorizontalSpacing,
const VerticalSpacing(8, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
null,
),
h4: DefaultTextBlockStyle(
@ -311,7 +311,7 @@ class DefaultStyles {
),
baseHorizontalSpacing,
const VerticalSpacing(6, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
null,
),
h5: DefaultTextBlockStyle(
@ -325,7 +325,7 @@ class DefaultStyles {
),
baseHorizontalSpacing,
const VerticalSpacing(6, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
null,
),
h6: DefaultTextBlockStyle(
@ -339,42 +339,42 @@ class DefaultStyles {
),
baseHorizontalSpacing,
const VerticalSpacing(4, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
null,
),
lineHeightNormal: DefaultTextBlockStyle(
baseStyle.copyWith(height: 1.15),
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null,
),
lineHeightTight: DefaultTextBlockStyle(
baseStyle.copyWith(height: 1.30),
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null,
),
lineHeightOneAndHalf: DefaultTextBlockStyle(
baseStyle.copyWith(height: 1.55),
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null,
),
lineHeightDouble: DefaultTextBlockStyle(
baseStyle.copyWith(height: 2),
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null,
),
paragraph: DefaultTextBlockStyle(
baseStyle,
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null,
),
bold: const TextStyle(fontWeight: FontWeight.bold),
@ -422,8 +422,8 @@ class DefaultStyles {
color: Colors.grey.withOpacity(0.6),
),
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null),
lists: DefaultListBlockStyle(
baseStyle,
@ -453,7 +453,7 @@ class DefaultStyles {
),
baseHorizontalSpacing,
baseVerticalSpacing,
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(2),
@ -468,15 +468,15 @@ class DefaultStyles {
align: DefaultTextBlockStyle(
baseStyle,
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null,
),
leading: DefaultTextBlockStyle(
baseStyle,
baseHorizontalSpacing,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
VerticalSpacing.zero,
VerticalSpacing.zero,
null,
),
sizeSmall: const TextStyle(fontSize: 10),

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import '../../flutter_quill.dart'
show QuillToolbarProvider, kDefaultToolbarSize;
import '../controller/quill_controller.dart';
import '../l10n/widgets/localizations.dart';
import 'config/simple_toolbar_configurations.dart';
import 'config/toolbar_configurations.dart';
@ -40,8 +41,10 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
});
static QuillSimpleToolbar simple(
{required QuillSimpleToolbarConfigurations configurations}) {
{QuillController? controller,
QuillSimpleToolbarConfigurations? configurations}) {
return QuillSimpleToolbar(
controller: controller,
configurations: configurations,
);
}

@ -80,7 +80,9 @@ enum SearchButtonType {
@immutable
class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties {
const QuillSimpleToolbarConfigurations({
required this.controller,
@Deprecated(
'controller should be passed directly to the toolbar - this parameter will be removed in future versions.')
this.controller,
super.sharedConfigurations,
super.toolbarSectionSpacing = kToolbarSectionSpacing,
super.toolbarIconAlignment = WrapAlignment.center,
@ -166,7 +168,8 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties {
final Map<String, String>? fontFamilyValues;
final QuillController controller;
@Deprecated('controller will be removed in future versions.')
final QuillController? controller;
/// By default it will be
/// ```dart

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import '../controller/quill_controller.dart';
import '../document/attribute.dart';
import '../document/document.dart';
import 'base_toolbar.dart';
import 'buttons/alignment/select_alignment_buttons.dart';
import 'buttons/arrow_indicated_list_button.dart';
@ -9,13 +11,37 @@ import 'simple_toolbar_provider.dart';
class QuillSimpleToolbar extends StatelessWidget
implements PreferredSizeWidget {
const QuillSimpleToolbar({
required this.configurations,
factory QuillSimpleToolbar({
required QuillSimpleToolbarConfigurations? configurations,
QuillController? controller,
Key? key,
}) {
// ignore: deprecated_member_use_from_same_package
controller ??= configurations?.controller;
assert(controller != null,
'controller required. Provide controller directly (preferred) or indirectly through configurations (not recommended - will be removed in future versions).');
controller ??= QuillController(
document: Document(),
selection: const TextSelection.collapsed(offset: 0));
//
controller.toolbarConfigurations = configurations;
//
return QuillSimpleToolbar._(
controller: controller,
key: key,
);
}
const QuillSimpleToolbar._({
required this.controller,
super.key,
});
final QuillController controller;
/// The configurations for the toolbar widget of flutter quill
final QuillSimpleToolbarConfigurations configurations;
QuillSimpleToolbarConfigurations get configurations =>
controller.toolbarConfigurations;
double get _toolbarSize => configurations.toolbarSize * 1.4;
@ -23,34 +49,6 @@ class QuillSimpleToolbar extends StatelessWidget
Widget build(BuildContext context) {
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) {
final toolbarConfigurations =
context.requireQuillSimpleToolbarConfigurations;
@ -58,7 +56,6 @@ class QuillSimpleToolbar extends StatelessWidget
final globalIconSize = toolbarConfigurations.buttonOptions.base.iconSize;
final axis = toolbarConfigurations.axis;
final globalController = configurations.controller;
final divider = SizedBox(
height: _toolbarSize,
@ -68,258 +65,254 @@ class QuillSimpleToolbar extends StatelessWidget
space: configurations.sectionDividerSpace,
));
return [
if (configurations.showUndo)
QuillToolbarHistoryButton(
isUndo: true,
options: toolbarConfigurations.buttonOptions.undoHistory,
controller: globalController,
),
if (configurations.showRedo)
QuillToolbarHistoryButton(
isUndo: false,
options: toolbarConfigurations.buttonOptions.redoHistory,
controller: globalController,
),
if (configurations.showFontFamily)
QuillToolbarFontFamilyButton(
options: toolbarConfigurations.buttonOptions.fontFamily,
controller: globalController,
),
if (configurations.showFontSize)
QuillToolbarFontSizeButton(
options: toolbarConfigurations.buttonOptions.fontSize,
controller: globalController,
),
if (configurations.showBoldButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
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,
final groups = [
[
if (configurations.showUndo)
QuillToolbarHistoryButton(
isUndo: true,
options: toolbarConfigurations.buttonOptions.undoHistory,
controller: controller,
),
if (configurations.showRedo)
QuillToolbarHistoryButton(
isUndo: false,
options: toolbarConfigurations.buttonOptions.redoHistory,
controller: controller,
),
if (configurations.showFontFamily)
QuillToolbarFontFamilyButton(
options: toolbarConfigurations.buttonOptions.fontFamily,
controller: controller,
),
if (configurations.showFontSize)
QuillToolbarFontSizeButton(
options: toolbarConfigurations.buttonOptions.fontSize,
controller: controller,
),
if (configurations.showBoldButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
options: toolbarConfigurations.buttonOptions.bold,
controller: controller,
),
if (configurations.showItalicButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
options: toolbarConfigurations.buttonOptions.italic,
controller: controller,
),
if (configurations.showUnderLineButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
options: toolbarConfigurations.buttonOptions.underLine,
controller: controller,
),
if (configurations.showStrikeThrough)
QuillToolbarToggleStyleButton(
attribute: Attribute.strikeThrough,
options: toolbarConfigurations.buttonOptions.strikeThrough,
controller: controller,
),
),
if (configurations.showDirection)
QuillToolbarToggleStyleButton(
attribute: Attribute.rtl,
options: toolbarConfigurations.buttonOptions.direction,
controller: globalController,
),
if (configurations.showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
divider,
if (configurations.showLineHeightButton)
QuillToolbarSelectLineHeightStyleDropdownButton(
controller: globalController,
options: toolbarConfigurations
.buttonOptions.selectLineHeightStyleDropdownButton,
),
if (configurations.showHeaderStyle) ...[
if (configurations.headerStyleType.isOriginal)
QuillToolbarSelectHeaderStyleDropdownButton(
controller: globalController,
if (configurations.showInlineCode)
QuillToolbarToggleStyleButton(
attribute: Attribute.inlineCode,
options: toolbarConfigurations.buttonOptions.inlineCode,
controller: controller,
),
if (configurations.showSubscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.subscript,
options: toolbarConfigurations.buttonOptions.subscript,
controller: controller,
),
if (configurations.showSuperscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.superscript,
options: toolbarConfigurations.buttonOptions.superscript,
controller: controller,
),
if (configurations.showSmallButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.small,
options: toolbarConfigurations.buttonOptions.small,
controller: controller,
),
if (configurations.showColorButton)
QuillToolbarColorButton(
controller: controller,
isBackground: false,
options: toolbarConfigurations.buttonOptions.color,
),
if (configurations.showBackgroundColorButton)
QuillToolbarColorButton(
options: toolbarConfigurations.buttonOptions.backgroundColor,
controller: controller,
isBackground: true,
),
if (configurations.showClearFormat)
QuillToolbarClearFormatButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.clearFormat,
),
if (theEmbedButtons != null)
for (final builder in theEmbedButtons)
builder(
controller,
globalIconSize ?? kDefaultIconSize,
context.quillToolbarBaseButtonOptions?.iconTheme,
configurations.dialogTheme),
],
[
if (configurations.showAlignmentButtons)
QuillToolbarSelectAlignmentButtons(
controller: controller,
options: toolbarConfigurations
.buttonOptions.selectHeaderStyleDropdownButton,
)
else
QuillToolbarSelectHeaderStyleButtons(
controller: globalController,
options:
toolbarConfigurations.buttonOptions.selectHeaderStyleButtons,
.buttonOptions.selectAlignmentButtons
.copyWith(
showLeftAlignment: configurations.showLeftAlignment,
showCenterAlignment: configurations.showCenterAlignment,
showRightAlignment: configurations.showRightAlignment,
showJustifyAlignment: configurations.showJustifyAlignment,
),
),
if (configurations.showDirection)
QuillToolbarToggleStyleButton(
attribute: Attribute.rtl,
options: toolbarConfigurations.buttonOptions.direction,
controller: controller,
),
],
if (configurations.showDividers &&
configurations.showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
divider,
if (configurations.showListNumbers)
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
options: toolbarConfigurations.buttonOptions.listNumbers,
controller: globalController,
),
if (configurations.showListBullets)
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
options: toolbarConfigurations.buttonOptions.listBullets,
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.showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5])) ...[
divider,
[
if (configurations.showLineHeightButton)
QuillToolbarSelectLineHeightStyleDropdownButton(
controller: controller,
options: toolbarConfigurations
.buttonOptions.selectLineHeightStyleDropdownButton,
),
if (configurations.showHeaderStyle) ...[
if (configurations.headerStyleType.isOriginal)
QuillToolbarSelectHeaderStyleDropdownButton(
controller: controller,
options: toolbarConfigurations
.buttonOptions.selectHeaderStyleDropdownButton,
)
else
QuillToolbarSelectHeaderStyleButtons(
controller: controller,
options: toolbarConfigurations
.buttonOptions.selectHeaderStyleButtons,
),
],
],
[
if (configurations.showListNumbers)
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
options: toolbarConfigurations.buttonOptions.listNumbers,
controller: controller,
),
if (configurations.showListBullets)
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
options: toolbarConfigurations.buttonOptions.listBullets,
controller: controller,
),
if (configurations.showListCheck)
QuillToolbarToggleCheckListButton(
options: toolbarConfigurations.buttonOptions.toggleCheckList,
controller: controller,
),
if (configurations.showCodeBlock)
QuillToolbarToggleStyleButton(
attribute: Attribute.codeBlock,
options: toolbarConfigurations.buttonOptions.codeBlock,
controller: controller,
),
],
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.showDividers &&
isButtonGroupShown[4] &&
isButtonGroupShown[5])
divider,
if (configurations.showLink)
toolbarConfigurations.linkStyleType.isOriginal
? QuillToolbarLinkStyleButton(
controller: globalController,
options: toolbarConfigurations.buttonOptions.linkStyle,
)
: QuillToolbarLinkStyleButton2(
controller: globalController,
options: toolbarConfigurations.buttonOptions.linkStyle2,
[
if (configurations.showQuote)
QuillToolbarToggleStyleButton(
options: toolbarConfigurations.buttonOptions.quote,
controller: controller,
attribute: Attribute.blockQuote,
),
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: controller,
isIncrease: true,
options: toolbarConfigurations.buttonOptions.indentIncrease,
),
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: controller,
isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease,
),
],
[
if (configurations.showLink)
toolbarConfigurations.linkStyleType.isOriginal
? QuillToolbarLinkStyleButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.linkStyle,
)
: QuillToolbarLinkStyleButton2(
controller: controller,
options: toolbarConfigurations.buttonOptions.linkStyle2,
),
if (configurations.showSearchButton)
switch (configurations.searchButtonType) {
SearchButtonType.legacy => QuillToolbarLegacySearchButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.search,
),
if (configurations.showSearchButton)
switch (configurations.searchButtonType) {
SearchButtonType.legacy => QuillToolbarLegacySearchButton(
controller: globalController,
options: toolbarConfigurations.buttonOptions.search,
),
SearchButtonType.modern => QuillToolbarSearchButton(
controller: globalController,
options: toolbarConfigurations.buttonOptions.search,
),
},
if (configurations.showClipboardCut)
QuillToolbarClipboardButton(
options: toolbarConfigurations.buttonOptions.clipboardCut,
controller: globalController,
clipboardAction: ClipboardAction.cut,
),
if (configurations.showClipboardCopy)
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,
SearchButtonType.modern => QuillToolbarSearchButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.search,
),
},
if (configurations.showClipboardCut)
QuillToolbarClipboardButton(
options: toolbarConfigurations.buttonOptions.clipboardCut,
controller: controller,
clipboardAction: ClipboardAction.cut,
),
if (configurations.showClipboardCopy)
QuillToolbarClipboardButton(
options: toolbarConfigurations.buttonOptions.clipboardCopy,
controller: controller,
clipboardAction: ClipboardAction.copy,
),
if (configurations.showClipboardPaste)
QuillToolbarClipboardButton(
options: toolbarConfigurations.buttonOptions.clipboardPaste,
controller: controller,
clipboardAction: ClipboardAction.paste,
),
],
[
for (final customButton in configurations.customButtons)
QuillToolbarCustomButton(
options: customButton,
controller: globalController,
controller: controller,
),
],
];
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(

@ -1,6 +1,6 @@
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.
version: 10.1.0
version: 10.1.6
homepage: https://1o24bbs.com/c/bulletjournal/108/
repository: https://github.com/singerdmx/flutter-quill/
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -19,10 +19,10 @@ void main() {
MaterialApp(
home: Scaffold(
body: QuillSimpleToolbar(
configurations: QuillSimpleToolbarConfigurations(
controller: controller,
controller: controller,
configurations: const QuillSimpleToolbarConfigurations(
showRedo: false,
customButtons: const [
customButtons: [
QuillToolbarCustomButtonOptions(
tooltip: tooltip,
)
@ -56,9 +56,7 @@ void main() {
setUp(() {
controller = QuillController.basic();
editor = QuillEditor.basic(
configurations: QuillEditorConfigurations(
controller: controller,
),
controller: controller,
);
});
@ -142,8 +140,8 @@ void main() {
home: QuillEditor(
focusNode: FocusNode(),
scrollController: ScrollController(),
configurations: QuillEditorConfigurations(
controller: controller,
controller: controller,
configurations: const QuillEditorConfigurations(
autoFocus: true,
expands: true,
),

@ -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'));
});
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', () {
Style select(int start, int end) {
controller.updateSelection(

@ -4,6 +4,60 @@ import 'package:test/test.dart';
void main() {
group('collectStyle', () {
/// Lists and alignments have the same block attribute key but can have different values.
/// Changing the format value updates the document but must also update the toolbar button state
/// by ensuring the collectStyles method returns the attribute selected for the newly entered line.
test('Change block value type', () {
void doTest(Map<String, dynamic> start, Attribute attr,
Map<String, dynamic> change) {
/// Create a document with 2 lines of block attribute using [start]
/// Change the format of the last line using [attr] and verify [change]
final delta = Delta()
..insert('A')
..insert('\n', start)
..insert('B')
..insert('\n', start);
final document = Document.fromDelta(delta)
/// insert a newline
..insert(3, '\n');
/// Verify inserted blank line and block type has not changed
expect(
document.toDelta(),
Delta()
..insert('A')
..insert('\n', start)
..insert('B')
..insert('\n\n', start));
/// Change format of last (empty) line
document.format(4, 0, attr);
expect(
document.toDelta(),
Delta()
..insert('A')
..insert('\n', start)
..insert('B')
..insert('\n', start)
..insert('\n', change),
reason: 'document updated');
/// Verify that the reported style reflects the newly formatted state
expect(document.collectStyle(4, 0), Style.attr({attr.key: attr}),
reason: 'collectStyle reporting correct attribute');
}
doTest({'list': 'ordered'}, const ListAttribute('bullet'),
{'list': 'bullet'});
doTest({'list': 'checked'}, const ListAttribute('bullet'),
{'list': 'bullet'});
doTest({'align': 'center'}, const AlignAttribute('right'),
{'align': 'right'});
doTest({'align': 'left'}, const AlignAttribute('center'),
{'align': 'center'});
});
/// Enter key inserts newline as plain text without inline styles.
/// collectStyle needs to retrieve style of preceding line
test('Simulate double enter key at end', () {

@ -24,11 +24,11 @@ void main() {
await tester.pumpWidget(
MaterialApp(
home: QuillEditor.basic(
controller: controller,
// ignore: avoid_redundant_argument_values
configurations: QuillEditorConfigurations(
controller: controller,
// ignore: avoid_redundant_argument_values
),
configurations: const QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
),
),
),
);
@ -44,8 +44,8 @@ void main() {
home: QuillEditor(
focusNode: FocusNode(),
scrollController: ScrollController(),
controller: controller,
configurations: QuillEditorConfigurations(
controller: controller,
// ignore: avoid_redundant_argument_values
autoFocus: true,
expands: true,
@ -116,9 +116,9 @@ void main() {
home: QuillEditor(
focusNode: FocusNode(),
scrollController: ScrollController(),
controller: controller,
// ignore: avoid_redundant_argument_values
configurations: QuillEditorConfigurations(
controller: controller,
// ignore: avoid_redundant_argument_values
autoFocus: true,
expands: true,
@ -146,7 +146,8 @@ void main() {
await tester.pumpWidget(
MaterialApp(
home: QuillEditor.basic(
configurations: QuillEditorConfigurations(controller: controller),
controller: controller,
configurations: const QuillEditorConfigurations(),
focusNode: editorFocusNode,
),
),

Loading…
Cancel
Save