From 7d3731aaa02ad2496d3efc42820d3549a01e1f32 Mon Sep 17 00:00:00 2001 From: Ahmed Hnewa <73608287+freshtechtips@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:26:13 +0300 Subject: [PATCH] Fix a bug --- .../android/app/src/main/AndroidManifest.xml | 8 +- .../lib/embeds/toolbar/media_button.dart | 2 + flutter_quill_extensions/pubspec.yaml | 2 +- lib/src/models/rules/delete.dart | 40 +++++-- lib/src/models/rules/format.dart | 40 +++++-- lib/src/models/rules/insert.dart | 111 +++++++++++++++--- lib/src/models/rules/rule.dart | 28 ++++- lib/src/widgets/controller.dart | 8 +- lib/src/widgets/raw_editor.dart | 39 ++++-- .../widgets/toolbar/link_style_button.dart | 2 +- .../widgets/toolbar/link_style_button2.dart | 2 +- 11 files changed, 225 insertions(+), 57 deletions(-) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 12e55a7a..aeb699d6 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,13 @@ - + + + + + + + { void _submitLink() => Navigator.pop(context, _linkController.text); String? _validateLink(String? value) { + // TODO: Use [AutoFormatMultipleLinksRule.oneLineRegExp] + // in the next update if ((value?.isEmpty ?? false) || !AutoFormatMultipleLinksRule.linkRegExp.hasMatch(value!)) { return widget.validationMessage ?? 'That is not a valid URL'; diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 59995c59..7fe0fc23 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: flutter_quill: ^7.4.14 # In case you are working on changes for both libraries, # flutter_quill: - # path: ~/development/playground/framework_based/flutter/flutter-quill + # path: /Users/ahmedhnewa/development/playground/framework_based/flutter/flutter-quill http: ^1.1.0 image_picker: ">=1.0.4" diff --git a/lib/src/models/rules/delete.dart b/lib/src/models/rules/delete.dart index fd547825..6381636a 100644 --- a/lib/src/models/rules/delete.dart +++ b/lib/src/models/rules/delete.dart @@ -22,8 +22,14 @@ class EnsureLastLineBreakDeleteRule extends DeleteRule { const EnsureLastLineBreakDeleteRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { final itr = DeltaIterator(document)..skip(index + len!); return Delta() @@ -38,8 +44,14 @@ class CatchAllDeleteRule extends DeleteRule { const CatchAllDeleteRule(); @override - Delta applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { final itr = DeltaIterator(document)..skip(index + len!); return Delta() @@ -58,8 +70,14 @@ class PreserveLineStyleOnMergeRule extends DeleteRule { const PreserveLineStyleOnMergeRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { final itr = DeltaIterator(document)..skip(index); var op = itr.next(1); if (op.data != '\n') { @@ -116,8 +134,14 @@ class EnsureEmbedLineRule extends DeleteRule { const EnsureEmbedLineRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { final itr = DeltaIterator(document); var op = itr.skip(index); diff --git a/lib/src/models/rules/format.dart b/lib/src/models/rules/format.dart index 0424903a..45a40f11 100644 --- a/lib/src/models/rules/format.dart +++ b/lib/src/models/rules/format.dart @@ -23,8 +23,14 @@ class ResolveLineFormatRule extends FormatRule { const ResolveLineFormatRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (attribute!.scope != AttributeScope.BLOCK) { return null; } @@ -108,8 +114,14 @@ class FormatLinkAtCaretPositionRule extends FormatRule { const FormatLinkAtCaretPositionRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (attribute!.key != Attribute.link.key || len! > 0) { return null; } @@ -142,8 +154,14 @@ class ResolveInlineFormatRule extends FormatRule { const ResolveInlineFormatRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (attribute!.scope != AttributeScope.INLINE) { return null; } @@ -182,8 +200,14 @@ class ResolveImageFormatRule extends FormatRule { const ResolveImageFormatRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (attribute == null || attribute.key != Attribute.style.key) { return null; } diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index b8297411..41be0fa0 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -27,8 +27,16 @@ class PreserveLineStyleOnSplitRule extends InsertRule { const PreserveLineStyleOnSplitRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + // TODO: If the maintainer are not okay with this change then tell me + // so I can change it back + Map extraData = const {}, + }) { if (data is! String || data != '\n') { return null; } @@ -72,8 +80,14 @@ class PreserveBlockStyleOnInsertRule extends InsertRule { const PreserveBlockStyleOnInsertRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (data is! String || !data.contains('\n')) { // Only interested in text containing at least one newline character. return null; @@ -153,8 +167,14 @@ class AutoExitBlockRule extends InsertRule { } @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (data is! String || data != '\n') { return null; } @@ -217,8 +237,14 @@ class ResetLineFormatOnNewLineRule extends InsertRule { const ResetLineFormatOnNewLineRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (data is! String || data != '\n') { return null; } @@ -248,8 +274,14 @@ class InsertEmbedsRule extends InsertRule { const InsertEmbedsRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (data is String) { return null; } @@ -329,8 +361,30 @@ class AutoFormatMultipleLinksRule extends InsertRule { // http://www.example.com/?action=birds&brass=apparatus // https://example.net/ // URL generator tool (https://www.randomlists.com/urls) is used. - static const _linkPattern = r'^https?:\/\/[\w\-]+(\.[\w\-]+)*(:\d+)?(\/.*)?$'; - static final linkRegExp = RegExp(_linkPattern, caseSensitive: false); + + // TODO: You might want to rename those but everywhere even in + // flutter_quill_extensions + + static const _oneLinePattern = + r'^https?:\/\/[\w\-]+(\.[\w\-]+)*(:\d+)?(\/.*)?$'; + static const _detectLinkPattern = + r'https?:\/\/[\w\-]+(\.[\w\-]+)*(:\d+)?(\/[^\s]*)?'; + + /// It requires a valid link in one link + static final oneLineRegExp = RegExp( + _oneLinePattern, + caseSensitive: false, + ); + + /// It detect if there is a link in the text whatever if it in the middle etc + static final detectLinkRegExp = RegExp( + _detectLinkPattern, + caseSensitive: false, + ); + @Deprecated( + 'Please use [linkRegExp1] or [linkRegExp2]', + ) + static final linkRegExp = oneLineRegExp; @override Delta? applyRule( @@ -339,6 +393,7 @@ class AutoFormatMultipleLinksRule extends InsertRule { int? len, Object? data, Attribute? attribute, + Map extraData = const {}, }) { // Only format when inserting text. if (data is! String) return null; @@ -374,7 +429,7 @@ class AutoFormatMultipleLinksRule extends InsertRule { final affectedWords = '$leftWordPart$data$rightWordPart'; // Check for URL pattern. - final matches = linkRegExp.allMatches(affectedWords); + final matches = detectLinkRegExp.allMatches(affectedWords); // If there are no matches, do not apply any format. if (matches.isEmpty) return null; @@ -428,8 +483,14 @@ class AutoFormatLinksRule extends InsertRule { const AutoFormatLinksRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (data is! String || data != ' ') { return null; } @@ -468,8 +529,14 @@ class PreserveInlineStylesRule extends InsertRule { const PreserveInlineStylesRule(); @override - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { if (data is! String || data.contains('\n')) { return null; } @@ -514,8 +581,14 @@ class CatchAllInsertRule extends InsertRule { const CatchAllInsertRule(); @override - Delta applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }) { return Delta() ..retain(index + (len ?? 0)) ..insert(data); diff --git a/lib/src/models/rules/rule.dart b/lib/src/models/rules/rule.dart index 96a6d413..ac4c015c 100644 --- a/lib/src/models/rules/rule.dart +++ b/lib/src/models/rules/rule.dart @@ -10,19 +10,35 @@ enum RuleType { INSERT, DELETE, FORMAT } abstract class Rule { const Rule(); - Delta? apply(Delta document, int index, - {int? len, Object? data, Attribute? attribute}) { + Delta? apply( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + }) { validateArgs(len, data, attribute); - return applyRule(document, index, - len: len, data: data, attribute: attribute); + return applyRule( + document, + index, + len: len, + data: data, + attribute: attribute, + ); } void validateArgs(int? len, Object? data, Attribute? attribute); /// Applies heuristic rule to an operation on a [document] and returns /// resulting [Delta]. - Delta? applyRule(Delta document, int index, - {int? len, Object? data, Attribute? attribute}); + Delta? applyRule( + Delta document, + int index, { + int? len, + Object? data, + Attribute? attribute, + Map extraData = const {}, + }); RuleType get type; } diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index 7965d5d2..242faa55 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -227,8 +227,12 @@ class QuillController extends ChangeNotifier { } void replaceText( - int index, int len, Object? data, TextSelection? textSelection, - {bool ignoreFocus = false}) { + int index, + int len, + Object? data, + TextSelection? textSelection, { + bool ignoreFocus = false, + }) { assert(data is String || data is Embeddable); if (onReplaceText != null && !onReplaceText!(index, len, data)) { diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index ffcde5e9..4b1288b7 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -533,7 +533,8 @@ class RawEditorState extends EditorState ? const BoxConstraints.expand() : BoxConstraints( minHeight: widget.minHeight ?? 0.0, - maxHeight: widget.maxHeight ?? double.infinity); + maxHeight: widget.maxHeight ?? double.infinity, + ); // Please notice that this change will make the check fixed // so if we ovveride the platform in material app theme data @@ -1512,13 +1513,23 @@ class RawEditorState extends EditorState final index = textEditingValue.selection.baseOffset; final length = textEditingValue.selection.extentOffset - index; final copied = controller.copiedImageUrl!; - controller.replaceText(index, length, BlockEmbed.image(copied.url), null); + controller.replaceText( + index, + length, + BlockEmbed.image(copied.url), + null, + ); if (copied.styleString.isNotEmpty) { - controller.formatText(getEmbedNode(controller, index + 1).offset, 1, - StyleAttribute(copied.styleString)); + controller.formatText( + getEmbedNode(controller, index + 1).offset, + 1, + StyleAttribute(copied.styleString), + ); } controller.copiedImageUrl = null; - await Clipboard.setData(const ClipboardData(text: '')); + await Clipboard.setData( + const ClipboardData(text: ''), + ); return; } @@ -1531,7 +1542,13 @@ class RawEditorState extends EditorState final text = await Clipboard.getData(Clipboard.kTextPlain); if (text != null) { _replaceText( - ReplaceTextIntent(textEditingValue, text.text!, selection, cause)); + ReplaceTextIntent( + textEditingValue, + text.text!, + selection, + cause, + ), + ); bringIntoView(textEditingValue.selection.extent); @@ -1539,8 +1556,9 @@ class RawEditorState extends EditorState userUpdateTextEditingValue( TextEditingValue( text: textEditingValue.text, - selection: - TextSelection.collapsed(offset: textEditingValue.selection.end), + selection: TextSelection.collapsed( + offset: textEditingValue.selection.end, + ), ), cause, ); @@ -1548,14 +1566,15 @@ class RawEditorState extends EditorState return; } - if (widget.onImagePaste != null) { + final onImagePaste = widget.onImagePaste; + if (onImagePaste != null) { final image = await Pasteboard.image; if (image == null) { return; } - final imageUrl = await widget.onImagePaste!(image); + final imageUrl = await onImagePaste(image); if (imageUrl == null) { return; } diff --git a/lib/src/widgets/toolbar/link_style_button.dart b/lib/src/widgets/toolbar/link_style_button.dart index 11bf6657..2e1d2374 100644 --- a/lib/src/widgets/toolbar/link_style_button.dart +++ b/lib/src/widgets/toolbar/link_style_button.dart @@ -184,7 +184,7 @@ class _LinkDialogState extends State<_LinkDialog> { super.initState(); _link = widget.link ?? ''; _text = widget.text ?? ''; - linkRegExp = widget.linkRegExp ?? AutoFormatMultipleLinksRule.linkRegExp; + linkRegExp = widget.linkRegExp ?? AutoFormatMultipleLinksRule.oneLineRegExp; _linkController = TextEditingController(text: _link); _textController = TextEditingController(text: _text); } diff --git a/lib/src/widgets/toolbar/link_style_button2.dart b/lib/src/widgets/toolbar/link_style_button2.dart index 98140312..895a08f6 100644 --- a/lib/src/widgets/toolbar/link_style_button2.dart +++ b/lib/src/widgets/toolbar/link_style_button2.dart @@ -379,7 +379,7 @@ class _LinkStyleDialogState extends State { String? _validateLink(String? value) { if ((value?.isEmpty ?? false) || - !AutoFormatMultipleLinksRule.linkRegExp.hasMatch(value!)) { + !AutoFormatMultipleLinksRule.oneLineRegExp.hasMatch(value!)) { return widget.validationMessage ?? 'That is not a valid URL'; }