New dev update (#1582)

* Prepare to release
pull/1601/head v9.0.0
Ellet 1 year ago committed by GitHub
parent 1d581858ac
commit e063afe61d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/workflows/build.yml
  2. 9
      CHANGELOG.md
  3. 9
      README.md
  4. 9
      flutter_quill_extensions/CHANGELOG.md
  5. 8
      flutter_quill_extensions/lib/extensions/controller_ext.dart
  6. 96
      flutter_quill_extensions/lib/utils/quill_image_utils.dart
  7. 4
      flutter_quill_extensions/pubspec.yaml
  8. 9
      flutter_quill_test/CHANGELOG.md
  9. 2
      flutter_quill_test/pubspec.yaml
  10. 3
      lib/flutter_quill.dart
  11. 2
      lib/quill_delta.dart
  12. 30
      lib/src/models/documents/document.dart
  13. 2
      lib/src/models/documents/history.dart
  14. 2
      lib/src/models/documents/nodes/block.dart
  15. 2
      lib/src/models/documents/nodes/leaf.dart
  16. 2
      lib/src/models/documents/nodes/line.dart
  17. 2
      lib/src/models/documents/nodes/node.dart
  18. 829
      lib/src/models/quill_delta.dart
  19. 2
      lib/src/models/rules/delete.dart
  20. 2
      lib/src/models/rules/format.dart
  21. 2
      lib/src/models/rules/insert.dart
  22. 2
      lib/src/models/rules/rule.dart
  23. 2
      lib/src/models/structs/doc_change.dart
  24. 1
      lib/src/packages/quill_markdown/delta_to_markdown.dart
  25. 1
      lib/src/packages/quill_markdown/markdown_to_delta.dart
  26. 1
      lib/src/packages/quill_markdown/utils.dart
  27. 2
      lib/src/utils/delta.dart
  28. 17
      lib/src/widgets/quill/quill_controller.dart
  29. 4
      lib/src/widgets/raw_editor/raw_editor_state.dart
  30. 2
      lib/src/widgets/toolbar/base_toolbar.dart
  31. 37
      lib/src/widgets/toolbar/buttons/alignment/select_alignment_button.dart
  32. 8
      lib/src/widgets/toolbar/buttons/alignment/select_alignment_buttons.dart
  33. 43
      lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_button.dart
  34. 16
      lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart
  35. 4
      lib/src/widgets/toolbar/simple_toolbar.dart
  36. 7
      pubspec.yaml
  37. 9
      quill_html_converter/CHANGELOG.md
  38. 2
      quill_html_converter/pubspec.yaml
  39. 1
      test/widgets/controller_test.dart
  40. 2
      version.dart

@ -4,6 +4,7 @@ on:
push:
paths:
- 'pubspec.yaml'
- 'flutter_quill_extensions/pubspec.yaml'
jobs:
build_linux:

@ -2,7 +2,14 @@
All notable changes to this project will be documented in this file.
## 9.0.0-dev-11
## 9.0.0
* This version is quite stable but it's not how we wanted to be, because the lack of time and there are not too many maintainers active, we decided to publish it, we might make a new breaking changes verion
## 9.0.1-dev.1
* Flutter Quill Extensions:
* Update `QuillImageUtilities` and fixining some bugs
## 9.0.1-dev
* Test new GitHub workflows
## 9.0.0-dev-10

@ -43,6 +43,7 @@ it's in the GitHub repo instead.
- [Table of contents](#table-of-contents)
- [Screenshots](#screenshots)
- [Installation](#installation)
- [Platform Specific Configurations](#platform-specific-configurations)
- [Usage](#usage)
- [Migration](#migration)
- [Input / Output](#input--output)
@ -102,7 +103,7 @@ dependencies:
These versions are tested and well-supported, you shouldn't get a build failure -->
## Usage
## Platform Specific Configurations
Before using the package, we must inform you the package use the following plugins:
```
@ -112,7 +113,11 @@ Before using the package, we must inform you the package use the following plugi
super_clipboard
```
All of them doesn't require any platform spesefic setup, except [super_clipboard](https://pub.dev/packages/super_clipboard) which needs some setup on Android only, it's optional but to support copying images and pasting them into editor then you must setup it, open the page in pub.dev and read the `README.md` to get the instructions.
All of them doesn't require any platform specific setup, except [super_clipboard](https://pub.dev/packages/super_clipboard) which needs some setup on Android only, it's used to support copying images and pasting them into editor then you must setup it, open the page in pub.dev and read the `README.md` to get the instructions.
The minSdkVersion is `23` as `super_clipboard` requires it
## Usage
First, you need to instantiate a controller

@ -2,7 +2,14 @@
All notable changes to this project will be documented in this file.
## 9.0.0-dev-11
## 9.0.0
* This version is quite stable but it's not how we wanted to be, because the lack of time and there are not too many maintainers active, we decided to publish it, we might make a new breaking changes verion
## 9.0.1-dev.1
* Flutter Quill Extensions:
* Update `QuillImageUtilities` and fixining some bugs
## 9.0.1-dev
* Test new GitHub workflows
## 9.0.0-dev-10

@ -1,7 +1,5 @@
import 'package:flutter_quill/flutter_quill.dart';
import '../utils/quill_image_utils.dart';
/// Extension functions on [QuillController]
/// that make it easier to insert the embed blocks
///
@ -43,10 +41,4 @@ extension QuillControllerExt on QuillController {
..skipRequestKeyboard = true
..replaceText(index, length, BlockEmbed.video(videoUrl), null);
}
QuillImageUtilities get imageUtilities {
return QuillImageUtilities(
controller: this,
);
}
}

@ -2,10 +2,9 @@ import 'dart:io' show Directory, File, Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_quill/flutter_quill.dart' as quill;
import 'package:flutter_quill/quill_delta.dart';
import 'package:path/path.dart' as path;
import 'utils.dart';
typedef OnGenerateNewFileNameCallback = String Function(
String currentFileName,
String fileExt,
@ -13,10 +12,10 @@ typedef OnGenerateNewFileNameCallback = String Function(
class QuillImageUtilities {
const QuillImageUtilities({
required this.controller,
required this.document,
});
final quill.QuillController controller;
final quill.Document document;
/// Private function that is throw an error if the platform is web
static void _webIsNotSupported(String functionName) {
@ -172,31 +171,49 @@ class QuillImageUtilities {
required bool onlyLocalImages,
}) {
_webIsNotSupported('getImagesPathsFromDocument');
final images = controller.document.root.children
.whereType<quill.Line>()
.where((node) {
if (node.isEmpty) {
return false;
}
final firstNode = node.children.first;
if (firstNode is! quill.Embed) {
return false;
}
// final images = document.root.children
// .whereType<quill.Line>()
// .where((node) {
// if (node.isEmpty) {
// return false;
// }
// final firstNode = node.children.first;
// if (firstNode is! quill.Embed) {
// return false;
// }
// if (firstNode.value.type != quill.BlockEmbed.imageType) {
// return false;
// }
// final imageSource = firstNode.value.data;
// if (imageSource is! String) {
// return false;
// }
// if (onlyLocalImages && isHttpBasedUrl(imageSource)) {
// return false;
// }
// return imageSource.trim().isNotEmpty;
// })
// .toList()
// .map((e) => (e.children.first as quill.Embed).value.data as String);
final images = <String>[];
for (final item in document.toDelta().toJson()) {
if (item is! Map) {
return [];
}
if (!item.containsKey(Operation.insertKey)) {
return [];
}
final insertValue = item[Operation.insertKey];
if (firstNode.value.type != quill.BlockEmbed.imageType) {
return false;
}
final imageSource = firstNode.value.data;
if (imageSource is! String) {
return false;
}
if (onlyLocalImages && isHttpBasedUrl(imageSource)) {
return false;
}
return imageSource.trim().isNotEmpty;
})
.toList()
.map((e) => (e.children.first as quill.Embed).value.data as String);
// Check if the insert value is a map with the "image" key
if (insertValue is Map &&
insertValue.containsKey(quill.BlockEmbed.imageType)) {
final String imageUrl = insertValue[quill.BlockEmbed.imageType];
images.add(imageUrl);
}
}
return images;
}
@ -249,9 +266,9 @@ class QuillImageUtilities {
///
/// Returns a list of cached image paths found in the document.
/// On non-mobile platforms, this function returns an empty list.
Future<Iterable<String>> getCachedImagePathsFromDocument({
Iterable<String> getCachedImagePathsFromDocument({
String? replaceUnexistentImagesWith,
}) async {
}) {
_webIsNotSupported('getCachedImagePathsFromDocument');
final imagePaths = getImagesPathsFromDocument(
onlyLocalImages: true,
@ -262,25 +279,6 @@ class QuillImageUtilities {
final isCurrentImageCached = isImageCached(imagePath);
return isCurrentImageCached;
}).toList();
// Remove all the images that doesn't exists
for (final imagePath in cachesImagePaths) {
final file = File(imagePath);
final exists = await file.exists();
if (!exists) {
final index = cachesImagePaths.indexOf(imagePath);
if (index == -1) {
continue;
}
cachesImagePaths.removeAt(index);
if (replaceUnexistentImagesWith != null) {
cachesImagePaths.insert(
index,
replaceUnexistentImagesWith,
);
}
}
}
return cachesImagePaths;
}
}

@ -1,6 +1,6 @@
name: flutter_quill_extensions
description: Embed extensions for flutter_quill including image, video, formula and etc.
version: 9.0.0-dev-11
version: 9.0.0
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/
@ -44,7 +44,7 @@ dependencies:
url_launcher: ^6.2.1
super_clipboard: ^0.7.3
gal: ^2.1.3
gal_linux: ^0.0.1-dev-3
gal_linux: ^0.0.1
image_picker: ^1.0.4
dev_dependencies:

@ -2,7 +2,14 @@
All notable changes to this project will be documented in this file.
## 9.0.0-dev-11
## 9.0.0
* This version is quite stable but it's not how we wanted to be, because the lack of time and there are not too many maintainers active, we decided to publish it, we might make a new breaking changes verion
## 9.0.1-dev.1
* Flutter Quill Extensions:
* Update `QuillImageUtilities` and fixining some bugs
## 9.0.1-dev
* Test new GitHub workflows
## 9.0.0-dev-10

@ -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: 9.0.0-dev-11
version: 9.0.0
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/

@ -12,7 +12,6 @@ export 'src/models/documents/nodes/leaf.dart';
export 'src/models/documents/nodes/line.dart';
export 'src/models/documents/nodes/node.dart';
export 'src/models/documents/style.dart';
export 'src/models/quill_delta.dart';
export 'src/models/structs/doc_change.dart';
export 'src/models/structs/image_url.dart';
export 'src/models/structs/link_dialog_action.dart';
@ -33,6 +32,6 @@ export 'src/widgets/raw_editor/raw_editor.dart';
export 'src/widgets/raw_editor/raw_editor_state.dart';
export 'src/widgets/style_widgets/style_widgets.dart';
export 'src/widgets/toolbar/base_toolbar.dart';
export 'src/widgets/toolbar/buttons/select_header_style_button.dart';
export 'src/widgets/toolbar/buttons/hearder_style/select_header_style_button.dart';
export 'src/widgets/toolbar/simple_toolbar.dart';
export 'src/widgets/utils/provider.dart';

@ -1,3 +1,3 @@
library flutter_quill.delta;
export 'src/models/quill_delta.dart';
export 'package:dart_quill_delta/dart_quill_delta.dart';

@ -1,7 +1,7 @@
import 'dart:async' show StreamController;
import '../../../quill_delta.dart';
import '../../widgets/quill/embeds.dart';
import '../quill_delta.dart';
import '../rules/rule.dart';
import '../structs/doc_change.dart';
import '../structs/history_changed.dart';
@ -53,12 +53,13 @@ class Document {
_rules.setCustomRules(customRules);
}
final StreamController<DocChange> _observer = StreamController.broadcast();
final StreamController<DocChange> documentChangeObserver =
StreamController.broadcast();
final History _history = History();
final History history = History();
/// Stream of [DocChange]s applied to this document.
Stream<DocChange> get changes => _observer.stream;
Stream<DocChange> get changes => documentChangeObserver.stream;
/// Inserts [data] in this document at specified [index].
///
@ -285,7 +286,7 @@ class Document {
///
/// In case the [change] is invalid, behavior of this method is unspecified.
void compose(Delta delta, ChangeSource changeSource) {
assert(!_observer.isClosed);
assert(!documentChangeObserver.isClosed);
delta.trim();
assert(delta.isNotEmpty);
@ -320,21 +321,21 @@ class Document {
throw StateError('Compose failed');
}
final change = DocChange(originalDelta, delta, changeSource);
_observer.add(change);
_history.handleDocChange(change);
documentChangeObserver.add(change);
history.handleDocChange(change);
}
HistoryChanged undo() {
return _history.undo(this);
return history.undo(this);
}
HistoryChanged redo() {
return _history.redo(this);
return history.redo(this);
}
bool get hasUndo => _history.hasUndo;
bool get hasUndo => history.hasUndo;
bool get hasRedo => _history.hasRedo;
bool get hasRedo => history.hasRedo;
static Delta _transform(Delta delta) {
final res = Delta();
@ -384,8 +385,8 @@ class Document {
}
void close() {
_observer.close();
_history.clear();
documentChangeObserver.close();
history.clear();
}
/// Returns plain text representation of this document.
@ -450,4 +451,7 @@ enum ChangeSource {
/// Change originated from a remote action.
remote,
/// Silent change.
silent;
}

@ -1,4 +1,4 @@
import '../quill_delta.dart';
import '../../../quill_delta.dart';
import '../structs/doc_change.dart';
import '../structs/history_changed.dart';
import 'document.dart';

@ -1,4 +1,4 @@
import '../../quill_delta.dart';
import '../../../../quill_delta.dart';
import 'container.dart';
import 'line.dart';
import 'node.dart';

@ -1,7 +1,7 @@
import 'dart:math' as math;
import '../../../../quill_delta.dart';
import '../../../widgets/quill/embeds.dart';
import '../../quill_delta.dart';
import '../style.dart';
import 'embeddable.dart';
import 'line.dart';

@ -2,8 +2,8 @@ import 'dart:math' as math;
import 'package:collection/collection.dart';
import '../../../../quill_delta.dart';
import '../../../widgets/quill/embeds.dart';
import '../../quill_delta.dart';
import '../../structs/offset_value.dart';
import '../attribute.dart';
import '../style.dart';

@ -1,7 +1,7 @@
import 'dart:collection';
import '../../../../quill_delta.dart';
import '../../../widgets/quill/embeds.dart';
import '../../quill_delta.dart';
import '../attribute.dart';
import '../style.dart';
import 'container.dart';

@ -1,829 +0,0 @@
export 'package:dart_quill_delta/dart_quill_delta.dart';
// /// Implementation of Quill Delta format in Dart.
// library;
// import 'dart:math' as math;
// import 'package:collection/collection.dart';
// import 'package:diff_match_patch/diff_match_patch.dart' as dmp;
// import 'package:quiver/core.dart';
// const _attributeEquality = DeepCollectionEquality();
// const _valueEquality = DeepCollectionEquality();
// /// Decoder function to convert raw `data` object into a user-defined data type.
// ///
// /// Useful with embedded content.
// typedef DataDecoder = Object? Function(Object data);
// /// Default data decoder which simply passes through the original value.
// Object? _passThroughDataDecoder(Object? data) => data;
// /// Operation performed on a rich-text document.
// class Operation {
// Operation._(this.key, this.length, this.data, Map? attributes)
// : assert(_validKeys.contains(key), 'Invalid operation key "$key".'),
// assert(() {
// if (key != Operation.insertKey) return true;
// return data is String ? data.length == length : length == 1;
// }(), 'Length of insert operation must be equal to the data length.'),
// _attributes =
// attributes != null ? Map<String, dynamic>.from(attributes) : null;
// /// Creates operation which deletes [length] of characters.
// factory Operation.delete(int length) =>
// Operation._(Operation.deleteKey, length, '', null);
// /// Creates operation which inserts [text] with optional [attributes].
// factory Operation.insert(dynamic data, [Map<String, dynamic>? attributes]) =>
// Operation._(Operation.insertKey, data is String ? data.length : 1, data,
// attributes);
// /// Creates operation which retains [length] of characters and optionally
// /// applies attributes.
// factory Operation.retain(int? length, [Map<String, dynamic>? attributes]) =>
// Operation._(Operation.retainKey, length, '', attributes);
// /// Key of insert operations.
// static const String insertKey = 'insert';
// /// Key of delete operations.
// static const String deleteKey = 'delete';
// /// Key of retain operations.
// static const String retainKey = 'retain';
// /// Key of attributes collection.
// static const String attributesKey = 'attributes';
// static const List<String> _validKeys = [insertKey, deleteKey, retainKey];
// /// Key of this operation, can be "insert", "delete" or "retain".
// final String key;
// /// Length of this operation.
// final int? length;
// /// Payload of "insert" operation, for other types is set to empty string.
// final Object? data;
// /// Rich-text attributes set by this operation, can be `null`.
// Map<String, dynamic>? get attributes =>
// _attributes == null ? null : Map<String, dynamic>.from(_attributes!);
// final Map<String, dynamic>? _attributes;
// /// Creates new [Operation] from JSON payload.
// ///
// /// If `dataDecoder` parameter is not null then it is used to additionally
// /// decode the operation's data object. Only applied to insert operations.
// static Operation fromJson(Map data, {DataDecoder? dataDecoder}) {
// dataDecoder ??= _passThroughDataDecoder;
// final map = Map<String, dynamic>.from(data);
// if (map.containsKey(Operation.insertKey)) {
// final data = dataDecoder(map[Operation.insertKey]);
// final dataLength = data is String ? data.length : 1;
// return Operation._(
// Operation.insertKey, dataLength, data, map[Operation.attributesKey]);
// } else if (map.containsKey(Operation.deleteKey)) {
// final int? length = map[Operation.deleteKey];
// return Operation._(Operation.deleteKey, length, '', null);
// } else if (map.containsKey(Operation.retainKey)) {
// final int? length = map[Operation.retainKey];
// return Operation._(
// Operation.retainKey, length, '', map[Operation.attributesKey]);
// }
// throw ArgumentError.value(data, 'Invalid data for Delta operation.');
// }
// /// Returns JSON-serializable representation of this operation.
// Map<String, dynamic> toJson() {
// final json = {key: value};
// if (_attributes != null) json[Operation.attributesKey] = attributes;
// return json;
// }
// /// Returns value of this operation.
// ///
// /// For insert operations this returns text, for delete and retain - length.
// dynamic get value => (key == Operation.insertKey) ? data : length;
// /// Returns `true` if this is a delete operation.
// bool get isDelete => key == Operation.deleteKey;
// /// Returns `true` if this is an insert operation.
// bool get isInsert => key == Operation.insertKey;
// /// Returns `true` if this is a retain operation.
// bool get isRetain => key == Operation.retainKey;
// /// Returns `true` if this operation has no attributes, e.g. is plain text.
// bool get isPlain => _attributes == null || _attributes!.isEmpty;
// /// Returns `true` if this operation sets at least one attribute.
// bool get isNotPlain => !isPlain;
// /// Returns `true` is this operation is empty.
// ///
// /// An operation is considered empty if its [length] is equal to `0`.
// bool get isEmpty => length == 0;
// /// Returns `true` is this operation is not empty.
// bool get isNotEmpty => length! > 0;
// @override
// bool operator ==(other) {
// if (identical(this, other)) return true;
// if (other is! Operation) return false;
// final typedOther = other;
// return key == typedOther.key &&
// length == typedOther.length &&
// _valueEquality.equals(data, typedOther.data) &&
// hasSameAttributes(typedOther);
// }
// /// Returns `true` if this operation has attribute specified by [name].
// bool hasAttribute(String name) =>
// isNotPlain && _attributes!.containsKey(name);
// /// Returns `true` if [other] operation has the same attributes as this one.
// bool hasSameAttributes(Operation other) {
// // treat null and empty equal
// if ((_attributes?.isEmpty ?? true) &&
// (other._attributes?.isEmpty ?? true)) {
// return true;
// }
// return _attributeEquality.equals(_attributes, other._attributes);
// }
// @override
// int get hashCode {
// if (_attributes != null && _attributes!.isNotEmpty) {
// final attrsHash =
// hashObjects(_attributes!.entries.map((e) => hash2(e.key, e.value)));
// return hash3(key, value, attrsHash);
// }
// return hash2(key, value);
// }
// @override
// String toString() {
// final attr = attributes == null ? '' : ' + $attributes';
// final text = isInsert
// ? (data is String
// ? (data as String).replaceAll('\n', '')
// : data.toString())
// : '$length';
// return '$key$text$attr';
// }
// }
// /// Delta represents a document or a modification of a document as a sequence of
// /// insert, delete and retain operations.
// ///
// /// Delta consisting of only "insert" operations is usually referred to as
// /// "document delta". When delta includes also "retain" or "delete" operations
// /// it is a "change delta".
// class Delta {
// /// Creates new empty [Delta].
// factory Delta() => Delta._(<Operation>[]);
// Delta._(List<Operation> operations) : _operations = operations;
// /// Creates new [Delta] from [other].
// factory Delta.from(Delta other) =>
// Delta._(List<Operation>.from(other._operations));
// /// Creates new [Delta] from a List of Operation
// factory Delta.fromOperations(List<Operation> operations) =>
// Delta._(operations.toList());
// // Placeholder char for embed in diff()
// static final String _kNullCharacter = String.fromCharCode(0);
// /// Transforms two attribute sets.
// static Map<String, dynamic>? transformAttributes(
// Map<String, dynamic>? a, Map<String, dynamic>? b, bool priority) {
// if (a == null) return b;
// if (b == null) return null;
// if (!priority) return b;
// final result = b.keys.fold<Map<String, dynamic>>({}, (attributes, key) {
// if (!a.containsKey(key)) attributes[key] = b[key];
// return attributes;
// });
// return result.isEmpty ? null : result;
// }
// /// Composes two attribute sets.
// static Map<String, dynamic>? composeAttributes(
// Map<String, dynamic>? a, Map<String, dynamic>? b,
// {bool keepNull = false}) {
// a ??= const {};
// b ??= const {};
// final result = Map<String, dynamic>.from(a)..addAll(b);
// final keys = result.keys.toList(growable: false);
// if (!keepNull) {
// for (final key in keys) {
// if (result[key] == null) result.remove(key);
// }
// }
// return result.isEmpty ? null : result;
// }
// ///get anti-attr result base on base
// static Map<String, dynamic> invertAttributes(
// Map<String, dynamic>? attr, Map<String, dynamic>? base) {
// attr ??= const {};
// base ??= const {};
// final baseInverted = base.keys.fold({}, (dynamic memo, key) {
// if (base![key] != attr![key] && attr.containsKey(key)) {
// memo[key] = base[key];
// }
// return memo;
// });
// final inverted =
// Map<String, dynamic>.from(attr.keys.fold(baseInverted, (memo, key) {
// if (base![key] != attr![key] && !base.containsKey(key)) {
// memo[key] = null;
// }
// return memo;
// }));
// return inverted;
// }
// /// Returns diff between two attribute sets
// static Map<String, dynamic>? diffAttributes(
// Map<String, dynamic>? a, Map<String, dynamic>? b) {
// a ??= const {};
// b ??= const {};
// final attributes = <String, dynamic>{};
// for (final key in (a.keys.toList()..addAll(b.keys))) {
// if (a[key] != b[key]) {
// attributes[key] = b.containsKey(key) ? b[key] : null;
// }
// }
// return attributes.keys.isNotEmpty ? attributes : null;
// }
// final List<Operation> _operations;
// int _modificationCount = 0;
// /// Creates [Delta] from de-serialized JSON representation.
// ///
// /// If `dataDecoder` parameter is not null then it is used to additionally
// /// decode the operation's data object. Only applied to insert operations.
// static Delta fromJson(List data, {DataDecoder? dataDecoder}) {
// return Delta._(data
// .map((op) => Operation.fromJson(op, dataDecoder: dataDecoder))
// .toList());
// }
// /// Returns list of operations in this delta.
// List<Operation> toList() => List.from(_operations);
// /// Returns JSON-serializable version of this delta.
// List toJson() => toList().map((operation) => operation.toJson()).toList();
// /// Returns `true` if this delta is empty.
// bool get isEmpty => _operations.isEmpty;
// /// Returns `true` if this delta is not empty.
// bool get isNotEmpty => _operations.isNotEmpty;
// /// Returns number of operations in this delta.
// int get length => _operations.length;
// /// Returns [Operation] at specified [index] in this delta.
// Operation operator [](int index) => _operations[index];
// /// Returns [Operation] at specified [index] in this delta.
// Operation elementAt(int index) => _operations.elementAt(index);
// /// Returns the first [Operation] in this delta.
// Operation get first => _operations.first;
// /// Returns the last [Operation] in this delta.
// Operation get last => _operations.last;
// @override
// bool operator ==(dynamic other) {
// if (identical(this, other)) return true;
// if (other is! Delta) return false;
// final typedOther = other;
// const comparator = ListEquality<Operation>(DefaultEquality<Operation>());
// return comparator.equals(_operations, typedOther._operations);
// }
// @override
// int get hashCode => hashObjects(_operations);
// /// Retain [count] of characters from current position.
// void retain(int count, [Map<String, dynamic>? attributes]) {
// assert(count >= 0);
// if (count == 0) return; // no-op
// push(Operation.retain(count, attributes));
// }
// /// Insert [data] at current position.
// void insert(dynamic data, [Map<String, dynamic>? attributes]) {
// if (data is String && data.isEmpty) return; // no-op
// push(Operation.insert(data, attributes));
// }
// /// Delete [count] characters from current position.
// void delete(int count) {
// assert(count >= 0);
// if (count == 0) return;
// push(Operation.delete(count));
// }
// void _mergeWithTail(Operation operation) {
// assert(isNotEmpty);
// assert(last.key == operation.key);
// assert(operation.data is String && last.data is String);
// final length = operation.length! + last.length!;
// final lastText = last.data as String;
// final opText = operation.data as String;
// final resultText = lastText + opText;
// final index = _operations.length;
// _operations.replaceRange(index - 1, index, [
// Operation._(operation.key, length, resultText, operation.attributes),
// ]);
// }
// /// Pushes new operation into this delta.
// ///
// /// Performs compaction by composing [operation] with current tail operation
// /// of this delta, when possible. For instance, if current tail is
// /// `insert('abc')` and pushed operation is `insert('123')` then existing
// /// tail is replaced with `insert('abc123')` - a compound result of the two
// /// operations.
// void push(Operation operation) {
// if (operation.isEmpty) return;
// var index = _operations.length;
// final lastOp = _operations.isNotEmpty ? _operations.last : null;
// if (lastOp != null) {
// if (lastOp.isDelete && operation.isDelete) {
// _mergeWithTail(operation);
// return;
// }
// if (lastOp.isDelete && operation.isInsert) {
// index -= 1; // Always insert before deleting
// final nLastOp = (index > 0) ? _operations.elementAt(index - 1) : null;
// if (nLastOp == null) {
// _operations.insert(0, operation);
// return;
// }
// }
// if (lastOp.isInsert && operation.isInsert) {
// if (lastOp.hasSameAttributes(operation) &&
// operation.data is String &&
// lastOp.data is String) {
// _mergeWithTail(operation);
// return;
// }
// }
// if (lastOp.isRetain && operation.isRetain) {
// if (lastOp.hasSameAttributes(operation)) {
// _mergeWithTail(operation);
// return;
// }
// }
// }
// if (index == _operations.length) {
// _operations.add(operation);
// } else {
// final opAtIndex = _operations.elementAt(index);
// _operations.replaceRange(index, index + 1, [operation, opAtIndex]);
// }
// _modificationCount++;
// }
// /// Composes next operation from [thisIter] and [otherIter].
// ///
// /// Returns new operation or `null` if operations from [thisIter] and
// /// [otherIter] nullify each other. For instance, for the pair `insert('abc')`
// /// and `delete(3)` composition result would be empty string.
// Operation? _composeOperation(
// DeltaIterator thisIter, DeltaIterator otherIter) {
// if (otherIter.isNextInsert) return otherIter.next();
// if (thisIter.isNextDelete) return thisIter.next();
// final length = math.min(thisIter.peekLength(), otherIter.peekLength());
// final thisOp = thisIter.next(length);
// final otherOp = otherIter.next(length);
// assert(thisOp.length == otherOp.length);
// if (otherOp.isRetain) {
// final attributes = composeAttributes(
// thisOp.attributes,
// otherOp.attributes,
// keepNull: thisOp.isRetain,
// );
// if (thisOp.isRetain) {
// return Operation.retain(thisOp.length, attributes);
// } else if (thisOp.isInsert) {
// return Operation.insert(thisOp.data, attributes);
// } else {
// throw StateError('Unreachable');
// }
// } else {
// // otherOp == delete && thisOp in [retain, insert]
// assert(otherOp.isDelete);
// if (thisOp.isRetain) return otherOp;
// assert(thisOp.isInsert);
// // otherOp(delete) + thisOp(insert) => null
// }
// return null;
// }
// /// Composes this delta with [other] and returns new [Delta].
// ///
// /// It is not required for this and [other] delta to represent a document
// /// delta (consisting only of insert operations).
// Delta compose(Delta other) {
// final result = Delta();
// final thisIter = DeltaIterator(this);
// final otherIter = DeltaIterator(other);
// while (thisIter.hasNext || otherIter.hasNext) {
// final newOp = _composeOperation(thisIter, otherIter);
// if (newOp != null) result.push(newOp);
// }
// return result..trim();
// }
// /// Returns a new lazy Iterable with elements that are created by calling
// /// f on each element of this Iterable in iteration order.
// ///
// /// Convenience method
// Iterable<T> map<T>(T Function(Operation) f) {
// return _operations.map<T>(f);
// }
// /// Returns a [Delta] containing differences between 2 [Delta]s.
// /// If [cleanupSemantic] is `true` (default), applies the following:
// ///
// /// The diff of "mouse" and "sofas" is
// /// [delete(1), insert("s"), retain(1),
// /// delete("u"), insert("fa"), retain(1), delete(1)].
// /// While this is the optimum diff, it is difficult for humans to understand.
// /// Semantic cleanup rewrites the diff,
// /// expanding it into a more intelligible format.
// /// The above example would become: [(-1, "mouse"), (1, "sofas")].
// /// (source: https://github.com/google/diff-match-patch/wiki/API)
// ///
// /// Useful when one wishes to display difference between 2 documents
// Delta diff(Delta other, {bool cleanupSemantic = true}) {
// if (_operations.equals(other._operations)) {
// return Delta();
// }
// final stringThis = map((op) {
// if (op.isInsert) {
// return op.data is String ? op.data : _kNullCharacter;
// }
// final prep = this == other ? 'on' : 'with';
// throw ArgumentError('diff() call $prep non-document');
// }).join();
// final stringOther = other.map((op) {
// if (op.isInsert) {
// return op.data is String ? op.data : _kNullCharacter;
// }
// final prep = this == other ? 'on' : 'with';
// throw ArgumentError('diff() call $prep non-document');
// }).join();
// final retDelta = Delta();
// final diffResult = dmp.diff(stringThis, stringOther);
// if (cleanupSemantic) {
// dmp.DiffMatchPatch().diffCleanupSemantic(diffResult);
// }
// final thisIter = DeltaIterator(this);
// final otherIter = DeltaIterator(other);
// for (final component in diffResult) {
// var length = component.text.length;
// while (length > 0) {
// var opLength = 0;
// switch (component.operation) {
// case dmp.DIFF_INSERT:
// opLength = math.min(otherIter.peekLength(), length);
// retDelta.push(otherIter.next(opLength));
// break;
// case dmp.DIFF_DELETE:
// opLength = math.min(length, thisIter.peekLength());
// thisIter.next(opLength);
// retDelta.delete(opLength);
// break;
// case dmp.DIFF_EQUAL:
// opLength = math.min(
// math.min(thisIter.peekLength(), otherIter.peekLength()),
// length,
// );
// final thisOp = thisIter.next(opLength);
// final otherOp = otherIter.next(opLength);
// if (thisOp.data == otherOp.data) {
// retDelta.retain(
// opLength,
// diffAttributes(thisOp.attributes, otherOp.attributes),
// );
// } else {
// retDelta
// ..push(otherOp)
// ..delete(opLength);
// }
// break;
// }
// length -= opLength;
// }
// }
// return retDelta..trim();
// }
// /// Transforms next operation from [otherIter] against next operation in
// /// [thisIter].
// ///
// /// Returns `null` if both operations nullify each other.
// Operation? _transformOperation(
// DeltaIterator thisIter, DeltaIterator otherIter, bool priority) {
// if (thisIter.isNextInsert && (priority || !otherIter.isNextInsert)) {
// return Operation.retain(thisIter.next().length);
// } else if (otherIter.isNextInsert) {
// return otherIter.next();
// }
// final length = math.min(thisIter.peekLength(), otherIter.peekLength());
// final thisOp = thisIter.next(length);
// final otherOp = otherIter.next(length);
// assert(thisOp.length == otherOp.length);
// // At this point only delete and retain operations are possible.
// if (thisOp.isDelete) {
// // otherOp is either delete or retain, so they nullify each other.
// return null;
// } else if (otherOp.isDelete) {
// return otherOp;
// } else {
// // Retain otherOp which is either retain or insert.
// return Operation.retain(
// length,
// transformAttributes(thisOp.attributes, otherOp.attributes, priority),
// );
// }
// }
// /// Transforms [other] delta against operations in this delta.
// Delta transform(Delta other, bool priority) {
// final result = Delta();
// final thisIter = DeltaIterator(this);
// final otherIter = DeltaIterator(other);
// while (thisIter.hasNext || otherIter.hasNext) {
// final newOp = _transformOperation(thisIter, otherIter, priority);
// if (newOp != null) result.push(newOp);
// }
// return result..trim();
// }
// /// Removes trailing retain operation with empty attributes, if present.
// void trim() {
// if (isNotEmpty) {
// final last = _operations.last;
// if (last.isRetain && last.isPlain) _operations.removeLast();
// }
// }
// /// Removes trailing '\n'
// void _trimNewLine() {
// if (isNotEmpty) {
// final lastOp = _operations.last;
// final lastOpData = lastOp.data;
// if (lastOpData is String && lastOpData.endsWith('\n')) {
// _operations.removeLast();
// if (lastOpData.length > 1) {
// insert(lastOpData.substring(0, lastOpData.length - 1),
// lastOp.attributes);
// }
// }
// }
// }
// /// Concatenates [other] with this delta and returns the result.
// Delta concat(Delta other, {bool trimNewLine = false}) {
// final result = Delta.from(this);
// if (trimNewLine) {
// result._trimNewLine();
// }
// if (other.isNotEmpty) {
// // In case first operation of other can be merged with last operation in
// // our list.
// result.push(other._operations.first);
// result._operations.addAll(other._operations.sublist(1));
// }
// return result;
// }
// /// Inverts this delta against [base].
// ///
// /// Returns new delta which negates effect of this delta when applied to
// /// [base]. This is an equivalent of "undo" operation on deltas.
// Delta invert(Delta base) {
// final inverted = Delta();
// if (base.isEmpty) return inverted;
// var baseIndex = 0;
// for (final op in _operations) {
// if (op.isInsert) {
// inverted.delete(op.length!);
// } else if (op.isRetain && op.isPlain) {
// inverted.retain(op.length!);
// baseIndex += op.length!;
// } else if (op.isDelete || (op.isRetain && op.isNotPlain)) {
// final length = op.length!;
// final sliceDelta = base.slice(baseIndex, baseIndex + length);
// sliceDelta.toList().forEach((baseOp) {
// if (op.isDelete) {
// inverted.push(baseOp);
// } else if (op.isRetain && op.isNotPlain) {
// final invertAttr =
// invertAttributes(op.attributes, baseOp.attributes);
// inverted.retain(
// baseOp.length!, invertAttr.isEmpty ? null : invertAttr);
// }
// });
// baseIndex += length;
// } else {
// throw StateError('Unreachable');
// }
// }
// inverted.trim();
// return inverted;
// }
// /// Returns slice of this delta from [start] index (inclusive) to [end]
// /// (exclusive).
// Delta slice(int start, [int? end]) {
// final delta = Delta();
// var index = 0;
// final opIterator = DeltaIterator(this);
// final actualEnd = end ?? DeltaIterator.maxLength;
// while (index < actualEnd && opIterator.hasNext) {
// Operation op;
// if (index < start) {
// op = opIterator.next(start - index);
// } else {
// op = opIterator.next(actualEnd - index);
// delta.push(op);
// }
// index += op.length!;
// }
// return delta;
// }
// /// Transforms [index] against this delta.
// ///
// /// Any "delete" operation before specified [index] shifts it backward, as
// /// well as any "insert" operation shifts it forward.
// ///
// /// The [force] argument is used to resolve scenarios when there is an
// /// insert operation at the same position as [index]. If [force] is set to
// /// `true` (default) then position is forced to shift forward, otherwise
// /// position stays at the same index. In other words setting [force] to
// /// `false` gives higher priority to the transformed position.
// ///
// /// Useful to adjust caret or selection positions.
// int transformPosition(int index, {bool force = true}) {
// final iter = DeltaIterator(this);
// var offset = 0;
// while (iter.hasNext && offset <= index) {
// final op = iter.next();
// if (op.isDelete) {
// index -= math.min(op.length!, index - offset);
// continue;
// } else if (op.isInsert && (offset < index || force)) {
// index += op.length!;
// }
// offset += op.length!;
// }
// return index;
// }
// @override
// String toString() => _operations.join('\n');
// }
// /// Specialized iterator for [Delta]s.
// class DeltaIterator {
// DeltaIterator(this.delta) : _modificationCount = delta._modificationCount;
// static const int maxLength = 1073741824;
// final Delta delta;
// final int _modificationCount;
// int _index = 0;
// int _offset = 0;
// bool get isNextInsert => nextOperationKey == Operation.insertKey;
// bool get isNextDelete => nextOperationKey == Operation.deleteKey;
// bool get isNextRetain => nextOperationKey == Operation.retainKey;
// String? get nextOperationKey {
// if (_index < delta.length) {
// return delta.elementAt(_index).key;
// } else {
// return null;
// }
// }
// bool get hasNext => peekLength() < maxLength;
// /// Returns length of next operation without consuming it.
// ///
// /// Returns [maxLength] if there is no more operations left to iterate.
// int peekLength() {
// if (_index < delta.length) {
// final operation = delta._operations[_index];
// return operation.length! - _offset;
// }
// return maxLength;
// }
// /// Consumes and returns next operation.
// ///
// /// Optional [length] specifies maximum length of operation to return. Note
// /// that actual length of returned operation may be less than specified value.
// ///
// /// If this iterator reached the end of the Delta then returns a retain
// /// operation with its length set to [maxLength].
// // TODO: Note that we used double.infinity as the default value
// // for length here
// // but this can now cause a type error since operation length is
// // expected to be an int. Changing default length to [maxLength] is
// // a workaround to avoid breaking changes.
// Operation next([int length = maxLength]) {
// if (_modificationCount != delta._modificationCount) {
// throw ConcurrentModificationError(delta);
// }
// if (_index < delta.length) {
// final op = delta.elementAt(_index);
// final opKey = op.key;
// final opAttributes = op.attributes;
// final currentOffset = _offset;
// final actualLength = math.min(op.length! - currentOffset, length);
// if (actualLength == op.length! - currentOffset) {
// _index++;
// _offset = 0;
// } else {
// _offset += actualLength;
// }
// final opData = op.isInsert && op.data is String
// ? (op.data as String)
// .substring(currentOffset, currentOffset + actualLength)
// : op.data;
// final opIsNotEmpty =
// opData is String ? opData.isNotEmpty : true; // embeds are never empty
// final opLength = opData is String ? opData.length : 1;
// final opActualLength = opIsNotEmpty ? opLength : actualLength;
// return Operation._(opKey, opActualLength, opData, opAttributes);
// }
// return Operation.retain(length);
// }
// /// Skips [length] characters in source delta.
// ///
// /// Returns last skipped operation, or `null` if there was nothing to skip.
// Operation? skip(int length) {
// var skipped = 0;
// Operation? op;
// while (skipped < length && hasNext) {
// final opLength = peekLength();
// final skip = math.min(length - skipped, opLength);
// op = next(skip);
// skipped += op.length!;
// }
// return op;
// }
// }

@ -1,8 +1,8 @@
import 'package:meta/meta.dart' show immutable;
import '../../../quill_delta.dart';
import '../documents/attribute.dart';
import '../documents/nodes/embeddable.dart';
import '../quill_delta.dart';
import 'rule.dart';
/// A heuristic rule for delete operations.

@ -1,7 +1,7 @@
import 'package:meta/meta.dart' show immutable;
import '../../../quill_delta.dart';
import '../documents/attribute.dart';
import '../quill_delta.dart';
import 'rule.dart';
/// A heuristic rule for format (retain) operations.

@ -1,11 +1,11 @@
import 'package:meta/meta.dart' show immutable;
import '../../../quill_delta.dart';
import '../../extensions/uri_ext.dart';
import '../../models/documents/document.dart';
import '../documents/attribute.dart';
import '../documents/nodes/embeddable.dart';
import '../documents/style.dart';
import '../quill_delta.dart';
import 'rule.dart';
/// A heuristic rule for insert operations.

@ -1,8 +1,8 @@
import 'package:meta/meta.dart' show immutable;
import '../../../quill_delta.dart';
import '../documents/attribute.dart';
import '../documents/document.dart';
import '../quill_delta.dart';
import 'delete.dart';
import 'format.dart';
import 'insert.dart';

@ -1,7 +1,7 @@
import 'package:meta/meta.dart' show immutable;
import '../../../quill_delta.dart';
import '../documents/document.dart';
import '../quill_delta.dart';
@immutable
class DocChange {

@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:collection/collection.dart';
import '../../../flutter_quill.dart';
import '../../../quill_delta.dart';
import './custom_quill_attributes.dart';
import './utils.dart';

@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
import 'package:markdown/markdown.dart' as md;
import '../../../flutter_quill.dart';
import '../../../quill_delta.dart';
import './custom_quill_attributes.dart';
import './embeddable_table_syntax.dart';
import './utils.dart';

@ -1,6 +1,7 @@
//ignore_for_file: cast_nullable_to_non_nullable
import '../../../flutter_quill.dart';
import '../../../quill_delta.dart';
import './embeddable_table_syntax.dart';
/// To allow embedding images/videos in horizontal mode.

@ -3,9 +3,9 @@ import 'dart:ui';
import 'package:meta/meta.dart' show immutable;
import '../../quill_delta.dart';
import '../models/documents/attribute.dart';
import '../models/documents/nodes/node.dart';
import '../models/quill_delta.dart';
// Diff between two texts - old text and new text
@immutable

@ -6,12 +6,12 @@ import 'package:html2md/html2md.dart' as html2md;
import 'package:markdown/markdown.dart' as md;
import '../../../markdown_quill.dart';
import '../../../quill_delta.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/document.dart';
import '../../models/documents/nodes/embeddable.dart';
import '../../models/documents/nodes/leaf.dart';
import '../../models/documents/style.dart';
import '../../models/quill_delta.dart';
import '../../models/structs/doc_change.dart';
import '../../models/structs/image_url.dart';
import '../../models/structs/offset_value.dart';
@ -54,13 +54,24 @@ class QuillController extends ChangeNotifier {
notifyListeners();
}
void updateDocument(Document newDocument) {
void setContents(
Delta delta, {
ChangeSource changeSource = ChangeSource.local,
}) {
final newDocument = Document.fromDelta(delta);
final change = DocChange(_document.toDelta(), delta, changeSource);
newDocument.documentChangeObserver.add(change);
newDocument.history.handleDocChange(change);
_document = newDocument;
notifyListeners();
}
/// The current font family, null to use the default one
String? _selectedFontFamily;
/// The current font family, null to use the default one
String? get selectedFontFamily => _selectedFontFamily;
void selectFontFamily(String? newFontFamily) {
@ -69,6 +80,8 @@ class QuillController extends ChangeNotifier {
/// The current font size, null to use the default one
String? _selectedFontSize;
/// The current font size, null to use the default one
String? get selectedFontSize => _selectedFontSize;
void selectFontSize(String? newFontSize) {

@ -217,8 +217,8 @@ class QuillRawEditorState extends EditorState
final delta = deltaFromCliboard.compose(controller.document.toDelta());
controller
..updateDocument(
Document.fromDelta(delta),
..setContents(
delta,
)
..updateSelection(
TextSelection.collapsed(

@ -14,13 +14,13 @@ export 'buttons/color/color_button.dart';
export 'buttons/custom_button_button.dart';
export 'buttons/font_family_button.dart';
export 'buttons/font_size_button.dart';
export 'buttons/hearder_style/select_header_style_buttons.dart';
export 'buttons/history_button.dart';
export 'buttons/indent_button.dart';
export 'buttons/link_style2_button.dart';
export 'buttons/link_style_button.dart';
export 'buttons/quill_icon_button.dart';
export 'buttons/search/search_button.dart';
export 'buttons/select_header_style_buttons.dart';
export 'buttons/toggle_check_list_button.dart';
export 'buttons/toggle_style_button.dart';

@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import '../../../../models/documents/attribute.dart';
import '../../../quill/quill_controller.dart';
enum _AlignmentOptions {
left(attribute: Attribute.leftAlignment),
center(attribute: Attribute.centerAlignment),
right(attribute: Attribute.rightAlignment),
justifyMinWidth(attribute: Attribute.justifyAlignment);
const _AlignmentOptions({required this.attribute});
final Attribute attribute;
}
class QuillToolbarSelectAlignmentButton extends StatelessWidget {
const QuillToolbarSelectAlignmentButton(
{required this.controller, super.key});
final QuillController controller;
@override
Widget build(BuildContext context) {
return MenuAnchor(
menuChildren: _AlignmentOptions.values
.map(
(e) => MenuItemButton(
child: Text(e.name),
onPressed: () {
controller.formatSelection(e.attribute);
},
),
)
.toList(),
);
}
}

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import '../../../models/config/toolbar/buttons/select_alignment_configurations.dart';
import '../../../models/documents/attribute.dart';
import '../../quill/quill_controller.dart';
import 'toggle_style_button.dart';
import '../../../../models/config/toolbar/buttons/select_alignment_configurations.dart';
import '../../../../models/documents/attribute.dart';
import '../../../quill/quill_controller.dart';
import '../toggle_style_button.dart';
enum _AlignmentOptions {
left(attribute: Attribute.leftAlignment),

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import '../../../../translations.dart';
import '../../../models/config/toolbar/buttons/select_header_style_configurations.dart';
import '../../../models/documents/attribute.dart';
import '../../quill/quill_controller.dart';
import '../../../../../translations.dart';
import '../../../../models/config/toolbar/buttons/select_header_style_configurations.dart';
import '../../../../models/documents/attribute.dart';
import '../../../quill/quill_controller.dart';
enum _HeaderStyleOptions {
normal,
@ -31,6 +31,7 @@ class QuillToolbarSelectHeaderStyleButton extends StatefulWidget {
class _QuillToolbarSelectHeaderStyleButtonState
extends State<QuillToolbarSelectHeaderStyleButton> {
var _selectedItem = _HeaderStyleOptions.normal;
final _controller = MenuController();
@override
void initState() {
super.initState();
@ -110,25 +111,35 @@ class _QuillToolbarSelectHeaderStyleButtonState
@override
Widget build(BuildContext context) {
return DropdownButton<_HeaderStyleOptions>(
value: _selectedItem,
items: _HeaderStyleOptions.values
return MenuAnchor(
controller: _controller,
menuChildren: _HeaderStyleOptions.values
.map(
(e) => DropdownMenuItem<_HeaderStyleOptions>(
value: e,
(e) => MenuItemButton(
child: Text(_label(e)),
onTap: () {
onPressed: () {
widget.controller.formatSelection(getAttributeByOptionsItem(e));
setState(() => _selectedItem = e);
},
),
)
.toList(),
onChanged: (newItem) {
if (newItem == null) {
return;
}
setState(() => _selectedItem = newItem);
},
child: IconButton(
onPressed: () {
if (_controller.isOpen) {
_controller.close();
return;
}
_controller.open();
},
icon: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(_label(_selectedItem)),
const Icon(Icons.arrow_drop_down),
],
),
),
);
}
}

@ -1,14 +1,14 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import '../../../../extensions.dart';
import '../../../extensions/quill_configurations_ext.dart';
import '../../../l10n/extensions/localizations.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../quill/quill_controller.dart';
import '../base_toolbar.dart';
import '../../../../../extensions.dart';
import '../../../../extensions/quill_configurations_ext.dart';
import '../../../../l10n/extensions/localizations.dart';
import '../../../../models/documents/attribute.dart';
import '../../../../models/documents/style.dart';
import '../../../../models/themes/quill_icon_theme.dart';
import '../../../quill/quill_controller.dart';
import '../../base_toolbar.dart';
class QuillToolbarSelectHeaderStyleButtons extends StatefulWidget {
const QuillToolbarSelectHeaderStyleButtons({

@ -6,9 +6,9 @@ import '../../models/config/toolbar/toolbar_configurations.dart';
import '../../models/documents/attribute.dart';
import '../utils/provider.dart';
import 'base_toolbar.dart';
import 'buttons/alignment/select_alignment_buttons.dart';
import 'buttons/arrow_indicated_list_button.dart';
import 'buttons/select_alignment_buttons.dart';
import 'buttons/select_header_style_button.dart';
import 'buttons/hearder_style/select_header_style_button.dart';
class QuillSimpleToolbar extends StatelessWidget
implements PreferredSizeWidget {

@ -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: 9.0.0-dev-11
version: 9.0.0
homepage: https://1o24bbs.com/c/bulletjournal/108/
repository: https://github.com/singerdmx/flutter-quill/
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/
@ -44,12 +44,14 @@ dependencies:
intl: ^0.18.1
# Normal packages
dart_quill_delta: ^0.0.1
collection: ^1.17.0
flutter_colorpicker: ^1.0.3
quiver: ^3.2.1
equatable: ^2.0.5
meta: ^1.9.1
flutter_colorpicker: ^1.0.3
# For converting HTML to Quill delta
markdown: ^7.1.1
html2md: ^1.3.1
@ -60,7 +62,6 @@ dependencies:
flutter_keyboard_visibility: ^5.4.1
device_info_plus: ^9.1.0
super_clipboard: ^0.7.3
dart_quill_delta: ^0.0.1
dev_dependencies:
flutter_lints: ^3.0.1

@ -2,7 +2,14 @@
All notable changes to this project will be documented in this file.
## 9.0.0-dev-11
## 9.0.0
* This version is quite stable but it's not how we wanted to be, because the lack of time and there are not too many maintainers active, we decided to publish it, we might make a new breaking changes verion
## 9.0.1-dev.1
* Flutter Quill Extensions:
* Update `QuillImageUtilities` and fixining some bugs
## 9.0.1-dev
* Test new GitHub workflows
## 9.0.0-dev-10

@ -1,6 +1,6 @@
name: quill_html_converter
description: A extension for flutter_quill package to add support for dealing with conversion to/from html
version: 9.0.0-dev-11
version: 9.0.0
homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/
repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/
issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -1,5 +1,6 @@
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() {

@ -1 +1 @@
const version = '9.0.0-dev-11';
const version = '9.0.0';

Loading…
Cancel
Save