diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c020c25b..8f8e10880 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: paths: - 'pubspec.yaml' + - 'flutter_quill_extensions/pubspec.yaml' jobs: build_linux: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4392c9d05..a4d0bba20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 0bc825333..c946aefe4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index 4392c9d05..a4d0bba20 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -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 diff --git a/flutter_quill_extensions/lib/extensions/controller_ext.dart b/flutter_quill_extensions/lib/extensions/controller_ext.dart index d1687ea7c..9ae008396 100644 --- a/flutter_quill_extensions/lib/extensions/controller_ext.dart +++ b/flutter_quill_extensions/lib/extensions/controller_ext.dart @@ -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, - ); - } } diff --git a/flutter_quill_extensions/lib/utils/quill_image_utils.dart b/flutter_quill_extensions/lib/utils/quill_image_utils.dart index 4a366c092..e0e85b1d6 100644 --- a/flutter_quill_extensions/lib/utils/quill_image_utils.dart +++ b/flutter_quill_extensions/lib/utils/quill_image_utils.dart @@ -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() - .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() + // .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 = []; + 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> getCachedImagePathsFromDocument({ + Iterable 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; } } diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index d328ff175..5c26fbe50 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -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: diff --git a/flutter_quill_test/CHANGELOG.md b/flutter_quill_test/CHANGELOG.md index 4392c9d05..a4d0bba20 100644 --- a/flutter_quill_test/CHANGELOG.md +++ b/flutter_quill_test/CHANGELOG.md @@ -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 diff --git a/flutter_quill_test/pubspec.yaml b/flutter_quill_test/pubspec.yaml index e5856b719..7b1a400fa 100644 --- a/flutter_quill_test/pubspec.yaml +++ b/flutter_quill_test/pubspec.yaml @@ -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/ diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index 0e14a5a9b..791a371c6 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -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'; diff --git a/lib/quill_delta.dart b/lib/quill_delta.dart index 8eb6b1a66..dc3a0a01c 100644 --- a/lib/quill_delta.dart +++ b/lib/quill_delta.dart @@ -1,3 +1,3 @@ library flutter_quill.delta; -export 'src/models/quill_delta.dart'; +export 'package:dart_quill_delta/dart_quill_delta.dart'; diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart index 70c48f67d..aa0495a2f 100644 --- a/lib/src/models/documents/document.dart +++ b/lib/src/models/documents/document.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 _observer = StreamController.broadcast(); + final StreamController documentChangeObserver = + StreamController.broadcast(); - final History _history = History(); + final History history = History(); /// Stream of [DocChange]s applied to this document. - Stream get changes => _observer.stream; + Stream 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; } diff --git a/lib/src/models/documents/history.dart b/lib/src/models/documents/history.dart index 638855f90..11082e4fa 100644 --- a/lib/src/models/documents/history.dart +++ b/lib/src/models/documents/history.dart @@ -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'; diff --git a/lib/src/models/documents/nodes/block.dart b/lib/src/models/documents/nodes/block.dart index 886ad392a..c135543df 100644 --- a/lib/src/models/documents/nodes/block.dart +++ b/lib/src/models/documents/nodes/block.dart @@ -1,4 +1,4 @@ -import '../../quill_delta.dart'; +import '../../../../quill_delta.dart'; import 'container.dart'; import 'line.dart'; import 'node.dart'; diff --git a/lib/src/models/documents/nodes/leaf.dart b/lib/src/models/documents/nodes/leaf.dart index 115f7058b..adda90abf 100644 --- a/lib/src/models/documents/nodes/leaf.dart +++ b/lib/src/models/documents/nodes/leaf.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'; diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index d202ba08a..c26b3f009 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/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'; diff --git a/lib/src/models/documents/nodes/node.dart b/lib/src/models/documents/nodes/node.dart index 3b78603c6..49a346ef3 100644 --- a/lib/src/models/documents/nodes/node.dart +++ b/lib/src/models/documents/nodes/node.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'; diff --git a/lib/src/models/quill_delta.dart b/lib/src/models/quill_delta.dart deleted file mode 100644 index 8b31045e5..000000000 --- a/lib/src/models/quill_delta.dart +++ /dev/null @@ -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.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? 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? 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 _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? get attributes => -// _attributes == null ? null : Map.from(_attributes!); -// final Map? _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.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 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._([]); - -// Delta._(List operations) : _operations = operations; - -// /// Creates new [Delta] from [other]. -// factory Delta.from(Delta other) => -// Delta._(List.from(other._operations)); - -// /// Creates new [Delta] from a List of Operation -// factory Delta.fromOperations(List operations) => -// Delta._(operations.toList()); - -// // Placeholder char for embed in diff() -// static final String _kNullCharacter = String.fromCharCode(0); - -// /// Transforms two attribute sets. -// static Map? transformAttributes( -// Map? a, Map? b, bool priority) { -// if (a == null) return b; -// if (b == null) return null; - -// if (!priority) return b; - -// final result = b.keys.fold>({}, (attributes, key) { -// if (!a.containsKey(key)) attributes[key] = b[key]; -// return attributes; -// }); - -// return result.isEmpty ? null : result; -// } - -// /// Composes two attribute sets. -// static Map? composeAttributes( -// Map? a, Map? b, -// {bool keepNull = false}) { -// a ??= const {}; -// b ??= const {}; - -// final result = Map.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 invertAttributes( -// Map? attr, Map? 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.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? diffAttributes( -// Map? a, Map? b) { -// a ??= const {}; -// b ??= const {}; - -// final attributes = {}; -// 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 _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 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(DefaultEquality()); -// return comparator.equals(_operations, typedOther._operations); -// } - -// @override -// int get hashCode => hashObjects(_operations); - -// /// Retain [count] of characters from current position. -// void retain(int count, [Map? 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? 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 map(T Function(Operation) f) { -// return _operations.map(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; -// } -// } diff --git a/lib/src/models/rules/delete.dart b/lib/src/models/rules/delete.dart index a6718445b..78b94f6bc 100644 --- a/lib/src/models/rules/delete.dart +++ b/lib/src/models/rules/delete.dart @@ -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. diff --git a/lib/src/models/rules/format.dart b/lib/src/models/rules/format.dart index ebe76418a..7f731e7a4 100644 --- a/lib/src/models/rules/format.dart +++ b/lib/src/models/rules/format.dart @@ -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. diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index f57cb910b..7f8686c4f 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -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. diff --git a/lib/src/models/rules/rule.dart b/lib/src/models/rules/rule.dart index 976fda433..d7132b3e2 100644 --- a/lib/src/models/rules/rule.dart +++ b/lib/src/models/rules/rule.dart @@ -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'; diff --git a/lib/src/models/structs/doc_change.dart b/lib/src/models/structs/doc_change.dart index 33e398ec9..50453995d 100644 --- a/lib/src/models/structs/doc_change.dart +++ b/lib/src/models/structs/doc_change.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 { diff --git a/lib/src/packages/quill_markdown/delta_to_markdown.dart b/lib/src/packages/quill_markdown/delta_to_markdown.dart index ba1518b7d..a7c7e9dc6 100644 --- a/lib/src/packages/quill_markdown/delta_to_markdown.dart +++ b/lib/src/packages/quill_markdown/delta_to_markdown.dart @@ -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'; diff --git a/lib/src/packages/quill_markdown/markdown_to_delta.dart b/lib/src/packages/quill_markdown/markdown_to_delta.dart index 76f5b4ba0..76285a151 100644 --- a/lib/src/packages/quill_markdown/markdown_to_delta.dart +++ b/lib/src/packages/quill_markdown/markdown_to_delta.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'; diff --git a/lib/src/packages/quill_markdown/utils.dart b/lib/src/packages/quill_markdown/utils.dart index ba4c6b859..59d4ce3c8 100644 --- a/lib/src/packages/quill_markdown/utils.dart +++ b/lib/src/packages/quill_markdown/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. diff --git a/lib/src/utils/delta.dart b/lib/src/utils/delta.dart index 5c244e016..da6cf5450 100644 --- a/lib/src/utils/delta.dart +++ b/lib/src/utils/delta.dart @@ -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 diff --git a/lib/src/widgets/quill/quill_controller.dart b/lib/src/widgets/quill/quill_controller.dart index 8bbf10950..11c8951b6 100644 --- a/lib/src/widgets/quill/quill_controller.dart +++ b/lib/src/widgets/quill/quill_controller.dart @@ -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) { diff --git a/lib/src/widgets/raw_editor/raw_editor_state.dart b/lib/src/widgets/raw_editor/raw_editor_state.dart index 84af33167..b8adf76ed 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state.dart @@ -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( diff --git a/lib/src/widgets/toolbar/base_toolbar.dart b/lib/src/widgets/toolbar/base_toolbar.dart index c849eda72..bd051cc94 100644 --- a/lib/src/widgets/toolbar/base_toolbar.dart +++ b/lib/src/widgets/toolbar/base_toolbar.dart @@ -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'; diff --git a/lib/src/widgets/toolbar/buttons/alignment/select_alignment_button.dart b/lib/src/widgets/toolbar/buttons/alignment/select_alignment_button.dart new file mode 100644 index 000000000..6161cb183 --- /dev/null +++ b/lib/src/widgets/toolbar/buttons/alignment/select_alignment_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(), + ); + } +} diff --git a/lib/src/widgets/toolbar/buttons/select_alignment_buttons.dart b/lib/src/widgets/toolbar/buttons/alignment/select_alignment_buttons.dart similarity index 85% rename from lib/src/widgets/toolbar/buttons/select_alignment_buttons.dart rename to lib/src/widgets/toolbar/buttons/alignment/select_alignment_buttons.dart index 7259a2c59..651af856d 100644 --- a/lib/src/widgets/toolbar/buttons/select_alignment_buttons.dart +++ b/lib/src/widgets/toolbar/buttons/alignment/select_alignment_buttons.dart @@ -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), diff --git a/lib/src/widgets/toolbar/buttons/select_header_style_button.dart b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_button.dart similarity index 79% rename from lib/src/widgets/toolbar/buttons/select_header_style_button.dart rename to lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_button.dart index da04b84aa..27a513197 100644 --- a/lib/src/widgets/toolbar/buttons/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_button.dart @@ -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 { 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), + ], + ), + ), ); } } diff --git a/lib/src/widgets/toolbar/buttons/select_header_style_buttons.dart b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart similarity index 94% rename from lib/src/widgets/toolbar/buttons/select_header_style_buttons.dart rename to lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart index a49151860..9f762aa0e 100644 --- a/lib/src/widgets/toolbar/buttons/select_header_style_buttons.dart +++ b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart @@ -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({ diff --git a/lib/src/widgets/toolbar/simple_toolbar.dart b/lib/src/widgets/toolbar/simple_toolbar.dart index 4f91cb346..c72461e8a 100644 --- a/lib/src/widgets/toolbar/simple_toolbar.dart +++ b/lib/src/widgets/toolbar/simple_toolbar.dart @@ -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 { diff --git a/pubspec.yaml b/pubspec.yaml index 2d9c3572e..9b2dbf6e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 diff --git a/quill_html_converter/CHANGELOG.md b/quill_html_converter/CHANGELOG.md index 4392c9d05..a4d0bba20 100644 --- a/quill_html_converter/CHANGELOG.md +++ b/quill_html_converter/CHANGELOG.md @@ -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 diff --git a/quill_html_converter/pubspec.yaml b/quill_html_converter/pubspec.yaml index d09180207..94f20e159 100644 --- a/quill_html_converter/pubspec.yaml +++ b/quill_html_converter/pubspec.yaml @@ -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/ diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart index 930cdccfc..72fc6f94f 100644 --- a/test/widgets/controller_test.dart +++ b/test/widgets/controller_test.dart @@ -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() { diff --git a/version.dart b/version.dart index 8ed75fba4..44108adca 100644 --- a/version.dart +++ b/version.dart @@ -1 +1 @@ -const version = '9.0.0-dev-11'; +const version = '9.0.0';