From 8196311720c024202d045faaa0abab3c2c73036a Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 8 Oct 2022 22:18:22 -0700 Subject: [PATCH 001/204] Fixed the read only demo page --- example/lib/pages/home_page.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index fcff61f2..743ec9b4 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -347,6 +347,7 @@ class _HomePageState extends State { } void _readOnly() { + Navigator.pop(super.context); Navigator.push( super.context, MaterialPageRoute( From ccf1850e9a6b3dc5763a2e2314272b02872d5738 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Sun, 9 Oct 2022 17:46:29 -0400 Subject: [PATCH 002/204] Allow disabling of selection toolbar. (#972) --- example/lib/pages/home_page.dart | 1 + lib/src/widgets/editor.dart | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 743ec9b4..93b5ae48 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -108,6 +108,7 @@ class _HomePageState extends State { autoFocus: false, readOnly: false, placeholder: 'Add content', + enableSelectionToolbar: !(kIsWeb || _isDesktop()), expands: false, padding: EdgeInsets.zero, onImagePaste: _onImagePaste, diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 5568b8ba..ec835d05 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -157,6 +157,7 @@ class QuillEditor extends StatefulWidget { this.paintCursorAboveText, this.placeholder, this.enableInteractiveSelection = true, + this.enableSelectionToolbar = true, this.scrollBottomInset = 0, this.minHeight, this.maxHeight, @@ -266,8 +267,14 @@ class QuillEditor extends StatefulWidget { /// When this is false, the text selection cannot be adjusted by /// the user, text cannot be copied, and the user cannot paste into /// the text field from the clipboard. + /// + /// To disable just the selection toolbar, set enableSelectionToolbar + /// to false. final bool enableInteractiveSelection; + /// Whether to show the cut/copy/paste menu when selecting text. + final bool enableSelectionToolbar; + /// The minimum height to be occupied by this editor. /// /// This only has effect if [scrollable] is set to `true` and [expands] is @@ -435,6 +442,9 @@ class QuillEditorState extends State theme.colorScheme.primary.withOpacity(0.40); } + final showSelectionToolbar = + widget.enableInteractiveSelection && widget.enableSelectionToolbar; + final child = RawEditor( key: _editorKey, controller: widget.controller, @@ -447,10 +457,10 @@ class QuillEditorState extends State placeholder: widget.placeholder, onLaunchUrl: widget.onLaunchUrl, toolbarOptions: ToolbarOptions( - copy: widget.enableInteractiveSelection, - cut: widget.enableInteractiveSelection, - paste: widget.enableInteractiveSelection, - selectAll: widget.enableInteractiveSelection, + copy: showSelectionToolbar, + cut: showSelectionToolbar, + paste: showSelectionToolbar, + selectAll: showSelectionToolbar, ), showSelectionHandles: isMobile(theme.platform), showCursor: widget.showCursor, From eceeb1805f0c7cce9cebe38d4910eae3ef0cd6dd Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 9 Oct 2022 17:09:59 -0700 Subject: [PATCH 003/204] Upgrade to 6.0.7 --- CHANGELOG.md | 5 ++++- example/lib/pages/home_page.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e8f538..4c4f03a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ +# [6.0.7] +* Allow disabling of selection toolbar. + # [6.0.6+1] -* Revert 6.0.6 +* Revert 6.0.6. # [6.0.6] * Fix wrong custom embed key. diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 93b5ae48..0836a98e 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -108,7 +108,7 @@ class _HomePageState extends State { autoFocus: false, readOnly: false, placeholder: 'Add content', - enableSelectionToolbar: !(kIsWeb || _isDesktop()), + enableSelectionToolbar: isMobile(), expands: false, padding: EdgeInsets.zero, onImagePaste: _onImagePaste, diff --git a/pubspec.yaml b/pubspec.yaml index 5cfb1351..7894b846 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.0.6+1 +version: 6.0.7 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 27d9ac3bdd09169a37a5748b6ac1dad46c66b2f3 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Mon, 10 Oct 2022 14:08:18 -0400 Subject: [PATCH 004/204] Make QuillController.document mutable. (#973) --- lib/src/widgets/controller.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index db6637a5..e6b32180 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -17,14 +17,15 @@ typedef DeleteCallback = void Function(int cursorPosition, bool forward); class QuillController extends ChangeNotifier { QuillController({ - required this.document, + required Document document, required TextSelection selection, bool keepStyleOnNewLine = false, this.onReplaceText, this.onDelete, this.onSelectionCompleted, this.onSelectionChanged, - }) : _selection = selection, + }) : _document = document, + _selection = selection, _keepStyleOnNewLine = keepStyleOnNewLine; factory QuillController.basic() { @@ -35,7 +36,12 @@ class QuillController extends ChangeNotifier { } /// Document managed by this controller. - final Document document; + Document _document; + Document get document => _document; + set document(doc) { + _document = doc; + notifyListeners(); + } /// Tells whether to keep or reset the [toggledStyle] /// when user adds a new line. From 5516e70f63f43a087d8fe1a5214db329bd7a92ce Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 10 Oct 2022 11:11:01 -0700 Subject: [PATCH 005/204] Upgrade to 6.0.8 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c4f03a6..f63cb021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.0.8] +* Make QuillController.document mutable. + # [6.0.7] * Allow disabling of selection toolbar. diff --git a/pubspec.yaml b/pubspec.yaml index 7894b846..7c03910e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.0.7 +version: 6.0.8 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From b7b1afc9e61a4d010cd067258b237a500830c59e Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Mon, 10 Oct 2022 14:29:17 -0400 Subject: [PATCH 006/204] Fixes null pointer when setting documents. (#974) --- lib/src/widgets/controller.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index e6b32180..00c4f37f 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -40,6 +40,10 @@ class QuillController extends ChangeNotifier { Document get document => _document; set document(doc) { _document = doc; + + // Prevent the selection from + _selection = const TextSelection(baseOffset: 0, extentOffset: 0); + notifyListeners(); } From f52ea506c888eb64bfc417a1fb3da50a039d937a Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 10 Oct 2022 12:11:00 -0700 Subject: [PATCH 007/204] Upgrade to 6.0.8+1 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f63cb021..8d0d9021 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.0.8+1] +* Fixes null pointer when setting documents. + # [6.0.8] * Make QuillController.document mutable. diff --git a/pubspec.yaml b/pubspec.yaml index 7c03910e..9c991fc8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.0.8 +version: 6.0.8+1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From aa8f82f7aad7e8188008057d236870afc66468e0 Mon Sep 17 00:00:00 2001 From: Michael Allen Date: Thu, 20 Oct 2022 14:26:44 -0700 Subject: [PATCH 008/204] - don't throw showAutocorrectionPromptRect not implemented. The function is called with every keystroke as a user is typing (#981) --- .../raw_editor/raw_editor_state_text_input_client_mixin.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index 24cdc955..a72fb88e 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -283,7 +283,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState @override void showAutocorrectionPromptRect(int start, int end) { - throw UnimplementedError(); + // this is called VERY OFTEN when editing a document, no longer throw an exception + // throw UnimplementedError(); } @override From 391b21b4f1ba31085706484eb18e89764b5411ff Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 20 Oct 2022 15:15:37 -0700 Subject: [PATCH 009/204] Upgrade to 6.0.9 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d0d9021..7ecf0220 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.0.9] +* Don't throw showAutocorrectionPromptRect not implemented. The function is called with every keystroke as a user is typing. + # [6.0.8+1] * Fixes null pointer when setting documents. diff --git a/pubspec.yaml b/pubspec.yaml index 9c991fc8..860ee74e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.0.8+1 +version: 6.0.9 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From c4c512552b6ae690e0a1cbf04bc41bdf0dcfd0be Mon Sep 17 00:00:00 2001 From: Luis Felipe Murguia Ramos <65832922+seel-channel@users.noreply.github.com> Date: Thu, 20 Oct 2022 21:37:18 -0700 Subject: [PATCH 010/204] [Multiline Select] HomePage allows to select paragraph line on triple click (#983) Co-authored-by: Luis Felipe Murguia Ramos --- example/lib/pages/home_page.dart | 149 ++++++++++++++++++++++++------- 1 file changed, 119 insertions(+), 30 deletions(-) diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 0836a98e..15623a81 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -17,6 +17,12 @@ import 'package:tuple/tuple.dart'; import '../universal_ui/universal_ui.dart'; import 'read_only_page.dart'; +enum _SelectionType { + none, + word, + // line, +} + class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); @@ -25,6 +31,14 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { QuillController? _controller; final FocusNode _focusNode = FocusNode(); + Timer? _selectAllTimer; + _SelectionType _selectionType = _SelectionType.none; + + @override + void dispose() { + _selectAllTimer?.cancel(); + super.dispose(); + } @override void initState() { @@ -99,39 +113,109 @@ class _HomePageState extends State { ); } + bool _onTripleClickSelection() { + final controller = _controller!; + + _selectAllTimer?.cancel(); + _selectAllTimer = null; + + // If you want to select all text after paragraph, uncomment this line + // if (_selectionType == _SelectionType.line) { + // final selection = TextSelection( + // baseOffset: 0, + // extentOffset: controller.document.length, + // ); + + // controller.updateSelection(selection, ChangeSource.REMOTE); + + // _selectionType = _SelectionType.none; + + // return true; + // } + + if (controller.selection.isCollapsed) { + _selectionType = _SelectionType.none; + } + + if (_selectionType == _SelectionType.none) { + _selectionType = _SelectionType.word; + _startTripleClickTimer(); + return false; + } + + if (_selectionType == _SelectionType.word) { + final child = controller.document.queryChild( + controller.selection.baseOffset, + ); + final offset = child.node?.documentOffset ?? 0; + final length = child.node?.length ?? 0; + + final selection = TextSelection( + baseOffset: offset, + extentOffset: offset + length, + ); + + controller.updateSelection(selection, ChangeSource.REMOTE); + + // _selectionType = _SelectionType.line; + + _selectionType = _SelectionType.none; + + _startTripleClickTimer(); + + return true; + } + + return false; + } + + void _startTripleClickTimer() { + _selectAllTimer = Timer(const Duration(milliseconds: 900), () { + _selectionType = _SelectionType.none; + }); + } + Widget _buildWelcomeEditor(BuildContext context) { - var quillEditor = QuillEditor( - controller: _controller!, - scrollController: ScrollController(), - scrollable: true, - focusNode: _focusNode, - autoFocus: false, - readOnly: false, - placeholder: 'Add content', - enableSelectionToolbar: isMobile(), - expands: false, - padding: EdgeInsets.zero, - onImagePaste: _onImagePaste, - customStyles: DefaultStyles( - h1: DefaultTextBlockStyle( - const TextStyle( - fontSize: 32, - color: Colors.black, - height: 1.15, - fontWeight: FontWeight.w300, - ), - const Tuple2(16, 0), - const Tuple2(0, 0), - null), - sizeSmall: const TextStyle(fontSize: 9), + Widget quillEditor = MouseRegion( + cursor: SystemMouseCursors.text, + child: QuillEditor( + controller: _controller!, + scrollController: ScrollController(), + scrollable: true, + focusNode: _focusNode, + autoFocus: false, + readOnly: false, + placeholder: 'Add content', + enableSelectionToolbar: isMobile(), + expands: false, + padding: EdgeInsets.zero, + onImagePaste: _onImagePaste, + onTapUp: (details, p1) { + return _onTripleClickSelection(); + }, + customStyles: DefaultStyles( + h1: DefaultTextBlockStyle( + const TextStyle( + fontSize: 32, + color: Colors.black, + height: 1.15, + fontWeight: FontWeight.w300, + ), + const Tuple2(16, 0), + const Tuple2(0, 0), + null), + sizeSmall: const TextStyle(fontSize: 9), + ), + embedBuilders: [ + ...FlutterQuillEmbeds.builders(), + NotesEmbedBuilder(addEditNote: _addEditNote) + ], ), - embedBuilders: [ - ...FlutterQuillEmbeds.builders(), - NotesEmbedBuilder(addEditNote: _addEditNote) - ], ); if (kIsWeb) { - quillEditor = QuillEditor( + quillEditor = MouseRegion( + cursor: SystemMouseCursors.text, + child: QuillEditor( controller: _controller!, scrollController: ScrollController(), scrollable: true, @@ -141,6 +225,9 @@ class _HomePageState extends State { placeholder: 'Add content', expands: false, padding: EdgeInsets.zero, + onTapUp: (details, p1) { + return _onTripleClickSelection(); + }, customStyles: DefaultStyles( h1: DefaultTextBlockStyle( const TextStyle( @@ -154,7 +241,9 @@ class _HomePageState extends State { null), sizeSmall: const TextStyle(fontSize: 9), ), - embedBuilders: defaultEmbedBuildersWeb); + embedBuilders: defaultEmbedBuildersWeb, + ), + ); } var toolbar = QuillToolbar.basic( controller: _controller!, From e8c1c91d27dc15cfc37c1f9e8148f8e764cce567 Mon Sep 17 00:00:00 2001 From: Erik Bjorklund Date: Thu, 20 Oct 2022 23:38:25 -0500 Subject: [PATCH 011/204] upgrades device info plus to ^7.0.0 (#978) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 860ee74e..e3754c79 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: characters: ^1.2.0 diff_match_patch: ^0.4.1 i18n_extension: ^5.0.1 - device_info_plus: ^4.0.0 + device_info_plus: ^7.0.0 platform: ^3.1.0 pasteboard: ^0.2.0 From 2ac7b2789849262d70344647aff09a5426402c4c Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 20 Oct 2022 21:44:09 -0700 Subject: [PATCH 012/204] Upgrade to 6.0.10 --- CHANGELOG.md | 3 +++ README.md | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ecf0220..5f46630f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.0.10] +* Upgrade device info plus to ^7.0.0. + # [6.0.9] * Don't throw showAutocorrectionPromptRect not implemented. The function is called with every keystroke as a user is typing. diff --git a/README.md b/README.md index 06fcd9e8..56e621ef 100644 --- a/README.md +++ b/README.md @@ -85,11 +85,11 @@ _controller = QuillController( For web development, use `flutter config --enable-web` for flutter or use [ReactQuill] for React. It is required to provide `EmbedBuilder`, e.g. [defaultEmbedBuildersWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L99). -Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L241). +Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L334). ## Desktop -It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L221). +It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L314). ## Configuration diff --git a/pubspec.yaml b/pubspec.yaml index e3754c79..119d9ce6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.0.9 +version: 6.0.10 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 47d80f12e57f8e8f0fe6ad8da758b5bc08b05d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?hasan=20ko=C3=A7?= <76748696+HasanKoc33@users.noreply.github.com> Date: Mon, 24 Oct 2022 13:16:05 +0300 Subject: [PATCH 013/204] Update toolbar.i18n.dart (#984) --- lib/src/translations/toolbar.i18n.dart | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index d0af83f0..ae5e1ea1 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -351,20 +351,20 @@ extension Localization on String { 'Text': 'Text', 'What is entered is not a link': 'What is entered is not a link', 'Resize': 'Resize', - 'Width': 'Width', - 'Height': 'Height', - 'Size': 'Size', - 'Small': 'Small', - 'Large': 'Large', - 'Huge': 'Huge', - 'Clear': 'Clear', - 'Font': 'Font', - 'Search': 'Search', + 'Width': 'Genişlik', + 'Height': 'Yükseklik', + 'Size': 'Boyut', + 'Small': 'Küçük', + 'Large': 'Büyük', + 'Huge': 'Daha Büyük', + 'Clear': 'Temizle', + 'Font': 'Yazı tipi', + 'Search': 'Ara', 'matches': 'matches', 'showing match': 'showing match', 'Prev': 'Prev', - 'Next': 'Next', - 'Camera': 'Camera', + 'Next': 'Devam', + 'Camera': 'Kamera', 'Video': 'Video', }, 'uk': { From 6456c5b91aeef5c464804e2e2d9b0d34f3c6bf79 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Thu, 27 Oct 2022 00:38:17 -0400 Subject: [PATCH 014/204] Add keyboard shortcuts for editor actions (#986) --- example/lib/pages/home_page.dart | 19 +- example/linux/my_application.cc | 2 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 +- example/pubspec.yaml | 4 +- lib/src/widgets/raw_editor.dart | 234 +++++++++++++++++- lib/src/widgets/toolbar/search_button.dart | 135 +--------- lib/src/widgets/toolbar/search_dialog.dart | 134 ++++++++++ 7 files changed, 373 insertions(+), 157 deletions(-) create mode 100644 lib/src/widgets/toolbar/search_dialog.dart diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 15623a81..7b3c57ba 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -92,24 +92,7 @@ class _HomePageState extends State { color: Colors.grey.shade800, child: _buildMenuBar(context), ), - body: RawKeyboardListener( - focusNode: FocusNode(), - onKey: (event) { - if (event.data.isControlPressed && event.character == 'b') { - if (_controller! - .getSelectionStyle() - .attributes - .keys - .contains('bold')) { - _controller! - .formatSelection(Attribute.clone(Attribute.bold, null)); - } else { - _controller!.formatSelection(Attribute.bold); - } - } - }, - child: _buildWelcomeEditor(context), - ), + body: _buildWelcomeEditor(context), ); } diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc index 8ef02f26..5f64f84a 100644 --- a/example/linux/my_application.cc +++ b/example/linux/my_application.cc @@ -27,7 +27,7 @@ static void my_application_activate(GApplication* application) { // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). - gboolean use_header_bar = TRUE; + gboolean use_header_bar = FALSE; #ifdef GDK_WINDOWING_X11 GdkScreen *screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 886c66ec..5b11d5f3 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,7 +5,7 @@ import FlutterMacOS import Foundation -import device_info_plus_macos +import device_info_plus import pasteboard import path_provider_macos import url_launcher_macos diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f271d0b7..caaa4ab9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -29,8 +29,8 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.4 path_provider: ^2.0.9 - filesystem_picker: ^2.0.0 - file_picker: ^4.6.1 + filesystem_picker: ^3.1.0 + file_picker: ^5.2.2 flutter_quill: path: ../ flutter_quill_extensions: diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 1b4cd939..d724bb97 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; // ignore: unnecessary_import import 'dart:typed_data'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -38,6 +37,7 @@ import 'raw_editor/raw_editor_state_text_input_client_mixin.dart'; import 'text_block.dart'; import 'text_line.dart'; import 'text_selection.dart'; +import 'toolbar/search_dialog.dart'; class RawEditor extends StatefulWidget { const RawEditor( @@ -370,13 +370,59 @@ class RawEditorState extends EditorState data: _styles!, child: Shortcuts( shortcuts: { - // shortcuts added for Windows platform + // shortcuts added for Desktop platforms. LogicalKeySet(LogicalKeyboardKey.escape): const HideSelectionToolbarIntent(), LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyZ): const UndoTextIntent(SelectionChangedCause.keyboard), LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyY): const RedoTextIntent(SelectionChangedCause.keyboard), + + // Selection formatting. + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyB): + const ToggleTextStyleIntent(Attribute.bold), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyU): + const ToggleTextStyleIntent(Attribute.underline), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyI): + const ToggleTextStyleIntent(Attribute.italic), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyS): + const ToggleTextStyleIntent(Attribute.strikeThrough), + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.backquote): + const ToggleTextStyleIntent(Attribute.inlineCode), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyL): + const ToggleTextStyleIntent(Attribute.ul), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyO): + const ToggleTextStyleIntent(Attribute.ol), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyB): + const ToggleTextStyleIntent(Attribute.blockQuote), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.tilde): + const ToggleTextStyleIntent(Attribute.codeBlock), + // Indent + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.bracketRight): + const IndentSelectionIntent(true), + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.bracketLeft): + const IndentSelectionIntent(false), + + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF): + const OpenSearchIntent(), + + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit1): + const ApplyHeaderIntent(Attribute.h1), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit2): + const ApplyHeaderIntent(Attribute.h2), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit3): + const ApplyHeaderIntent(Attribute.h3), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit0): + const ApplyHeaderIntent(Attribute.header), + + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyL): const ApplyCheckListIntent(), }, child: Actions( actions: _actions, @@ -1168,6 +1214,17 @@ class RawEditorState extends EditorState _UpdateTextSelectionToAdjacentLineAction< ExtendSelectionVerticallyToAdjacentLineIntent>(this); + late final _ToggleTextStyleAction _formatSelectionAction = + _ToggleTextStyleAction(this); + + late final _IndentSelectionAction _indentSelectionAction = + _IndentSelectionAction(this); + + late final _OpenSearchAction _openSearchAction = _OpenSearchAction(this); + late final _ApplyHeaderAction _applyHeaderAction = _ApplyHeaderAction(this); + late final _ApplyCheckListAction _applyCheckListAction = + _ApplyCheckListAction(this); + late final Map> _actions = >{ DoNothingAndStopPropagationTextIntent: DoNothingAction(consumesKey: false), ReplaceTextIntent: _replaceTextAction, @@ -1214,6 +1271,13 @@ class RawEditorState extends EditorState _makeOverridable(_HideSelectionToolbarAction(this)), UndoTextIntent: _makeOverridable(_UndoKeyboardAction(this)), RedoTextIntent: _makeOverridable(_RedoKeyboardAction(this)), + + OpenSearchIntent: _openSearchAction, + // Selection Formatting + ToggleTextStyleIntent: _formatSelectionAction, + IndentSelectionIntent: _indentSelectionAction, + ApplyHeaderIntent: _applyHeaderAction, + ApplyCheckListIntent: _applyCheckListAction, }; @override @@ -1962,3 +2026,169 @@ class _RedoKeyboardAction extends ContextAction { @override bool get isActionEnabled => true; } + +class ToggleTextStyleIntent extends Intent { + const ToggleTextStyleIntent(this.attribute); + + final Attribute attribute; +} + +// Toggles a text style (underline, bold, italic, strikethrough) on, or off. +class _ToggleTextStyleAction extends Action { + _ToggleTextStyleAction(this.state); + + final RawEditorState state; + + bool _isStyleActive(Attribute styleAttr, Map attrs) { + if (styleAttr.key == Attribute.list.key) { + final attribute = attrs[styleAttr.key]; + if (attribute == null) { + return false; + } + return attribute.value == styleAttr.value; + } + return attrs.containsKey(styleAttr.key); + } + + @override + void invoke(ToggleTextStyleIntent intent, [BuildContext? context]) { + final isActive = _isStyleActive( + intent.attribute, state.controller.getSelectionStyle().attributes); + state.controller.formatSelection( + isActive ? Attribute.clone(intent.attribute, null) : intent.attribute); + } + + @override + bool get isActionEnabled => true; +} + +class IndentSelectionIntent extends Intent { + const IndentSelectionIntent(this.isIncrease); + + final bool isIncrease; +} + +// Toggles a text style (underline, bold, italic, strikethrough) on, or off. +class _IndentSelectionAction extends Action { + _IndentSelectionAction(this.state); + + final RawEditorState state; + + @override + void invoke(IndentSelectionIntent intent, [BuildContext? context]) { + final indent = + state.controller.getSelectionStyle().attributes[Attribute.indent.key]; + if (indent == null) { + if (intent.isIncrease) { + state.controller.formatSelection(Attribute.indentL1); + } + return; + } + if (indent.value == 1 && !intent.isIncrease) { + state.controller + .formatSelection(Attribute.clone(Attribute.indentL1, null)); + return; + } + if (intent.isIncrease) { + state.controller + .formatSelection(Attribute.getIndentLevel(indent.value + 1)); + return; + } + state.controller + .formatSelection(Attribute.getIndentLevel(indent.value - 1)); + } + + @override + bool get isActionEnabled => true; +} + +class OpenSearchIntent extends Intent { + const OpenSearchIntent(); +} + +// Toggles a text style (underline, bold, italic, strikethrough) on, or off. +class _OpenSearchAction extends ContextAction { + _OpenSearchAction(this.state); + + final RawEditorState state; + + @override + Future invoke(OpenSearchIntent intent, [BuildContext? context]) async { + await showDialog( + context: context!, + builder: (_) => SearchDialog(controller: state.controller, text: ''), + ); + } + + @override + bool get isActionEnabled => true; +} + +class ApplyHeaderIntent extends Intent { + const ApplyHeaderIntent(this.header); + + final Attribute header; +} + +// Toggles a text style (underline, bold, italic, strikethrough) on, or off. +class _ApplyHeaderAction extends Action { + _ApplyHeaderAction(this.state); + + final RawEditorState state; + + Attribute _getHeaderValue() { + return state.controller + .getSelectionStyle() + .attributes[Attribute.header.key] ?? + Attribute.header; + } + + @override + void invoke(ApplyHeaderIntent intent, [BuildContext? context]) { + final _attribute = + _getHeaderValue() == intent.header ? Attribute.header : intent.header; + state.controller.formatSelection(_attribute); + } + + @override + bool get isActionEnabled => true; +} + +class ApplyCheckListIntent extends Intent { + const ApplyCheckListIntent(); +} + +// Toggles a text style (underline, bold, italic, strikethrough) on, or off. +class _ApplyCheckListAction extends Action { + _ApplyCheckListAction(this.state); + + final RawEditorState state; + + bool _getIsToggled() { + final attrs = state.controller.getSelectionStyle().attributes; + var attribute = state.controller.toolbarButtonToggler[Attribute.list.key]; + + if (attribute == null) { + attribute = attrs[Attribute.list.key]; + } else { + // checkbox tapping causes controller.selection to go to offset 0 + state.controller.toolbarButtonToggler.remove(Attribute.list.key); + } + + if (attribute == null) { + return false; + } + return attribute.value == Attribute.unchecked.value || + attribute.value == Attribute.checked.value; + } + + @override + void invoke(ApplyCheckListIntent intent, [BuildContext? context]) { + state.controller.formatSelection(_getIsToggled() + ? Attribute.clone(Attribute.unchecked, null) + : Attribute.unchecked); + } + + @override + bool get isActionEnabled => true; +} diff --git a/lib/src/widgets/toolbar/search_button.dart b/lib/src/widgets/toolbar/search_button.dart index 52bc2068..b9436bf1 100644 --- a/lib/src/widgets/toolbar/search_button.dart +++ b/lib/src/widgets/toolbar/search_button.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; -import '../../models/documents/document.dart'; import '../../models/themes/quill_dialog_theme.dart'; import '../../models/themes/quill_icon_theme.dart'; -import '../../translations/toolbar.i18n.dart'; import '../controller.dart'; import '../toolbar.dart'; +import 'search_dialog.dart'; class SearchButton extends StatelessWidget { const SearchButton({ @@ -52,140 +51,10 @@ class SearchButton extends StatelessWidget { Future _onPressedHandler(BuildContext context) async { await showDialog( context: context, - builder: (_) => _SearchDialog( + builder: (_) => SearchDialog( controller: controller, dialogTheme: dialogTheme, text: ''), ).then(_searchSubmitted); } void _searchSubmitted(String? value) {} } - -class _SearchDialog extends StatefulWidget { - const _SearchDialog( - {required this.controller, this.dialogTheme, this.text, Key? key}) - : super(key: key); - - final QuillController controller; - final QuillDialogTheme? dialogTheme; - final String? text; - - @override - _SearchDialogState createState() => _SearchDialogState(); -} - -class _SearchDialogState extends State<_SearchDialog> { - late String _text; - late TextEditingController _controller; - late List? _offsets; - late int _index; - - @override - void initState() { - super.initState(); - _text = widget.text ?? ''; - _offsets = null; - _index = 0; - _controller = TextEditingController(text: _text); - } - - @override - Widget build(BuildContext context) { - return StatefulBuilder(builder: (context, setState) { - var label = ''; - if (_offsets != null) { - label = '${_offsets!.length} ${'matches'.i18n}'; - if (_offsets!.isNotEmpty) { - label += ', ${'showing match'.i18n} ${_index + 1}'; - } - } - return AlertDialog( - backgroundColor: widget.dialogTheme?.dialogBackgroundColor, - content: Container( - height: 100, - child: Column( - children: [ - TextField( - keyboardType: TextInputType.multiline, - style: widget.dialogTheme?.inputTextStyle, - decoration: InputDecoration( - labelText: 'Search'.i18n, - labelStyle: widget.dialogTheme?.labelTextStyle, - floatingLabelStyle: widget.dialogTheme?.labelTextStyle), - autofocus: true, - onChanged: _textChanged, - controller: _controller, - ), - if (_offsets != null) - Padding( - padding: const EdgeInsets.all(8), - child: Text(label, textAlign: TextAlign.left), - ), - ], - ), - ), - actions: [ - if (_offsets != null && _offsets!.isNotEmpty && _index > 0) - TextButton( - onPressed: () { - setState(() { - _index -= 1; - }); - _moveToPosition(); - }, - child: Text( - 'Prev'.i18n, - style: widget.dialogTheme?.labelTextStyle, - ), - ), - if (_offsets != null && - _offsets!.isNotEmpty && - _index < _offsets!.length - 1) - TextButton( - onPressed: () { - setState(() { - _index += 1; - }); - _moveToPosition(); - }, - child: Text( - 'Next'.i18n, - style: widget.dialogTheme?.labelTextStyle, - ), - ), - if (_offsets == null && _text.isNotEmpty) - TextButton( - onPressed: () { - setState(() { - _offsets = widget.controller.document.search(_text); - _index = 0; - }); - if (_offsets!.isNotEmpty) { - _moveToPosition(); - } - }, - child: Text( - 'Ok'.i18n, - style: widget.dialogTheme?.labelTextStyle, - ), - ), - ], - ); - }); - } - - void _moveToPosition() { - widget.controller.updateSelection( - TextSelection( - baseOffset: _offsets![_index], - extentOffset: _offsets![_index] + _text.length), - ChangeSource.LOCAL); - } - - void _textChanged(String value) { - setState(() { - _text = value; - _offsets = null; - _index = 0; - }); - } -} diff --git a/lib/src/widgets/toolbar/search_dialog.dart b/lib/src/widgets/toolbar/search_dialog.dart new file mode 100644 index 00000000..84b40ef4 --- /dev/null +++ b/lib/src/widgets/toolbar/search_dialog.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; + +import '../../../flutter_quill.dart' hide Text; +import '../../../translations.dart'; + +class SearchDialog extends StatefulWidget { + const SearchDialog( + {required this.controller, this.dialogTheme, this.text, Key? key}) + : super(key: key); + + final QuillController controller; + final QuillDialogTheme? dialogTheme; + final String? text; + + @override + _SearchDialogState createState() => _SearchDialogState(); +} + +class _SearchDialogState extends State { + late String _text; + late TextEditingController _controller; + late List? _offsets; + late int _index; + + @override + void initState() { + super.initState(); + _text = widget.text ?? ''; + _offsets = null; + _index = 0; + _controller = TextEditingController(text: _text); + } + + @override + Widget build(BuildContext context) { + return StatefulBuilder(builder: (context, setState) { + var label = ''; + if (_offsets != null) { + label = '${_offsets!.length} ${'matches'.i18n}'; + if (_offsets!.isNotEmpty) { + label += ', ${'showing match'.i18n} ${_index + 1}'; + } + } + return AlertDialog( + backgroundColor: widget.dialogTheme?.dialogBackgroundColor, + content: Container( + height: 100, + child: Column( + children: [ + TextField( + keyboardType: TextInputType.multiline, + style: widget.dialogTheme?.inputTextStyle, + decoration: InputDecoration( + labelText: 'Search'.i18n, + labelStyle: widget.dialogTheme?.labelTextStyle, + floatingLabelStyle: widget.dialogTheme?.labelTextStyle), + autofocus: true, + onChanged: _textChanged, + controller: _controller, + ), + if (_offsets != null) + Padding( + padding: const EdgeInsets.all(8), + child: Text(label, textAlign: TextAlign.left), + ), + ], + ), + ), + actions: [ + if (_offsets != null && _offsets!.isNotEmpty && _index > 0) + TextButton( + onPressed: () { + setState(() { + _index -= 1; + }); + _moveToPosition(); + }, + child: Text( + 'Prev'.i18n, + style: widget.dialogTheme?.labelTextStyle, + ), + ), + if (_offsets != null && + _offsets!.isNotEmpty && + _index < _offsets!.length - 1) + TextButton( + onPressed: () { + setState(() { + _index += 1; + }); + _moveToPosition(); + }, + child: Text( + 'Next'.i18n, + style: widget.dialogTheme?.labelTextStyle, + ), + ), + if (_offsets == null && _text.isNotEmpty) + TextButton( + onPressed: () { + setState(() { + _offsets = widget.controller.document.search(_text); + _index = 0; + }); + if (_offsets!.isNotEmpty) { + _moveToPosition(); + } + }, + child: Text( + 'Ok'.i18n, + style: widget.dialogTheme?.labelTextStyle, + ), + ), + ], + ); + }); + } + + void _moveToPosition() { + widget.controller.updateSelection( + TextSelection( + baseOffset: _offsets![_index], + extentOffset: _offsets![_index] + _text.length), + ChangeSource.LOCAL); + } + + void _textChanged(String value) { + setState(() { + _text = value; + _offsets = null; + _index = 0; + }); + } +} From b42759009fcff34faebd492f6c5c41a709b25435 Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 26 Oct 2022 21:41:18 -0700 Subject: [PATCH 015/204] Upgrade to 6.1.0 --- CHANGELOG.md | 3 +++ README.md | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f46630f..8488e208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.0] +* Add keyboard shortcuts for editor actions. + # [6.0.10] * Upgrade device info plus to ^7.0.0. diff --git a/README.md b/README.md index 56e621ef..3c78be72 100644 --- a/README.md +++ b/README.md @@ -85,11 +85,11 @@ _controller = QuillController( For web development, use `flutter config --enable-web` for flutter or use [ReactQuill] for React. It is required to provide `EmbedBuilder`, e.g. [defaultEmbedBuildersWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L99). -Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L334). +Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L317). ## Desktop -It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L314). +It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L297). ## Configuration diff --git a/pubspec.yaml b/pubspec.yaml index 119d9ce6..6188f44d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.0.10 +version: 6.1.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From f8ab567190363aeab39bb46580061ab55987cf25 Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 26 Oct 2022 21:46:32 -0700 Subject: [PATCH 016/204] Code cleanup --- lib/src/widgets/toolbar/search_dialog.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/toolbar/search_dialog.dart b/lib/src/widgets/toolbar/search_dialog.dart index 84b40ef4..b8ce67ac 100644 --- a/lib/src/widgets/toolbar/search_dialog.dart +++ b/lib/src/widgets/toolbar/search_dialog.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; -import '../../../flutter_quill.dart' hide Text; import '../../../translations.dart'; +import '../../models/documents/document.dart'; +import '../../models/themes/quill_dialog_theme.dart'; +import '../controller.dart'; class SearchDialog extends StatefulWidget { const SearchDialog( From 5e0a7fd60fe8fd531ca339b7a88f1a526b10d700 Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 26 Oct 2022 21:53:02 -0700 Subject: [PATCH 017/204] Code cleanup --- .../raw_editor/raw_editor_state_text_input_client_mixin.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index a72fb88e..5eced2bf 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -283,8 +283,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState @override void showAutocorrectionPromptRect(int start, int end) { - // this is called VERY OFTEN when editing a document, no longer throw an exception - // throw UnimplementedError(); + // this is called VERY OFTEN when editing a document, no longer throw + // an exception } @override From ee1775349c046835457512ec1b837db14dbe1cce Mon Sep 17 00:00:00 2001 From: Rob Snider <53707285+kulture-rob-snider@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:53:32 -0400 Subject: [PATCH 018/204] fix order list numbering (#988) --- .../widgets/style_widgets/number_point.dart | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/lib/src/widgets/style_widgets/number_point.dart b/lib/src/widgets/style_widgets/number_point.dart index f1ffddf1..a47c1ef1 100644 --- a/lib/src/widgets/style_widgets/number_point.dart +++ b/lib/src/widgets/style_widgets/number_point.dart @@ -27,51 +27,40 @@ class QuillNumberPoint extends StatelessWidget { @override Widget build(BuildContext context) { - var s = index.toString(); - int? level = 0; - if (!attrs.containsKey(Attribute.indent.key) && - !indentLevelCounts.containsKey(1)) { - indentLevelCounts.clear(); - return Container( - alignment: AlignmentDirectional.topEnd, - width: width, - padding: EdgeInsetsDirectional.only(end: padding), - child: Text(withDot ? '$s.' : s, style: style), - ); - } - if (attrs.containsKey(Attribute.indent.key)) { - level = attrs[Attribute.indent.key]!.value; - } else { - // first level but is back from previous indent level - // supposed to be "2." - indentLevelCounts[0] = 1; - } - if (indentLevelCounts.containsKey(level! + 1)) { - // last visited level is done, going up - indentLevelCounts.remove(level + 1); - } - final count = (indentLevelCounts[level] ?? 0) + 1; - indentLevelCounts[level] = count; - - s = count.toString(); - if (level % 3 == 1) { - // a. b. c. d. e. ... - s = _toExcelSheetColumnTitle(count); - } else if (level % 3 == 2) { - // i. ii. iii. ... - s = _intToRoman(count); - } - // level % 3 == 0 goes back to 1. 2. 3. + final olString = _getOlString(); return Container( alignment: AlignmentDirectional.topEnd, width: width, padding: EdgeInsetsDirectional.only(end: padding), - child: Text(withDot ? '$s.' : s, style: style), + child: Text('$olString${withDot ? '.' : ''}', style: style), ); } - String _toExcelSheetColumnTitle(int n) { + String _getOlString() { + final int indentLevel = attrs[Attribute.indent.key]?.value ?? 0; + + if (indentLevelCounts.containsKey(indentLevel + 1)) { + // last visited level is done, going up + indentLevelCounts.remove(indentLevel + 1); + } + + final count = (indentLevelCounts[indentLevel] ?? 0) + 1; + indentLevelCounts[indentLevel] = count; + + final numberingMode = indentLevel % 3; + if (numberingMode == 1) { + // a. b. c. + return _intToAlpha(count); + } else if (numberingMode == 2) { + // i. ii. iii. + return _intToRoman(count); + } + + return count.toString(); + } + + String _intToAlpha(int n) { final result = StringBuffer(); while (n > 0) { n--; @@ -93,14 +82,11 @@ class QuillNumberPoint extends StatelessWidget { final builder = StringBuffer(); for (var a = 0; a < arabianRomanNumbers.length; a++) { - final times = (num / arabianRomanNumbers[a]) - .truncate(); // equals 1 only when arabianRomanNumbers[a] = num + final times = (num / arabianRomanNumbers[a]).truncate(); // equals 1 only when arabianRomanNumbers[a] = num // executes n times where n is the number of times you have to add // the current roman number value to reach current num. builder.write(romanNumbers[a] * times); - num -= times * - arabianRomanNumbers[ - a]; // subtract previous roman number value from num + num -= times * arabianRomanNumbers[a]; // subtract previous roman number value from num } return builder.toString().toLowerCase(); From d15174e4b6814c83cd41ba22378400294c56f075 Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 27 Oct 2022 11:59:07 -0700 Subject: [PATCH 019/204] Upgrade to 6.1.1 --- CHANGELOG.md | 3 +++ lib/src/widgets/style_widgets/number_point.dart | 7 +++++-- pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8488e208..127c2717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.1] +* Fix order list numbering. + # [6.1.0] * Add keyboard shortcuts for editor actions. diff --git a/lib/src/widgets/style_widgets/number_point.dart b/lib/src/widgets/style_widgets/number_point.dart index a47c1ef1..836dbed0 100644 --- a/lib/src/widgets/style_widgets/number_point.dart +++ b/lib/src/widgets/style_widgets/number_point.dart @@ -82,11 +82,14 @@ class QuillNumberPoint extends StatelessWidget { final builder = StringBuffer(); for (var a = 0; a < arabianRomanNumbers.length; a++) { - final times = (num / arabianRomanNumbers[a]).truncate(); // equals 1 only when arabianRomanNumbers[a] = num + final times = (num / arabianRomanNumbers[a]) + .truncate(); // equals 1 only when arabianRomanNumbers[a] = num // executes n times where n is the number of times you have to add // the current roman number value to reach current num. builder.write(romanNumbers[a] * times); - num -= times * arabianRomanNumbers[a]; // subtract previous roman number value from num + num -= times * + arabianRomanNumbers[ + a]; // subtract previous roman number value from num } return builder.toString().toLowerCase(); diff --git a/pubspec.yaml b/pubspec.yaml index 6188f44d..44d81e5d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.0 +version: 6.1.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 6c9698e377b7a99e3bc50d1203b5db18821b5008 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Tue, 1 Nov 2022 22:17:51 -0400 Subject: [PATCH 020/204] Add typing shortcuts (#994) Start lists with "1." or "-". Indent / Dedent lists with and . --- lib/src/utils/cast.dart | 1 + lib/src/widgets/controller.dart | 20 +++ lib/src/widgets/raw_editor.dart | 143 ++++++++++++++++++--- lib/src/widgets/toolbar/indent_button.dart | 22 +--- 4 files changed, 145 insertions(+), 41 deletions(-) create mode 100644 lib/src/utils/cast.dart diff --git a/lib/src/utils/cast.dart b/lib/src/utils/cast.dart new file mode 100644 index 00000000..9bc50fdb --- /dev/null +++ b/lib/src/utils/cast.dart @@ -0,0 +1 @@ +T? castOrNull(dynamic x) => x is T ? x : null; diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index 00c4f37f..fd85666b 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -102,6 +102,26 @@ class QuillController extends ChangeNotifier { .mergeAll(toggledStyle); } + // Increases or decreases the indent of the current selection by 1. + void indentSelection(bool isIncrease) { + final indent = getSelectionStyle().attributes[Attribute.indent.key]; + if (indent == null) { + if (isIncrease) { + formatSelection(Attribute.indentL1); + } + return; + } + if (indent.value == 1 && !isIncrease) { + formatSelection(Attribute.clone(Attribute.indentL1, null)); + return; + } + if (isIncrease) { + formatSelection(Attribute.getIndentLevel(indent.value + 1)); + return; + } + formatSelection(Attribute.getIndentLevel(indent.value - 1)); + } + /// Returns all styles for each node within selection List> getAllIndividualSelectionStyles() { final styles = document.collectAllIndividualStyles( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index d724bb97..e480bc57 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -19,7 +19,9 @@ import '../models/documents/nodes/block.dart'; import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/line.dart'; import '../models/documents/nodes/node.dart'; +import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/style.dart'; +import '../utils/cast.dart'; import '../utils/delta.dart'; import '../utils/embeds.dart'; import '../utils/platform.dart'; @@ -428,6 +430,7 @@ class RawEditorState extends EditorState actions: _actions, child: Focus( focusNode: widget.focusNode, + onKey: _onKey, child: QuillKeyboardListener( child: Container( constraints: constraints, @@ -440,6 +443,125 @@ class RawEditorState extends EditorState ); } + KeyEventResult _onKey(node, RawKeyEvent event) { + // Don't handle key if there is a meta key pressed. + if (event.isAltPressed || event.isControlPressed || event.isMetaPressed) { + return KeyEventResult.ignored; + } + + if (event is! RawKeyDownEvent) { + return KeyEventResult.ignored; + } + + // Don't handle key if there is an active selection. + if (controller.selection.baseOffset != controller.selection.extentOffset) { + return KeyEventResult.ignored; + } + + // Handle indenting blocks when pressing the tab key. + if (event.logicalKey == LogicalKeyboardKey.tab) { + return _handleTabKey(event); + } + + // Handle inserting lists when space is pressed following + // a list initiating phrase. + if (event.logicalKey == LogicalKeyboardKey.space) { + return _handleSpaceKey(event); + } + + return KeyEventResult.ignored; + } + + KeyEventResult _handleSpaceKey(RawKeyEvent event) { + final child = + controller.document.queryChild(controller.selection.baseOffset); + if (child.node == null) { + return KeyEventResult.ignored; + } + + final line = child.node as Line?; + if (line == null) { + return KeyEventResult.ignored; + } + + final text = castOrNull(line.first); + if (text == null) { + return KeyEventResult.ignored; + } + + const olKeyPhrase = '1.'; + const ulKeyPhrase = '-'; + + if (text.value == olKeyPhrase) { + _updateSelectionForKeyPhrase(olKeyPhrase, Attribute.ol); + } else if (text.value == ulKeyPhrase) { + _updateSelectionForKeyPhrase(ulKeyPhrase, Attribute.ul); + } else { + return KeyEventResult.ignored; + } + + return KeyEventResult.handled; + } + + KeyEventResult _handleTabKey(RawKeyEvent event) { + final child = + controller.document.queryChild(controller.selection.baseOffset); + + KeyEventResult insertTabCharacter() { + controller.replaceText(controller.selection.baseOffset, 0, '\t', null); + _moveCursor(1); + return KeyEventResult.handled; + } + + if (child.node == null) { + return insertTabCharacter(); + } + + final node = child.node!; + + final parent = node.parent; + if (parent == null || parent is! Block) { + return insertTabCharacter(); + } + + if (node is! Line || (node.isNotEmpty && node.first is! leaf.Text)) { + return insertTabCharacter(); + } + + if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty) { + return insertTabCharacter(); + } + + final parentBlock = parent; + if (parentBlock.style.containsKey(Attribute.ol.key) || + parentBlock.style.containsKey(Attribute.ul.key) || + parentBlock.style.containsKey(Attribute.checked.key)) { + controller.indentSelection(!event.isShiftPressed); + return KeyEventResult.handled; + } + + return insertTabCharacter(); + } + + void _moveCursor(int chars) { + final selection = controller.selection; + controller.updateSelection( + controller.selection.copyWith( + baseOffset: selection.baseOffset + chars, + extentOffset: selection.baseOffset + chars), + ChangeSource.LOCAL); + } + + void _updateSelectionForKeyPhrase(String phrase, Attribute attribute) { + controller + ..formatSelection(attribute) + ..replaceText(controller.selection.baseOffset - phrase.length, + phrase.length, '', null); + + // It is unclear why the selection moves forward the edit distance. + _moveCursor(-2); + } + void _handleSelectionChanged( TextSelection selection, SelectionChangedCause cause) { final oldSelection = controller.selection; @@ -2076,26 +2198,7 @@ class _IndentSelectionAction extends Action { @override void invoke(IndentSelectionIntent intent, [BuildContext? context]) { - final indent = - state.controller.getSelectionStyle().attributes[Attribute.indent.key]; - if (indent == null) { - if (intent.isIncrease) { - state.controller.formatSelection(Attribute.indentL1); - } - return; - } - if (indent.value == 1 && !intent.isIncrease) { - state.controller - .formatSelection(Attribute.clone(Attribute.indentL1, null)); - return; - } - if (intent.isIncrease) { - state.controller - .formatSelection(Attribute.getIndentLevel(indent.value + 1)); - return; - } - state.controller - .formatSelection(Attribute.getIndentLevel(indent.value - 1)); + state.controller.indentSelection(intent.isIncrease); } @override diff --git a/lib/src/widgets/toolbar/indent_button.dart b/lib/src/widgets/toolbar/indent_button.dart index 227c56e9..24dac736 100644 --- a/lib/src/widgets/toolbar/indent_button.dart +++ b/lib/src/widgets/toolbar/indent_button.dart @@ -42,27 +42,7 @@ class _IndentButtonState extends State { fillColor: iconFillColor, borderRadius: widget.iconTheme?.borderRadius ?? 2, onPressed: () { - final indent = widget.controller - .getSelectionStyle() - .attributes[Attribute.indent.key]; - if (indent == null) { - if (widget.isIncrease) { - widget.controller.formatSelection(Attribute.indentL1); - } - return; - } - if (indent.value == 1 && !widget.isIncrease) { - widget.controller - .formatSelection(Attribute.clone(Attribute.indentL1, null)); - return; - } - if (widget.isIncrease) { - widget.controller - .formatSelection(Attribute.getIndentLevel(indent.value + 1)); - return; - } - widget.controller - .formatSelection(Attribute.getIndentLevel(indent.value - 1)); + widget.controller.indentSelection(widget.isIncrease); }, afterPressed: widget.afterButtonPressed, ); From 1602045809206c0eb42d087be5f73966fc7d6639 Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 1 Nov 2022 19:21:12 -0700 Subject: [PATCH 021/204] Upgrade to 6.1.2 --- CHANGELOG.md | 3 +++ lib/src/widgets/raw_editor.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 127c2717..bd10c99e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.2] +* Add typing shortcuts. + # [6.1.1] * Fix order list numbering. diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index e480bc57..a9dba768 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -17,9 +17,9 @@ import '../models/documents/attribute.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/block.dart'; import '../models/documents/nodes/embeddable.dart'; +import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/nodes/line.dart'; import '../models/documents/nodes/node.dart'; -import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/style.dart'; import '../utils/cast.dart'; import '../utils/delta.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 44d81e5d..06768f35 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.1 +version: 6.1.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From fab011a1cc4d54492178add584a14ce4645e026b Mon Sep 17 00:00:00 2001 From: vahid75hj Date: Wed, 2 Nov 2022 15:55:04 +0330 Subject: [PATCH 022/204] Translation error corrected in Persian(fa) (#995) --- lib/src/translations/toolbar.i18n.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index ae5e1ea1..20b67489 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -637,7 +637,7 @@ extension Localization on String { }, 'fa': { 'Paste a link': 'جایگذاری لینک', - 'Ok': 'اوکی', + 'Ok': 'تایید', 'Select Color': 'انتخاب رنگ', 'Gallery': 'گالری', 'Link': 'لینک', @@ -650,7 +650,7 @@ extension Localization on String { 'Zoom': 'بزرگنمایی', 'Saved': 'ذخیره شد', 'Text': 'متن', - 'What is entered is not a link': 'ورودی وارد شده لینک نمی باشد', + 'What is entered is not a link': 'لینک وارد شده معتبر نمی باشد', 'Resize': 'تغییر اندازه', 'Width': 'عرض', 'Height': 'طول', From 503f66dd2b945696dc3defe3aa5dacd5119b86eb Mon Sep 17 00:00:00 2001 From: francksoudan <112876604+francksoudan@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:55:03 +0100 Subject: [PATCH 023/204] exports more nodes to the public API (#996) --- lib/flutter_quill.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index eae6c5de..e74a41ac 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -2,8 +2,10 @@ library flutter_quill; export 'src/models/documents/attribute.dart'; export 'src/models/documents/document.dart'; +export 'src/models/documents/nodes/block.dart'; export 'src/models/documents/nodes/embeddable.dart'; 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'; From 7d4a0b0303d8b6ad9b908508ba9287f988dea52f Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 5 Nov 2022 19:44:18 -0700 Subject: [PATCH 024/204] Revert "fix order list numbering (#988)" This reverts commit ee1775349c046835457512ec1b837db14dbe1cce. --- .../widgets/style_widgets/number_point.dart | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/src/widgets/style_widgets/number_point.dart b/lib/src/widgets/style_widgets/number_point.dart index 836dbed0..f1ffddf1 100644 --- a/lib/src/widgets/style_widgets/number_point.dart +++ b/lib/src/widgets/style_widgets/number_point.dart @@ -27,40 +27,51 @@ class QuillNumberPoint extends StatelessWidget { @override Widget build(BuildContext context) { - final olString = _getOlString(); + var s = index.toString(); + int? level = 0; + if (!attrs.containsKey(Attribute.indent.key) && + !indentLevelCounts.containsKey(1)) { + indentLevelCounts.clear(); + return Container( + alignment: AlignmentDirectional.topEnd, + width: width, + padding: EdgeInsetsDirectional.only(end: padding), + child: Text(withDot ? '$s.' : s, style: style), + ); + } + if (attrs.containsKey(Attribute.indent.key)) { + level = attrs[Attribute.indent.key]!.value; + } else { + // first level but is back from previous indent level + // supposed to be "2." + indentLevelCounts[0] = 1; + } + if (indentLevelCounts.containsKey(level! + 1)) { + // last visited level is done, going up + indentLevelCounts.remove(level + 1); + } + final count = (indentLevelCounts[level] ?? 0) + 1; + indentLevelCounts[level] = count; + + s = count.toString(); + if (level % 3 == 1) { + // a. b. c. d. e. ... + s = _toExcelSheetColumnTitle(count); + } else if (level % 3 == 2) { + // i. ii. iii. ... + s = _intToRoman(count); + } + // level % 3 == 0 goes back to 1. 2. 3. return Container( alignment: AlignmentDirectional.topEnd, width: width, padding: EdgeInsetsDirectional.only(end: padding), - child: Text('$olString${withDot ? '.' : ''}', style: style), + child: Text(withDot ? '$s.' : s, style: style), ); } - String _getOlString() { - final int indentLevel = attrs[Attribute.indent.key]?.value ?? 0; - - if (indentLevelCounts.containsKey(indentLevel + 1)) { - // last visited level is done, going up - indentLevelCounts.remove(indentLevel + 1); - } - - final count = (indentLevelCounts[indentLevel] ?? 0) + 1; - indentLevelCounts[indentLevel] = count; - - final numberingMode = indentLevel % 3; - if (numberingMode == 1) { - // a. b. c. - return _intToAlpha(count); - } else if (numberingMode == 2) { - // i. ii. iii. - return _intToRoman(count); - } - - return count.toString(); - } - - String _intToAlpha(int n) { + String _toExcelSheetColumnTitle(int n) { final result = StringBuffer(); while (n > 0) { n--; From 127cb52502ade06dd98fe6e69670a1df0a1fbb8f Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 5 Nov 2022 19:46:07 -0700 Subject: [PATCH 025/204] Upgrade to 6.1.3 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd10c99e..b19eb3e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.3] +* Revert "fix order list numbering (#988)". + # [6.1.2] * Add typing shortcuts. diff --git a/pubspec.yaml b/pubspec.yaml index 06768f35..2ece4db2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.2 +version: 6.1.3 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 60d51e28c65d8a13b622ecfdc70806c578773deb Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 5 Nov 2022 22:24:24 -0700 Subject: [PATCH 026/204] Fix static analysis error --- lib/src/widgets/text_block.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index e4b2990d..f30e71cc 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -2,14 +2,20 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:tuple/tuple.dart'; -import '../../flutter_quill.dart'; +import '../models/documents/attribute.dart'; import '../models/documents/nodes/block.dart'; import '../models/documents/nodes/line.dart'; import '../utils/delta.dart'; import 'box.dart'; +import 'controller.dart'; import 'cursor.dart'; +import 'default_styles.dart'; import 'delegate.dart'; +import 'editor.dart'; import 'link.dart'; +import 'style_widgets/bullet_point.dart'; +import 'style_widgets/checkbox_point.dart'; +import 'style_widgets/number_point.dart'; import 'text_line.dart'; import 'text_selection.dart'; From ff5e78c026fbecb0d20217046480788a5a2ec561 Mon Sep 17 00:00:00 2001 From: Uriel Aispuro Date: Tue, 8 Nov 2022 08:55:11 -0700 Subject: [PATCH 027/204] Add double quotes validation (#1002) --- lib/src/widgets/raw_editor.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index a9dba768..e26c3f16 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -293,8 +293,9 @@ class RawEditorState extends EditorState var _doc = controller.document; if (_doc.isEmpty() && widget.placeholder != null) { + final raw = widget.placeholder?.replaceAll(r'"', '\\"'); _doc = Document.fromJson(jsonDecode( - '[{"attributes":{"placeholder":true},"insert":"${widget.placeholder}\\n"}]')); + '[{"attributes":{"placeholder":true},"insert":"$raw\\n"}]')); } Widget child = CompositedTransformTarget( From 897a0fbf9f56864fe1dc2da9c030f00f42cbefad Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 8 Nov 2022 07:57:27 -0800 Subject: [PATCH 028/204] Upgrade to 6.1.4 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b19eb3e3..c086ca0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.4] +* Add double quotes validation. + # [6.1.3] * Revert "fix order list numbering (#988)". diff --git a/pubspec.yaml b/pubspec.yaml index 2ece4db2..3f0e7edb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.3 +version: 6.1.4 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From bb31a7901ccd3c76fc264414832c874703ae8c47 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Sat, 12 Nov 2022 14:07:50 -0500 Subject: [PATCH 029/204] Fix formatting exception (#1006) --- lib/src/widgets/raw_editor.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index e26c3f16..bfa0476c 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; + // ignore: unnecessary_import import 'dart:typed_data'; @@ -266,6 +267,7 @@ class RawEditorState extends EditorState // Focus bool _didAutoFocus = false; + bool get _hasFocus => widget.focusNode.hasFocus; // Theme @@ -275,6 +277,7 @@ class RawEditorState extends EditorState @override List> get pasteStyle => _pasteStyle; List> _pasteStyle = >[]; + @override String get pastePlainText => _pastePlainText; String _pastePlainText = ''; @@ -554,13 +557,13 @@ class RawEditorState extends EditorState } void _updateSelectionForKeyPhrase(String phrase, Attribute attribute) { + controller.replaceText(controller.selection.baseOffset - phrase.length, + phrase.length, '\n', null); + _moveCursor(-phrase.length); controller ..formatSelection(attribute) - ..replaceText(controller.selection.baseOffset - phrase.length, - phrase.length, '', null); - - // It is unclear why the selection moves forward the edit distance. - _moveCursor(-2); + // Remove the added newline. + ..replaceText(controller.selection.baseOffset + 1, 1, '', null); } void _handleSelectionChanged( @@ -1678,6 +1681,7 @@ class _DocumentBoundary extends _TextBoundary { @override TextPosition getLeadingTextBoundaryAt(TextPosition position) => const TextPosition(offset: 0); + @override TextPosition getTrailingTextBoundaryAt(TextPosition position) { return TextPosition( From a0afc6d64457a22c509770aa8558fae3ae7fe362 Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 12 Nov 2022 11:09:30 -0800 Subject: [PATCH 030/204] Upgrade to 6.1.5 --- CHANGELOG.md | 3 +++ lib/src/widgets/raw_editor.dart | 1 - pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c086ca0e..4a497090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.5] +* Fix formatting exception. + # [6.1.4] * Add double quotes validation. diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index bfa0476c..8c9ea96a 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; - // ignore: unnecessary_import import 'dart:typed_data'; diff --git a/pubspec.yaml b/pubspec.yaml index 3f0e7edb..216a0414 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.4 +version: 6.1.5 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 0afc6cc1f3d4c64e9884fdcfa01ea32e8eb91c16 Mon Sep 17 00:00:00 2001 From: francksoudan <112876604+francksoudan@users.noreply.github.com> Date: Mon, 14 Nov 2022 20:18:50 +0100 Subject: [PATCH 031/204] completes french translations (#1007) --- lib/src/translations/toolbar.i18n.dart | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 20b67489..e69f5eb6 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -178,29 +178,30 @@ extension Localization on String { 'Please first select some text to transform into a link.': "Veuillez d'abord sélectionner un texte à transformer en lien.", 'Open': 'Ouverte', - 'Copy': 'Copie', + 'Copy': 'Copier', 'Remove': 'Supprimer', 'Save': 'Sauvegarder', 'Zoom': 'Zoom', 'Saved': 'Enregistrée', 'Text': 'Text', - 'What is entered is not a link': 'What is entered is not a link', - 'Resize': 'Resize', - 'Width': 'Width', - 'Height': 'Height', - 'Size': 'Size', - 'Small': 'Small', - 'Large': 'Large', - 'Huge': 'Huge', - 'Clear': 'Clear', - 'Font': 'Font', - 'Search': 'Search', - 'matches': 'matches', - 'showing match': 'showing match', - 'Prev': 'Prev', - 'Next': 'Next', - 'Camera': 'Camera', - 'Video': 'Video', + 'What is entered is not a link': + "Ce qui est saisi n'est pas un lien", + 'Resize': 'Redimensionner', + 'Width': 'Largeur', + 'Height': 'Hauteur', + 'Size': 'Taille', + 'Small': 'Petit', + 'Large': 'Grand', + 'Huge': 'Énorme', + 'Clear': 'Supprimer la mise en forme', + 'Font': 'Police', + 'Search': 'Rechercher', + 'matches': 'correspondances', + 'showing match': 'voir la correspondance', + 'Prev': 'Précédent', + 'Next': 'Suivant', + 'Camera': 'Caméra', + 'Video': 'Vidéo', }, 'zh_cn': { 'Paste a link': '粘贴链接', From 0e76fb1631a0889e596ab9e7b13580211a34e092 Mon Sep 17 00:00:00 2001 From: Vadim Reznichenko Date: Thu, 17 Nov 2022 23:03:04 +0300 Subject: [PATCH 032/204] Update pubspec.yaml (#1008) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 216a0414..f3db29bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ dependencies: pedantic: ^1.11.1 characters: ^1.2.0 diff_match_patch: ^0.4.1 - i18n_extension: ^5.0.1 + i18n_extension: ^6.0.0 device_info_plus: ^7.0.0 platform: ^3.1.0 pasteboard: ^0.2.0 From 7c63a9123b420a1504653dfe734ef91663454e44 Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 17 Nov 2022 12:12:27 -0800 Subject: [PATCH 033/204] Upgrade to 6.1.6 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a497090..f0729aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.6] +* Upgrade i18n_extension to 6.0.0. + # [6.1.5] * Fix formatting exception. diff --git a/pubspec.yaml b/pubspec.yaml index f3db29bd..ea16c8bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.5 +version: 6.1.6 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From fa046ac78f6a9b51053fe3bc4ae0f94477fb50a4 Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 17 Nov 2022 12:45:39 -0800 Subject: [PATCH 034/204] Format code --- lib/src/translations/toolbar.i18n.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index e69f5eb6..de1e28bb 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -184,8 +184,7 @@ extension Localization on String { 'Zoom': 'Zoom', 'Saved': 'Enregistrée', 'Text': 'Text', - 'What is entered is not a link': - "Ce qui est saisi n'est pas un lien", + 'What is entered is not a link': "Ce qui est saisi n'est pas un lien", 'Resize': 'Redimensionner', 'Width': 'Largeur', 'Height': 'Hauteur', From 1e730b66b12d650d138be3bf21278c9809b80048 Mon Sep 17 00:00:00 2001 From: Paprikadobi Date: Mon, 19 Dec 2022 15:22:38 +0100 Subject: [PATCH 035/204] Add czech localizations (#1028) --- lib/src/translations/toolbar.i18n.dart | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index de1e28bb..55968c80 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -800,6 +800,39 @@ extension Localization on String { 'Camera': 'Camera', 'Video': 'Video', }, + 'cs': { + 'Paste a link': 'Vložte odkaz', + 'Ok': 'Ok', + 'Select Color': 'Vyberte barvu', + 'Gallery': 'Galerie', + 'Link': 'Odkaz', + 'Please first select some text to transform into a link.': + 'Nejprve vyberte nějaký text, který chcete převést na odkaz.', + 'Open': 'Otevřít', + 'Copy': 'Kopírovat', + 'Remove': 'Odstranit', + 'Save': 'Uložit', + 'Zoom': 'Přiblížit', + 'Saved': 'Uloženo', + 'Text': 'Text', + 'What is entered is not a link': 'Zadaný vstup není odkaz', + 'Resize': 'Změnit velikost', + 'Width': 'Šířka', + 'Height': 'Výška', + 'Size': 'Velikost', + 'Small': 'Malý', + 'Large': 'Velký', + 'Huge': 'Obrovský', + 'Clear': 'Smazat', + 'Font': 'Písmo', + 'Search': 'Hledat', + 'matches': 'odpovídá', + 'showing match': 'zobrazuje odpovídající', + 'Prev': 'Předchozí', + 'Next': 'Další', + 'Camera': 'Kamera', + 'Video': 'Video', + }, }; String get i18n => localize(this, _t); From fc5210f893a4812de728c0ddd4aa4cd109b9b44d Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 19 Dec 2022 14:24:45 -0800 Subject: [PATCH 036/204] Upgrade to 6.1.7 --- CHANGELOG.md | 3 +++ README.md | 3 ++- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0729aeb..3cacd97d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.7] +* Add czech localizations. + # [6.1.6] * Upgrade i18n_extension to 6.0.0. diff --git a/README.md b/README.md index 3c78be72..126f5faa 100644 --- a/README.md +++ b/README.md @@ -325,10 +325,11 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -Currently, translations are available for these 22 locales: +Currently, translations are available for these 23 locales: * `Locale('en')` * `Locale('ar')` +* `Locale('cs')` * `Locale('de')` * `Locale('da')` * `Locale('fr')` diff --git a/pubspec.yaml b/pubspec.yaml index ea16c8bb..deaeb0e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.6 +version: 6.1.7 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From c47adc6ceec7cbebc55cd65a8966ea9a7c1c48c4 Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 19 Dec 2022 14:47:05 -0800 Subject: [PATCH 037/204] Upgrade device_info_plus --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index deaeb0e4..e7a4ba38 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: characters: ^1.2.0 diff_match_patch: ^0.4.1 i18n_extension: ^6.0.0 - device_info_plus: ^7.0.0 + device_info_plus: ^8.0.0 platform: ^3.1.0 pasteboard: ^0.2.0 From f66145b153df2beceb7359c5297f25615100e5bb Mon Sep 17 00:00:00 2001 From: li3317 Date: Fri, 23 Dec 2022 17:50:14 -0500 Subject: [PATCH 038/204] use flutter_youtube_player package --- example/lib/universal_ui/universal_ui.dart | 2 +- .../lib/embeds/widgets/youtube_video_app.dart | 2 +- flutter_quill_extensions/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/lib/universal_ui/universal_ui.dart b/example/lib/universal_ui/universal_ui.dart index 443255a4..4b493986 100644 --- a/example/lib/universal_ui/universal_ui.dart +++ b/example/lib/universal_ui/universal_ui.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:universal_html/html.dart' as html; -import 'package:youtube_player_flutter_quill/youtube_player_flutter_quill.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; import '../widgets/responsive_widget.dart'; import 'fake_ui.dart' if (dart.library.html) 'real_ui.dart' as ui_instance; diff --git a/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart b/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart index 8032b30f..02e53fbe 100644 --- a/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart +++ b/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart @@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:youtube_player_flutter_quill/youtube_player_flutter_quill.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; class YoutubeVideoApp extends StatefulWidget { const YoutubeVideoApp( diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 01a85962..4aef2ba6 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: image_picker: ^0.8.5+3 photo_view: ^0.14.0 video_player: ^2.4.2 - youtube_player_flutter_quill: ^8.2.2 + youtube_player_flutter: ^8.1.1 gallery_saver: ^2.3.2 math_keyboard: ^0.1.6 string_validator: ^0.3.0 From 65c38a8d470228b8614e856296c3da2561d7b2d3 Mon Sep 17 00:00:00 2001 From: li3317 Date: Sat, 24 Dec 2022 01:28:57 -0500 Subject: [PATCH 039/204] Recalculate focus when showing keyboard --- CHANGELOG.md | 3 +++ lib/src/widgets/raw_editor.dart | 8 +++++++- pubspec.yaml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cacd97d..15832947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.8] +* Recalculate focus when showing keyboard. + # [6.1.7] * Add czech localizations. diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 8c9ea96a..ce10e84f 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1073,8 +1073,14 @@ class RawEditorState extends EditorState return; } if (_hasFocus) { + final keyboardAlreadyShown = _keyboardVisible; openConnectionIfNeeded(); - _showCaretOnScreen(); + if (keyboardAlreadyShown) { + _showCaretOnScreen(); + } else { + /// delay 500 milliseconds for waiting keyboard show up + Future.delayed(const Duration(milliseconds: 500), _showCaretOnScreen); + } } else { widget.focusNode.requestFocus(); } diff --git a/pubspec.yaml b/pubspec.yaml index e7a4ba38..2e7a33f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.7 +version: 6.1.8 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From fb26c842fff28b55a07e14b4d18b9e984804d896 Mon Sep 17 00:00:00 2001 From: X Code Date: Fri, 23 Dec 2022 23:25:48 -0800 Subject: [PATCH 040/204] Bump keyboard show up wait to 1 sec --- CHANGELOG.md | 3 +++ example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- lib/src/widgets/raw_editor.dart | 4 ++-- pubspec.yaml | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15832947..6d85b7e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.9] +* Bump keyboard show up wait to 1 sec. + # [6.1.8] * Recalculate focus when showing keyboard. diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf4..4f8d4d24 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 1e8c3c90..88359b22 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 1cce8762..1a971747 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -351,7 +351,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -433,7 +433,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -482,7 +482,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index ce10e84f..6108b7fb 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1078,8 +1078,8 @@ class RawEditorState extends EditorState if (keyboardAlreadyShown) { _showCaretOnScreen(); } else { - /// delay 500 milliseconds for waiting keyboard show up - Future.delayed(const Duration(milliseconds: 500), _showCaretOnScreen); + /// delay 1000 milliseconds for waiting keyboard show up + Future.delayed(const Duration(milliseconds: 1000), _showCaretOnScreen); } } else { widget.focusNode.requestFocus(); diff --git a/pubspec.yaml b/pubspec.yaml index 2e7a33f3..cafc0d6e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.8 +version: 6.1.9 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From d22e46b75885a8bcb002862caa873c99f9e0350f Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Sat, 24 Dec 2022 22:55:28 +0800 Subject: [PATCH 041/204] Alter (#1032) --- README.md | 94 ++++----- doc_cn.md | 264 +++++++++++++++---------- lib/src/translations/toolbar.i18n.dart | 59 ++++-- 3 files changed, 255 insertions(+), 162 deletions(-) diff --git a/README.md b/README.md index 126f5faa..843c97fa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

A rich text editor for Flutter

@@ -20,20 +20,37 @@ [github-forks-badge]: https://img.shields.io/github/forks/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff [github-forks-link]: https://github.com/singerdmx/flutter-quill/network/members +[中文文档](./doc_cn.md) + +--- FlutterQuill is a rich text editor and a [Quill] component for [Flutter]. This library is a WYSIWYG editor built for the modern mobile platform, with web compatibility under development. Check out our [Youtube Playlist] or [Code Introduction] to take a detailed walkthrough of the code base. You can join our [Slack Group] for discussion. -Demo App: https://bulletjournal.us/home/index.html +Demo App: [BULLET JOURNAL](https://bulletjournal.us/home/index.html) + +Pub: [FlutterQuill] -Pub: https://pub.dev/packages/flutter_quill +## Presentations + +

+ 1 + 1 +

+ +

+ 1 + 1 +

+ +--- ## Usage See the `example` directory for a minimal example of how to use FlutterQuill. You typically just need to instantiate a controller: -``` +```dart QuillController _controller = QuillController.basic(); ``` @@ -54,6 +71,7 @@ Column( ], ) ``` + Check out [Sample Page] for advanced usage. ## Input / Output @@ -63,9 +81,9 @@ This library uses [Quill] as an internal data format. * Use `_controller.document.toDelta()` to extract the deltas. * Use `_controller.document.toPlainText()` to extract plain text. -FlutterQuill provides some JSON serialisation support, so that you can save and open documents. To save a document as JSON, do something like the following: +FlutterQuill provides some JSON serialization support, so that you can save and open documents. To save a document as JSON, do something like the following: -``` +```dart var json = jsonEncode(_controller.document.toDelta().toJson()); ``` @@ -73,11 +91,12 @@ You can then write this to storage. To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this: -``` +```dart var myJSON = jsonDecode(incomingJSONText); _controller = QuillController( document: Document.fromJson(myJSON), - selection: TextSelection.collapsed(offset: 0)); + selection: TextSelection.collapsed(offset: 0), + ); ``` ## Web @@ -93,30 +112,36 @@ It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample ## Configuration -The `QuillToolbar` class lets you customise which formatting options are available. +The `QuillToolbar` class lets you customize which formatting options are available. [Sample Page] provides sample code for advanced usage and configuration. ### Font Size -Within the editor toolbar, a drop-down with font-sizing capabilities is available. This can be enabled or disabled with `showFontSize`. + +Within the editor toolbar, a drop-down with font-sizing capabilities is available. This can be enabled or disabled with `showFontSize`. When enabled, the default font-size values can be modified via _optional_ `fontSizeValues`. `fontSizeValues` accepts a `Map` consisting of a `String` title for the font size and a `String` value for the font size. Example: -``` + +```dart fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'} ``` -Font size can be cleared with a value of `0`, for example: -``` +Font size can be cleared with a value of `0`, for example: + +```dart fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '0'} ``` ### Font Family + To use your own fonts, update your [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) and pass in `fontFamilyValues`. More details at [this change](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa), [this article](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) and [this](https://www.flutterbeads.com/change-font-family-flutter/). ### Custom Buttons + You may add custom buttons to the _end_ of the toolbar, via the `customButtons` option, which is a `List` of `QuillCustomButton`. To add an Icon, we should use a new QuillCustomButton class -``` + +```dart QuillCustomButton( icon:Icons.ac_unit, onTap: () { @@ -126,7 +151,8 @@ To add an Icon, we should use a new QuillCustomButton class ``` Each `QuillCustomButton` is used as part of the `customButtons` option as follows: -``` + +```dart QuillToolbar.basic( (...), customButtons: [ @@ -153,16 +179,15 @@ QuillToolbar.basic( ] ``` - ## Embed Blocks As of version 6.0, embed blocks are not provided by default as part of this package. Instead, this package provides an interface to all the user to provide there own implementations for embed blocks. Implementations for image, video and formula embed blocks is proved in a separate package [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions). -Provide a list of embed +Provide a list of embed ### Using the embed blocks from `flutter_quill_extensions` -``` +```dart import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; QuillEditor.basic( @@ -176,12 +201,11 @@ QuillToolbar.basic( ); ``` - - ### Custom Size Image for Mobile Define `mobileWidth`, `mobileHeight`, `mobileMargin`, `mobileAlignment` as follows: -``` + +```dart { "insert": { "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" @@ -312,9 +336,8 @@ And voila, we have a custom widget inside of the rich text editor! 1

-> For more info and a video example, see the [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877) - -> For more details, check out [this YouTube video](https://youtu.be/pI5p5j7cfHc) +> 1. For more info and a video example, see the [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877) +> 2. For more details, check out [this YouTube video](https://youtu.be/pI5p5j7cfHc) ### Translation @@ -325,7 +348,7 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -Currently, translations are available for these 23 locales: +Currently, translations are available for these 24 locales: * `Locale('en')` * `Locale('ar')` @@ -350,24 +373,11 @@ Currently, translations are available for these 23 locales: * `Locale('fa')` * `Locale('hi')` * `Locale('sr')` +* `Locale('jp')` #### Contributing to translations -The translation file is located at [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations! - ---- - -

- 1 - 1 -

- - -

- 1 - 1 -

- +The translation file is located at [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations! ## Sponsors @@ -376,10 +386,6 @@ The translation file is located at [toolbar.i18n.dart](lib/src/translations/tool "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" width="150px" height="150px"> ---- - -[Chinese Documentation](./doc_cn.md) - [Quill]: https://quilljs.com/docs/formats [Flutter]: https://github.com/flutter/flutter [FlutterQuill]: https://pub.dev/packages/flutter_quill diff --git a/doc_cn.md b/doc_cn.md index 2bf9de93..870ce674 100644 --- a/doc_cn.md +++ b/doc_cn.md @@ -1,4 +1,4 @@ -

+

支持 Flutter 平台的富文本编辑器

@@ -20,24 +20,41 @@ [github-forks-badge]: https://img.shields.io/github/forks/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff [github-forks-link]: https://github.com/singerdmx/flutter-quill/network/members +[原文档](./README.md) -FlutterQuill 是一个富文本编辑器,同样也是 [Quill] 在 [Flutter] 的版本。 +--- + +`FlutterQuill` 是一个富文本编辑器,也是 [Quill](https://quilljs.com/docs/formats) 在 [Flutter](https://github.com/flutter/flutter) 的版本 + +该库是为移动平台构建的『所见即所得』的富文本编辑器,同时我们还正在对 `Web` 平台进行兼容。查看我们的 [Youtube 播放列表](https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2) 或 [代码介绍](https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md) 以了解代码的详细内容。你可以加入我们的 [Slack Group](https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g) 来进行讨论 -该库是为移动平台构建的 “ 所见即所得 ” 的富文本编辑器,同时我们还正在对 Web 平台进行兼容。查看我们的 [Youtube 播放列表] 或 [代码介绍] 以了解代码的详细内容。你可以加入我们的 [Slack Group] 来进行讨论。 +示例 `App` : [BULLET JOURNAL](https://bulletjournal.us/home/index.html) -Demo App: https://bulletjournal.us/home/index.html +`Pub` : [FlutterQuill](https://pub.dev/packages/flutter_quill) -Pub: https://pub.dev/packages/flutter_quill +## 效果展示 + +

+ 1 + 1 +

+ +

+ 1 + 1 +

+ +--- ## 用法 -查看 `示例` 目录来学习 FlutterQuill 最简单的使用方法,你通常只需要实例化一个控制器: +查看 `示例` 目录来学习 `FlutterQuill` 最简单的使用方法,你通常只需要一个控制器实例: -``` +```dart QuillController _controller = QuillController.basic(); ``` -然后在你的 App 中嵌入工具栏和编辑器,例如: +然后在你的 `App` 中嵌入工具栏 `QuillToolbar` 和编辑器 `QuillEditor` ,如: ```dart Column( @@ -47,75 +64,88 @@ Column( child: Container( child: QuillEditor.basic( controller: _controller, - readOnly: false, // true for view only mode + readOnly: false, // 为 true 时只读 ), ), ) ], ) ``` -查看 [示例页面] 以了解高级用户。 -## 输入 / 输出 +查看 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart) 查看高级用法 -该库使用 [Quill] 作为内部数据格式。 +## 保存和读取 -* 使用 `_controller.document.toDelta()` 获取增量。 -* 使用 `_controller.document.toPlainText()` 获取纯文本。 +该库使用 [Quill 格式](https://quilljs.com/docs/formats) 作为内部数据格式 -FlutterQuill 提供了一些 JSON 序列化支持,以便您可以保存和打开文档。 要将文档保存为 JSON 类型,请执行以下操作: +* 使用 `_controller.document.toDelta()` 获取 [Delta 格式](https://quilljs.com/docs/delta/) +* 使用 `_controller.document.toPlainText()` 获取纯文本 -``` +`FlutterQuill` 提供了一些 `JSON` 序列化支持,以便你保存和打开文档 + +要将文档转化为 `JSON` 类型,请执行以下操作: + +```dart var json = jsonEncode(_controller.document.toDelta().toJson()); ``` -然后你就可以将其存储。 +要将 `FlutterQuill` 使用之前存储的 `JSON` 数据,请执行以下操作: -想要 FlutterQuill 编辑器使用你之前存储的 JSON 数据,请执行以下操作: - -``` +```dart var myJSON = jsonDecode(incomingJSONText); _controller = QuillController( document: Document.fromJson(myJSON), - selection: TextSelection.collapsed(offset: 0)); + selection: TextSelection.collapsed(offset: 0), + ); ``` -## Web -对于 web 开发,请执行 `flutter config --enable-web` 来获取对 flutter 的支持或使用 [ReactQuill] 获取对 React 的支持。 +## Web 端 -进行 Web 开发需要提供 `EmbedBuilder`, 参考:[defaultEmbedBuilderWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L29). -进行 Web 开发还需要提供 `webImagePickImpl`, 参考: [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L225). +对于 `Web` 开发,请执行 `flutter config --enable-web` 来获取 `Flutter` 的支持,或使用 [ReactQuill](https://github.com/zenoamaro/react-quill) 获取对 `React` 的支持 +进行 `Web` 开发需要提供 `EmbedBuilder` ,参见 [defaultEmbedBuilderWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L29) -## Desktop +进行 `Web` 开发还需要提供 `webImagePickImpl` ,参见 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L225) -在桌面端进行工具栏按钮开发,需要提供 `filePickImpl`。参考: [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L205). +## 桌面端 + +进行桌面端工具栏按钮开发需要提供 `filePickImpl` ,参见 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L205) ## 配置 -`QuillToolbar` 类允许您自定义可用的格式选项。[示例页面] 提供了高级使用和配置的示例代码。 +`QuillToolbar` 类允许你自定义可用的格式选项,参见 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart) 提供了高级使用和配置的示例代码 ### 字号 -在编辑器工具栏中,提供了具有字号功能的下拉菜单。 这可以通过 `showFontSize` 启用或禁用。 -启用后,可以通过*可选的* `fontSizeValues` 属性修改默认字号。 `fontSizeValues` 接受一个 `Map`,其中包含一个 `String` 类型的标题和一个 `String` 类型的字号。 例子: -``` -fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'} +在工具栏中提供了选择字号的下拉菜单,可通过 `showFontSize` 来启用或禁用 + +启用后,可以通过 *可选的* `fontSizeValues` 属性修改默认字号 + +`fontSizeValues` 接收一个 `Map`,其中包含一个 `String` 类型的标题和一个 `String` 类型的字号,如: + +```dart +fontSizeValues: const {'小字号': '8', '中字号': '24.5', '大字号': '46'} ``` 字体大小可以使用 `0` 值清除,例如: -``` -fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '0'} + +```dart +fontSizeValues: const {'小字号': '8', '中字号': '24.5', '大字号': '46', '清除': '0'} ``` ### 字体 -想要使用你自己的字体,请更新你的 [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) 并且传入 `fontFamilyValues`。详情内容请查看 [this change](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa), [this article](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) 和 [this](https://www.flutterbeads.com/change-font-family-flutter/)。 + +想要使用你自己的字体,请更新你的 [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) 并且传入 `fontFamilyValues` + +详见 [这个 Commit](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa) 和 [这篇文章](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) 以及 [这个教程](https://www.flutterbeads.com/change-font-family-flutter/) ### 自定义按钮 -您可以通过 `customButtons` 可选参数将自定义按钮添加到工具栏的*末尾*,该参数接收的了行是 `QuillCustomButton` 的 `List`。 -要添加一个 Icon,我们应该实例化一个新的新的 `QuillCustomButton` -``` +你可以通过 `customButtons` 可选参数将自定义按钮添加到工具栏的 *末尾* ,该参数接收 `QuillCustomButton` 的 `List` + +要添加一个 `Icon` ,我们应该实例化一个新的 `QuillCustomButton` + +```dart QuillCustomButton( icon:Icons.ac_unit, onTap: () { @@ -124,8 +154,9 @@ fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': ' ), ``` -每个 `QuillCustomButton` 都是 `customButtons` 可选参数的一部分,如下所示: -``` +每个 `QuillCustomButton` 都是 `customButtons` 可选参数的一部分,如: + +```dart QuillToolbar.basic( (...), customButtons: [ @@ -135,14 +166,12 @@ QuillToolbar.basic( debugPrint('snowflake1'); } ), - QuillCustomButton( icon:Icons.ac_unit, onTap: () { debugPrint('snowflake2'); } ), - QuillCustomButton( icon:Icons.ac_unit, onTap: () { @@ -152,10 +181,33 @@ QuillToolbar.basic( ] ``` +## 嵌入块 + +自 `6.0` 版本,本库不默认支持嵌入块,反之本库提供接口给所有用户来创建所需的嵌入块。 + +若需要图片、视频、公式块的支持,请查看独立库 [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions) + +### 根据 `flutter_quill_extensions` 使用自定义嵌入块 + +```dart +import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; + +QuillEditor.basic( + controller: controller, + embedBuilders: FlutterQuillEmbeds.builders(), +); + +QuillToolbar.basic( + controller: controller, + embedButtons: FlutterQuillEmbeds.buttons(), +); +``` + ### 移动端上自定义图片尺寸 定义`mobileWidth`、`mobileHeight`、`mobileMargin`、`mobileAlignment`如下: -``` + +```dart { "insert": { "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" @@ -167,13 +219,14 @@ QuillToolbar.basic( ``` ### 自定义嵌入块 -有时您想在文本中添加一些自定义内容或者是自定义小部件。 比如向文本添加注释,或者在文本编辑器中添加的任何自定义内容。 -您唯一需要做的就是添加一个 `CustomBlockEmbed` 并将其映射到 `customElementsEmbedBuilder` 中,以将自定义块内的数据转换为一个 widget! +有时你想在文本中添加一些自定义内容或者是自定义小部件 + +比如向文本添加注释,或者在文本编辑器中添加的任何自定义内容 -例子: +你唯一需要做的就是添加一个 `CustomBlockEmbed` 并将其映射到 `customElementsEmbedBuilder` 中,以将自定义块内的数据转换为一个 `Widget` ,如: -从 `CustomBlockEmbed` 开始,我们在这里扩展它并添加对 'Note' widget 的方法,这就是 `Document`,`flutter_quill` 使用它来呈现富文本。 +先从 `CustomBlockEmbed` `extent` 出一个 `NotesBlockEmbed` 类,并添加两个方法以返回 `Document` 用以 `flutter_quill` 渲染富文本 ```dart class NotesBlockEmbed extends CustomBlockEmbed { @@ -188,43 +241,53 @@ class NotesBlockEmbed extends CustomBlockEmbed { } ``` -然后,我们需要将这个 “notes” 类型映射到 widget 中。在例子中,我使用 `ListTile` 来显示它,使用 `onTap` 方法俩编辑内容,另外不要忘记将此方法添加到 `QuillEditor` 中。 +然后,我们需要将这个 `notes` 类型映射到其想渲染出的 `Widget` 中 + +在这里我们使用 `ListTile` 来渲染它,并使用 `onTap` 方法来编辑内容,最后不要忘记将此方法添加到 `QuillEditor` 中 ```dart -Widget customElementsEmbedBuilder( - BuildContext context, - QuillController controller, - CustomBlockEmbed block, - bool readOnly, - void Function(GlobalKey videoContainerKey)? onVideoInit, -) { - switch (block.type) { - case 'notes': - final notes = NotesBlockEmbed(block.data).document; - - return Material( - color: Colors.transparent, - child: ListTile( - title: Text( - notes.toPlainText().replaceAll('\n', ' '), - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - leading: const Icon(Icons.notes), - onTap: () => _addEditNote(context, document: notes), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - side: const BorderSide(color: Colors.grey), - ), +class NotesEmbedBuilder implements EmbedBuilder { + NotesEmbedBuilder({required this.addEditNote}); + + Future Function(BuildContext context, {Document? document}) addEditNote; + + @override + String get key => 'notes'; + + @override + Widget build( + BuildContext context, + QuillController controller, + Embed node, + bool readOnly, + ) { + final notes = NotesBlockEmbed(node.value.data).document; + + return Material( + color: Colors.transparent, + child: ListTile( + title: Text( + notes.toPlainText().replaceAll('\n', ' '), + maxLines: 3, + overflow: TextOverflow.ellipsis, ), - ); - default: - return const SizedBox(); + leading: const Icon(Icons.notes), + onTap: () => addEditNote(context, document: notes), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: const BorderSide(color: Colors.grey), + ), + ), + ); } } ``` -然后,编写一个方法来添加/编辑内容,`showDialog` 方法显示 Quill 编辑器以编辑内容,用户编辑完成后,需要检查文档是否有内容,如果有内容,在 `CustomBlockEmbed` 中添加/编辑 `NotesBlockEmbed`(注意,如果没有在 `NotesBlockEmbed` 中传递 `CustomBlockEmbed` ,编辑将不会生效)。 +最后我们编写一个方法来添加或编辑内容 + +`showDialog` 方法先显示 `Quill` 编辑器以让用户编辑内容,编辑完成后,我们需要检查文档是否有内容,若有则在 `BlockEmbed.custom` 传入添加或编辑了的 `NotesBlockEmbed` + +注意,如果我们没有在 `BlockEmbed.custom` 传如我们所自定义的 `CustomBlockEmbed` ,那么编辑将不会生效 ```dart Future _addEditNote(BuildContext context, {Document? document}) async { @@ -274,34 +337,34 @@ Future _addEditNote(BuildContext context, {Document? document}) async { } ``` -这样我们就成功的在富文本编辑器中添加了一个自定义小组件。 +这样我们就成功的在富文本编辑器中添加了一个自定义小组件

1

-> 更多信息和视频示例,请参阅 [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877) - -> 有关更多详细信息,请查看 [this YouTube video](https://youtu.be/pI5p5j7cfHc) +> 1. 更多信息和视频示例,请参阅 [这个特性的 PR](https://github.com/singerdmx/flutter-quill/pull/877) +> 2. 有关更多详细信息,请查看 [这个 Youtube 视频](https://youtu.be/pI5p5j7cfHc) ### 翻译 -该库为 quill 工具栏和编辑器提供翻译,除非您设置自己的语言环境,否则它将遵循系统语言环境: +该库为 `QuillToolbar` 和 `QuillEditor` 提供了部分翻译,且若你未设置自己的语言环境,则它将使用系统语言环境: ```dart QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -目前,可提供以下 22 种语言环境的翻译: +目前,可提供以下 24 种语言环境的翻译: * `Locale('en')` * `Locale('ar')` +* `Locale('cs')` * `Locale('de')` * `Locale('da')` * `Locale('fr')` -* `Locale('zh', 'CN')` -* `Locale('zh', 'HK')` +* `Locale('zh', 'cn')` +* `Locale('zh', 'hk')` * `Locale('ko')` * `Locale('ru')` * `Locale('es')` @@ -317,24 +380,17 @@ QuillEditor(locale: Locale('fr'), ...) * `Locale('fa')` * `Locale('hi')` * `Locale('sr')` +* `Locale('jp')` #### 贡献翻译 -翻译文件位于 [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart)。 随意贡献您自己的翻译,只需复制英文翻译映射并将值替换为您的翻译。 然后打开一个拉取请求,这样每个人都可以从您的翻译中受益! - ---- - -

- 1 - 1 -

+翻译文件位于 [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart)。 -

- 1 - 1 -

+随意贡献你自己的翻译,只需复制英文翻译映射并将值替换为你的翻译即可。 +然后打开一个拉取请求,这样每个人都可以从你的翻译中受益! +--- ## 帮助 @@ -343,11 +399,9 @@ QuillEditor(locale: Locale('fr'), ...) "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" width="150px" height="150px"> -[Quill]: https://quilljs.com/docs/formats -[Flutter]: https://github.com/flutter/flutter -[FlutterQuill]: https://pub.dev/packages/flutter_quill -[ReactQuill]: https://github.com/zenoamaro/react-quill -[Youtube 播放列表]: https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2 -[Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g -[示例页面]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart -[代码介绍]: https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md +## 赞助 + + + diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 55968c80..850893b8 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -209,7 +209,7 @@ extension Localization on String { 'Gallery': '相簿', 'Link': '链接', 'Please first select some text to transform into a link.': - '请先选择一些要转化为链接的文本', + '请先选择需转化为链接的文本', 'Open': '打开', 'Copy': '复制', 'Remove': '移除', @@ -222,18 +222,51 @@ extension Localization on String { 'Width': '宽度', 'Height': '高度', 'Size': '文字大小', - 'Small': 'Small', - 'Large': 'Large', - 'Huge': 'Huge', - 'Clear': 'Clear', - 'Font': 'Font', - 'Search': 'Search', - 'matches': 'matches', - 'showing match': 'showing match', - 'Prev': 'Prev', - 'Next': 'Next', - 'Camera': 'Camera', - 'Video': 'Video', + 'Small': '小字号', + 'Large': '大字号', + 'Huge': '超大字号', + 'Clear': '清除', + 'Font': '字体', + 'Search': '搜索', + 'matches': '结果', + 'showing match': '显示结果', + 'Prev': '上一个', + 'Next': '下一个', + 'Camera': '拍照', + 'Video': '录像', + }, + 'jp': { + 'Paste a link': 'リンクをペースト', + 'Ok': '完了', + 'Select Color': '色を選択', + 'Gallery': '写真集', + 'Link': 'リンク', + 'Please first select some text to transform into a link.': + 'まずリンクに変換する文字を選択してください.', + 'Open': '開く', + 'Copy': 'コピー', + 'Remove': '削除', + 'Save': '保存', + 'Zoom': '拡大', + 'Saved': '保存済み', + 'Text': '文字', + 'What is entered is not a link': '入力されたのはリンクではありません', + 'Resize': 'サイズを調整', + 'Width': '幅', + 'Height': '高さ', + 'Size': 'サイズ', + 'Small': '小さい', + 'Large': '大きい', + 'Huge': 'でっかい', + 'Clear': 'クリア', + 'Font': 'フォント', + 'Search': '検索', + 'matches': '結果', + 'showing match': '結果を表示', + 'Prev': '前へ', + 'Next': '次へ', + 'Camera': 'カメラ', + 'Video': 'ビデオ', }, 'ko': { 'Paste a link': '링크를 붙여넣어 주세요.', From 28c2a7c87117cccb1ab0215ccaf150319bc0f71a Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 24 Dec 2022 07:58:09 -0800 Subject: [PATCH 042/204] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 843c97fa..8ff288a1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Demo App: [BULLET JOURNAL](https://bulletjournal.us/home/index.html) Pub: [FlutterQuill] -## Presentations +## Demo

1 From 10a99c2a7d86590402f6079e5131fd80e1d7a81b Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 24 Dec 2022 17:15:42 -0800 Subject: [PATCH 043/204] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ff288a1..36c887ff 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,6 @@ [github-forks-badge]: https://img.shields.io/github/forks/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff [github-forks-link]: https://github.com/singerdmx/flutter-quill/network/members -[中文文档](./doc_cn.md) - --- FlutterQuill is a rich text editor and a [Quill] component for [Flutter]. @@ -379,6 +377,8 @@ Currently, translations are available for these 24 locales: The translation file is located at [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations! +[中文文档](./doc_cn.md) + ## Sponsors From c25f50f018f676569969cd774a0fd37d89089455 Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 24 Dec 2022 17:18:18 -0800 Subject: [PATCH 044/204] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 36c887ff..c1bc2c9d 100644 --- a/README.md +++ b/README.md @@ -377,8 +377,6 @@ Currently, translations are available for these 24 locales: The translation file is located at [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations! -[中文文档](./doc_cn.md) - ## Sponsors @@ -394,3 +392,7 @@ The translation file is located at [toolbar.i18n.dart](lib/src/translations/tool [Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g [Sample Page]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart [Code Introduction]: https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md + +


+ +[中文文档](./doc_cn.md) From 6e74971ae16ca7d6c028816feec3a88ccecb3e6f Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 24 Dec 2022 17:19:52 -0800 Subject: [PATCH 045/204] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1bc2c9d..8d352c71 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ var myJSON = jsonDecode(incomingJSONText); _controller = QuillController( document: Document.fromJson(myJSON), selection: TextSelection.collapsed(offset: 0), - ); + ); ``` ## Web From 1597cb9181feb545332f55d5a20800029e86bb4f Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 24 Dec 2022 18:18:42 -0800 Subject: [PATCH 046/204] Delay focus calculation for iOS --- CHANGELOG.md | 3 +++ lib/src/widgets/raw_editor.dart | 9 +++++---- pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d85b7e3..3511bd67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.10] +* Delay focus calculation for iOS. + # [6.1.9] * Bump keyboard show up wait to 1 sec. diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 6108b7fb..467fb5ad 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1075,11 +1075,12 @@ class RawEditorState extends EditorState if (_hasFocus) { final keyboardAlreadyShown = _keyboardVisible; openConnectionIfNeeded(); - if (keyboardAlreadyShown) { - _showCaretOnScreen(); + if (!keyboardAlreadyShown || + defaultTargetPlatform == TargetPlatform.iOS) { + /// delay 500 milliseconds for waiting keyboard show up + Future.delayed(const Duration(milliseconds: 500), _showCaretOnScreen); } else { - /// delay 1000 milliseconds for waiting keyboard show up - Future.delayed(const Duration(milliseconds: 1000), _showCaretOnScreen); + _showCaretOnScreen(); } } else { widget.focusNode.requestFocus(); diff --git a/pubspec.yaml b/pubspec.yaml index cafc0d6e..a0736eee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.9 +version: 6.1.10 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From c130873416a3e131d516fbeda8fbf4a330c1407b Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 24 Dec 2022 18:53:03 -0800 Subject: [PATCH 047/204] Remove iOS hack for delaying focus calculation --- CHANGELOG.md | 3 +++ lib/src/widgets/raw_editor.dart | 3 +-- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3511bd67..e3f59a30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.11] +* Remove iOS hack for delaying focus calculation. + # [6.1.10] * Delay focus calculation for iOS. diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 467fb5ad..835f0db8 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1075,8 +1075,7 @@ class RawEditorState extends EditorState if (_hasFocus) { final keyboardAlreadyShown = _keyboardVisible; openConnectionIfNeeded(); - if (!keyboardAlreadyShown || - defaultTargetPlatform == TargetPlatform.iOS) { + if (!keyboardAlreadyShown) { /// delay 500 milliseconds for waiting keyboard show up Future.delayed(const Duration(milliseconds: 500), _showCaretOnScreen); } else { diff --git a/pubspec.yaml b/pubspec.yaml index a0736eee..8ef81718 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.10 +version: 6.1.11 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From f9b17e2cc2ee9a7c34fe20f7c02f576ba89c9e16 Mon Sep 17 00:00:00 2001 From: Dan Syrstad Date: Tue, 27 Dec 2022 10:42:38 -0600 Subject: [PATCH 048/204] Update doc regarding options for conversion to HTML. (#1036) --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 8d352c71..7cf09267 100644 --- a/README.md +++ b/README.md @@ -377,6 +377,16 @@ Currently, translations are available for these 24 locales: The translation file is located at [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations! +## Conversion to HTML + +Having your document stored in Quill Delta format is sometimes not enough. Often you'll need to convert +it to other formats such as HTML in order to publish it, or send an email. One option is to use +[vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document +to HTML. This package has full support for all Quill operations - including images, videos, formulas, +tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter. +It is a complete Dart port of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) +Typescript/Javascript package. + ## Sponsors From addc1dc8f6512962618ba8102fbe9c22290a9bce Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 27 Dec 2022 08:51:36 -0800 Subject: [PATCH 049/204] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7cf09267..0ae33110 100644 --- a/README.md +++ b/README.md @@ -384,7 +384,7 @@ it to other formats such as HTML in order to publish it, or send an email. One o [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document to HTML. This package has full support for all Quill operations - including images, videos, formulas, tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter. -It is a complete Dart port of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) +It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) Typescript/Javascript package. ## Sponsors From 8a4431f04d8ca549801b186833489647cb89f016 Mon Sep 17 00:00:00 2001 From: Tom Balmas Date: Mon, 2 Jan 2023 16:47:26 +0200 Subject: [PATCH 050/204] Update toolbar.i18n.dart (#1044) --- lib/src/translations/toolbar.i18n.dart | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 850893b8..20403c44 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -866,6 +866,39 @@ extension Localization on String { 'Camera': 'Kamera', 'Video': 'Video', }, + 'he': { + 'Paste a link': 'הדבק את הלינק', + 'Ok': 'אוקי', + 'Select Color': 'בחר צבע', + 'Gallery': 'גלריה', + 'Link': 'לינק', + 'Please first select some text to transform into a link.': + 'בבקשה תחילה בחר טקסט להפיכה ללינק', + 'Open': 'פתח', + 'Copy': 'העתק', + 'Remove': 'מחק', + 'Save': 'שמור', + 'Zoom': 'זום', + 'Saved': 'נשמר', + 'Text': 'טקסט', + 'What is entered is not a link': 'מה שהוכנס הוא לא לינק', + 'Resize': 'שנה גודל', + 'Width': 'רוחב', + 'Height': 'גובה', + 'Size': 'גודל', + 'Small': 'קטן', + 'Large': 'גדול', + 'Huge': 'ענק', + 'Clear': 'מחוק', + 'Font': 'פונט', + 'Search': 'חפש', + 'matches': 'תוצאות', + 'showing match': 'מציג תוצאות', + 'Prev': 'הקודם', + 'Next': 'הבא', + 'Camera': 'מצלמה', + 'Video': 'וידאו', + } }; String get i18n => localize(this, _t); From e0e4b46f872e32ebc44611810c30462a4c711c08 Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 2 Jan 2023 09:40:53 -0800 Subject: [PATCH 051/204] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0ae33110..b5f5e1dd 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -Currently, translations are available for these 24 locales: +Currently, translations are available for these 25 locales: * `Locale('en')` * `Locale('ar')` @@ -354,6 +354,7 @@ Currently, translations are available for these 24 locales: * `Locale('de')` * `Locale('da')` * `Locale('fr')` +* `Locale('he')` * `Locale('zh', 'cn')` * `Locale('zh', 'hk')` * `Locale('ko')` From f28f2e30c9beba1effa05eebb3e639891f1c0186 Mon Sep 17 00:00:00 2001 From: comfuture Date: Sat, 7 Jan 2023 00:59:13 +0900 Subject: [PATCH 052/204] korean translations (#1049) --- lib/src/translations/toolbar.i18n.dart | 32 +++++++++++++------------- lib/src/widgets/toolbar.dart | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 20403c44..a5c1c651 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -284,21 +284,21 @@ extension Localization on String { 'Saved': '저장되었습니다.', 'Text': '텍스트', 'What is entered is not a link': '입력한 내용은 링크가 아닙니다.', - 'Resize': 'Resize', - 'Width': 'Width', - 'Height': 'Height', - 'Small': 'Small', - 'Large': 'Large', - 'Huge': 'Huge', - 'Clear': 'Clear', - 'Font': 'Font', - 'Search': 'Search', - 'matches': 'matches', - 'showing match': 'showing match', - 'Prev': 'Prev', - 'Next': 'Next', - 'Camera': 'Camera', - 'Video': 'Video', + 'Resize': '크기조정', + 'Width': '넓이', + 'Height': '높이', + 'Small': '작게', + 'Large': '크게', + 'Huge': '매우크게', + 'Clear': '초기화', + 'Font': '글꼴', + 'Search': '검색', + 'matches': '결과', + 'showing match': '결과 보기', + 'Prev': '이전', + 'Next': '다음', + 'Camera': '카메라', + 'Video': '비디오', }, 'ru': { 'Paste a link': 'Вставить ссылку', @@ -866,7 +866,7 @@ extension Localization on String { 'Camera': 'Kamera', 'Video': 'Video', }, - 'he': { + 'he': { 'Paste a link': 'הדבק את הלינק', 'Ok': 'אוקי', 'Select Color': 'בחר צבע', diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 9f11eaf5..188af5f6 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -162,7 +162,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { 'Nunito': 'nunito', 'Pacifico': 'pacifico', 'Roboto Mono': 'roboto-mono', - 'Clear': 'Clear' + 'Clear'.i18n: 'Clear' }; return QuillToolbar( From 12bafd8d98ba95e531ea662c6dae667c2e9d69b8 Mon Sep 17 00:00:00 2001 From: X Code Date: Fri, 6 Jan 2023 08:02:12 -0800 Subject: [PATCH 053/204] Upgrade to 6.1.12 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f59a30..ccc1545a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.1.12] +* Apply i18n for default font dropdown option labels corresponding to 'Clear'. + # [6.1.11] * Remove iOS hack for delaying focus calculation. diff --git a/pubspec.yaml b/pubspec.yaml index 8ef81718..d5145582 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.11 +version: 6.1.12 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 1c5177953ff8687c1283578c258c75de023c6aff Mon Sep 17 00:00:00 2001 From: Nathenael D Date: Tue, 10 Jan 2023 10:41:55 -0500 Subject: [PATCH 054/204] aligns numerical and bullet lists along with text content (#1054) --- lib/src/widgets/text_block.dart | 23 ++--------------------- lib/src/widgets/text_line.dart | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index f30e71cc..24ee6441 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -135,6 +135,7 @@ class EditableTextBlock extends StatelessWidget { _buildLeading(context, line, index, indentLevelCounts, count), TextLine( line: line, + index: index, textDirection: textDirection, embedBuilder: embedBuilder, customStyleBuilder: customStyleBuilder, @@ -164,25 +165,6 @@ class EditableTextBlock extends StatelessWidget { Map indentLevelCounts, int count) { final defaultStyles = QuillStyles.getStyles(context, false); final attrs = line.style.attributes; - if (attrs[Attribute.list.key] == Attribute.ol) { - return QuillNumberPoint( - index: index, - indentLevelCounts: indentLevelCounts, - count: count, - style: defaultStyles!.leading!.style, - attrs: attrs, - width: 32, - padding: 8, - ); - } - - if (attrs[Attribute.list.key] == Attribute.ul) { - return QuillBulletPoint( - style: - defaultStyles!.leading!.style.copyWith(fontWeight: FontWeight.bold), - width: 32, - ); - } if (attrs[Attribute.list.key] == Attribute.checked) { return CheckboxPoint( @@ -235,8 +217,7 @@ class EditableTextBlock extends StatelessWidget { var baseIndent = 0.0; - if (attrs.containsKey(Attribute.list.key) || - attrs.containsKey(Attribute.codeBlock.key)) { + if (attrs.containsKey(Attribute.codeBlock.key)) { baseIndent = 32.0; } diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index b20ea22f..f3594a91 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -38,12 +38,14 @@ class TextLine extends StatefulWidget { required this.controller, required this.onLaunchUrl, required this.linkActionPicker, + this.index, this.textDirection, this.customStyleBuilder, Key? key, }) : super(key: key); final Line line; + final int? index; final TextDirection? textDirection; final EmbedsBuilder embedBuilder; final DefaultStyles styles; @@ -294,9 +296,22 @@ class _TextLineState extends State { final nodeStyle = textNode.style; final isLink = nodeStyle.containsKey(Attribute.link.key) && nodeStyle.attributes[Attribute.link.key]!.value != null; + final attrs = widget.line.style.attributes; + const whiteSpace = TextSpan(text: ' '); + + var leading = const TextSpan(); + if (attrs[Attribute.list.key] == Attribute.ol) { + leading = TextSpan(text: '${widget.index.toString()}.'); + } else if (attrs[Attribute.list.key] == Attribute.ul) { + leading = TextSpan( + text: '•', + style: + defaultStyles.leading!.style.copyWith(fontWeight: FontWeight.bold), + ); + } return TextSpan( - text: textNode.value, + children: [leading, whiteSpace, TextSpan(text: textNode.value)], style: _getInlineTextStyle( textNode, defaultStyles, nodeStyle, lineStyle, isLink), recognizer: isLink && canLaunchLinks ? _getRecognizer(node) : null, From 0efbb680f69c96121e6d8fd0bd976290ef8aabec Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 10 Jan 2023 07:49:13 -0800 Subject: [PATCH 055/204] Upgrade to 6.2.0 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc1545a..4827f6dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.2.0] +* Align numerical and bullet lists along with text content. + # [6.1.12] * Apply i18n for default font dropdown option labels corresponding to 'Clear'. diff --git a/pubspec.yaml b/pubspec.yaml index d5145582..00b3bb74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.1.12 +version: 6.2.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From afff4af7688b9a7888e7ec589b2767eb9c225723 Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 10 Jan 2023 08:07:41 -0800 Subject: [PATCH 056/204] Code cleanup --- lib/src/widgets/text_block.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 24ee6441..b5790e96 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -13,7 +13,6 @@ import 'default_styles.dart'; import 'delegate.dart'; import 'editor.dart'; import 'link.dart'; -import 'style_widgets/bullet_point.dart'; import 'style_widgets/checkbox_point.dart'; import 'style_widgets/number_point.dart'; import 'text_line.dart'; From 7e14392cc6ca28344168dab01d0e1304e2c600d2 Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 11 Jan 2023 12:48:27 -0800 Subject: [PATCH 057/204] Revert "aligns numerical and bullet lists along with text content (#1054)" This reverts commit 1c5177953ff8687c1283578c258c75de023c6aff. --- lib/src/widgets/text_block.dart | 23 +++++++++++++++++++++-- lib/src/widgets/text_line.dart | 17 +---------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index b5790e96..2073ba0d 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -134,7 +134,6 @@ class EditableTextBlock extends StatelessWidget { _buildLeading(context, line, index, indentLevelCounts, count), TextLine( line: line, - index: index, textDirection: textDirection, embedBuilder: embedBuilder, customStyleBuilder: customStyleBuilder, @@ -164,6 +163,25 @@ class EditableTextBlock extends StatelessWidget { Map indentLevelCounts, int count) { final defaultStyles = QuillStyles.getStyles(context, false); final attrs = line.style.attributes; + if (attrs[Attribute.list.key] == Attribute.ol) { + return QuillNumberPoint( + index: index, + indentLevelCounts: indentLevelCounts, + count: count, + style: defaultStyles!.leading!.style, + attrs: attrs, + width: 32, + padding: 8, + ); + } + + if (attrs[Attribute.list.key] == Attribute.ul) { + return QuillBulletPoint( + style: + defaultStyles!.leading!.style.copyWith(fontWeight: FontWeight.bold), + width: 32, + ); + } if (attrs[Attribute.list.key] == Attribute.checked) { return CheckboxPoint( @@ -216,7 +234,8 @@ class EditableTextBlock extends StatelessWidget { var baseIndent = 0.0; - if (attrs.containsKey(Attribute.codeBlock.key)) { + if (attrs.containsKey(Attribute.list.key) || + attrs.containsKey(Attribute.codeBlock.key)) { baseIndent = 32.0; } diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index f3594a91..b20ea22f 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -38,14 +38,12 @@ class TextLine extends StatefulWidget { required this.controller, required this.onLaunchUrl, required this.linkActionPicker, - this.index, this.textDirection, this.customStyleBuilder, Key? key, }) : super(key: key); final Line line; - final int? index; final TextDirection? textDirection; final EmbedsBuilder embedBuilder; final DefaultStyles styles; @@ -296,22 +294,9 @@ class _TextLineState extends State { final nodeStyle = textNode.style; final isLink = nodeStyle.containsKey(Attribute.link.key) && nodeStyle.attributes[Attribute.link.key]!.value != null; - final attrs = widget.line.style.attributes; - const whiteSpace = TextSpan(text: ' '); - - var leading = const TextSpan(); - if (attrs[Attribute.list.key] == Attribute.ol) { - leading = TextSpan(text: '${widget.index.toString()}.'); - } else if (attrs[Attribute.list.key] == Attribute.ul) { - leading = TextSpan( - text: '•', - style: - defaultStyles.leading!.style.copyWith(fontWeight: FontWeight.bold), - ); - } return TextSpan( - children: [leading, whiteSpace, TextSpan(text: textNode.value)], + text: textNode.value, style: _getInlineTextStyle( textNode, defaultStyles, nodeStyle, lineStyle, isLink), recognizer: isLink && canLaunchLinks ? _getRecognizer(node) : null, From e0016c98412f57784cd60bc9eb92964d0b2384bb Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 11 Jan 2023 15:13:44 -0800 Subject: [PATCH 058/204] Upgrade to 6.2.1 --- CHANGELOG.md | 3 +++ lib/src/widgets/text_block.dart | 1 + pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4827f6dc..66bc5e34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.2.1] +* Revert "Align numerical and bullet lists along with text content". + # [6.2.0] * Align numerical and bullet lists along with text content. diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 2073ba0d..f30e71cc 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -13,6 +13,7 @@ import 'default_styles.dart'; import 'delegate.dart'; import 'editor.dart'; import 'link.dart'; +import 'style_widgets/bullet_point.dart'; import 'style_widgets/checkbox_point.dart'; import 'style_widgets/number_point.dart'; import 'text_line.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 00b3bb74..54d1b81b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.2.0 +version: 6.2.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 8352fce964ccc5d21205ec8265a356c434ab456d Mon Sep 17 00:00:00 2001 From: Thea Choem <29684683+theachoem@users.noreply.github.com> Date: Sun, 15 Jan 2023 17:17:19 +0700 Subject: [PATCH 059/204] fix: nextLine getter null where no assertion (#1061) --- lib/src/models/documents/nodes/line.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index 3e3bbb04..4b6db80f 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/line.dart @@ -381,7 +381,7 @@ class Line extends Container { } final remaining = len - local; - if (remaining > 0) { + if (remaining > 0 && nextLine != null) { final rest = nextLine!.collectStyle(0, remaining); _handle(rest); } @@ -416,7 +416,7 @@ class Line extends Container { // TODO: add line style and parent's block style final remaining = len - local; - if (remaining > 0) { + if (remaining > 0 && nextLine != null) { final rest = nextLine!.collectAllIndividualStyles(0, remaining, beg: local); result.addAll(rest); @@ -450,7 +450,7 @@ class Line extends Container { } final remaining = len - local; - if (remaining > 0) { + if (remaining > 0 && nextLine != null) { final rest = nextLine!.collectAllStyles(0, remaining); result.addAll(rest); } @@ -501,7 +501,7 @@ class Line extends Container { } } - if (_len > 0) { + if (_len > 0 && nextLine != null) { _len = nextLine!._getPlainText(0, _len, plainText); } } From d95c3485303eab5860dfccd371cf0a4486bf7ea8 Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 15 Jan 2023 08:36:14 -0800 Subject: [PATCH 060/204] Upgrade to 6.2.2 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66bc5e34..40292ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.2.2] +* Fix: nextLine getter null where no assertion. + # [6.2.1] * Revert "Align numerical and bullet lists along with text content". diff --git a/pubspec.yaml b/pubspec.yaml index 54d1b81b..51686145 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.2.1 +version: 6.2.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 640c1b4990bf525511bbc6a98013921613652e7a Mon Sep 17 00:00:00 2001 From: Firdavsi Nurov <80065908+Vorun5@users.noreply.github.com> Date: Wed, 25 Jan 2023 07:00:31 +0300 Subject: [PATCH 061/204] fix Error: The non-abstract class 'RawEditorState' is missing implementations for these members (#1041) --- lib/src/widgets/raw_editor.dart | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 835f0db8..a26dfa48 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -237,8 +237,7 @@ class RawEditor extends StatefulWidget { State createState() => RawEditorState(); } -class RawEditorState extends EditorState - with +class RawEditorState extends EditorState with AutomaticKeepAliveClientMixin, WidgetsBindingObserver, TickerProviderStateMixin, @@ -1422,6 +1421,16 @@ class RawEditorState extends EditorState // this is needed for Scribble (Stylus input) in Apple platforms // and this package does not implement this feature } + + @override + void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) { + // TODO: implement didChangeInputControl + } + + @override + void performSelector(String selectorName) { + // TODO: implement performSelector + } } class _Editor extends MultiChildRenderObjectWidget { From ca921905e4499e85148523cdd3bef1e0262755b6 Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 24 Jan 2023 20:16:36 -0800 Subject: [PATCH 062/204] Upgrade to 6.3.0 --- CHANGELOG.md | 3 +++ example/macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- lib/src/widgets/raw_editor.dart | 10 ++++++---- lib/src/widgets/text_selection.dart | 7 +++---- .../widgets/toolbar/arrow_indicated_button_list.dart | 1 - lib/src/widgets/toolbar/quill_font_family_button.dart | 3 +-- lib/src/widgets/toolbar/quill_font_size_button.dart | 3 +-- pubspec.yaml | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40292ba2..8c909d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.3.0] +* Support Flutter 3.7. + # [6.2.2] * Fix: nextLine getter null where no assertion. diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 5b11d5f3..85987338 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,7 +7,7 @@ import Foundation import device_info_plus import pasteboard -import path_provider_macos +import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index a26dfa48..1cd69e22 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -237,7 +237,8 @@ class RawEditor extends StatefulWidget { State createState() => RawEditorState(); } -class RawEditorState extends EditorState with +class RawEditorState extends EditorState + with AutomaticKeepAliveClientMixin, WidgetsBindingObserver, TickerProviderStateMixin, @@ -1421,12 +1422,13 @@ class RawEditorState extends EditorState with // this is needed for Scribble (Stylus input) in Apple platforms // and this package does not implement this feature } - + @override - void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) { + void didChangeInputControl( + TextInputControl? oldControl, TextInputControl? newControl) { // TODO: implement didChangeInputControl } - + @override void performSelector(String selectorName) { // TODO: implement performSelector diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart index 770c3b3d..dd6d6c18 100644 --- a/lib/src/widgets/text_selection.dart +++ b/lib/src/widgets/text_selection.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import '../models/documents/nodes/node.dart'; @@ -82,7 +81,7 @@ class EditorTextSelectionOverlay { this.dragStartBehavior = DragStartBehavior.start, this.handlesVisible = false, }) { - final overlay = Overlay.of(context, rootOverlay: true)!; + final overlay = Overlay.of(context, rootOverlay: true); _toolbarController = AnimationController( duration: const Duration(milliseconds: 150), vsync: overlay); @@ -222,7 +221,7 @@ class EditorTextSelectionOverlay { void showToolbar() { assert(toolbar == null); toolbar = OverlayEntry(builder: _buildToolbar); - Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)! + Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor) .insert(toolbar!); _toolbarController.forward(from: 0); @@ -407,7 +406,7 @@ class EditorTextSelectionOverlay { _buildHandle(context, _TextSelectionHandlePosition.END)), ]; - Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)! + Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor) .insertAll(_handles!); } diff --git a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart index 52098099..1c6917a1 100644 --- a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart +++ b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart @@ -120,7 +120,6 @@ class _ArrowIndicatedButtonListState extends State /// ScrollBehavior without the Material glow effect. class _NoGlowBehavior extends ScrollBehavior { - @override Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) { return child; } diff --git a/lib/src/widgets/toolbar/quill_font_family_button.dart b/lib/src/widgets/toolbar/quill_font_family_button.dart index 065daf7d..88b3dda4 100644 --- a/lib/src/widgets/toolbar/quill_font_family_button.dart +++ b/lib/src/widgets/toolbar/quill_font_family_button.dart @@ -109,8 +109,7 @@ class _QuillFontFamilyButtonState extends State { void _showMenu() { final popupMenuTheme = PopupMenuTheme.of(context); final button = context.findRenderObject() as RenderBox; - final overlay = - Overlay.of(context)!.context.findRenderObject() as RenderBox; + final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final position = RelativeRect.fromRect( Rect.fromPoints( button.localToGlobal(Offset.zero, ancestor: overlay), diff --git a/lib/src/widgets/toolbar/quill_font_size_button.dart b/lib/src/widgets/toolbar/quill_font_size_button.dart index fc99618c..da6fa187 100644 --- a/lib/src/widgets/toolbar/quill_font_size_button.dart +++ b/lib/src/widgets/toolbar/quill_font_size_button.dart @@ -110,8 +110,7 @@ class _QuillFontSizeButtonState extends State { void _showMenu() { final popupMenuTheme = PopupMenuTheme.of(context); final button = context.findRenderObject() as RenderBox; - final overlay = - Overlay.of(context)!.context.findRenderObject() as RenderBox; + final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; final position = RelativeRect.fromRect( Rect.fromPoints( button.localToGlobal(Offset.zero, ancestor: overlay), diff --git a/pubspec.yaml b/pubspec.yaml index 51686145..466d5486 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.2.2 +version: 6.3.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 68f6ce30b767c74c5393b554a50ea4d57f68b6d0 Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 24 Jan 2023 20:56:01 -0800 Subject: [PATCH 063/204] Replace theme.toggleableActiveColor --- example/ios/Runner.xcodeproj/project.pbxproj | 4 +++- example/ios/Runner/Info.plist | 2 ++ lib/src/widgets/toolbar/link_style_button.dart | 2 +- lib/src/widgets/toolbar/select_alignment_button.dart | 2 +- lib/src/widgets/toolbar/select_header_style_button.dart | 2 +- lib/src/widgets/toolbar/toggle_style_button.dart | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 1a971747..3d83c0c2 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -210,6 +210,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -241,6 +242,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 55f5c308..b8fc1f1d 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -45,5 +45,7 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + diff --git a/lib/src/widgets/toolbar/link_style_button.dart b/lib/src/widgets/toolbar/link_style_button.dart index cd97fa98..fde466e4 100644 --- a/lib/src/widgets/toolbar/link_style_button.dart +++ b/lib/src/widgets/toolbar/link_style_button.dart @@ -77,7 +77,7 @@ class _LinkStyleButtonState extends State { ), fillColor: isToggled ? (widget.iconTheme?.iconSelectedFillColor ?? - theme.toggleableActiveColor) + Theme.of(context).primaryColor) : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor), borderRadius: widget.iconTheme?.borderRadius ?? 2, onPressed: pressedHandler, diff --git a/lib/src/widgets/toolbar/select_alignment_button.dart b/lib/src/widgets/toolbar/select_alignment_button.dart index c6fb32b3..596b095d 100644 --- a/lib/src/widgets/toolbar/select_alignment_button.dart +++ b/lib/src/widgets/toolbar/select_alignment_button.dart @@ -103,7 +103,7 @@ class _SelectAlignmentButtonState extends State { widget.iconTheme?.borderRadius ?? 2)), fillColor: _valueToText[_value] == _valueString[index] ? (widget.iconTheme?.iconSelectedFillColor ?? - theme.toggleableActiveColor) + Theme.of(context).primaryColor) : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor), onPressed: () { diff --git a/lib/src/widgets/toolbar/select_header_style_button.dart b/lib/src/widgets/toolbar/select_header_style_button.dart index ee53b681..72192398 100644 --- a/lib/src/widgets/toolbar/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/select_header_style_button.dart @@ -89,7 +89,7 @@ class _SelectHeaderStyleButtonState extends State { widget.iconTheme?.borderRadius ?? 2)), fillColor: isSelected ? (widget.iconTheme?.iconSelectedFillColor ?? - theme.toggleableActiveColor) + Theme.of(context).primaryColor) : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor), onPressed: () { diff --git a/lib/src/widgets/toolbar/toggle_style_button.dart b/lib/src/widgets/toolbar/toggle_style_button.dart index b1080a66..176b96b0 100644 --- a/lib/src/widgets/toolbar/toggle_style_button.dart +++ b/lib/src/widgets/toolbar/toggle_style_button.dart @@ -139,7 +139,7 @@ Widget defaultToggleStyleButtonBuilder( final fill = isEnabled ? isToggled == true ? (iconTheme?.iconSelectedFillColor ?? - theme.toggleableActiveColor) //Selected icon fill color + Theme.of(context).primaryColor) //Selected icon fill color : (iconTheme?.iconUnselectedFillColor ?? theme.canvasColor) //Unselected icon fill color : : (iconTheme?.disabledIconFillColor ?? From 8e2043fcc242371827e9907e19d933d2bf94387e Mon Sep 17 00:00:00 2001 From: Rajat Sharma Date: Fri, 27 Jan 2023 01:15:29 +0100 Subject: [PATCH 064/204] Update toolbar.dart (#1070) Add color property to the basic factory function --- lib/src/widgets/toolbar.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 188af5f6..ec7821ce 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -116,6 +116,10 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// The locale to use for the editor toolbar, defaults to system locale /// More at https://github.com/singerdmx/flutter-quill#translation Locale? locale, + + /// The color of the toolbar + Color? color, + Key? key, }) { final isButtonGroupShown = [ @@ -167,6 +171,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { return QuillToolbar( key: key, + color: color, toolbarHeight: toolbarIconSize * 2, toolbarSectionSpacing: toolbarSectionSpacing, toolbarIconAlignment: toolbarIconAlignment, From 61a4c6cc35ef678a812e7433b4a803e9ec7637bd Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 26 Jan 2023 16:19:01 -0800 Subject: [PATCH 065/204] Upgrade to 6.3.1 --- CHANGELOG.md | 3 +++ lib/src/widgets/toolbar.dart | 3 +-- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c909d3f..0df24919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.3.1] +* Add color property to the basic factory function. + # [6.3.0] * Support Flutter 3.7. diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index ec7821ce..5ea2ad18 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -116,10 +116,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// The locale to use for the editor toolbar, defaults to system locale /// More at https://github.com/singerdmx/flutter-quill#translation Locale? locale, - + /// The color of the toolbar Color? color, - Key? key, }) { final isButtonGroupShown = [ diff --git a/pubspec.yaml b/pubspec.yaml index 466d5486..68f4cdfd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.3.0 +version: 6.3.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 0e5556424d96fdd8d7a8fc9a3efee3180f1a4aba Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Tue, 31 Jan 2023 22:09:54 +0200 Subject: [PATCH 066/204] Added `unknownEmbedBuilder` to QuillEditor (#1073) * Added `unknownEmbedBuilder` to QuillEditor * `unknownEmbedBuilder` constructor parameter --- lib/src/widgets/editor.dart | 39 ++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index ec835d05..7dae5136 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -173,6 +173,7 @@ class QuillEditor extends StatefulWidget { this.onSingleLongTapMoveUpdate, this.onSingleLongTapEnd, this.embedBuilders, + this.unknownEmbedBuilder, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.customStyleBuilder, this.locale, @@ -359,6 +360,7 @@ class QuillEditor extends StatefulWidget { onSingleLongTapEnd; final Iterable? embedBuilders; + final EmbedsBuilder? unknownEmbedBuilder; final CustomStyleBuilder? customStyleBuilder; /// The locale to use for the editor toolbar, defaults to system locale @@ -491,7 +493,13 @@ class QuillEditorState extends State node, readOnly, ) => - _buildCustomBlockEmbed(node, context, controller, readOnly), + _buildCustomBlockEmbed( + node, + context, + controller, + readOnly, + widget.unknownEmbedBuilder, + ), linkActionPickerDelegate: widget.linkActionPickerDelegate, customStyleBuilder: widget.customStyleBuilder, floatingCursorDisabled: widget.floatingCursorDisabled, @@ -525,29 +533,38 @@ class QuillEditorState extends State return editor; } - Widget _buildCustomBlockEmbed(Embed node, BuildContext context, - QuillController controller, bool readOnly) { + Widget _buildCustomBlockEmbed( + Embed node, + BuildContext context, + QuillController controller, + bool readOnly, + EmbedsBuilder? unknownEmbedBuilder, + ) { final builders = widget.embedBuilders; + + var _node = node; + // Creates correct node for custom embed + if (node.value.type == BlockEmbed.customType) { + _node = Embed(CustomBlockEmbed.fromJsonString(node.value.data)); + } if (builders != null) { - var _node = node; - - // Creates correct node for custom embed - if (node.value.type == BlockEmbed.customType) { - _node = Embed(CustomBlockEmbed.fromJsonString(node.value.data)); - } - for (final builder in builders) { if (builder.key == _node.value.type) { return builder.build(context, controller, _node, readOnly); } } } + + if (unknownEmbedBuilder != null) { + return unknownEmbedBuilder(context, controller, _node, readOnly); + } throw UnimplementedError( 'Embeddable type "${node.value.type}" is not supported by supplied ' 'embed builders. You must pass your own builder function to ' - 'embedBuilders property of QuillEditor or QuillField widgets.', + 'embedBuilders property of QuillEditor or QuillField widgets or ' + 'specify an unknownEmbedBuilder.', ); } From 13f0a2c702896ae534d82cd7e72eaa95c0620e48 Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Wed, 1 Feb 2023 21:07:13 +0800 Subject: [PATCH 067/204] Alert (#1076) --- README.md | 6 +++--- doc_cn.md | 24 ++++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b5f5e1dd..5ec17c82 100644 --- a/README.md +++ b/README.md @@ -383,10 +383,10 @@ The translation file is located at [toolbar.i18n.dart](lib/src/translations/tool Having your document stored in Quill Delta format is sometimes not enough. Often you'll need to convert it to other formats such as HTML in order to publish it, or send an email. One option is to use [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document -to HTML. This package has full support for all Quill operations - including images, videos, formulas, -tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter. +to HTML. This package has full support for all Quill operations - including images, videos, formulas, +tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter. It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) -Typescript/Javascript package. +Typescript/Javascript package. ## Sponsors diff --git a/doc_cn.md b/doc_cn.md index 870ce674..01375221 100644 --- a/doc_cn.md +++ b/doc_cn.md @@ -187,7 +187,7 @@ QuillToolbar.basic( 若需要图片、视频、公式块的支持,请查看独立库 [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions) -### 根据 `flutter_quill_extensions` 使用自定义嵌入块 +### 根据 `flutter_quill_extensions` 使用图片、视频、公式等自定义嵌入块 ```dart import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; @@ -355,7 +355,7 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -目前,可提供以下 24 种语言环境的翻译: +目前,可提供以下 25 种语言环境的翻译: * `Locale('en')` * `Locale('ar')` @@ -363,6 +363,7 @@ QuillEditor(locale: Locale('fr'), ...) * `Locale('de')` * `Locale('da')` * `Locale('fr')` +* `Locale('he')` * `Locale('zh', 'cn')` * `Locale('zh', 'hk')` * `Locale('ko')` @@ -384,20 +385,23 @@ QuillEditor(locale: Locale('fr'), ...) #### 贡献翻译 -翻译文件位于 [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart)。 +翻译文件位于 [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart) -随意贡献你自己的翻译,只需复制英文翻译映射并将值替换为你的翻译即可。 +随意贡献你自己的翻译,只需复制英文翻译映射并将值替换为你的翻译即可 然后打开一个拉取请求,这样每个人都可以从你的翻译中受益! ---- +### 转化至 HTML -## 帮助 +将你的文档转为 `Quill Delta` 格式有时还不够,通常你需要将其转化为其他如 `HTML` 格式来分发他,或作为邮件发出 - - +一个方案是使用 [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) `Flutter` 包来转化至 `HTML` 格式。此包支持所以的 `Quill` 操作,包含图片、视频、公式、表格和注释 + +转化过程可以在 `vanilla Dart` 如服务器端或 `CLI` 执行,也可在 `Flutter` 中执行 + +其是流行且成熟的 [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) `Typescript/Javascript` 包的 `Dart` 部分 + +--- ## 赞助 From 11f65f9872f1e35aa05565726fd102225d95758e Mon Sep 17 00:00:00 2001 From: Anti-Core <71420694+1252158112@users.noreply.github.com> Date: Wed, 1 Feb 2023 23:41:15 +0800 Subject: [PATCH 068/204] fix error style when input chinese japanese or korean (#1078) --- lib/src/models/rules/insert.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index ef77e77a..949371b5 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -477,7 +477,7 @@ class PreserveInlineStylesRule extends InsertRule { } final itr = DeltaIterator(document); - final prev = itr.skip(index); + final prev = itr.skip(len == 0 ? index : index + 1); if (prev == null || prev.data is! String || (prev.data as String).contains('\n')) { From d8b03675b5e744e52f3cc72127cd3deee6472189 Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 1 Feb 2023 08:04:42 -0800 Subject: [PATCH 069/204] Upgrade to 6.3.2 --- CHANGELOG.md | 4 ++++ lib/src/widgets/editor.dart | 16 ++++++++-------- pubspec.yaml | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df24919..a7a5bd44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [6.3.2] +* Added `unknownEmbedBuilder` to QuillEditor. +* Fix error style when input chinese japanese or korean. + # [6.3.1] * Add color property to the basic factory function. diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 7dae5136..4160e0bb 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -494,12 +494,12 @@ class QuillEditorState extends State readOnly, ) => _buildCustomBlockEmbed( - node, - context, - controller, - readOnly, - widget.unknownEmbedBuilder, - ), + node, + context, + controller, + readOnly, + widget.unknownEmbedBuilder, + ), linkActionPickerDelegate: widget.linkActionPickerDelegate, customStyleBuilder: widget.customStyleBuilder, floatingCursorDisabled: widget.floatingCursorDisabled, @@ -541,7 +541,7 @@ class QuillEditorState extends State EmbedsBuilder? unknownEmbedBuilder, ) { final builders = widget.embedBuilders; - + var _node = node; // Creates correct node for custom embed if (node.value.type == BlockEmbed.customType) { @@ -555,7 +555,7 @@ class QuillEditorState extends State } } } - + if (unknownEmbedBuilder != null) { return unknownEmbedBuilder(context, controller, _node, readOnly); } diff --git a/pubspec.yaml b/pubspec.yaml index 68f4cdfd..cd284606 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.3.1 +version: 6.3.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 74544bd945a9d212ca1e8d6b3053dbecee22b720 Mon Sep 17 00:00:00 2001 From: Rajender Katkuri <76048287+rajenderK7@users.noreply.github.com> Date: Wed, 1 Feb 2023 22:13:03 +0530 Subject: [PATCH 070/204] Remove transparent color of ImageVideoUtils dialog (#1079) --- .../lib/embeds/toolbar/image_video_utils.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart b/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart index a72f1996..aef7c9e7 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart @@ -80,7 +80,6 @@ class ImageVideoUtils { context: context, builder: (ctx) => AlertDialog( contentPadding: EdgeInsets.zero, - backgroundColor: Colors.transparent, content: Column( mainAxisSize: MainAxisSize.min, children: [ From 0046ca6782dd6f9da95b724ecfc4c499f8cad119 Mon Sep 17 00:00:00 2001 From: Htoo Pyae Linn <85918337+htoopyaelinn56@users.noreply.github.com> Date: Mon, 6 Feb 2023 19:48:56 +0630 Subject: [PATCH 071/204] Small doc change to avoid json parse error (#1086) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ec17c82..b31a8270 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ You can then write this to storage. To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this: ```dart -var myJSON = jsonDecode(incomingJSONText); +var myJSON = jsonDecode(r'{"insert":"hello\n"}'); _controller = QuillController( document: Document.fromJson(myJSON), selection: TextSelection.collapsed(offset: 0), From e01449c9ebabddc3eb8d55afed03171e22f592ac Mon Sep 17 00:00:00 2001 From: Eric Martineau Date: Tue, 7 Feb 2023 14:40:22 -0700 Subject: [PATCH 072/204] fix: Fixed handling of mac intents (#1089) --- lib/src/widgets/raw_editor.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 1cd69e22..e5836a14 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1431,7 +1431,14 @@ class RawEditorState extends EditorState @override void performSelector(String selectorName) { - // TODO: implement performSelector + final intent = intentForMacOSSelector(selectorName); + + if (intent != null) { + final primaryContext = primaryFocus?.context; + if (primaryContext != null) { + Actions.invoke(primaryContext, intent); + } + } } } From 32a69741378acaacdbfc1f39b6e2f47815e100ae Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 7 Feb 2023 13:49:39 -0800 Subject: [PATCH 073/204] Upgrade to 6.3.3 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7a5bd44..7c86e5f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.3.3] +* Fixed handling of mac intents. + # [6.3.2] * Added `unknownEmbedBuilder` to QuillEditor. * Fix error style when input chinese japanese or korean. diff --git a/pubspec.yaml b/pubspec.yaml index cd284606..5e1787b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.3.2 +version: 6.3.3 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 171d0b35243d5c1009fba6cdfe4ac5d3da994f2d Mon Sep 17 00:00:00 2001 From: Muhammad Faiz Date: Fri, 10 Feb 2023 22:38:55 +0800 Subject: [PATCH 074/204] Update toolbar.i18n.dart (#1094) --- lib/src/translations/toolbar.i18n.dart | 35 +++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index a5c1c651..724c0add 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -898,7 +898,40 @@ extension Localization on String { 'Next': 'הבא', 'Camera': 'מצלמה', 'Video': 'וידאו', - } + }, + 'ms': { + 'Paste a link': 'Tampal Pautan', + 'Ok': 'Ok', + 'Select Color': 'Pilih Warna', + 'Gallery': 'Galeri', + 'Link': 'Pautan', + 'Please first select some text to transform into a link.': + 'Sila pilih beberapa patah perkataan untuk diubah menjadi pautan.', + 'Open': 'Buka', + 'Copy': 'Salin', + 'Remove': 'Buang', + 'Save': 'Simpan', + 'Zoom': 'Zum', + 'Saved': 'Telah Disimpan', + 'Text': 'Perkataan', + 'What is entered is not a link': 'Apa yang diisi bukan pautan', + 'Resize': 'Ubah saiz', + 'Width': 'Lebar', + 'Height': 'Tinggi', + 'Size': 'Saiz', + 'Small': 'Kecil', + 'Large': 'Besar', + 'Huge': 'Amat Besar', + 'Clear': 'Padam', + 'Font': 'Fon', + 'Search': 'Carian', + 'matches': 'padanan', + 'showing match': 'menunjukkan padanan', + 'Prev': 'Sebelum', + 'Next': 'Seterusnya', + 'Camera': 'Kamera', + 'Video': 'Video', + }, }; String get i18n => localize(this, _t); From 852c8b7f189e8e29ddcee84ea6132a583b25a2be Mon Sep 17 00:00:00 2001 From: X Code Date: Fri, 10 Feb 2023 19:50:41 -0800 Subject: [PATCH 075/204] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b31a8270..bd809b78 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -Currently, translations are available for these 25 locales: +Currently, translations are available for these 26 locales: * `Locale('en')` * `Locale('ar')` @@ -367,6 +367,7 @@ Currently, translations are available for these 25 locales: * `Locale('pl')` * `Locale('vi')` * `Locale('id')` +* `Locale('ms')` * `Locale('nl')` * `Locale('no')` * `Locale('fa')` From 9da4f6330d7074838deded79a80de9e89155ab7f Mon Sep 17 00:00:00 2001 From: Michael Allen Date: Sat, 11 Feb 2023 12:34:16 -0800 Subject: [PATCH 076/204] update clipboard status prior to showing selected text overlay (#1096) --- lib/src/widgets/text_selection.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart index dd6d6c18..74bcf55f 100644 --- a/lib/src/widgets/text_selection.dart +++ b/lib/src/widgets/text_selection.dart @@ -83,6 +83,11 @@ class EditorTextSelectionOverlay { }) { final overlay = Overlay.of(context, rootOverlay: true); + // Clipboard status is only checked on first instance of ClipboardStatusNotifier + // if state has changed after creation, but prior to our listener being created + // we won't know the status unless there is forced update i.e. occasionally no paste + this.clipboardStatus.update(); + _toolbarController = AnimationController( duration: const Duration(milliseconds: 150), vsync: overlay); } From 85fd04a6d72431d828b7de8ab089d8b4d86f638b Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 11 Feb 2023 12:38:40 -0800 Subject: [PATCH 077/204] Upgrade to 6.3.4 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c86e5f9..1501ec48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.3.4] +* Update clipboard status prior to showing selected text overlay. + # [6.3.3] * Fixed handling of mac intents. diff --git a/pubspec.yaml b/pubspec.yaml index 5e1787b9..43548a05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.3.3 +version: 6.3.4 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 50f0b9961829c5a3da12f2ccdeebc202eb5c827b Mon Sep 17 00:00:00 2001 From: veselv2010 <32619716+veselv2010@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:48:56 +0300 Subject: [PATCH 078/204] feat: Ability to add custom shortcuts (#1097) --- lib/src/widgets/editor.dart | 7 +++++++ lib/src/widgets/raw_editor.dart | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 4160e0bb..13f5ee78 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -180,6 +180,8 @@ class QuillEditor extends StatefulWidget { this.floatingCursorDisabled = false, this.textSelectionControls, this.onImagePaste, + this.customShortcuts, + this.customActions, Key? key}) : super(key: key); @@ -394,6 +396,9 @@ class QuillEditor extends StatefulWidget { /// Returns the url of the image if the image should be inserted. final Future Function(Uint8List imageBytes)? onImagePaste; + final Map? customShortcuts; + final Map>? customActions; + @override QuillEditorState createState() => QuillEditorState(); } @@ -504,6 +509,8 @@ class QuillEditorState extends State customStyleBuilder: widget.customStyleBuilder, floatingCursorDisabled: widget.floatingCursorDisabled, onImagePaste: widget.onImagePaste, + customShortcuts: widget.customShortcuts, + customActions: widget.customActions, ); final editor = I18n( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index e5836a14..b42c76bc 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -70,6 +70,8 @@ class RawEditor extends StatefulWidget { this.minHeight, this.maxContentWidth, this.customStyles, + this.customShortcuts, + this.customActions, this.expands = false, this.autoFocus = false, this.keyboardAppearance = Brightness.light, @@ -227,6 +229,9 @@ class RawEditor extends StatefulWidget { final Future Function(Uint8List imageBytes)? onImagePaste; + final Map? customShortcuts; + final Map>? customActions; + /// Builder function for embeddable objects. final EmbedsBuilder embedBuilder; final LinkActionPickerDelegate linkActionPickerDelegate; @@ -428,9 +433,14 @@ class RawEditorState extends EditorState LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.keyL): const ApplyCheckListIntent(), + + if (widget.customShortcuts != null) ...widget.customShortcuts!, }, child: Actions( - actions: _actions, + actions: { + ..._actions, + if (widget.customActions != null) ...widget.customActions!, + }, child: Focus( focusNode: widget.focusNode, onKey: _onKey, From 97fd5d2ecafc08b52213f6543259d46cc9a6d794 Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 13 Feb 2023 08:07:40 -0800 Subject: [PATCH 079/204] Upgrade to 6.3.5 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1501ec48..439304c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.3.5] +* Ability to add custom shortcuts. + # [6.3.4] * Update clipboard status prior to showing selected text overlay. diff --git a/pubspec.yaml b/pubspec.yaml index 43548a05..1dc93bef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.3.4 +version: 6.3.5 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 76ea6c0e7ed694e63e41d7520c6e0e1ebdb5d7be Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 13 Feb 2023 08:10:08 -0800 Subject: [PATCH 080/204] Fix analysis error --- lib/src/translations/toolbar.i18n.dart | 3 ++- lib/src/widgets/text_selection.dart | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 724c0add..fa5ea0f4 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -906,7 +906,8 @@ extension Localization on String { 'Gallery': 'Galeri', 'Link': 'Pautan', 'Please first select some text to transform into a link.': - 'Sila pilih beberapa patah perkataan untuk diubah menjadi pautan.', + 'Sila pilih beberapa patah perkataan' + ' untuk diubah menjadi pautan.', 'Open': 'Buka', 'Copy': 'Salin', 'Remove': 'Buang', diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart index 74bcf55f..811d3fe6 100644 --- a/lib/src/widgets/text_selection.dart +++ b/lib/src/widgets/text_selection.dart @@ -83,10 +83,13 @@ class EditorTextSelectionOverlay { }) { final overlay = Overlay.of(context, rootOverlay: true); - // Clipboard status is only checked on first instance of ClipboardStatusNotifier - // if state has changed after creation, but prior to our listener being created - // we won't know the status unless there is forced update i.e. occasionally no paste - this.clipboardStatus.update(); + // Clipboard status is only checked on first instance of + // ClipboardStatusNotifier + // if state has changed after creation, but prior to + // our listener being created + // we won't know the status unless there is forced update + // i.e. occasionally no paste + clipboardStatus.update(); _toolbarController = AnimationController( duration: const Duration(milliseconds: 150), vsync: overlay); From e81ac15976bc61fa9cbd745c54ef1447cb3e5856 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Thu, 16 Feb 2023 04:52:24 +0000 Subject: [PATCH 081/204] Vertical toolbar (#1101) --- CHANGELOG.md | 5 + lib/src/widgets/toolbar.dart | 84 +++++++++------- .../toolbar/arrow_indicated_button_list.dart | 80 ++++++++++----- .../toolbar/select_header_style_button.dart | 97 ++++++++++--------- pubspec.yaml | 2 +- 5 files changed, 163 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 439304c0..17eed562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# [6.4.0] +* Use `axis` to make the toolbar vertical. +* Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis. +* Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`. + # [6.3.5] * Ability to add custom shortcuts. diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 5ea2ad18..390317c2 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -45,8 +45,10 @@ const double kIconButtonFactor = 1.77; class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { const QuillToolbar({ required this.children, - this.toolbarHeight = 36, + this.axis = Axis.horizontal, + this.toolbarSize = 36, this.toolbarIconAlignment = WrapAlignment.center, + this.toolbarIconCrossAlignment = WrapCrossAlignment.center, this.toolbarSectionSpacing = 4, this.multiRowsDisplay = true, this.color, @@ -58,9 +60,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { factory QuillToolbar.basic({ required QuillController controller, + Axis axis = Axis.horizontal, double toolbarIconSize = kDefaultIconSize, double toolbarSectionSpacing = 4, WrapAlignment toolbarIconAlignment = WrapAlignment.center, + WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center, bool showDividers = true, bool showFontFamily = true, bool showFontSize = true, @@ -170,10 +174,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { return QuillToolbar( key: key, + axis: axis, color: color, - toolbarHeight: toolbarIconSize * 2, + toolbarSize: toolbarIconSize * 2, toolbarSectionSpacing: toolbarSectionSpacing, toolbarIconAlignment: toolbarIconAlignment, + toolbarIconCrossAlignment: toolbarIconCrossAlignment, multiRowsDisplay: multiRowsDisplay, customButtons: customButtons, locale: locale, @@ -334,11 +340,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showAlignmentButtons) SelectAlignmentButton( controller: controller, @@ -365,14 +367,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showHeaderStyle) SelectHeaderStyleButton( controller: controller, + axis: axis, iconSize: toolbarIconSize, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -383,11 +382,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showListNumbers) ToggleStyleButton( attribute: Attribute.ol, @@ -427,11 +422,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDividers && isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5])) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showQuote) ToggleStyleButton( attribute: Attribute.blockQuote, @@ -460,11 +451,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { afterButtonPressed: afterButtonPressed, ), if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), if (showLink) LinkStyleButton( controller: controller, @@ -484,11 +471,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ), if (customButtons.isNotEmpty) if (showDividers) - VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey.shade400, - ), + _dividerOnAxis(axis), for (var customButton in customButtons) QuillIconButton( highlightElevation: 0, @@ -503,10 +486,28 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ); } + static Widget _dividerOnAxis(Axis axis) { + if (axis == Axis.horizontal) { + return const VerticalDivider( + indent: 12, + endIndent: 12, + color: Colors.grey, + ); + } else { + return const Divider( + indent: 12, + endIndent: 12, + color: Colors.grey, + ); + } + } + final List children; - final double toolbarHeight; + final Axis axis; + final double toolbarSize; final double toolbarSectionSpacing; final WrapAlignment toolbarIconAlignment; + final WrapCrossAlignment toolbarIconCrossAlignment; final bool multiRowsDisplay; /// The color of the toolbar. @@ -523,7 +524,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { final List customButtons; @override - Size get preferredSize => Size.fromHeight(toolbarHeight); + Size get preferredSize => axis == Axis.horizontal + ? Size.fromHeight(toolbarSize) + : Size.fromWidth(toolbarSize); @override Widget build(BuildContext context) { @@ -531,16 +534,23 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { initialLocale: locale, child: multiRowsDisplay ? Wrap( + direction: axis, alignment: toolbarIconAlignment, + crossAxisAlignment: toolbarIconCrossAlignment, runSpacing: 4, spacing: toolbarSectionSpacing, children: children, ) : Container( - constraints: - BoxConstraints.tightFor(height: preferredSize.height), + constraints: BoxConstraints.tightFor( + height: axis == Axis.horizontal ? toolbarSize : null, + width: axis == Axis.vertical ? toolbarSize : null, + ), color: color ?? Theme.of(context).canvasColor, - child: ArrowIndicatedButtonList(buttons: children), + child: ArrowIndicatedButtonList( + axis: axis, + buttons: children, + ), ), ); } diff --git a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart index 1c6917a1..94e500ff 100644 --- a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart +++ b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart @@ -7,9 +7,13 @@ import 'package:flutter/material.dart'; /// The arrow indicators are automatically hidden if the list is not /// scrollable in the direction of the respective arrow. class ArrowIndicatedButtonList extends StatefulWidget { - const ArrowIndicatedButtonList({required this.buttons, Key? key}) - : super(key: key); + const ArrowIndicatedButtonList({ + required this.axis, + required this.buttons, + Key? key, + }) : super(key: key); + final Axis axis; final List buttons; @override @@ -20,8 +24,8 @@ class ArrowIndicatedButtonList extends StatefulWidget { class _ArrowIndicatedButtonListState extends State with WidgetsBindingObserver { final ScrollController _controller = ScrollController(); - bool _showLeftArrow = false; - bool _showRightArrow = false; + bool _showBackwardArrow = false; + bool _showForwardArrow = false; @override void initState() { @@ -40,13 +44,19 @@ class _ArrowIndicatedButtonListState extends State @override Widget build(BuildContext context) { - return Row( - children: [ - _buildLeftArrow(), - _buildScrollableList(), - _buildRightColor(), - ], - ); + final children = [ + _buildBackwardArrow(), + _buildScrollableList(), + _buildForwardArrow(), + ]; + + return widget.axis == Axis.horizontal + ? Row( + children: children, + ) + : Column( + children: children, + ); } @override @@ -63,20 +73,29 @@ class _ArrowIndicatedButtonListState extends State if (!mounted) return; setState(() { - _showLeftArrow = + _showBackwardArrow = _controller.position.minScrollExtent != _controller.position.pixels; - _showRightArrow = + _showForwardArrow = _controller.position.maxScrollExtent != _controller.position.pixels; }); } - Widget _buildLeftArrow() { + Widget _buildBackwardArrow() { + IconData? icon; + if (_showBackwardArrow) { + if (widget.axis == Axis.horizontal) { + icon = Icons.arrow_left; + } else { + icon = Icons.arrow_drop_up; + } + } + return SizedBox( width: 8, child: Transform.translate( // Move the icon a few pixels to center it offset: const Offset(-5, 0), - child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null, + child: icon != null ? Icon(icon, size: 18) : null, ), ); } @@ -87,18 +106,24 @@ class _ArrowIndicatedButtonListState extends State // Remove the glowing effect, as we already have the arrow indicators behavior: _NoGlowBehavior(), // The CustomScrollView is necessary so that the children are not - // stretched to the height of the toolbar, https://bit.ly/3uC3bjI + // stretched to the height of the toolbar: + // https://stackoverflow.com/a/65998731/7091839 child: CustomScrollView( - scrollDirection: Axis.horizontal, + scrollDirection: widget.axis, controller: _controller, physics: const ClampingScrollPhysics(), slivers: [ SliverFillRemaining( hasScrollBody: false, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: widget.buttons, - ), + child: widget.axis == Axis.horizontal + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: widget.buttons, + ) + : Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: widget.buttons, + ), ) ], ), @@ -106,13 +131,22 @@ class _ArrowIndicatedButtonListState extends State ); } - Widget _buildRightColor() { + Widget _buildForwardArrow() { + IconData? icon; + if (_showForwardArrow) { + if (widget.axis == Axis.horizontal) { + icon = Icons.arrow_right; + } else { + icon = Icons.arrow_drop_down; + } + } + return SizedBox( width: 8, child: Transform.translate( // Move the icon a few pixels to center it offset: const Offset(-5, 0), - child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null, + child: icon != null ? Icon(icon, size: 18) : null, ), ); } diff --git a/lib/src/widgets/toolbar/select_header_style_button.dart b/lib/src/widgets/toolbar/select_header_style_button.dart index 72192398..5e1dee9f 100644 --- a/lib/src/widgets/toolbar/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/select_header_style_button.dart @@ -10,6 +10,7 @@ import '../toolbar.dart'; class SelectHeaderStyleButton extends StatefulWidget { const SelectHeaderStyleButton({ required this.controller, + this.axis = Axis.horizontal, this.iconSize = kDefaultIconSize, this.iconTheme, this.attributes = const [ @@ -23,6 +24,7 @@ class SelectHeaderStyleButton extends StatefulWidget { }) : super(key: key); final QuillController controller; + final Axis axis; final double iconSize; final QuillIconTheme? iconTheme; final List attributes; @@ -67,53 +69,60 @@ class _SelectHeaderStyleButtonState extends State { fontSize: widget.iconSize * 0.7, ); - return Row( - mainAxisSize: MainAxisSize.min, - children: widget.attributes.map((attribute) { - final isSelected = _selectedAttribute == attribute; - return Padding( - // ignore: prefer_const_constructors - padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), - child: ConstrainedBox( - constraints: BoxConstraints.tightFor( - width: widget.iconSize * kIconButtonFactor, - height: widget.iconSize * kIconButtonFactor, - ), - child: RawMaterialButton( - hoverElevation: 0, - highlightElevation: 0, - elevation: 0, - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - widget.iconTheme?.borderRadius ?? 2)), - fillColor: isSelected - ? (widget.iconTheme?.iconSelectedFillColor ?? - Theme.of(context).primaryColor) - : (widget.iconTheme?.iconUnselectedFillColor ?? - theme.canvasColor), - onPressed: () { - final _attribute = _selectedAttribute == attribute - ? Attribute.header - : attribute; - widget.controller.formatSelection(_attribute); - widget.afterButtonPressed?.call(); - }, - child: Text( - _valueToText[attribute] ?? '', - style: style.copyWith( - color: isSelected - ? (widget.iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) - : (widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), - ), + final children = widget.attributes.map((attribute) { + final isSelected = _selectedAttribute == attribute; + return Padding( + // ignore: prefer_const_constructors + padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), + child: ConstrainedBox( + constraints: BoxConstraints.tightFor( + width: widget.iconSize * kIconButtonFactor, + height: widget.iconSize * kIconButtonFactor, + ), + child: RawMaterialButton( + hoverElevation: 0, + highlightElevation: 0, + elevation: 0, + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + widget.iconTheme?.borderRadius ?? 2)), + fillColor: isSelected + ? (widget.iconTheme?.iconSelectedFillColor ?? + Theme.of(context).primaryColor) + : (widget.iconTheme?.iconUnselectedFillColor ?? + theme.canvasColor), + onPressed: () { + final _attribute = _selectedAttribute == attribute + ? Attribute.header + : attribute; + widget.controller.formatSelection(_attribute); + widget.afterButtonPressed?.call(); + }, + child: Text( + _valueToText[attribute] ?? '', + style: style.copyWith( + color: isSelected + ? (widget.iconTheme?.iconSelectedColor ?? + theme.primaryIconTheme.color) + : (widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color), ), ), ), - ); - }).toList(), - ); + ), + ); + }).toList(); + + return widget.axis == Axis.horizontal + ? Row( + mainAxisSize: MainAxisSize.min, + children: children, + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: children, + ); } void _didChangeEditingValue() { diff --git a/pubspec.yaml b/pubspec.yaml index 1dc93bef..951881cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.3.5 +version: 6.4.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From fdbebef79bff4f722583ca6593df27682f2e237e Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 15 Feb 2023 20:55:17 -0800 Subject: [PATCH 082/204] Format code --- lib/src/widgets/toolbar.dart | 3 +-- .../toolbar/arrow_indicated_button_list.dart | 12 ++++++------ .../toolbar/select_header_style_button.dart | 14 +++++++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 390317c2..58171ad1 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -470,8 +470,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { afterButtonPressed: afterButtonPressed, ), if (customButtons.isNotEmpty) - if (showDividers) - _dividerOnAxis(axis), + if (showDividers) _dividerOnAxis(axis), for (var customButton in customButtons) QuillIconButton( highlightElevation: 0, diff --git a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart index 94e500ff..84f19854 100644 --- a/lib/src/widgets/toolbar/arrow_indicated_button_list.dart +++ b/lib/src/widgets/toolbar/arrow_indicated_button_list.dart @@ -51,12 +51,12 @@ class _ArrowIndicatedButtonListState extends State ]; return widget.axis == Axis.horizontal - ? Row( - children: children, - ) - : Column( - children: children, - ); + ? Row( + children: children, + ) + : Column( + children: children, + ); } @override diff --git a/lib/src/widgets/toolbar/select_header_style_button.dart b/lib/src/widgets/toolbar/select_header_style_button.dart index 5e1dee9f..f27998b8 100644 --- a/lib/src/widgets/toolbar/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/select_header_style_button.dart @@ -69,7 +69,7 @@ class _SelectHeaderStyleButtonState extends State { fontSize: widget.iconSize * 0.7, ); - final children = widget.attributes.map((attribute) { + final children = widget.attributes.map((attribute) { final isSelected = _selectedAttribute == attribute; return Padding( // ignore: prefer_const_constructors @@ -85,13 +85,13 @@ class _SelectHeaderStyleButtonState extends State { elevation: 0, visualDensity: VisualDensity.compact, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - widget.iconTheme?.borderRadius ?? 2)), + borderRadius: + BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), fillColor: isSelected ? (widget.iconTheme?.iconSelectedFillColor ?? - Theme.of(context).primaryColor) + Theme.of(context).primaryColor) : (widget.iconTheme?.iconUnselectedFillColor ?? - theme.canvasColor), + theme.canvasColor), onPressed: () { final _attribute = _selectedAttribute == attribute ? Attribute.header @@ -104,9 +104,9 @@ class _SelectHeaderStyleButtonState extends State { style: style.copyWith( color: isSelected ? (widget.iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) + theme.primaryIconTheme.color) : (widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), + theme.iconTheme.color), ), ), ), From c68c2bd030f6563ebcd39897d2934a13ebffacc5 Mon Sep 17 00:00:00 2001 From: sourabhguptazeil <108905944+sourabhguptazeil@users.noreply.github.com> Date: Thu, 16 Feb 2023 21:38:49 +1300 Subject: [PATCH 083/204] Added a property to control the detect word boundary behaviour (#1102) --- lib/src/widgets/delegate.dart | 9 +++++++-- lib/src/widgets/editor.dart | 23 +++++++++++++++++------ lib/src/widgets/text_selection.dart | 3 +++ 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index a75b64e7..a7ad9866 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -66,7 +66,9 @@ class EditorTextSelectionGestureDetectorBuilder { /// Creates a [EditorTextSelectionGestureDetectorBuilder]. /// /// The [delegate] must not be null. - EditorTextSelectionGestureDetectorBuilder({required this.delegate}); + EditorTextSelectionGestureDetectorBuilder({ + required this.delegate, + this.detectWordBoundary = true}); /// The delegate for this [EditorTextSelectionGestureDetectorBuilder]. /// @@ -83,6 +85,8 @@ class EditorTextSelectionGestureDetectorBuilder { /// a stylus. bool shouldShowSelectionToolbar = true; + bool detectWordBoundary = true; + /// The [State] of the [EditableText] for which the builder will provide a /// [EditorTextSelectionGestureDetector]. @protected @@ -354,7 +358,8 @@ class EditorTextSelectionGestureDetectorBuilder { onDragSelectionUpdate: onDragSelectionUpdate, onDragSelectionEnd: onDragSelectionEnd, behavior: behavior, - child: child, + detectWordBoundary: detectWordBoundary, + child: child ); } } diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 13f5ee78..b8d007d2 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -182,6 +182,7 @@ class QuillEditor extends StatefulWidget { this.onImagePaste, this.customShortcuts, this.customActions, + this.detectWordBoundary = true, Key? key}) : super(key: key); @@ -399,6 +400,8 @@ class QuillEditor extends StatefulWidget { final Map? customShortcuts; final Map>? customActions; + final bool detectWordBoundary; + @override QuillEditorState createState() => QuillEditorState(); } @@ -413,7 +416,8 @@ class QuillEditorState extends State void initState() { super.initState(); _selectionGestureDetectorBuilder = - _QuillEditorSelectionGestureDetectorBuilder(this); + _QuillEditorSelectionGestureDetectorBuilder(this, + widget.detectWordBoundary); } @override @@ -591,10 +595,11 @@ class QuillEditorState extends State class _QuillEditorSelectionGestureDetectorBuilder extends EditorTextSelectionGestureDetectorBuilder { - _QuillEditorSelectionGestureDetectorBuilder(this._state) - : super(delegate: _state); + _QuillEditorSelectionGestureDetectorBuilder(this._state, this._detectWordBoundary) + : super(delegate: _state, detectWordBoundary: _detectWordBoundary); final QuillEditorState _state; + final bool _detectWordBoundary; @override void onForcePressStart(ForcePressDetails details) { @@ -712,9 +717,15 @@ class _QuillEditorSelectionGestureDetectorBuilder case PointerDeviceKind.unknown: // On macOS/iOS/iPadOS a touch tap places the cursor at the edge // of the word. - renderEditor! - ..selectWordEdge(SelectionChangedCause.tap) - ..onSelectionCompleted(); + if (_detectWordBoundary) { + renderEditor! + ..selectWordEdge(SelectionChangedCause.tap) + ..onSelectionCompleted(); + } else { + renderEditor! + ..selectPosition(cause: SelectionChangedCause.tap) + ..onSelectionCompleted(); + } break; case PointerDeviceKind.trackpad: // TODO: Handle this case. diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart index 811d3fe6..7930d468 100644 --- a/lib/src/widgets/text_selection.dart +++ b/lib/src/widgets/text_selection.dart @@ -714,6 +714,7 @@ class EditorTextSelectionGestureDetector extends StatefulWidget { this.onDragSelectionUpdate, this.onDragSelectionEnd, this.behavior, + this.detectWordBoundary = true, Key? key, }) : super(key: key); @@ -789,6 +790,8 @@ class EditorTextSelectionGestureDetector extends StatefulWidget { /// Child below this widget. final Widget child; + final bool detectWordBoundary; + @override State createState() => _EditorTextSelectionGestureDetectorState(); From 83ad28bf7bddd46277e5cecb746b6827d7ca121c Mon Sep 17 00:00:00 2001 From: sourabhguptazeil <108905944+sourabhguptazeil@users.noreply.github.com> Date: Fri, 17 Feb 2023 03:11:06 +1300 Subject: [PATCH 084/204] Added a property to control the detect word boundary behaviour (#1103) --- lib/src/widgets/delegate.dart | 3 ++- lib/src/widgets/editor.dart | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index a7ad9866..1de60822 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -341,7 +341,8 @@ class EditorTextSelectionGestureDetectorBuilder { /// /// The [child] or its subtree should contain [EditableText]. Widget build( - {required HitTestBehavior behavior, required Widget child, Key? key}) { + {required HitTestBehavior behavior, required Widget child, Key? key, + bool detectWordBoundary = true}) { return EditorTextSelectionGestureDetector( key: key, onTapDown: onTapDown, diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index b8d007d2..fcf1e9d3 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -522,6 +522,7 @@ class QuillEditorState extends State child: selectionEnabled ? _selectionGestureDetectorBuilder.build( behavior: HitTestBehavior.translucent, + detectWordBoundary: widget.detectWordBoundary, child: child, ) : child, From efd09b535ae709a31e5728571aaef8c0f60740f6 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Thu, 16 Feb 2023 14:12:35 +0000 Subject: [PATCH 085/204] Add `contextMenuBuilder` to `RawEditor` (#1105) --- lib/src/widgets/editor.dart | 9 +- lib/src/widgets/raw_editor.dart | 110 ++++++++++++++++-- ...editor_state_selection_delegate_mixin.dart | 9 +- 3 files changed, 108 insertions(+), 20 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index fcf1e9d3..c65898f2 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -467,12 +467,9 @@ class QuillEditorState extends State readOnly: widget.readOnly, placeholder: widget.placeholder, onLaunchUrl: widget.onLaunchUrl, - toolbarOptions: ToolbarOptions( - copy: showSelectionToolbar, - cut: showSelectionToolbar, - paste: showSelectionToolbar, - selectAll: showSelectionToolbar, - ), + contextMenuBuilder: showSelectionToolbar + ? RawEditor.defaultContextMenuBuilder + : null, showSelectionHandles: isMobile(theme.platform), showCursor: widget.showCursor, cursorStyle: CursorStyle( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index b42c76bc..8c6c61af 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -57,12 +57,7 @@ class RawEditor extends StatefulWidget { this.readOnly = false, this.placeholder, this.onLaunchUrl, - this.toolbarOptions = const ToolbarOptions( - copy: true, - cut: true, - paste: true, - selectAll: true, - ), + this.contextMenuBuilder = defaultContextMenuBuilder, this.showSelectionHandles = false, bool? showCursor, this.textCapitalization = TextCapitalization.none, @@ -114,11 +109,24 @@ class RawEditor extends StatefulWidget { /// a link in the document. final ValueChanged? onLaunchUrl; - /// Configuration of toolbar options. + /// Builds the text selection toolbar when requested by the user. + /// + /// See also: + /// * [EditableText.contextMenuBuilder], which builds the default + /// text selection toolbar for [EditableText]. /// - /// By default, all options are enabled. If [readOnly] is true, - /// paste and cut will be disabled regardless. - final ToolbarOptions toolbarOptions; + /// If not provided, no context menu will be shown. + final QuillEditorContextMenuBuilder? contextMenuBuilder; + + static Widget defaultContextMenuBuilder( + BuildContext context, + RawEditorState state, + ) { + return AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: state.contextMenuButtonItems, + anchors: state.contextMenuAnchors, + ); + } /// Whether to show selection handles. /// @@ -293,6 +301,76 @@ class RawEditorState extends EditorState TextDirection get _textDirection => Directionality.of(context); + /// Returns the [ContextMenuButtonItem]s representing the buttons in this + /// platform's default selection menu for [RawEditor]. + /// + /// Copied from [EditableTextState]. + List get contextMenuButtonItems { + return EditableText.getEditableButtonItems( + clipboardStatus: _clipboardStatus.value, + onCopy: copyEnabled + ? () => copySelection(SelectionChangedCause.toolbar) + : null, + onCut: cutEnabled + ? () => cutSelection(SelectionChangedCause.toolbar) + : null, + onPaste: pasteEnabled + ? () => pasteText(SelectionChangedCause.toolbar) + : null, + onSelectAll: selectAllEnabled + ? () => selectAll(SelectionChangedCause.toolbar) + : null, + ); + } + + /// Returns the anchor points for the default context menu. + /// + /// Copied from [EditableTextState]. + TextSelectionToolbarAnchors get contextMenuAnchors { + final glyphHeights = _getGlyphHeights(); + final selection = textEditingValue.selection; + final points = renderEditor.getEndpointsForSelection(selection); + return TextSelectionToolbarAnchors.fromSelection( + renderBox: renderEditor, + startGlyphHeight: glyphHeights.item1, + endGlyphHeight: glyphHeights.item2, + selectionEndpoints: points, + ); + } + + /// Gets the line heights at the start and end of the selection for the given + /// [RawEditorState]. + /// + /// Copied from [EditableTextState]. + Tuple2 _getGlyphHeights() { + final selection = textEditingValue.selection; + + // Only calculate handle rects if the text in the previous frame + // is the same as the text in the current frame. This is done because + // widget.renderObject contains the renderEditable from the previous frame. + // If the text changed between the current and previous frames then + // widget.renderObject.getRectForComposingRange might fail. In cases where + // the current frame is different from the previous we fall back to + // renderObject.preferredLineHeight. + final prevText = renderEditor.document.toPlainText(); + final currText = textEditingValue.text; + if (prevText != currText || !selection.isValid || selection.isCollapsed) { + return Tuple2( + renderEditor.preferredLineHeight(selection.base), + renderEditor.preferredLineHeight(selection.base), + ); + } + + final startCharacterRect = + renderEditor.getLocalRectForCaret(selection.base); + final endCharacterRect = + renderEditor.getLocalRectForCaret(selection.extent); + return Tuple2( + startCharacterRect.height, + endCharacterRect.height, + ); + } + @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); @@ -2333,3 +2411,15 @@ class _ApplyCheckListAction extends Action { @override bool get isActionEnabled => true; } + +/// Signature for a widget builder that builds a context menu for the given +/// [RawEditorState]. +/// +/// See also: +/// +/// * [EditableTextContextMenuBuilder], which performs the same role for +/// [EditableText] +typedef QuillEditorContextMenuBuilder = Widget Function( + BuildContext context, + RawEditorState rawEditorState, +); diff --git a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart index 8b494dd1..98cfb407 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart @@ -150,14 +150,15 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState } @override - bool get cutEnabled => widget.toolbarOptions.cut && !widget.readOnly; + bool get cutEnabled => widget.contextMenuBuilder != null && !widget.readOnly; @override - bool get copyEnabled => widget.toolbarOptions.copy; + bool get copyEnabled => widget.contextMenuBuilder != null; @override - bool get pasteEnabled => widget.toolbarOptions.paste && !widget.readOnly; + bool get pasteEnabled => widget.contextMenuBuilder != null + && !widget.readOnly; @override - bool get selectAllEnabled => widget.toolbarOptions.selectAll; + bool get selectAllEnabled => widget.contextMenuBuilder != null; } From 06d62d61bbbb08acda70a727944d9e1a0f821765 Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 16 Feb 2023 08:04:07 -0800 Subject: [PATCH 086/204] Upgrade to 6.4.1 --- CHANGELOG.md | 3 ++ lib/src/widgets/delegate.dart | 47 ++++++++++--------- lib/src/widgets/editor.dart | 12 ++--- lib/src/widgets/raw_editor.dart | 10 ++-- ...editor_state_selection_delegate_mixin.dart | 4 +- pubspec.yaml | 2 +- 6 files changed, 40 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17eed562..637cfbd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.4.1] +* Control the detect word boundary behaviour. + # [6.4.0] * Use `axis` to make the toolbar vertical. * Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis. diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index 1de60822..ce8e65a3 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -66,9 +66,8 @@ class EditorTextSelectionGestureDetectorBuilder { /// Creates a [EditorTextSelectionGestureDetectorBuilder]. /// /// The [delegate] must not be null. - EditorTextSelectionGestureDetectorBuilder({ - required this.delegate, - this.detectWordBoundary = true}); + EditorTextSelectionGestureDetectorBuilder( + {required this.delegate, this.detectWordBoundary = true}); /// The delegate for this [EditorTextSelectionGestureDetectorBuilder]. /// @@ -341,26 +340,28 @@ class EditorTextSelectionGestureDetectorBuilder { /// /// The [child] or its subtree should contain [EditableText]. Widget build( - {required HitTestBehavior behavior, required Widget child, Key? key, - bool detectWordBoundary = true}) { + {required HitTestBehavior behavior, + required Widget child, + Key? key, + bool detectWordBoundary = true}) { return EditorTextSelectionGestureDetector( - key: key, - onTapDown: onTapDown, - onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null, - onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null, - onSingleTapUp: onSingleTapUp, - onSingleTapCancel: onSingleTapCancel, - onSingleLongTapStart: onSingleLongTapStart, - onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate, - onSingleLongTapEnd: onSingleLongTapEnd, - onDoubleTapDown: onDoubleTapDown, - onSecondarySingleTapUp: onSecondarySingleTapUp, - onDragSelectionStart: onDragSelectionStart, - onDragSelectionUpdate: onDragSelectionUpdate, - onDragSelectionEnd: onDragSelectionEnd, - behavior: behavior, - detectWordBoundary: detectWordBoundary, - child: child - ); + key: key, + onTapDown: onTapDown, + onForcePressStart: + delegate.forcePressEnabled ? onForcePressStart : null, + onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null, + onSingleTapUp: onSingleTapUp, + onSingleTapCancel: onSingleTapCancel, + onSingleLongTapStart: onSingleLongTapStart, + onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate, + onSingleLongTapEnd: onSingleLongTapEnd, + onDoubleTapDown: onDoubleTapDown, + onSecondarySingleTapUp: onSecondarySingleTapUp, + onDragSelectionStart: onDragSelectionStart, + onDragSelectionUpdate: onDragSelectionUpdate, + onDragSelectionEnd: onDragSelectionEnd, + behavior: behavior, + detectWordBoundary: detectWordBoundary, + child: child); } } diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index c65898f2..7c8d7c10 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -416,8 +416,8 @@ class QuillEditorState extends State void initState() { super.initState(); _selectionGestureDetectorBuilder = - _QuillEditorSelectionGestureDetectorBuilder(this, - widget.detectWordBoundary); + _QuillEditorSelectionGestureDetectorBuilder( + this, widget.detectWordBoundary); } @override @@ -467,9 +467,8 @@ class QuillEditorState extends State readOnly: widget.readOnly, placeholder: widget.placeholder, onLaunchUrl: widget.onLaunchUrl, - contextMenuBuilder: showSelectionToolbar - ? RawEditor.defaultContextMenuBuilder - : null, + contextMenuBuilder: + showSelectionToolbar ? RawEditor.defaultContextMenuBuilder : null, showSelectionHandles: isMobile(theme.platform), showCursor: widget.showCursor, cursorStyle: CursorStyle( @@ -593,7 +592,8 @@ class QuillEditorState extends State class _QuillEditorSelectionGestureDetectorBuilder extends EditorTextSelectionGestureDetectorBuilder { - _QuillEditorSelectionGestureDetectorBuilder(this._state, this._detectWordBoundary) + _QuillEditorSelectionGestureDetectorBuilder( + this._state, this._detectWordBoundary) : super(delegate: _state, detectWordBoundary: _detectWordBoundary); final QuillEditorState _state; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 8c6c61af..d6496ee6 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -311,12 +311,10 @@ class RawEditorState extends EditorState onCopy: copyEnabled ? () => copySelection(SelectionChangedCause.toolbar) : null, - onCut: cutEnabled - ? () => cutSelection(SelectionChangedCause.toolbar) - : null, - onPaste: pasteEnabled - ? () => pasteText(SelectionChangedCause.toolbar) - : null, + onCut: + cutEnabled ? () => cutSelection(SelectionChangedCause.toolbar) : null, + onPaste: + pasteEnabled ? () => pasteText(SelectionChangedCause.toolbar) : null, onSelectAll: selectAllEnabled ? () => selectAll(SelectionChangedCause.toolbar) : null, diff --git a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart index 98cfb407..5da7cef2 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart @@ -156,8 +156,8 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState bool get copyEnabled => widget.contextMenuBuilder != null; @override - bool get pasteEnabled => widget.contextMenuBuilder != null - && !widget.readOnly; + bool get pasteEnabled => + widget.contextMenuBuilder != null && !widget.readOnly; @override bool get selectAllEnabled => widget.contextMenuBuilder != null; diff --git a/pubspec.yaml b/pubspec.yaml index 951881cb..6300860a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.4.0 +version: 6.4.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 967af17a756d52e0abf17003f32b091bc229ec27 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Fri, 17 Feb 2023 01:32:29 +0000 Subject: [PATCH 087/204] Replace `buildToolbar` with `contextMenuBuilder` (#1106) --- lib/src/widgets/raw_editor.dart | 4 +- lib/src/widgets/text_selection.dart | 84 ++++------------------------- 2 files changed, 13 insertions(+), 75 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index d6496ee6..22738b8f 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1050,13 +1050,15 @@ class RawEditorState extends EditorState value: textEditingValue, context: context, debugRequiredFor: widget, - toolbarLayerLink: _toolbarLayerLink, startHandleLayerLink: _startHandleLayerLink, endHandleLayerLink: _endHandleLayerLink, renderObject: renderEditor, selectionCtrls: widget.selectionCtrls, selectionDelegate: this, clipboardStatus: _clipboardStatus, + contextMenuBuilder: widget.contextMenuBuilder == null + ? null + : (context) => widget.contextMenuBuilder!(context, this), ); _selectionOverlay!.handlesVisible = _shouldShowSelectionHandles(); _selectionOverlay!.showHandles(); diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart index 7930d468..ad1398e0 100644 --- a/lib/src/widgets/text_selection.dart +++ b/lib/src/widgets/text_selection.dart @@ -69,7 +69,6 @@ class EditorTextSelectionOverlay { EditorTextSelectionOverlay({ required this.value, required this.context, - required this.toolbarLayerLink, required this.startHandleLayerLink, required this.endHandleLayerLink, required this.renderObject, @@ -77,12 +76,11 @@ class EditorTextSelectionOverlay { required this.selectionCtrls, required this.selectionDelegate, required this.clipboardStatus, + required this.contextMenuBuilder, this.onSelectionHandleTapped, this.dragStartBehavior = DragStartBehavior.start, this.handlesVisible = false, }) { - final overlay = Overlay.of(context, rootOverlay: true); - // Clipboard status is only checked on first instance of // ClipboardStatusNotifier // if state has changed after creation, but prior to @@ -90,9 +88,6 @@ class EditorTextSelectionOverlay { // we won't know the status unless there is forced update // i.e. occasionally no paste clipboardStatus.update(); - - _toolbarController = AnimationController( - duration: const Duration(milliseconds: 150), vsync: overlay); } TextEditingValue value; @@ -122,10 +117,6 @@ class EditorTextSelectionOverlay { /// Debugging information for explaining why the [Overlay] is required. final Widget debugRequiredFor; - /// The object supplied to the [CompositedTransformTarget] that wraps the text - /// field. - final LayerLink toolbarLayerLink; - /// The objects supplied to the [CompositedTransformTarget] that wraps the /// location of start selection handle. final LayerLink startHandleLayerLink; @@ -144,6 +135,11 @@ class EditorTextSelectionOverlay { /// text field. final TextSelectionDelegate selectionDelegate; + /// {@macro flutter.widgets.EditableText.contextMenuBuilder} + /// + /// If not provided, no context menu will be built. + final WidgetBuilder? contextMenuBuilder; + /// Determines the way that drag start behavior is handled. /// /// If set to [DragStartBehavior.start], handle drag behavior will @@ -177,7 +173,6 @@ class EditorTextSelectionOverlay { /// Useful because the actual value of the clipboard can only be checked /// asynchronously (see [Clipboard.getData]). final ClipboardStatusNotifier clipboardStatus; - late AnimationController _toolbarController; /// A pair of handles. If this is non-null, there are always 2, though the /// second is hidden when the selection is collapsed. @@ -188,8 +183,6 @@ class EditorTextSelectionOverlay { TextSelection get _selection => value.selection; - Animation get _toolbarOpacity => _toolbarController.view; - void setHandlesVisible(bool visible) { if (handlesVisible == visible) { return; @@ -220,7 +213,6 @@ class EditorTextSelectionOverlay { /// To hide the whole overlay, see [hide]. void hideToolbar() { assert(toolbar != null); - _toolbarController.stop(); toolbar!.remove(); toolbar = null; } @@ -228,10 +220,12 @@ class EditorTextSelectionOverlay { /// Shows the toolbar by inserting it into the [context]'s overlay. void showToolbar() { assert(toolbar == null); - toolbar = OverlayEntry(builder: _buildToolbar); + if (contextMenuBuilder == null) return; + toolbar = OverlayEntry(builder: (context) { + return contextMenuBuilder!(context); + }); Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor) .insert(toolbar!); - _toolbarController.forward(from: 0); // make sure handles are visible as well if (_handles == null) { @@ -319,63 +313,6 @@ class EditorTextSelectionOverlay { ..bringIntoView(textPosition); } - Widget _buildToolbar(BuildContext context) { - // Find the horizontal midpoint, just above the selected text. - List endpoints; - - try { - // building with an invalid selection with throw an exception - // This happens where the selection has changed, but the toolbar - // hasn't been dismissed yet. - endpoints = renderObject.getEndpointsForSelection(_selection); - } catch (_) { - return Container(); - } - - final editingRegion = Rect.fromPoints( - renderObject.localToGlobal(Offset.zero), - renderObject.localToGlobal(renderObject.size.bottomRight(Offset.zero)), - ); - - final baseLineHeight = renderObject.preferredLineHeight(_selection.base); - final extentLineHeight = - renderObject.preferredLineHeight(_selection.extent); - final smallestLineHeight = math.min(baseLineHeight, extentLineHeight); - final isMultiline = endpoints.last.point.dy - endpoints.first.point.dy > - smallestLineHeight / 2; - - // If the selected text spans more than 1 line, - // horizontally center the toolbar. - // Derived from both iOS and Android. - final midX = isMultiline - ? editingRegion.width / 2 - : (endpoints.first.point.dx + endpoints.last.point.dx) / 2; - - final midpoint = Offset( - midX, - // The y-coordinate won't be made use of most likely. - endpoints[0].point.dy - baseLineHeight, - ); - - return FadeTransition( - opacity: _toolbarOpacity, - child: CompositedTransformFollower( - link: toolbarLayerLink, - showWhenUnlinked: false, - offset: -editingRegion.topLeft, - child: selectionCtrls.buildToolbar( - context, - editingRegion, - baseLineHeight, - midpoint, - endpoints, - selectionDelegate, - clipboardStatus, - null), - ), - ); - } - void markNeedsBuild([Duration? duration]) { if (_handles != null) { _handles![0].markNeedsBuild(); @@ -399,7 +336,6 @@ class EditorTextSelectionOverlay { /// Final cleanup. void dispose() { hide(); - _toolbarController.dispose(); } /// Builds the handles by inserting them into the [context]'s overlay. From e3b664e319325fc9889a78457bddd37fd805254e Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 16 Feb 2023 19:38:16 -0800 Subject: [PATCH 088/204] Upgrade to 6.4.2 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 637cfbd3..cf178f88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.4.2] +* Replace `buildToolbar` with `contextMenuBuilder`. + # [6.4.1] * Control the detect word boundary behaviour. diff --git a/pubspec.yaml b/pubspec.yaml index 6300860a..9542532c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.4.1 +version: 6.4.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From ab5690a6d7a159e32469402cb9abe7b3bed5e6ce Mon Sep 17 00:00:00 2001 From: mark8044 <87546778+mark8044@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:31:02 -0800 Subject: [PATCH 089/204] Update some dependencies (#1107) * Update pubspec.yaml * Update pubspec.yaml * Update pubspec.yaml * Update CHANGELOG.md --- CHANGELOG.md | 3 +++ pubspec.yaml | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf178f88..647476ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.4.3] +* Update dependencies (collection: 1.17.0, flutter_keyboard_visibility: 5.4.0, quiver: 3.2.1, tuple: 2.0.1, url_launcher: 6.1.9, characters: 1.2.1, i18n_extension: 7.0.0, device_info_plus: 8.1.0) + # [6.4.2] * Replace `buildToolbar` with `contextMenuBuilder`. diff --git a/pubspec.yaml b/pubspec.yaml index 9542532c..dda8c4ee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.4.2 +version: 6.4.3 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill @@ -12,17 +12,17 @@ environment: dependencies: flutter: sdk: flutter - collection: ^1.16.0 + collection: ^1.17.0 flutter_colorpicker: ^1.0.3 - flutter_keyboard_visibility: ^5.2.0 - quiver: ^3.1.0 - tuple: ^2.0.0 - url_launcher: ^6.1.2 + flutter_keyboard_visibility: ^5.4.0 + quiver: ^3.2.1 + tuple: ^2.0.1 + url_launcher: ^6.1.9 pedantic: ^1.11.1 - characters: ^1.2.0 + characters: ^1.2.1 diff_match_patch: ^0.4.1 - i18n_extension: ^6.0.0 - device_info_plus: ^8.0.0 + i18n_extension: ^7.0.0 + device_info_plus: ^8.1.0 platform: ^3.1.0 pasteboard: ^0.2.0 From 70fb6017965bc687308d5ecd63a6a636fbb19833 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Sun, 19 Feb 2023 14:31:18 +0000 Subject: [PATCH 090/204] Fix some MissingPluginExceptions in tests (#1108) --- lib/src/utils/platform.dart | 4 ++++ lib/src/widgets/raw_editor.dart | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/src/utils/platform.dart b/lib/src/utils/platform.dart index 27d183aa..96cb866b 100644 --- a/lib/src/utils/platform.dart +++ b/lib/src/utils/platform.dart @@ -26,6 +26,10 @@ bool isAppleOS([TargetPlatform? targetPlatform]) { } Future isIOSSimulator() async { + if (!isAppleOS()) { + return false; + } + final deviceInfo = DeviceInfoPlugin(); final osInfo = await deviceInfo.deviceInfo; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 22738b8f..75a4203c 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'dart:math' as math; // ignore: unnecessary_import import 'dart:typed_data'; @@ -844,6 +845,9 @@ class RawEditorState extends EditorState if (isKeyboardOS()) { _keyboardVisible = true; + } else if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) { + // treat tests like a keyboard OS + _keyboardVisible = true; } else { // treat iOS Simulator like a keyboard OS isIOSSimulator().then((isIosSimulator) { From df8056c5b26a86e2eef00c7fb8989eef74269abb Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Mon, 20 Feb 2023 16:15:09 +0000 Subject: [PATCH 091/204] Upgrade to 6.4.4 (#1109) --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 647476ba..8e8c2dee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [6.4.4] +* Increased compatibility with Flutter widget tests. + # [6.4.3] * Update dependencies (collection: 1.17.0, flutter_keyboard_visibility: 5.4.0, quiver: 3.2.1, tuple: 2.0.1, url_launcher: 6.1.9, characters: 1.2.1, i18n_extension: 7.0.0, device_info_plus: 8.1.0) diff --git a/pubspec.yaml b/pubspec.yaml index dda8c4ee..06f8fa10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.4.3 +version: 6.4.4 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From dbe57bd28356a0932f71a093bfdef05e9df0e672 Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Tue, 21 Feb 2023 13:34:35 +0800 Subject: [PATCH 092/204] Update doc_cn.md (#1110) --- doc_cn.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc_cn.md b/doc_cn.md index 01375221..4eef88d0 100644 --- a/doc_cn.md +++ b/doc_cn.md @@ -92,7 +92,7 @@ var json = jsonEncode(_controller.document.toDelta().toJson()); 要将 `FlutterQuill` 使用之前存储的 `JSON` 数据,请执行以下操作: ```dart -var myJSON = jsonDecode(incomingJSONText); +var myJSON = jsonDecode(r'{"insert":"hello\n"}'); _controller = QuillController( document: Document.fromJson(myJSON), selection: TextSelection.collapsed(offset: 0), @@ -355,7 +355,7 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -目前,可提供以下 25 种语言环境的翻译: +目前,可提供以下 26 种语言环境的翻译: * `Locale('en')` * `Locale('ar')` @@ -376,6 +376,7 @@ QuillEditor(locale: Locale('fr'), ...) * `Locale('pl')` * `Locale('vi')` * `Locale('id')` +* `Locale('ms')` * `Locale('nl')` * `Locale('no')` * `Locale('fa')` From a747e22f35b3cb42b872192cc561f31ecefeb2b7 Mon Sep 17 00:00:00 2001 From: Cris Liao Date: Mon, 6 Mar 2023 22:31:12 +0800 Subject: [PATCH 093/204] Improve ImageEmbedBuilderWeb (#1125) --- example/lib/universal_ui/universal_ui.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/example/lib/universal_ui/universal_ui.dart b/example/lib/universal_ui/universal_ui.dart index 4b493986..d18e7fbd 100644 --- a/example/lib/universal_ui/universal_ui.dart +++ b/example/lib/universal_ui/universal_ui.dart @@ -44,8 +44,12 @@ class ImageEmbedBuilderWeb implements EmbedBuilder { return const SizedBox(); } final size = MediaQuery.of(context).size; - UniversalUI().platformViewRegistry.registerViewFactory( - imageUrl, (viewId) => html.ImageElement()..src = imageUrl); + UniversalUI().platformViewRegistry.registerViewFactory(imageUrl, (viewId) { + return html.ImageElement() + ..src = imageUrl + ..style.height = 'auto' + ..style.width = 'auto'; + }); return Padding( padding: EdgeInsets.only( right: ResponsiveWidget.isMediumScreen(context) From a864e29ba115209e602003e8b92990a6d46bde2f Mon Sep 17 00:00:00 2001 From: n7484443 Date: Sat, 11 Mar 2023 23:53:22 +0900 Subject: [PATCH 094/204] Add korean translation 'Size' (#1127) --- lib/src/translations/toolbar.i18n.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index fa5ea0f4..252421ae 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -287,6 +287,7 @@ extension Localization on String { 'Resize': '크기조정', 'Width': '넓이', 'Height': '높이', + 'Size': '크기', 'Small': '작게', 'Large': '크게', 'Huge': '매우크게', From 3e9452e675e8734ff50364c5f7b5d34088d5ff05 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Sun, 12 Mar 2023 12:56:15 +0000 Subject: [PATCH 095/204] Remove tuples (#1128) * Update dependencies of `flutter_quill_extensions` * Override `intl` in example Running "flutter pub get" in example... Resolving dependencies... (1.0s) Because every version of flutter_quill from path depends on i18n_extension ^7.0.0 and no versions of i18n_extension match >7.0.0 <8.0.0, every version of flutter_quill from path requires i18n_extension 7.0.0. And because i18n_extension 7.0.0 depends on intl ^0.18.0 and math_keyboard 0.1.8 depends on intl ^0.17.0, flutter_quill from path is incompatible with math_keyboard 0.1.8. Because every version of flutter_quill_extensions from path depends on math_keyboard ^0.1.8 and no versions of math_keyboard match >0.1.8 <0.2.0, every version of flutter_quill_extensions from path requires math_keyboard 0.1.8. Thus, flutter_quill from path is incompatible with flutter_quill_extensions from path. So, because app depends on both flutter_quill_extensions from path and flutter_quill from path, version solving failed. pub get failed * Remove all `tuple` imports * Create struct for vertical spacing * Create struct for history items * Create struct for individual styles offsetvalue * Override `intl` in `flutter_quill_extensions` * Create struct for (nullable) image width/height * Create struct for image url * Create struct for text links * Create struct for glyph heights * Use `OffsetValue` struct for embed node * Create struct for next new line * Create struct for segment leaf nodes * Create struct for history undo/redo result * Downgrade `i18n_extension` to `6.0.0` * Bump to 7.0.0 Required for `flutter_quill_extensions` to have access to the new structs. --- CHANGELOG.md | 3 ++ README.md | 2 +- doc_cn.md | 2 +- example/lib/pages/home_page.dart | 12 ++--- .../lib/embeds/builders.dart | 21 ++++---- flutter_quill_extensions/pubspec.yaml | 12 ++--- lib/flutter_quill.dart | 5 ++ lib/src/models/documents/document.dart | 31 +++++------ lib/src/models/documents/history.dart | 22 ++++---- lib/src/models/documents/nodes/line.dart | 10 ++-- lib/src/models/rules/insert.dart | 32 +++++++----- lib/src/models/structs/doc_change.dart | 18 +++++++ lib/src/models/structs/history_changed.dart | 9 ++++ lib/src/models/structs/image_url.dart | 9 ++++ lib/src/models/structs/offset_value.dart | 5 ++ lib/src/models/structs/optional_size.dart | 14 +++++ lib/src/models/structs/segment_leaf_node.dart | 8 +++ lib/src/models/structs/vertical_spacing.dart | 9 ++++ lib/src/utils/embeds.dart | 7 ++- lib/src/widgets/controller.dart | 34 ++++++------- lib/src/widgets/default_styles.dart | 51 +++++++++++-------- lib/src/widgets/editor.dart | 8 +-- lib/src/widgets/raw_editor.dart | 43 ++++++++++------ ...editor_state_selection_delegate_mixin.dart | 6 +-- lib/src/widgets/text_block.dart | 32 ++++++------ lib/src/widgets/text_line.dart | 8 +-- .../widgets/toolbar/link_style_button.dart | 30 ++++++----- pubspec.yaml | 5 +- 28 files changed, 275 insertions(+), 173 deletions(-) create mode 100644 lib/src/models/structs/doc_change.dart create mode 100644 lib/src/models/structs/history_changed.dart create mode 100644 lib/src/models/structs/image_url.dart create mode 100644 lib/src/models/structs/offset_value.dart create mode 100644 lib/src/models/structs/optional_size.dart create mode 100644 lib/src/models/structs/segment_leaf_node.dart create mode 100644 lib/src/models/structs/vertical_spacing.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e8c2dee..80ce4dae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.0] +* Breaking change: Tuples are no longer used. They have been replaced with a number of data classes. + # [6.4.4] * Increased compatibility with Flutter widget tests. diff --git a/README.md b/README.md index bd809b78..cd772c47 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ Future _addEditNote(BuildContext context, {Document? document}) async { final length = controller.selection.extentOffset - index; if (isEditing) { - final offset = getEmbedNode(controller, controller.selection.start).item1; + final offset = getEmbedNode(controller, controller.selection.start).offset; controller.replaceText( offset, 1, block, TextSelection.collapsed(offset: offset)); } else { diff --git a/doc_cn.md b/doc_cn.md index 4eef88d0..d76f4c2e 100644 --- a/doc_cn.md +++ b/doc_cn.md @@ -328,7 +328,7 @@ Future _addEditNote(BuildContext context, {Document? document}) async { final length = controller.selection.extentOffset - index; if (isEditing) { - final offset = getEmbedNode(controller, controller.selection.start).item1; + final offset = getEmbedNode(controller, controller.selection.start).offset; controller.replaceText( offset, 1, block, TextSelection.collapsed(offset: offset)); } else { diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 7b3c57ba..6ed01d74 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -12,7 +12,6 @@ import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:tuple/tuple.dart'; import '../universal_ui/universal_ui.dart'; import 'read_only_page.dart'; @@ -184,8 +183,8 @@ class _HomePageState extends State { height: 1.15, fontWeight: FontWeight.w300, ), - const Tuple2(16, 0), - const Tuple2(0, 0), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), null), sizeSmall: const TextStyle(fontSize: 9), ), @@ -219,8 +218,8 @@ class _HomePageState extends State { height: 1.15, fontWeight: FontWeight.w300, ), - const Tuple2(16, 0), - const Tuple2(0, 0), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), null), sizeSmall: const TextStyle(fontSize: 9), ), @@ -476,7 +475,8 @@ class _HomePageState extends State { final length = controller.selection.extentOffset - index; if (isEditing) { - final offset = getEmbedNode(controller, controller.selection.start).item1; + final offset = + getEmbedNode(controller, controller.selection.start).offset; controller.replaceText( offset, 1, block, TextSelection.collapsed(offset: offset)); } else { diff --git a/flutter_quill_extensions/lib/embeds/builders.dart b/flutter_quill_extensions/lib/embeds/builders.dart index c443cc59..4e7ebcbc 100644 --- a/flutter_quill_extensions/lib/embeds/builders.dart +++ b/flutter_quill_extensions/lib/embeds/builders.dart @@ -7,7 +7,6 @@ import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/translations.dart'; import 'package:gallery_saver/gallery_saver.dart'; import 'package:math_keyboard/math_keyboard.dart'; -import 'package:tuple/tuple.dart'; import 'utils.dart'; import 'widgets/image.dart'; @@ -30,7 +29,7 @@ class ImageEmbedBuilder implements EmbedBuilder { var image; final imageUrl = standardizeImageUrl(node.value.data); - Tuple2? _widthHeight; + OptionalSize? _imageSize; final style = node.style.attributes['style']; if (base.isMobile() && style != null) { final _attrs = base.parseKeyValuePairs(style.value.toString(), { @@ -46,7 +45,7 @@ class ImageEmbedBuilder implements EmbedBuilder { 'mobileWidth and mobileHeight must be specified'); final w = double.parse(_attrs[Attribute.mobileWidth]!); final h = double.parse(_attrs[Attribute.mobileHeight]!); - _widthHeight = Tuple2(w, h); + _imageSize = OptionalSize(w, h); final m = _attrs[Attribute.mobileMargin] == null ? 0.0 : double.parse(_attrs[Attribute.mobileMargin]!); @@ -57,9 +56,9 @@ class ImageEmbedBuilder implements EmbedBuilder { } } - if (_widthHeight == null) { + if (_imageSize == null) { image = imageByUrl(imageUrl); - _widthHeight = Tuple2((image as Image).width, image.height); + _imageSize = OptionalSize((image as Image).width, image.height); } if (!readOnly && base.isMobile()) { @@ -87,10 +86,10 @@ class ImageEmbedBuilder implements EmbedBuilder { controller ..skipRequestKeyboard = true ..formatText( - res.item1, 1, StyleAttribute(attr)); + res.offset, 1, StyleAttribute(attr)); }, - imageWidth: _widthHeight?.item1, - imageHeight: _widthHeight?.item2, + imageWidth: _imageSize?.width, + imageHeight: _imageSize?.height, maxWidth: _screenSize.width, maxHeight: _screenSize.height); }); @@ -103,10 +102,10 @@ class ImageEmbedBuilder implements EmbedBuilder { onPressed: () { final imageNode = getEmbedNode(controller, controller.selection.start) - .item2; + .value; final imageUrl = imageNode.value.data; controller.copiedImageUrl = - Tuple2(imageUrl, getImageStyleString(controller)); + ImageUrl(imageUrl, getImageStyleString(controller)); Navigator.pop(context); }, ); @@ -117,7 +116,7 @@ class ImageEmbedBuilder implements EmbedBuilder { onPressed: () { final offset = getEmbedNode(controller, controller.selection.start) - .item1; + .offset; controller.replaceText(offset, 1, '', TextSelection.collapsed(offset: offset)); Navigator.pop(context); diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 4aef2ba6..5abc6d38 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,19 +12,19 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^6.0.0 + flutter_quill: ^7.0.0 image_picker: ^0.8.5+3 photo_view: ^0.14.0 video_player: ^2.4.2 youtube_player_flutter: ^8.1.1 gallery_saver: ^2.3.2 - math_keyboard: ^0.1.6 - string_validator: ^0.3.0 + math_keyboard: ^0.1.8 + string_validator: ^1.0.0 -#dependency_overrides: -# flutter_quill: -# path: ../ +# dependency_overrides: +# flutter_quill: +# path: ../ dev_dependencies: flutter_test: diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index e74a41ac..aefc566c 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -9,6 +9,11 @@ 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/offset_value.dart'; +export 'src/models/structs/optional_size.dart'; +export 'src/models/structs/vertical_spacing.dart'; export 'src/models/themes/quill_custom_button.dart'; export 'src/models/themes/quill_dialog_theme.dart'; export 'src/models/themes/quill_icon_theme.dart'; diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart index e3e2237f..1b4d5aef 100644 --- a/lib/src/models/documents/document.dart +++ b/lib/src/models/documents/document.dart @@ -1,9 +1,11 @@ import 'dart:async'; -import 'package:tuple/tuple.dart'; - import '../quill_delta.dart'; import '../rules/rule.dart'; +import '../structs/doc_change.dart'; +import '../structs/history_changed.dart'; +import '../structs/offset_value.dart'; +import '../structs/segment_leaf_node.dart'; import 'attribute.dart'; import 'history.dart'; import 'nodes/block.dart'; @@ -50,13 +52,12 @@ class Document { _rules.setCustomRules(customRules); } - final StreamController> _observer = - StreamController.broadcast(); + final StreamController _observer = StreamController.broadcast(); final History _history = History(); - /// Stream of [Change]s applied to this document. - Stream> get changes => _observer.stream; + /// Stream of [DocChange]s applied to this document. + Stream get changes => _observer.stream; /// Inserts [data] in this document at specified [index]. /// @@ -158,7 +159,7 @@ class Document { } /// Returns all styles for each node within selection - List> collectAllIndividualStyles(int index, int len) { + List> collectAllIndividualStyles(int index, int len) { final res = queryChild(index); return (res.node as Line).collectAllIndividualStyles(res.offset, len); } @@ -216,19 +217,15 @@ class Document { } /// Given offset, find its leaf node in document - Tuple2 querySegmentLeafNode(int offset) { + SegmentLeafNode querySegmentLeafNode(int offset) { final result = queryChild(offset); if (result.node == null) { - return const Tuple2(null, null); + return const SegmentLeafNode(null, null); } final line = result.node as Line; final segmentResult = line.queryChild(result.offset, false); - if (segmentResult.node == null) { - return Tuple2(line, null); - } - final segment = segmentResult.node as Leaf; - return Tuple2(line, segment); + return SegmentLeafNode(line, segmentResult.node as Leaf?); } /// Composes [change] Delta into this document. @@ -276,16 +273,16 @@ class Document { if (_delta != _root.toDelta()) { throw 'Compose failed'; } - final change = Tuple3(originalDelta, delta, changeSource); + final change = DocChange(originalDelta, delta, changeSource); _observer.add(change); _history.handleDocChange(change); } - Tuple2 undo() { + HistoryChanged undo() { return _history.undo(this); } - Tuple2 redo() { + HistoryChanged redo() { return _history.redo(this); } diff --git a/lib/src/models/documents/history.dart b/lib/src/models/documents/history.dart index d406505e..fa87700c 100644 --- a/lib/src/models/documents/history.dart +++ b/lib/src/models/documents/history.dart @@ -1,6 +1,6 @@ -import 'package:tuple/tuple.dart'; - import '../quill_delta.dart'; +import '../structs/doc_change.dart'; +import '../structs/history_changed.dart'; import 'document.dart'; class History { @@ -32,12 +32,12 @@ class History { ///record delay final int interval; - void handleDocChange(Tuple3 change) { + void handleDocChange(DocChange docChange) { if (ignoreChange) return; - if (!userOnly || change.item3 == ChangeSource.LOCAL) { - record(change.item2, change.item1); + if (!userOnly || docChange.source == ChangeSource.LOCAL) { + record(docChange.change, docChange.before); } else { - transform(change.item2); + transform(docChange.change); } } @@ -85,9 +85,9 @@ class History { } } - Tuple2 _change(Document doc, List source, List dest) { + HistoryChanged _change(Document doc, List source, List dest) { if (source.isEmpty) { - return const Tuple2(false, 0); + return const HistoryChanged(false, 0); } final delta = source.removeLast(); // look for insert or delete @@ -107,14 +107,14 @@ class History { ignoreChange = true; doc.compose(delta, ChangeSource.LOCAL); ignoreChange = false; - return Tuple2(true, len); + return HistoryChanged(true, len); } - Tuple2 undo(Document doc) { + HistoryChanged undo(Document doc) { return _change(doc, stack.undo, stack.redo); } - Tuple2 redo(Document doc) { + HistoryChanged redo(Document doc) { return _change(doc, stack.redo, stack.undo); } } diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index 4b6db80f..a8cca75c 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/line.dart @@ -1,9 +1,9 @@ import 'dart:math' as math; import 'package:collection/collection.dart'; -import 'package:tuple/tuple.dart'; import '../../quill_delta.dart'; +import '../../structs/offset_value.dart'; import '../attribute.dart'; import '../style.dart'; import 'block.dart'; @@ -391,10 +391,10 @@ class Line extends Container { /// Returns each node segment's offset in selection /// with its corresponding style as a list - List> collectAllIndividualStyles(int offset, int len, + List> collectAllIndividualStyles(int offset, int len, {int beg = 0}) { final local = math.min(length - offset, len); - final result = >[]; + final result = >[]; final data = queryChild(offset, true); var node = data.node as Leaf?; @@ -402,12 +402,12 @@ class Line extends Container { var pos = 0; if (node is Text) { pos = node.length - data.offset; - result.add(Tuple2(beg, node.style)); + result.add(OffsetValue(beg, node.style)); } while (!node!.isLast && pos < local) { node = node.next as Leaf; if (node is Text) { - result.add(Tuple2(pos + beg, node.style)); + result.add(OffsetValue(pos + beg, node.style)); pos += node.length; } } diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index 949371b5..93cf4742 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -1,5 +1,3 @@ -import 'package:tuple/tuple.dart'; - import '../../models/documents/document.dart'; import '../documents/attribute.dart'; import '../documents/nodes/embeddable.dart'; @@ -56,7 +54,7 @@ class PreserveLineStyleOnSplitRule extends InsertRule { return delta; } final nextNewLine = _getNextNewLine(itr); - final attributes = nextNewLine.item1?.attributes; + final attributes = nextNewLine.operation?.attributes; return delta..insert('\n', attributes); } @@ -86,7 +84,8 @@ class PreserveBlockStyleOnInsertRule extends InsertRule { // Look for the next newline. final nextNewLine = _getNextNewLine(itr); final lineStyle = - Style.fromJson(nextNewLine.item1?.attributes ?? {}); + Style.fromJson( + nextNewLine.operation?.attributes ?? {}); final blockStyle = lineStyle.getBlocksExceptHeader(); // Are we currently in a block? If not then ignore. @@ -126,8 +125,8 @@ class PreserveBlockStyleOnInsertRule extends InsertRule { // Reset style of the original newline character if needed. if (resetStyle.isNotEmpty) { delta - ..retain(nextNewLine.item2!) - ..retain((nextNewLine.item1!.data as String).indexOf('\n')) + ..retain(nextNewLine.skipped!) + ..retain((nextNewLine.operation!.data as String).indexOf('\n')) ..retain(1, resetStyle); } @@ -188,10 +187,10 @@ class AutoExitBlockRule extends InsertRule { // Keep looking for the next newline character to see if it shares the same // block style as `cur`. final nextNewLine = _getNextNewLine(itr); - if (nextNewLine.item1 != null && - nextNewLine.item1!.attributes != null && - Style.fromJson(nextNewLine.item1!.attributes).getBlockExceptHeader() == - blockStyle) { + if (nextNewLine.operation != null && + nextNewLine.operation!.attributes != null && + Style.fromJson(nextNewLine.operation!.attributes).getBlockExceptHeader() + == blockStyle) { // We are not at the end of this block, ignore. return null; } @@ -524,15 +523,22 @@ class CatchAllInsertRule extends InsertRule { } } -Tuple2 _getNextNewLine(DeltaIterator iterator) { +_NextNewLine _getNextNewLine(DeltaIterator iterator) { Operation op; for (var skipped = 0; iterator.hasNext; skipped += op.length!) { op = iterator.next(); final lineBreak = (op.data is String ? op.data as String? : '')!.indexOf('\n'); if (lineBreak >= 0) { - return Tuple2(op, skipped); + return _NextNewLine(op, skipped); } } - return const Tuple2(null, null); + return const _NextNewLine(null, null); +} + +class _NextNewLine { + const _NextNewLine(this.operation, this.skipped); + + final Operation? operation; + final int? skipped; } diff --git a/lib/src/models/structs/doc_change.dart b/lib/src/models/structs/doc_change.dart new file mode 100644 index 00000000..6c999331 --- /dev/null +++ b/lib/src/models/structs/doc_change.dart @@ -0,0 +1,18 @@ +import '../../../flutter_quill.dart'; + +class DocChange { + DocChange( + this.before, + this.change, + this.source, + ); + + /// Document state before [change]. + final Delta before; + + /// Change delta applied to the document. + final Delta change; + + /// The source of this change. + final ChangeSource source; +} diff --git a/lib/src/models/structs/history_changed.dart b/lib/src/models/structs/history_changed.dart new file mode 100644 index 00000000..abb61567 --- /dev/null +++ b/lib/src/models/structs/history_changed.dart @@ -0,0 +1,9 @@ +class HistoryChanged { + const HistoryChanged( + this.changed, + this.len, + ); + + final bool changed; + final int? len; +} diff --git a/lib/src/models/structs/image_url.dart b/lib/src/models/structs/image_url.dart new file mode 100644 index 00000000..097e199b --- /dev/null +++ b/lib/src/models/structs/image_url.dart @@ -0,0 +1,9 @@ +class ImageUrl { + const ImageUrl( + this.url, + this.styleString, + ); + + final String url; + final String styleString; +} diff --git a/lib/src/models/structs/offset_value.dart b/lib/src/models/structs/offset_value.dart new file mode 100644 index 00000000..0f9e558b --- /dev/null +++ b/lib/src/models/structs/offset_value.dart @@ -0,0 +1,5 @@ +class OffsetValue { + OffsetValue(this.offset, this.value); + final int offset; + final T value; +} diff --git a/lib/src/models/structs/optional_size.dart b/lib/src/models/structs/optional_size.dart new file mode 100644 index 00000000..a887b468 --- /dev/null +++ b/lib/src/models/structs/optional_size.dart @@ -0,0 +1,14 @@ +class OptionalSize { + OptionalSize( + this.width, + this.height, + ); + + /// If non-null, requires the child to have exactly this width. + /// If null, the child is free to choose its own width. + final double? width; + + /// If non-null, requires the child to have exactly this height. + /// If null, the child is free to choose its own height. + final double? height; +} diff --git a/lib/src/models/structs/segment_leaf_node.dart b/lib/src/models/structs/segment_leaf_node.dart new file mode 100644 index 00000000..676f037f --- /dev/null +++ b/lib/src/models/structs/segment_leaf_node.dart @@ -0,0 +1,8 @@ +import '../../../flutter_quill.dart'; + +class SegmentLeafNode { + const SegmentLeafNode(this.line, this.leaf); + + final Line? line; + final Leaf? leaf; +} diff --git a/lib/src/models/structs/vertical_spacing.dart b/lib/src/models/structs/vertical_spacing.dart new file mode 100644 index 00000000..54f76f7c --- /dev/null +++ b/lib/src/models/structs/vertical_spacing.dart @@ -0,0 +1,9 @@ +class VerticalSpacing { + const VerticalSpacing( + this.top, + this.bottom, + ); + + final double top; + final double bottom; +} diff --git a/lib/src/utils/embeds.dart b/lib/src/utils/embeds.dart index 7ee2dd0d..db693ba8 100644 --- a/lib/src/utils/embeds.dart +++ b/lib/src/utils/embeds.dart @@ -1,11 +1,10 @@ import 'dart:math'; -import 'package:tuple/tuple.dart'; - import '../models/documents/nodes/leaf.dart'; +import '../models/structs/offset_value.dart'; import '../widgets/controller.dart'; -Tuple2 getEmbedNode(QuillController controller, int offset) { +OffsetValue getEmbedNode(QuillController controller, int offset) { var offset = controller.selection.start; var embedNode = controller.queryNode(offset); if (embedNode == null || !(embedNode is Embed)) { @@ -13,7 +12,7 @@ Tuple2 getEmbedNode(QuillController controller, int offset) { embedNode = controller.queryNode(offset); } if (embedNode != null && embedNode is Embed) { - return Tuple2(offset, embedNode); + return OffsetValue(offset, embedNode); } return throw 'Embed node not found by offset $offset'; diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index fd85666b..30396377 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -2,7 +2,6 @@ import 'dart:math' as math; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; -import 'package:tuple/tuple.dart'; import '../models/documents/attribute.dart'; import '../models/documents/document.dart'; @@ -10,6 +9,9 @@ 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'; import '../utils/delta.dart'; typedef ReplaceTextCallback = bool Function(int index, int len, Object? data); @@ -82,12 +84,7 @@ class QuillController extends ChangeNotifier { /// removing or listeners to this instance. bool _isDisposed = false; - // item1: Document state before [change]. - // - // item2: Change delta applied to the document. - // - // item3: The source of this change. - Stream> get changes => document.changes; + Stream get changes => document.changes; TextEditingValue get plainTextEditingValue => TextEditingValue( text: document.toPlainText(), @@ -123,7 +120,7 @@ class QuillController extends ChangeNotifier { } /// Returns all styles for each node within selection - List> getAllIndividualSelectionStyles() { + List> getAllIndividualSelectionStyles() { final styles = document.collectAllIndividualStyles( selection.start, selection.end - selection.start); return styles; @@ -145,9 +142,9 @@ class QuillController extends ChangeNotifier { } void undo() { - final tup = document.undo(); - if (tup.item1) { - _handleHistoryChange(tup.item2); + final result = document.undo(); + if (result.changed) { + _handleHistoryChange(result.len); } } @@ -167,9 +164,9 @@ class QuillController extends ChangeNotifier { } void redo() { - final tup = document.redo(); - if (tup.item1) { - _handleHistoryChange(tup.item2); + final result = document.redo(); + if (result.changed) { + _handleHistoryChange(result.len); } } @@ -369,16 +366,15 @@ class QuillController extends ChangeNotifier { /// Given offset, find its leaf node in document Leaf? queryNode(int offset) { - return document.querySegmentLeafNode(offset).item2; + return document.querySegmentLeafNode(offset).leaf; } /// Clipboard for image url and its corresponding style - /// item1 is url and item2 is style string - Tuple2? _copiedImageUrl; + ImageUrl? _copiedImageUrl; - Tuple2? get copiedImageUrl => _copiedImageUrl; + ImageUrl? get copiedImageUrl => _copiedImageUrl; - set copiedImageUrl(Tuple2? value) { + set copiedImageUrl(ImageUrl? value) { _copiedImageUrl = value; Clipboard.setData(const ClipboardData(text: '')); } diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index 6c766e2b..08acb154 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; import '../models/documents/attribute.dart'; import '../models/documents/style.dart'; +import '../models/structs/vertical_spacing.dart'; import '../utils/platform.dart'; import 'style_widgets/checkbox_point.dart'; @@ -44,11 +44,11 @@ class DefaultTextBlockStyle { final TextStyle style; /// Vertical spacing around a text block. - final Tuple2 verticalSpacing; + final VerticalSpacing verticalSpacing; /// Vertical spacing for individual lines within a text block. /// - final Tuple2 lineSpacing; + final VerticalSpacing lineSpacing; /// Decoration of a text block. /// @@ -125,8 +125,8 @@ class InlineCodeStyle { class DefaultListBlockStyle extends DefaultTextBlockStyle { DefaultListBlockStyle( TextStyle style, - Tuple2 verticalSpacing, - Tuple2 lineSpacing, + VerticalSpacing verticalSpacing, + VerticalSpacing lineSpacing, BoxDecoration? decoration, this.checkboxUIBuilder, ) : super(style, verticalSpacing, lineSpacing, decoration); @@ -193,7 +193,7 @@ class DefaultStyles { height: 1.3, decoration: TextDecoration.none, ); - const baseSpacing = Tuple2(6, 0); + const baseSpacing = VerticalSpacing(6, 0); String fontFamily; if (isAppleOS(themeData.platform)) { fontFamily = 'Menlo'; @@ -216,8 +216,8 @@ class DefaultStyles { fontWeight: FontWeight.w300, decoration: TextDecoration.none, ), - const Tuple2(16, 0), - const Tuple2(0, 0), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), null), h2: DefaultTextBlockStyle( defaultTextStyle.style.copyWith( @@ -227,8 +227,8 @@ class DefaultStyles { fontWeight: FontWeight.normal, decoration: TextDecoration.none, ), - const Tuple2(8, 0), - const Tuple2(0, 0), + const VerticalSpacing(8, 0), + const VerticalSpacing(0, 0), null), h3: DefaultTextBlockStyle( defaultTextStyle.style.copyWith( @@ -238,11 +238,14 @@ class DefaultStyles { fontWeight: FontWeight.w500, decoration: TextDecoration.none, ), - const Tuple2(8, 0), - const Tuple2(0, 0), + const VerticalSpacing(8, 0), + const VerticalSpacing(0, 0), null), paragraph: DefaultTextBlockStyle( - baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null), + baseStyle, + const VerticalSpacing(0, 0), + const VerticalSpacing(0, 0), + null), bold: const TextStyle(fontWeight: FontWeight.bold), italic: const TextStyle(fontStyle: FontStyle.italic), small: const TextStyle(fontSize: 12), @@ -272,15 +275,15 @@ class DefaultStyles { height: 1.5, color: Colors.grey.withOpacity(0.6), ), - const Tuple2(0, 0), - const Tuple2(0, 0), + const VerticalSpacing(0, 0), + const VerticalSpacing(0, 0), null), lists: DefaultListBlockStyle( - baseStyle, baseSpacing, const Tuple2(0, 6), null, null), + baseStyle, baseSpacing, const VerticalSpacing(0, 6), null, null), quote: DefaultTextBlockStyle( TextStyle(color: baseStyle.color!.withOpacity(0.6)), baseSpacing, - const Tuple2(6, 2), + const VerticalSpacing(6, 2), BoxDecoration( border: Border( left: BorderSide(width: 4, color: Colors.grey.shade300), @@ -294,17 +297,23 @@ class DefaultStyles { height: 1.15, ), baseSpacing, - const Tuple2(0, 0), + const VerticalSpacing(0, 0), BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(2), )), indent: DefaultTextBlockStyle( - baseStyle, baseSpacing, const Tuple2(0, 6), null), + baseStyle, baseSpacing, const VerticalSpacing(0, 6), null), align: DefaultTextBlockStyle( - baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null), + baseStyle, + const VerticalSpacing(0, 0), + const VerticalSpacing(0, 0), + null), leading: DefaultTextBlockStyle( - baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null), + baseStyle, + const VerticalSpacing(0, 0), + const VerticalSpacing(0, 0), + null), sizeSmall: const TextStyle(fontSize: 10), sizeLarge: const TextStyle(fontSize: 18), sizeHuge: const TextStyle(fontSize: 22)); diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 7c8d7c10..7246a881 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -9,13 +9,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:i18n_extension/i18n_widget.dart'; -import 'package:tuple/tuple.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/container.dart' as container_node; import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/leaf.dart'; import '../models/documents/style.dart'; +import '../models/structs/offset_value.dart'; import '../utils/platform.dart'; import 'box.dart'; import 'controller.dart'; @@ -38,7 +38,7 @@ abstract class EditorState extends State EditorTextSelectionOverlay? get selectionOverlay; - List> get pasteStyle; + List> get pasteStyle; String get pastePlainText; @@ -645,11 +645,11 @@ class _QuillEditorSelectionGestureDetectorBuilder final pos = renderEditor!.getPositionForOffset(details.globalPosition); final result = editor!.widget.controller.document.querySegmentLeafNode(pos.offset); - final line = result.item1; + final line = result.line; if (line == null) { return false; } - final segmentLeaf = result.item2; + final segmentLeaf = result.leaf; if (segmentLeaf == null && line.length == 1) { editor!.widget.controller.updateSelection( TextSelection.collapsed(offset: pos.offset), ChangeSource.LOCAL); diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 75a4203c..f78b18c3 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -12,8 +12,8 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:pasteboard/pasteboard.dart'; -import 'package:tuple/tuple.dart'; +import '../../flutter_quill.dart'; import '../models/documents/attribute.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/block.dart'; @@ -22,6 +22,7 @@ import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/nodes/line.dart'; import '../models/documents/nodes/node.dart'; import '../models/documents/style.dart'; +import '../models/structs/vertical_spacing.dart'; import '../utils/cast.dart'; import '../utils/delta.dart'; import '../utils/embeds.dart'; @@ -288,8 +289,8 @@ class RawEditorState extends EditorState // for pasting style @override - List> get pasteStyle => _pasteStyle; - List> _pasteStyle = >[]; + List> get pasteStyle => _pasteStyle; + List> _pasteStyle = >[]; @override String get pastePlainText => _pastePlainText; @@ -331,8 +332,8 @@ class RawEditorState extends EditorState final points = renderEditor.getEndpointsForSelection(selection); return TextSelectionToolbarAnchors.fromSelection( renderBox: renderEditor, - startGlyphHeight: glyphHeights.item1, - endGlyphHeight: glyphHeights.item2, + startGlyphHeight: glyphHeights.startGlyphHeight, + endGlyphHeight: glyphHeights.endGlyphHeight, selectionEndpoints: points, ); } @@ -341,7 +342,7 @@ class RawEditorState extends EditorState /// [RawEditorState]. /// /// Copied from [EditableTextState]. - Tuple2 _getGlyphHeights() { + _GlyphHeights _getGlyphHeights() { final selection = textEditingValue.selection; // Only calculate handle rects if the text in the previous frame @@ -354,7 +355,7 @@ class RawEditorState extends EditorState final prevText = renderEditor.document.toPlainText(); final currText = textEditingValue.text; if (prevText != currText || !selection.isValid || selection.isCollapsed) { - return Tuple2( + return _GlyphHeights( renderEditor.preferredLineHeight(selection.base), renderEditor.preferredLineHeight(selection.base), ); @@ -364,7 +365,7 @@ class RawEditorState extends EditorState renderEditor.getLocalRectForCaret(selection.base); final endCharacterRect = renderEditor.getLocalRectForCaret(selection.extent); - return Tuple2( + return _GlyphHeights( startCharacterRect.height, endCharacterRect.height, ); @@ -414,7 +415,7 @@ class RawEditorState extends EditorState /// baseline. // This implies that the first line has no styles applied to it. final baselinePadding = - EdgeInsets.only(top: _styles!.paragraph!.verticalSpacing.item1); + EdgeInsets.only(top: _styles!.paragraph!.verticalSpacing.top); child = BaselineProxy( textStyle: _styles!.paragraph!.style, padding: baselinePadding, @@ -778,7 +779,7 @@ class RawEditorState extends EditorState return editableTextLine; } - Tuple2 _getVerticalSpacingForLine( + VerticalSpacing _getVerticalSpacingForLine( Line line, DefaultStyles? defaultStyles) { final attrs = line.style.attributes; if (attrs.containsKey(Attribute.header.key)) { @@ -803,7 +804,7 @@ class RawEditorState extends EditorState return defaultStyles!.paragraph!.verticalSpacing; } - Tuple2 _getVerticalSpacingForBlock( + VerticalSpacing _getVerticalSpacingForBlock( Block node, DefaultStyles? defaultStyles) { final attrs = node.style.attributes; if (attrs.containsKey(Attribute.blockQuote.key)) { @@ -817,7 +818,7 @@ class RawEditorState extends EditorState } else if (attrs.containsKey(Attribute.align.key)) { return defaultStyles!.align!.verticalSpacing; } - return const Tuple2(0, 0); + return const VerticalSpacing(0, 0); } @override @@ -1281,10 +1282,10 @@ class RawEditorState extends EditorState final length = textEditingValue.selection.extentOffset - index; final copied = controller.copiedImageUrl!; controller.replaceText( - index, length, BlockEmbed.image(copied.item1), null); - if (copied.item2.isNotEmpty) { - controller.formatText(getEmbedNode(controller, index + 1).item1, 1, - StyleAttribute(copied.item2)); + index, length, BlockEmbed.image(copied.url), null); + if (copied.styleString.isNotEmpty) { + controller.formatText(getEmbedNode(controller, index + 1).offset, 1, + StyleAttribute(copied.styleString)); } controller.copiedImageUrl = null; await Clipboard.setData(const ClipboardData(text: '')); @@ -2427,3 +2428,13 @@ typedef QuillEditorContextMenuBuilder = Widget Function( BuildContext context, RawEditorState rawEditorState, ); + +class _GlyphHeights { + _GlyphHeights( + this.startGlyphHeight, + this.endGlyphHeight, + ); + + final double startGlyphHeight; + final double endGlyphHeight; +} diff --git a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart index 5da7cef2..fdd9244b 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart @@ -38,13 +38,13 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState if (insertedText == pastePlainText && pastePlainText != '') { final pos = start; for (var i = 0; i < pasteStyle.length; i++) { - final offset = pasteStyle[i].item1; - final style = pasteStyle[i].item2; + final offset = pasteStyle[i].offset; + final style = pasteStyle[i].value; widget.controller.formatTextStyle( pos + offset, i == pasteStyle.length - 1 ? pastePlainText.length - offset - : pasteStyle[i + 1].item1, + : pasteStyle[i + 1].offset, style); } } diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index f30e71cc..8ece7cf5 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:tuple/tuple.dart'; import '../models/documents/attribute.dart'; import '../models/documents/nodes/block.dart'; import '../models/documents/nodes/line.dart'; +import '../models/structs/vertical_spacing.dart'; import '../utils/delta.dart'; import 'box.dart'; import 'controller.dart'; @@ -78,7 +78,7 @@ class EditableTextBlock extends StatelessWidget { final QuillController controller; final TextDirection textDirection; final double scrollBottomInset; - final Tuple2 verticalSpacing; + final VerticalSpacing verticalSpacing; final TextSelection textSelection; final Color color; final DefaultStyles? styles; @@ -102,7 +102,7 @@ class EditableTextBlock extends StatelessWidget { return _EditableBlock( block: block, textDirection: textDirection, - padding: verticalSpacing as Tuple2, + padding: verticalSpacing, scrollBottomInset: scrollBottomInset, decoration: _getDecorationForBlock(block, defaultStyles) ?? const BoxDecoration(), @@ -243,7 +243,7 @@ class EditableTextBlock extends StatelessWidget { return baseIndent + extraIndent; } - Tuple2 _getSpacingForLine( + VerticalSpacing _getSpacingForLine( Line node, int index, int count, DefaultStyles? defaultStyles) { var top = 0.0, bottom = 0.0; @@ -252,22 +252,22 @@ class EditableTextBlock extends StatelessWidget { final level = attrs[Attribute.header.key]!.value; switch (level) { case 1: - top = defaultStyles!.h1!.verticalSpacing.item1; - bottom = defaultStyles.h1!.verticalSpacing.item2; + top = defaultStyles!.h1!.verticalSpacing.top; + bottom = defaultStyles.h1!.verticalSpacing.bottom; break; case 2: - top = defaultStyles!.h2!.verticalSpacing.item1; - bottom = defaultStyles.h2!.verticalSpacing.item2; + top = defaultStyles!.h2!.verticalSpacing.top; + bottom = defaultStyles.h2!.verticalSpacing.bottom; break; case 3: - top = defaultStyles!.h3!.verticalSpacing.item1; - bottom = defaultStyles.h3!.verticalSpacing.item2; + top = defaultStyles!.h3!.verticalSpacing.top; + bottom = defaultStyles.h3!.verticalSpacing.bottom; break; default: throw 'Invalid level $level'; } } else { - late Tuple2 lineSpacing; + late VerticalSpacing lineSpacing; if (attrs.containsKey(Attribute.blockQuote.key)) { lineSpacing = defaultStyles!.quote!.lineSpacing; } else if (attrs.containsKey(Attribute.indent.key)) { @@ -282,8 +282,8 @@ class EditableTextBlock extends StatelessWidget { // use paragraph linespacing as a default lineSpacing = defaultStyles!.paragraph!.lineSpacing; } - top = lineSpacing.item1; - bottom = lineSpacing.item2; + top = lineSpacing.top; + bottom = lineSpacing.bottom; } if (index == 1) { @@ -294,7 +294,7 @@ class EditableTextBlock extends StatelessWidget { bottom = 0.0; } - return Tuple2(top, bottom); + return VerticalSpacing(top, bottom); } } @@ -601,13 +601,13 @@ class _EditableBlock extends MultiChildRenderObjectWidget { final Block block; final TextDirection textDirection; - final Tuple2 padding; + final VerticalSpacing padding; final double scrollBottomInset; final Decoration decoration; final EdgeInsets? contentPadding; EdgeInsets get _padding => - EdgeInsets.only(top: padding.item1, bottom: padding.item2); + EdgeInsets.only(top: padding.top, bottom: padding.bottom); EdgeInsets get _contentPadding => contentPadding ?? EdgeInsets.zero; diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index b20ea22f..0f2995bb 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -6,7 +6,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:tuple/tuple.dart'; import 'package:url_launcher/url_launcher.dart'; import '../models/documents/attribute.dart'; @@ -16,6 +15,7 @@ import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/nodes/line.dart'; import '../models/documents/nodes/node.dart'; import '../models/documents/style.dart'; +import '../models/structs/vertical_spacing.dart'; import '../utils/color.dart'; import '../utils/font.dart'; import '../utils/platform.dart'; @@ -476,7 +476,7 @@ class EditableTextLine extends RenderObjectWidget { final Widget? leading; final Widget body; final double indentWidth; - final Tuple2 verticalSpacing; + final VerticalSpacing verticalSpacing; final TextDirection textDirection; final TextSelection textSelection; final Color color; @@ -526,8 +526,8 @@ class EditableTextLine extends RenderObjectWidget { EdgeInsetsGeometry _getPadding() { return EdgeInsetsDirectional.only( start: indentWidth, - top: verticalSpacing.item1, - bottom: verticalSpacing.item2); + top: verticalSpacing.top, + bottom: verticalSpacing.bottom); } } diff --git a/lib/src/widgets/toolbar/link_style_button.dart b/lib/src/widgets/toolbar/link_style_button.dart index fde466e4..80086608 100644 --- a/lib/src/widgets/toolbar/link_style_button.dart +++ b/lib/src/widgets/toolbar/link_style_button.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:tuple/tuple.dart'; import '../../models/documents/attribute.dart'; import '../../models/rules/insert.dart'; @@ -86,7 +85,7 @@ class _LinkStyleButtonState extends State { } void _openLinkDialog(BuildContext context) { - showDialog( + showDialog<_TextLink>( context: context, builder: (ctx) { final link = _getLinkAttributeValue(); @@ -96,7 +95,7 @@ class _LinkStyleButtonState extends State { if (link != null) { // text should be the link's corresponding text, not selection final leaf = - widget.controller.document.querySegmentLeafNode(index).item2; + widget.controller.document.querySegmentLeafNode(index).leaf; if (leaf != null) { text = leaf.toPlainText(); } @@ -122,24 +121,21 @@ class _LinkStyleButtonState extends State { ?.value; } - void _linkSubmitted(dynamic value) { - // text.isNotEmpty && link.isNotEmpty - final String text = (value as Tuple2).item1; - final String link = value.item2.trim(); - + void _linkSubmitted(_TextLink value) { var index = widget.controller.selection.start; var length = widget.controller.selection.end - index; if (_getLinkAttributeValue() != null) { // text should be the link's corresponding text, not selection - final leaf = widget.controller.document.querySegmentLeafNode(index).item2; + final leaf = widget.controller.document.querySegmentLeafNode(index).leaf; if (leaf != null) { final range = getLinkRange(leaf); index = range.start; length = range.end - range.start; } } - widget.controller.replaceText(index, length, text, null); - widget.controller.formatText(index, text.length, LinkAttribute(link)); + widget.controller.replaceText(index, length, value.text, null); + widget.controller.formatText( + index, value.text.length, LinkAttribute(value.link)); } } @@ -240,6 +236,16 @@ class _LinkDialogState extends State<_LinkDialog> { } void _applyLink() { - Navigator.pop(context, Tuple2(_text.trim(), _link.trim())); + Navigator.pop(context, _TextLink(_text.trim(), _link.trim())); } } + +class _TextLink { + _TextLink( + this.text, + this.link, + ); + + final String text; + final String link; +} diff --git a/pubspec.yaml b/pubspec.yaml index 06f8fa10..95b4454b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 6.4.4 +version: 7.0.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill @@ -16,12 +16,11 @@ dependencies: flutter_colorpicker: ^1.0.3 flutter_keyboard_visibility: ^5.4.0 quiver: ^3.2.1 - tuple: ^2.0.1 url_launcher: ^6.1.9 pedantic: ^1.11.1 characters: ^1.2.1 diff_match_patch: ^0.4.1 - i18n_extension: ^7.0.0 + i18n_extension: ^6.0.0 device_info_plus: ^8.1.0 platform: ^3.1.0 pasteboard: ^0.2.0 From 3c64a08c6a436799f3eae3ada87a0168dc482d32 Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 12 Mar 2023 05:59:59 -0700 Subject: [PATCH 096/204] Reformat code --- lib/src/models/rules/insert.dart | 10 ++++----- lib/src/widgets/default_styles.dart | 21 ++++++------------- lib/src/widgets/raw_editor.dart | 3 +-- .../widgets/toolbar/link_style_button.dart | 10 ++++----- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index 93cf4742..8ef6c196 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -83,9 +83,8 @@ class PreserveBlockStyleOnInsertRule extends InsertRule { // Look for the next newline. final nextNewLine = _getNextNewLine(itr); - final lineStyle = - Style.fromJson( - nextNewLine.operation?.attributes ?? {}); + final lineStyle = Style.fromJson( + nextNewLine.operation?.attributes ?? {}); final blockStyle = lineStyle.getBlocksExceptHeader(); // Are we currently in a block? If not then ignore. @@ -189,8 +188,9 @@ class AutoExitBlockRule extends InsertRule { final nextNewLine = _getNextNewLine(itr); if (nextNewLine.operation != null && nextNewLine.operation!.attributes != null && - Style.fromJson(nextNewLine.operation!.attributes).getBlockExceptHeader() - == blockStyle) { + Style.fromJson(nextNewLine.operation!.attributes) + .getBlockExceptHeader() == + blockStyle) { // We are not at the end of this block, ignore. return null; } diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index 08acb154..f9fef5d0 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -241,11 +241,8 @@ class DefaultStyles { const VerticalSpacing(8, 0), const VerticalSpacing(0, 0), null), - paragraph: DefaultTextBlockStyle( - baseStyle, - const VerticalSpacing(0, 0), - const VerticalSpacing(0, 0), - null), + paragraph: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), + const VerticalSpacing(0, 0), null), bold: const TextStyle(fontWeight: FontWeight.bold), italic: const TextStyle(fontStyle: FontStyle.italic), small: const TextStyle(fontSize: 12), @@ -304,16 +301,10 @@ class DefaultStyles { )), indent: DefaultTextBlockStyle( baseStyle, baseSpacing, const VerticalSpacing(0, 6), null), - align: DefaultTextBlockStyle( - baseStyle, - const VerticalSpacing(0, 0), - const VerticalSpacing(0, 0), - null), - leading: DefaultTextBlockStyle( - baseStyle, - const VerticalSpacing(0, 0), - const VerticalSpacing(0, 0), - null), + align: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), + const VerticalSpacing(0, 0), null), + leading: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), + const VerticalSpacing(0, 0), null), sizeSmall: const TextStyle(fontSize: 10), sizeLarge: const TextStyle(fontSize: 18), sizeHuge: const TextStyle(fontSize: 22)); diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index f78b18c3..6220cbea 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1281,8 +1281,7 @@ 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)); diff --git a/lib/src/widgets/toolbar/link_style_button.dart b/lib/src/widgets/toolbar/link_style_button.dart index 80086608..f48a15e4 100644 --- a/lib/src/widgets/toolbar/link_style_button.dart +++ b/lib/src/widgets/toolbar/link_style_button.dart @@ -134,8 +134,8 @@ class _LinkStyleButtonState extends State { } } widget.controller.replaceText(index, length, value.text, null); - widget.controller.formatText( - index, value.text.length, LinkAttribute(value.link)); + widget.controller + .formatText(index, value.text.length, LinkAttribute(value.link)); } } @@ -242,9 +242,9 @@ class _LinkDialogState extends State<_LinkDialog> { class _TextLink { _TextLink( - this.text, - this.link, - ); + this.text, + this.link, + ); final String text; final String link; From 1be23063e01b9bb7198496d62d1ad254c18c4321 Mon Sep 17 00:00:00 2001 From: mark8044 <87546778+mark8044@users.noreply.github.com> Date: Sun, 12 Mar 2023 10:40:29 -0700 Subject: [PATCH 097/204] Update dependency (#1129) --- CHANGELOG.md | 3 +++ pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80ce4dae..fa6238a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.1] +* Update i18n_extension depedency to version 8.0.0 + # [7.0.0] * Breaking change: Tuples are no longer used. They have been replaced with a number of data classes. diff --git a/pubspec.yaml b/pubspec.yaml index 95b4454b..740a0696 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.0 +version: 7.0.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill @@ -20,7 +20,7 @@ dependencies: pedantic: ^1.11.1 characters: ^1.2.1 diff_match_patch: ^0.4.1 - i18n_extension: ^6.0.0 + i18n_extension: ^8.0.0 device_info_plus: ^8.1.0 platform: ^3.1.0 pasteboard: ^0.2.0 From 8f8d46637841500d4275a634695110c1b16d30ef Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 12 Mar 2023 10:50:04 -0700 Subject: [PATCH 098/204] Fix import --- lib/src/models/structs/doc_change.dart | 3 ++- lib/src/models/structs/segment_leaf_node.dart | 3 ++- lib/src/widgets/delegate.dart | 5 ++++- lib/src/widgets/raw_editor.dart | 2 +- lib/src/widgets/toolbar/clear_format_button.dart | 5 ++++- lib/src/widgets/toolbar/history_button.dart | 4 +++- lib/src/widgets/toolbar/indent_button.dart | 4 +++- 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/src/models/structs/doc_change.dart b/lib/src/models/structs/doc_change.dart index 6c999331..d2772a59 100644 --- a/lib/src/models/structs/doc_change.dart +++ b/lib/src/models/structs/doc_change.dart @@ -1,4 +1,5 @@ -import '../../../flutter_quill.dart'; +import '../documents/document.dart'; +import '../quill_delta.dart'; class DocChange { DocChange( diff --git a/lib/src/models/structs/segment_leaf_node.dart b/lib/src/models/structs/segment_leaf_node.dart index 676f037f..43921b93 100644 --- a/lib/src/models/structs/segment_leaf_node.dart +++ b/lib/src/models/structs/segment_leaf_node.dart @@ -1,4 +1,5 @@ -import '../../../flutter_quill.dart'; +import '../documents/nodes/leaf.dart'; +import '../documents/nodes/line.dart'; class SegmentLeafNode { const SegmentLeafNode(this.line, this.leaf); diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index ce8e65a3..63f446fe 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -3,8 +3,11 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import '../../flutter_quill.dart'; +import '../models/documents/attribute.dart'; +import '../models/documents/nodes/leaf.dart'; import '../utils/platform.dart'; +import 'controller.dart'; +import 'editor.dart'; import 'text_selection.dart'; typedef EmbedsBuilder = Widget Function( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 6220cbea..b0e2ea0d 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -13,7 +13,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:pasteboard/pasteboard.dart'; -import '../../flutter_quill.dart'; import '../models/documents/attribute.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/block.dart'; @@ -22,6 +21,7 @@ import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/nodes/line.dart'; import '../models/documents/nodes/node.dart'; import '../models/documents/style.dart'; +import '../models/structs/offset_value.dart'; import '../models/structs/vertical_spacing.dart'; import '../utils/cast.dart'; import '../utils/delta.dart'; diff --git a/lib/src/widgets/toolbar/clear_format_button.dart b/lib/src/widgets/toolbar/clear_format_button.dart index 2e98a0f8..f601bd28 100644 --- a/lib/src/widgets/toolbar/clear_format_button.dart +++ b/lib/src/widgets/toolbar/clear_format_button.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; -import '../../../flutter_quill.dart'; +import '../../models/documents/attribute.dart'; +import '../../models/themes/quill_icon_theme.dart'; +import '../controller.dart'; +import '../toolbar.dart'; class ClearFormatButton extends StatefulWidget { const ClearFormatButton({ diff --git a/lib/src/widgets/toolbar/history_button.dart b/lib/src/widgets/toolbar/history_button.dart index dafd4ca3..a3abc7f2 100644 --- a/lib/src/widgets/toolbar/history_button.dart +++ b/lib/src/widgets/toolbar/history_button.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; -import '../../../flutter_quill.dart'; +import '../../models/themes/quill_icon_theme.dart'; +import '../controller.dart'; +import '../toolbar.dart'; class HistoryButton extends StatefulWidget { const HistoryButton({ diff --git a/lib/src/widgets/toolbar/indent_button.dart b/lib/src/widgets/toolbar/indent_button.dart index 24dac736..129ef8b1 100644 --- a/lib/src/widgets/toolbar/indent_button.dart +++ b/lib/src/widgets/toolbar/indent_button.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; -import '../../../flutter_quill.dart'; +import '../../models/themes/quill_icon_theme.dart'; +import '../controller.dart'; +import '../toolbar.dart'; class IndentButton extends StatefulWidget { const IndentButton({ From 0ee17ecf3572c68a80a73df63cae730cbc77b7bc Mon Sep 17 00:00:00 2001 From: austinstoker Date: Sat, 18 Mar 2023 14:46:17 -0600 Subject: [PATCH 099/204] Fix the demo page so adding custom notes works on the web version. (#1134) Co-authored-by: Austin Stoker --- example/lib/pages/home_page.dart | 56 +++++++++++++++++--------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 6ed01d74..1aa7566a 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -198,33 +198,35 @@ class _HomePageState extends State { quillEditor = MouseRegion( cursor: SystemMouseCursors.text, child: QuillEditor( - controller: _controller!, - scrollController: ScrollController(), - scrollable: true, - focusNode: _focusNode, - autoFocus: false, - readOnly: false, - placeholder: 'Add content', - expands: false, - padding: EdgeInsets.zero, - onTapUp: (details, p1) { - return _onTripleClickSelection(); - }, - customStyles: DefaultStyles( - h1: DefaultTextBlockStyle( - const TextStyle( - fontSize: 32, - color: Colors.black, - height: 1.15, - fontWeight: FontWeight.w300, - ), - const VerticalSpacing(16, 0), - const VerticalSpacing(0, 0), - null), - sizeSmall: const TextStyle(fontSize: 9), - ), - embedBuilders: defaultEmbedBuildersWeb, - ), + controller: _controller!, + scrollController: ScrollController(), + scrollable: true, + focusNode: _focusNode, + autoFocus: false, + readOnly: false, + placeholder: 'Add content', + expands: false, + padding: EdgeInsets.zero, + onTapUp: (details, p1) { + return _onTripleClickSelection(); + }, + customStyles: DefaultStyles( + h1: DefaultTextBlockStyle( + const TextStyle( + fontSize: 32, + color: Colors.black, + height: 1.15, + fontWeight: FontWeight.w300, + ), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), + null), + sizeSmall: const TextStyle(fontSize: 9), + ), + embedBuilders: [ + ...defaultEmbedBuildersWeb, + NotesEmbedBuilder(addEditNote: _addEditNote), + ]), ); } var toolbar = QuillToolbar.basic( From 41367f15954ece2f1a4ee1bed88e18f38de6df93 Mon Sep 17 00:00:00 2001 From: DINO SOLANO Date: Mon, 20 Mar 2023 12:21:22 +0100 Subject: [PATCH 100/204] added translations italian (#1137) --- lib/src/translations/toolbar.i18n.dart | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 252421ae..055daea5 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -934,6 +934,40 @@ extension Localization on String { 'Camera': 'Kamera', 'Video': 'Video', }, + 'it': { + 'Paste a link': 'Incolla un collegamento', + 'Ok': 'Ok', + 'Select Color': 'Seleziona Colore', + 'Gallery': 'Galleria', + 'Link': 'Collegamento', + 'Please first select some text to transform into a link.': + 'Per prima cosa seleziona del testo da trasformare in un link.', + 'Open': 'Apri', + 'Copy': 'Copia', + 'Remove': 'Rimuovi', + 'Save': 'Salva', + 'Zoom': 'Ingrandisci', + 'Saved': 'Salvato', + 'Text': 'Testo', + 'What is entered is not a link': + 'Ciò che viene inserito non è un collegamento', + 'Resize': 'Ridimensiona', + 'Width': 'Larghezza', + 'Height': 'Altezza', + 'Size': 'Dimensione', + 'Small': 'Piccolo', + 'Large': 'Largo', + 'Huge': 'Enorme', + 'Clear': 'Cancella', + 'Font': 'Font', + 'Search': 'Ricerca', + 'matches': 'corrispondenze', + 'showing match': 'visualizza corrispondenza', + 'Prev': 'Prec', + 'Next': 'Succ', + 'Camera': 'Camera', + 'Video': 'Video', + }, }; String get i18n => localize(this, _t); From 34cba178f4e98cc49c3f784dc6f57730908cadbf Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 20 Mar 2023 04:47:05 -0700 Subject: [PATCH 101/204] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd772c47..285141a3 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -Currently, translations are available for these 26 locales: +Currently, translations are available for these 27 locales: * `Locale('en')` * `Locale('ar')` @@ -367,6 +367,7 @@ Currently, translations are available for these 26 locales: * `Locale('pl')` * `Locale('vi')` * `Locale('id')` +* `Locale('it')` * `Locale('ms')` * `Locale('nl')` * `Locale('no')` From b7951b02c9086ea42e7aad6d78e6c9b0297562e5 Mon Sep 17 00:00:00 2001 From: Bertrand Date: Sat, 25 Mar 2023 14:18:28 +0100 Subject: [PATCH 102/204] allow widgets to override widget span properties (#1141) --- README.md | 3 +- doc_cn.md | 3 +- example/lib/pages/home_page.dart | 3 +- example/lib/universal_ui/universal_ui.dart | 14 +++-- .../lib/embeds/builders.dart | 9 ++- lib/src/widgets/delegate.dart | 8 +-- lib/src/widgets/editor.dart | 38 +++---------- lib/src/widgets/embeds.dart | 6 ++ lib/src/widgets/text_line.dart | 56 ++++++++++++------- 9 files changed, 73 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 285141a3..d31e9a58 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ After that, we need to map this "notes" type into a widget. In that case, I used Don't forget to add this method to the `QuillEditor` after that! ```dart -class NotesEmbedBuilder implements EmbedBuilder { +class NotesEmbedBuilder extends EmbedBuilder { NotesEmbedBuilder({required this.addEditNote}); Future Function(BuildContext context, {Document? document}) addEditNote; @@ -255,6 +255,7 @@ class NotesEmbedBuilder implements EmbedBuilder { QuillController controller, Embed node, bool readOnly, + bool inline, ) { final notes = NotesBlockEmbed(node.value.data).document; diff --git a/doc_cn.md b/doc_cn.md index d76f4c2e..e4ba57af 100644 --- a/doc_cn.md +++ b/doc_cn.md @@ -246,7 +246,7 @@ class NotesBlockEmbed extends CustomBlockEmbed { 在这里我们使用 `ListTile` 来渲染它,并使用 `onTap` 方法来编辑内容,最后不要忘记将此方法添加到 `QuillEditor` 中 ```dart -class NotesEmbedBuilder implements EmbedBuilder { +class NotesEmbedBuilder extends EmbedBuilder { NotesEmbedBuilder({required this.addEditNote}); Future Function(BuildContext context, {Document? document}) addEditNote; @@ -260,6 +260,7 @@ class NotesEmbedBuilder implements EmbedBuilder { QuillController controller, Embed node, bool readOnly, + bool inline, ) { final notes = NotesBlockEmbed(node.value.data).document; diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 1aa7566a..40449aeb 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -487,7 +487,7 @@ class _HomePageState extends State { } } -class NotesEmbedBuilder implements EmbedBuilder { +class NotesEmbedBuilder extends EmbedBuilder { NotesEmbedBuilder({required this.addEditNote}); Future Function(BuildContext context, {Document? document}) addEditNote; @@ -501,6 +501,7 @@ class NotesEmbedBuilder implements EmbedBuilder { QuillController controller, Embed node, bool readOnly, + bool inline, ) { final notes = NotesBlockEmbed(node.value.data).document; diff --git a/example/lib/universal_ui/universal_ui.dart b/example/lib/universal_ui/universal_ui.dart index d18e7fbd..7b35f1f1 100644 --- a/example/lib/universal_ui/universal_ui.dart +++ b/example/lib/universal_ui/universal_ui.dart @@ -27,7 +27,7 @@ class UniversalUI { var ui = UniversalUI(); -class ImageEmbedBuilderWeb implements EmbedBuilder { +class ImageEmbedBuilderWeb extends EmbedBuilder { @override String get key => BlockEmbed.imageType; @@ -37,6 +37,7 @@ class ImageEmbedBuilderWeb implements EmbedBuilder { QuillController controller, Embed node, bool readOnly, + bool inline, ) { final imageUrl = node.value.data; if (isImageBase64(imageUrl)) { @@ -68,13 +69,18 @@ class ImageEmbedBuilderWeb implements EmbedBuilder { } } -class VideoEmbedBuilderWeb implements EmbedBuilder { +class VideoEmbedBuilderWeb extends EmbedBuilder { @override String get key => BlockEmbed.videoType; @override - Widget build(BuildContext context, QuillController controller, Embed node, - bool readOnly) { + Widget build( + BuildContext context, + QuillController controller, + Embed node, + bool readOnly, + bool inline, + ) { var videoUrl = node.value.data; if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { final youtubeID = YoutubePlayer.convertUrlToId(videoUrl); diff --git a/flutter_quill_extensions/lib/embeds/builders.dart b/flutter_quill_extensions/lib/embeds/builders.dart index 4e7ebcbc..4888da9f 100644 --- a/flutter_quill_extensions/lib/embeds/builders.dart +++ b/flutter_quill_extensions/lib/embeds/builders.dart @@ -14,7 +14,7 @@ import 'widgets/image_resizer.dart'; import 'widgets/video_app.dart'; import 'widgets/youtube_video_app.dart'; -class ImageEmbedBuilder implements EmbedBuilder { +class ImageEmbedBuilder extends EmbedBuilder { @override String get key => BlockEmbed.imageType; @@ -24,6 +24,7 @@ class ImageEmbedBuilder implements EmbedBuilder { QuillController controller, base.Embed node, bool readOnly, + bool inline, ) { assert(!kIsWeb, 'Please provide image EmbedBuilder for Web'); @@ -144,7 +145,7 @@ class ImageEmbedBuilder implements EmbedBuilder { } } -class VideoEmbedBuilder implements EmbedBuilder { +class VideoEmbedBuilder extends EmbedBuilder { VideoEmbedBuilder({this.onVideoInit}); final void Function(GlobalKey videoContainerKey)? onVideoInit; @@ -158,6 +159,7 @@ class VideoEmbedBuilder implements EmbedBuilder { QuillController controller, base.Embed node, bool readOnly, + bool inline, ) { assert(!kIsWeb, 'Please provide video EmbedBuilder for Web'); @@ -175,7 +177,7 @@ class VideoEmbedBuilder implements EmbedBuilder { } } -class FormulaEmbedBuilder implements EmbedBuilder { +class FormulaEmbedBuilder extends EmbedBuilder { @override String get key => BlockEmbed.formulaType; @@ -185,6 +187,7 @@ class FormulaEmbedBuilder implements EmbedBuilder { QuillController controller, base.Embed node, bool readOnly, + bool inline, ) { assert(!kIsWeb, 'Please provide formula EmbedBuilder for Web'); diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index 63f446fe..06fc31cb 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -8,14 +8,10 @@ import '../models/documents/nodes/leaf.dart'; import '../utils/platform.dart'; import 'controller.dart'; import 'editor.dart'; +import 'embeds.dart'; import 'text_selection.dart'; -typedef EmbedsBuilder = Widget Function( - BuildContext context, - QuillController controller, - Embed node, - bool readOnly, -); +typedef EmbedsBuilder = EmbedBuilder Function(Embed node); typedef CustomStyleBuilder = TextStyle Function(Attribute attribute); diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 7246a881..f15210ed 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -363,7 +363,7 @@ class QuillEditor extends StatefulWidget { onSingleLongTapEnd; final Iterable? embedBuilders; - final EmbedsBuilder? unknownEmbedBuilder; + final EmbedBuilder? unknownEmbedBuilder; final CustomStyleBuilder? customStyleBuilder; /// The locale to use for the editor toolbar, defaults to system locale @@ -492,19 +492,7 @@ class QuillEditorState extends State keyboardAppearance: widget.keyboardAppearance, enableInteractiveSelection: widget.enableInteractiveSelection, scrollPhysics: widget.scrollPhysics, - embedBuilder: ( - context, - controller, - node, - readOnly, - ) => - _buildCustomBlockEmbed( - node, - context, - controller, - readOnly, - widget.unknownEmbedBuilder, - ), + embedBuilder: _getEmbedBuilder, linkActionPickerDelegate: widget.linkActionPickerDelegate, customStyleBuilder: widget.customStyleBuilder, floatingCursorDisabled: widget.floatingCursorDisabled, @@ -541,31 +529,19 @@ class QuillEditorState extends State return editor; } - Widget _buildCustomBlockEmbed( - Embed node, - BuildContext context, - QuillController controller, - bool readOnly, - EmbedsBuilder? unknownEmbedBuilder, - ) { + EmbedBuilder _getEmbedBuilder(Embed node) { final builders = widget.embedBuilders; - var _node = node; - // Creates correct node for custom embed - if (node.value.type == BlockEmbed.customType) { - _node = Embed(CustomBlockEmbed.fromJsonString(node.value.data)); - } - if (builders != null) { for (final builder in builders) { - if (builder.key == _node.value.type) { - return builder.build(context, controller, _node, readOnly); + if (builder.key == node.value.type) { + return builder; } } } - if (unknownEmbedBuilder != null) { - return unknownEmbedBuilder(context, controller, _node, readOnly); + if (widget.unknownEmbedBuilder != null) { + return widget.unknownEmbedBuilder!; } throw UnimplementedError( diff --git a/lib/src/widgets/embeds.dart b/lib/src/widgets/embeds.dart index c0262a08..8cfe2657 100644 --- a/lib/src/widgets/embeds.dart +++ b/lib/src/widgets/embeds.dart @@ -7,12 +7,18 @@ import 'controller.dart'; abstract class EmbedBuilder { String get key; + bool get expanded => true; + + WidgetSpan buildWidgetSpan(Widget widget) { + return WidgetSpan(child: widget); + } Widget build( BuildContext context, QuillController controller, leaf.Embed node, bool readOnly, + bool inline, ); } diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 0f2995bb..3d4e02e0 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../models/documents/attribute.dart'; import '../models/documents/nodes/container.dart' as container_node; +import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/leaf.dart'; import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/nodes/line.dart'; @@ -132,17 +133,28 @@ class _TextLineState extends State { @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); + if (widget.line.hasEmbed && widget.line.childCount == 1) { - // For video, it is always single child - final embed = widget.line.children.single as Embed; - return EmbedProxy( - widget.embedBuilder( - context, - widget.controller, - embed, - widget.readOnly, - ), - ); + // Single child embeds can be expanded + var embed = widget.line.children.single as Embed; + // Creates correct node for custom embed + if (embed.value.type == BlockEmbed.customType) { + embed = Embed(CustomBlockEmbed.fromJsonString(embed.value.data)); + } + final embedBuilder = widget.embedBuilder(embed); + if (embedBuilder.expanded) { + // Creates correct node for custom embed + + return EmbedProxy( + embedBuilder.build( + context, + widget.controller, + embed, + widget.readOnly, + false, + ), + ); + } } final textSpan = _getTextSpanForWholeLine(context); final strutStyle = StrutStyle.fromTextStyle(textSpan.style!); @@ -173,24 +185,28 @@ class _TextLineState extends State { // The line could contain more than one Embed & more than one Text final textSpanChildren = []; var textNodes = LinkedList(); - for (final child in widget.line.children) { + for (var child in widget.line.children) { if (child is Embed) { if (textNodes.isNotEmpty) { textSpanChildren .add(_buildTextSpan(widget.styles, textNodes, lineStyle)); textNodes = LinkedList(); } - // Here it should be image - final embed = WidgetSpan( - child: EmbedProxy( - widget.embedBuilder( - context, - widget.controller, - child, - widget.readOnly, - ), + // Creates correct node for custom embed + if (child.value.type == BlockEmbed.customType) { + child = Embed(CustomBlockEmbed.fromJsonString(child.value.data)); + } + final embedBuilder = widget.embedBuilder(child); + final embedWidget = EmbedProxy( + embedBuilder.build( + context, + widget.controller, + child, + widget.readOnly, + true, ), ); + final embed = embedBuilder.buildWidgetSpan(embedWidget); textSpanChildren.add(embed); continue; } From f5f0c2d2d2f3ba52c28023edba76510b3cb6835d Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 25 Mar 2023 08:09:50 -0700 Subject: [PATCH 103/204] Upgrade to 7.0.2 --- CHANGELOG.md | 5 ++++- flutter_quill_extensions/pubspec.yaml | 2 +- lib/src/translations/toolbar.i18n.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6238a1..da1b5685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ +# [7.0.2] +* Allow widgets to override widget span properties. + # [7.0.1] -* Update i18n_extension depedency to version 8.0.0 +* Update i18n_extension dependency to version 8.0.0. # [7.0.0] * Breaking change: Tuples are no longer used. They have been replaced with a number of data classes. diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 5abc6d38..937fa233 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.0.0 + flutter_quill: ^7.0.2 image_picker: ^0.8.5+3 photo_view: ^0.14.0 diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 055daea5..2abcfb21 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -949,7 +949,7 @@ extension Localization on String { 'Zoom': 'Ingrandisci', 'Saved': 'Salvato', 'Text': 'Testo', - 'What is entered is not a link': + 'What is entered is not a link': 'Ciò che viene inserito non è un collegamento', 'Resize': 'Ridimensiona', 'Width': 'Larghezza', diff --git a/pubspec.yaml b/pubspec.yaml index 740a0696..06abf760 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.1 +version: 7.0.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From dfa2070e1c2b9224da3f5e2f0d897ee6e2a6f203 Mon Sep 17 00:00:00 2001 From: X Code Date: Sat, 25 Mar 2023 09:14:28 -0700 Subject: [PATCH 104/204] Clean up code --- lib/src/widgets/delegate.dart | 1 - lib/src/widgets/editor.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index 06fc31cb..fa5dbe1f 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -6,7 +6,6 @@ import 'package:flutter/scheduler.dart'; import '../models/documents/attribute.dart'; import '../models/documents/nodes/leaf.dart'; import '../utils/platform.dart'; -import 'controller.dart'; import 'editor.dart'; import 'embeds.dart'; import 'text_selection.dart'; diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index f15210ed..e733c0e0 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -12,7 +12,6 @@ import 'package:i18n_extension/i18n_widget.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/container.dart' as container_node; -import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/leaf.dart'; import '../models/documents/style.dart'; import '../models/structs/offset_value.dart'; From f3b0fc83f65ee1ff43f019c44cb2672347329de3 Mon Sep 17 00:00:00 2001 From: spChief Date: Mon, 27 Mar 2023 11:29:03 +0700 Subject: [PATCH 105/204] fix ordered list numeration for lists with more than one level of list (#1142) --- lib/src/widgets/style_widgets/number_point.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/style_widgets/number_point.dart b/lib/src/widgets/style_widgets/number_point.dart index f1ffddf1..fed46603 100644 --- a/lib/src/widgets/style_widgets/number_point.dart +++ b/lib/src/widgets/style_widgets/number_point.dart @@ -30,7 +30,7 @@ class QuillNumberPoint extends StatelessWidget { var s = index.toString(); int? level = 0; if (!attrs.containsKey(Attribute.indent.key) && - !indentLevelCounts.containsKey(1)) { + indentLevelCounts.isEmpty) { indentLevelCounts.clear(); return Container( alignment: AlignmentDirectional.topEnd, @@ -41,7 +41,7 @@ class QuillNumberPoint extends StatelessWidget { } if (attrs.containsKey(Attribute.indent.key)) { level = attrs[Attribute.indent.key]!.value; - } else { + } else if (!indentLevelCounts.containsKey(0)) { // first level but is back from previous indent level // supposed to be "2." indentLevelCounts[0] = 1; From 4c005e2a6134d358acb28854da5edc1aae7660e0 Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 26 Mar 2023 21:30:59 -0700 Subject: [PATCH 106/204] Upgrade to 7.0.3 --- CHANGELOG.md | 3 +++ lib/src/widgets/style_widgets/number_point.dart | 3 +-- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da1b5685..2971f876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.3] +* Fix ordered list numeration for lists with more than one level of list. + # [7.0.2] * Allow widgets to override widget span properties. diff --git a/lib/src/widgets/style_widgets/number_point.dart b/lib/src/widgets/style_widgets/number_point.dart index fed46603..1dbf2261 100644 --- a/lib/src/widgets/style_widgets/number_point.dart +++ b/lib/src/widgets/style_widgets/number_point.dart @@ -29,8 +29,7 @@ class QuillNumberPoint extends StatelessWidget { Widget build(BuildContext context) { var s = index.toString(); int? level = 0; - if (!attrs.containsKey(Attribute.indent.key) && - indentLevelCounts.isEmpty) { + if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { indentLevelCounts.clear(); return Container( alignment: AlignmentDirectional.topEnd, diff --git a/pubspec.yaml b/pubspec.yaml index 06abf760..60ce2a16 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.2 +version: 7.0.3 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From b389792002f3fada94695eddb29363e3297fc5d3 Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Wed, 29 Mar 2023 22:56:57 +0800 Subject: [PATCH 107/204] Update doc_cn.md (#1145) --- doc_cn.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc_cn.md b/doc_cn.md index e4ba57af..0286abf3 100644 --- a/doc_cn.md +++ b/doc_cn.md @@ -356,7 +356,7 @@ QuillToolbar(locale: Locale('fr'), ...) QuillEditor(locale: Locale('fr'), ...) ``` -目前,可提供以下 26 种语言环境的翻译: +目前,可提供以下 27 种语言环境的翻译: * `Locale('en')` * `Locale('ar')` @@ -377,6 +377,7 @@ QuillEditor(locale: Locale('fr'), ...) * `Locale('pl')` * `Locale('vi')` * `Locale('id')` +* `Locale('it')` * `Locale('ms')` * `Locale('nl')` * `Locale('no')` From 4e11298d4df04d722d64e4f05258428387128913 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Fri, 31 Mar 2023 17:36:44 -0400 Subject: [PATCH 108/204] Highlight the beginning selected, empty lines. (#1149) --- lib/src/widgets/text_line.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 3d4e02e0..2d743d3b 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -1107,6 +1107,19 @@ class RenderEditableTextLine extends RenderEditableBox { _selectedRects ??= _body!.getBoxesForSelection( local, ); + + // Paint a small rect at the start of empty lines that + // are contained by the selection. + if (line.isEmpty && + textSelection.baseOffset <= line.offset && + textSelection.extentOffset > line.offset + ) { + final lineHeight = + preferredLineHeight(TextPosition(offset: line.offset)); + _selectedRects?.add( + TextBox.fromLTRBD(0, 0, 3, lineHeight, textDirection)); + } + _paintSelection(context, effectiveOffset); } } From 5b482e0668a1d27086a289eb58f02f6bed9a7dd1 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Fri, 31 Mar 2023 17:36:59 -0400 Subject: [PATCH 109/204] Have text selection span full line height for uneven sized text. (#1150) --- lib/src/widgets/proxy.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/proxy.dart b/lib/src/widgets/proxy.dart index 8f4d231f..155e620a 100644 --- a/lib/src/widgets/proxy.dart +++ b/lib/src/widgets/proxy.dart @@ -290,7 +290,7 @@ class RenderParagraphProxy extends RenderProxyBox @override List getBoxesForSelection(TextSelection selection) => child! - .getBoxesForSelection(selection, boxHeightStyle: BoxHeightStyle.strut); + .getBoxesForSelection(selection, boxHeightStyle: BoxHeightStyle.max); @override void performLayout() { From 2c0a21a6383d4b730439a03eca37bd76a4cd5c2a Mon Sep 17 00:00:00 2001 From: X Code Date: Fri, 31 Mar 2023 20:01:11 -0700 Subject: [PATCH 110/204] Upgrade to 7.0.4 --- CHANGELOG.md | 3 +++ lib/src/widgets/text_line.dart | 11 +++++------ pubspec.yaml | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2971f876..4da7c3c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.4] +* Have text selection span full line height for uneven sized text. + # [7.0.3] * Fix ordered list numeration for lists with more than one level of list. diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 2d743d3b..b7174a2f 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -1111,13 +1111,12 @@ class RenderEditableTextLine extends RenderEditableBox { // Paint a small rect at the start of empty lines that // are contained by the selection. if (line.isEmpty && - textSelection.baseOffset <= line.offset && - textSelection.extentOffset > line.offset - ) { + textSelection.baseOffset <= line.offset && + textSelection.extentOffset > line.offset) { final lineHeight = - preferredLineHeight(TextPosition(offset: line.offset)); - _selectedRects?.add( - TextBox.fromLTRBD(0, 0, 3, lineHeight, textDirection)); + preferredLineHeight(TextPosition(offset: line.offset)); + _selectedRects + ?.add(TextBox.fromLTRBD(0, 0, 3, lineHeight, textDirection)); } _paintSelection(context, effectiveOffset); diff --git a/pubspec.yaml b/pubspec.yaml index 60ce2a16..b4a3eb33 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.3 +version: 7.0.4 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 1fbf8da48ba6942630e4a4a31611bb34dd5892b8 Mon Sep 17 00:00:00 2001 From: Jon Salmon <26483285+Jon-Salmon@users.noreply.github.com> Date: Sat, 1 Apr 2023 12:46:54 +0100 Subject: [PATCH 111/204] Release notes for 0.2.0 of flutter_quill_extensions (#1151) --- flutter_quill_extensions/CHANGELOG.md | 8 ++++++++ flutter_quill_extensions/pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index 13187808..442fb43d 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.2.0 + +* Allow widgets to override widget span properties [b7951b0](https://github.com/singerdmx/flutter-quill/commit/b7951b02c9086ea42e7aad6d78e6c9b0297562e5) +* Remove tuples [3e9452e](https://github.com/singerdmx/flutter-quill/commit/3e9452e675e8734ff50364c5f7b5d34088d5ff05) +* Remove transparent color of ImageVideoUtils dialog [74544bd](https://github.com/singerdmx/flutter-quill/commit/74544bd945a9d212ca1e8d6b3053dbecee22b720) +* Migrate to `youtube_player_flutter` from `youtube_player_flutter_quill` +* Updates to forumla button [5228f38](https://github.com/singerdmx/flutter-quill/commit/5228f389ba6f37d61d445cfe138c19fcf8766d71) + ## 0.1.0 * Initial release diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 937fa233..c8392dfb 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: 0.1.0 +version: 0.2.0 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions From c44dbec8f702f169590767e69f17c6db72c4d982 Mon Sep 17 00:00:00 2001 From: Jonathan Salmon Date: Sat, 1 Apr 2023 12:50:56 +0100 Subject: [PATCH 112/204] Add url_launcher to pubspec for flutter_quill_extensions --- flutter_quill_extensions/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index c8392dfb..5ca94b5c 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: gallery_saver: ^2.3.2 math_keyboard: ^0.1.8 string_validator: ^1.0.0 + url_launcher: ^6.1.9 # dependency_overrides: # flutter_quill: From 1f83bbd059253b83a595e74f5ab1294a77019418 Mon Sep 17 00:00:00 2001 From: Pwiz Date: Sun, 2 Apr 2023 23:51:08 +0800 Subject: [PATCH 113/204] Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac. (#1152) --- lib/src/widgets/raw_editor.dart | 184 ++++++++++++++++++++------------ 1 file changed, 113 insertions(+), 71 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index b0e2ea0d..444533a6 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:ui' as ui hide TextStyle; import 'dart:math' as math; // ignore: unnecessary_import import 'dart:typed_data'; @@ -371,6 +372,40 @@ class RawEditorState extends EditorState ); } + void _defaultOnTapOutside(PointerDownEvent event) { + /// The focus dropping behavior is only present on desktop platforms + /// and mobile browsers. + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + // On mobile platforms, we don't unfocus on touch events unless they're + // in the web browser, but we do unfocus for all other kinds of events. + switch (event.kind) { + case ui.PointerDeviceKind.touch: + if (kIsWeb) { + widget.focusNode.unfocus(); + } + break; + case ui.PointerDeviceKind.mouse: + case ui.PointerDeviceKind.stylus: + case ui.PointerDeviceKind.invertedStylus: + case ui.PointerDeviceKind.unknown: + widget.focusNode.unfocus(); + break; + case ui.PointerDeviceKind.trackpad: + throw UnimplementedError( + 'Unexpected pointer down event for trackpad'); + } + break; + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + widget.focusNode.unfocus(); + break; + } + } + @override Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); @@ -454,78 +489,85 @@ class RawEditorState extends EditorState minHeight: widget.minHeight ?? 0.0, maxHeight: widget.maxHeight ?? double.infinity); - return QuillStyles( - data: _styles!, - child: Shortcuts( - shortcuts: { - // shortcuts added for Desktop platforms. - LogicalKeySet(LogicalKeyboardKey.escape): - const HideSelectionToolbarIntent(), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyZ): - const UndoTextIntent(SelectionChangedCause.keyboard), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyY): - const RedoTextIntent(SelectionChangedCause.keyboard), - - // Selection formatting. - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyB): - const ToggleTextStyleIntent(Attribute.bold), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyU): - const ToggleTextStyleIntent(Attribute.underline), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyI): - const ToggleTextStyleIntent(Attribute.italic), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyS): - const ToggleTextStyleIntent(Attribute.strikeThrough), - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.backquote): - const ToggleTextStyleIntent(Attribute.inlineCode), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyL): - const ToggleTextStyleIntent(Attribute.ul), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyO): - const ToggleTextStyleIntent(Attribute.ol), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyB): - const ToggleTextStyleIntent(Attribute.blockQuote), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.tilde): - const ToggleTextStyleIntent(Attribute.codeBlock), - // Indent - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.bracketRight): - const IndentSelectionIntent(true), - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.bracketLeft): - const IndentSelectionIntent(false), - - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF): - const OpenSearchIntent(), - - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit1): - const ApplyHeaderIntent(Attribute.h1), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit2): - const ApplyHeaderIntent(Attribute.h2), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit3): - const ApplyHeaderIntent(Attribute.h3), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.digit0): - const ApplyHeaderIntent(Attribute.header), - - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyL): const ApplyCheckListIntent(), - - if (widget.customShortcuts != null) ...widget.customShortcuts!, - }, - child: Actions( - actions: { - ..._actions, - if (widget.customActions != null) ...widget.customActions!, + return TextFieldTapRegion( + onTapOutside: _defaultOnTapOutside, + child: QuillStyles( + data: _styles!, + child: Shortcuts( + shortcuts: { + // shortcuts added for Desktop platforms. + LogicalKeySet(LogicalKeyboardKey.escape): + const HideSelectionToolbarIntent(), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyZ): + const UndoTextIntent(SelectionChangedCause.keyboard), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyY): + const RedoTextIntent(SelectionChangedCause.keyboard), + + // Selection formatting. + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyB): + const ToggleTextStyleIntent(Attribute.bold), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyU): + const ToggleTextStyleIntent(Attribute.underline), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyI): + const ToggleTextStyleIntent(Attribute.italic), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyS): + const ToggleTextStyleIntent(Attribute.strikeThrough), + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.backquote): + const ToggleTextStyleIntent(Attribute.inlineCode), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyL): + const ToggleTextStyleIntent(Attribute.ul), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyO): + const ToggleTextStyleIntent(Attribute.ol), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyB): + const ToggleTextStyleIntent(Attribute.blockQuote), + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.tilde): + const ToggleTextStyleIntent(Attribute.codeBlock), + // Indent + LogicalKeySet(LogicalKeyboardKey.control, + LogicalKeyboardKey.bracketRight): + const IndentSelectionIntent(true), + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.bracketLeft): + const IndentSelectionIntent(false), + + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF): + const OpenSearchIntent(), + + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.digit1): + const ApplyHeaderIntent(Attribute.h1), + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.digit2): + const ApplyHeaderIntent(Attribute.h2), + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.digit3): + const ApplyHeaderIntent(Attribute.h3), + LogicalKeySet( + LogicalKeyboardKey.control, LogicalKeyboardKey.digit0): + const ApplyHeaderIntent(Attribute.header), + + LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyL): const ApplyCheckListIntent(), + + if (widget.customShortcuts != null) ...widget.customShortcuts!, }, - child: Focus( - focusNode: widget.focusNode, - onKey: _onKey, - child: QuillKeyboardListener( - child: Container( - constraints: constraints, - child: child, + child: Actions( + actions: { + ..._actions, + if (widget.customActions != null) ...widget.customActions!, + }, + child: Focus( + focusNode: widget.focusNode, + onKey: _onKey, + child: QuillKeyboardListener( + child: Container( + constraints: constraints, + child: child, + ), ), ), ), From 8163cd89abc6681d5ed3a26f1027a639d92630b5 Mon Sep 17 00:00:00 2001 From: Pwiz Date: Sun, 2 Apr 2023 23:52:06 +0800 Subject: [PATCH 114/204] Fix IME position bug for Mac and Windows (#1154) --- ..._editor_state_text_input_client_mixin.dart | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index 5eced2bf..5f98d56a 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -63,12 +63,44 @@ mixin RawEditorStateTextInputClientMixin on EditorState ); _updateSizeAndTransform(); + //update IME position for Windows + _updateComposingRectIfNeeded(); + //update IME position for Macos + _updateCaretRectIfNeeded(); _textInputConnection!.setEditingState(_lastKnownRemoteTextEditingValue!); } - _textInputConnection!.show(); } + void _updateComposingRectIfNeeded() { + final composingRange = _lastKnownRemoteTextEditingValue?.composing ?? + textEditingValue.composing; + if (hasConnection) { + assert(mounted); + final offset = composingRange.isValid ? composingRange.start : 0; + final composingRect = + renderEditor.getLocalRectForCaret(TextPosition(offset: offset)); + _textInputConnection!.setComposingRect(composingRect); + SchedulerBinding.instance + .addPostFrameCallback((_) => _updateComposingRectIfNeeded()); + } + } + + void _updateCaretRectIfNeeded() { + if (hasConnection) { + if (renderEditor.selection.isValid && + renderEditor.selection.isCollapsed) { + final currentTextPosition = + TextPosition(offset: renderEditor.selection.baseOffset); + final caretRect = + renderEditor.getLocalRectForCaret(currentTextPosition); + _textInputConnection!.setCaretRect(caretRect); + } + SchedulerBinding.instance + .addPostFrameCallback((_) => _updateCaretRectIfNeeded()); + } + } + /// Closes input connection if it's currently open. Otherwise does nothing. void closeConnectionIfNeeded() { if (!hasConnection) { From 36df0e3971f58cc99f0c8d53ccaee8241281a77a Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 2 Apr 2023 09:02:26 -0700 Subject: [PATCH 115/204] Upgrade to 7.0.5 --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da7c3c4..10d679ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [7.0.5] +* Fix IME position bug for Mac and Windows. +* Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac. + # [7.0.4] * Have text selection span full line height for uneven sized text. diff --git a/pubspec.yaml b/pubspec.yaml index b4a3eb33..6b051d89 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.4 +version: 7.0.5 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 95b8f24a8dbed0617c0f152211425abcde6d1858 Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 2 Apr 2023 09:03:45 -0700 Subject: [PATCH 116/204] Format code --- lib/src/widgets/raw_editor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 444533a6..474326d7 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:ui' as ui hide TextStyle; import 'dart:math' as math; // ignore: unnecessary_import import 'dart:typed_data'; +import 'dart:ui' as ui hide TextStyle; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; From 9b146f3fbcd26bae590d9f6ec13490045a6312d1 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Mon, 3 Apr 2023 22:41:04 +0800 Subject: [PATCH 117/204] Fix line style loss on new line from non string (#1157) --- lib/src/models/rules/insert.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index 8ef6c196..44124fbc 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -35,20 +35,20 @@ class PreserveLineStyleOnSplitRule extends InsertRule { final itr = DeltaIterator(document); final before = itr.skip(index); - if (before == null || - before.data is! String || - (before.data as String).endsWith('\n')) { + if (before == null) { return null; } - final after = itr.next(); - if (after.data is! String || (after.data as String).startsWith('\n')) { + if (before.data is String && (before.data as String).endsWith('\n')) { return null; } - final text = after.data as String; + final after = itr.next(); + if (after.data is String && (after.data as String).startsWith('\n')) { + return null; + } final delta = Delta()..retain(index + (len ?? 0)); - if (text.contains('\n')) { + if (after.data is String && (after.data as String).contains('\n')) { assert(after.isPlain); delta.insert('\n'); return delta; From d94fa6efa1746fadff131271a409bb3d52659313 Mon Sep 17 00:00:00 2001 From: X Code Date: Mon, 3 Apr 2023 07:52:38 -0700 Subject: [PATCH 118/204] Upgrade to 7.0.6 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d679ad..2c4c7a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.6] +* Fix line style loss on new line from non string. + # [7.0.5] * Fix IME position bug for Mac and Windows. * Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac. diff --git a/pubspec.yaml b/pubspec.yaml index 6b051d89..da1a7e8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.5 +version: 7.0.6 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 0d0340adda35a28b6a5f1bdaa7f82eb8eb406cb3 Mon Sep 17 00:00:00 2001 From: Pwiz Date: Tue, 4 Apr 2023 23:26:31 +0800 Subject: [PATCH 119/204] add TextFieldTapRegion for contextMenu (#1158) Co-authored-by: wangpw2 --- lib/src/widgets/raw_editor.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 474326d7..2c21fa5e 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -125,9 +125,11 @@ class RawEditor extends StatefulWidget { BuildContext context, RawEditorState state, ) { - return AdaptiveTextSelectionToolbar.buttonItems( - buttonItems: state.contextMenuButtonItems, - anchors: state.contextMenuAnchors, + return TextFieldTapRegion( + child: AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: state.contextMenuButtonItems, + anchors: state.contextMenuAnchors, + ), ); } From 765f54107ee23886966c24645910a04de591d0b5 Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 4 Apr 2023 08:40:57 -0700 Subject: [PATCH 120/204] Upgrade to 7.0.7 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c4c7a80..3940be80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.7] +* Add TextFieldTapRegion for contextMenu. + # [7.0.6] * Fix line style loss on new line from non string. diff --git a/pubspec.yaml b/pubspec.yaml index da1a7e8b..9965a90d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.6 +version: 7.0.7 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 50ad50170cfed19084ca44c4c1d4b0e81bc32aa4 Mon Sep 17 00:00:00 2001 From: Pwiz Date: Wed, 5 Apr 2023 12:30:24 +0800 Subject: [PATCH 121/204] Fix IME position bug with scroller (#1159) --- .../raw_editor_state_text_input_client_mixin.dart | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index 5f98d56a..e3b9ff5f 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -333,14 +333,11 @@ mixin RawEditorStateTextInputClientMixin on EditorState if (hasConnection) { // Asking for renderEditor.size here can cause errors if layout hasn't // occurred yet. So we schedule a post frame callback instead. - SchedulerBinding.instance.addPostFrameCallback((_) { - if (!mounted) { - return; - } - final size = renderEditor.size; - final transform = renderEditor.getTransformTo(null); - _textInputConnection?.setEditableSizeAndTransform(size, transform); - }); + final size = renderEditor.size; + final transform = renderEditor.getTransformTo(null); + _textInputConnection?.setEditableSizeAndTransform(size, transform); + SchedulerBinding.instance + .addPostFrameCallback((_) => _updateSizeAndTransform()); } } } From ccdbfb7bd90e99f43c8f93d0e7f34351d3c64757 Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 4 Apr 2023 21:32:31 -0700 Subject: [PATCH 122/204] Upgrade to 7.0.8 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3940be80..d7c48713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.8] +* Fix IME position bug with scroller. + # [7.0.7] * Add TextFieldTapRegion for contextMenu. diff --git a/pubspec.yaml b/pubspec.yaml index 9965a90d..2148afe6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.7 +version: 7.0.8 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 216b1ff1a88ca8998ade89e9c0f7338fae875ed1 Mon Sep 17 00:00:00 2001 From: veselv2010 <32619716+veselv2010@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:52:16 +0300 Subject: [PATCH 123/204] fix: Use const constructor for EmbedBuilder (#1160) --- lib/src/widgets/embeds.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/widgets/embeds.dart b/lib/src/widgets/embeds.dart index 8cfe2657..8d72cd5b 100644 --- a/lib/src/widgets/embeds.dart +++ b/lib/src/widgets/embeds.dart @@ -6,6 +6,8 @@ import '../models/themes/quill_icon_theme.dart'; import 'controller.dart'; abstract class EmbedBuilder { + const EmbedBuilder(); + String get key; bool get expanded => true; From f0ce4d05800c30829c14d473827902e294cf4c4e Mon Sep 17 00:00:00 2001 From: X Code Date: Wed, 5 Apr 2023 08:54:31 -0700 Subject: [PATCH 124/204] Upgrade to 7.0.9 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7c48713..bfc19ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.0.9] +* Use const constructor for EmbedBuilder. + # [7.0.8] * Fix IME position bug with scroller. diff --git a/pubspec.yaml b/pubspec.yaml index 2148afe6..8c90b6d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.8 +version: 7.0.9 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From d5984dea1726d8ea21178432b1a2e99dc883d6b2 Mon Sep 17 00:00:00 2001 From: spChief Date: Thu, 6 Apr 2023 20:41:07 +0700 Subject: [PATCH 125/204] fix ordered list numeration with several lists in document (#1163) --- lib/src/widgets/raw_editor.dart | 20 ++++++++++++++++++- .../widgets/style_widgets/number_point.dart | 1 + lib/src/widgets/text_block.dart | 11 +++++++--- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 2c21fa5e..f27fd604 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -756,13 +756,28 @@ class RawEditorState extends EditorState List _buildChildren(Document doc, BuildContext context) { final result = []; final indentLevelCounts = {}; + // this need for several ordered list in document + // we need to reset indents Map, if list finished + // List finished when there is node without Attribute.ol in styles + // So in this case we set clearIndents=true and send it + // to the next EditableTextBlock + var prevNodeOl = false; + var clearIndents = false; + for (final node in doc.root.children) { + final attrs = node.style.attributes; + + if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol) { + clearIndents = true; + } + + prevNodeOl = attrs[Attribute.list.key] == Attribute.ol; + if (node is Line) { final editableTextLine = _getEditableTextLineFromNode(node, context); result.add(Directionality( textDirection: getDirectionOfNode(node), child: editableTextLine)); } else if (node is Block) { - final attrs = node.style.attributes; final editableTextBlock = EditableTextBlock( block: node, controller: controller, @@ -782,11 +797,14 @@ class RawEditorState extends EditorState onLaunchUrl: widget.onLaunchUrl, cursorCont: _cursorCont, indentLevelCounts: indentLevelCounts, + clearIndents: clearIndents, onCheckboxTap: _handleCheckboxTap, readOnly: widget.readOnly, customStyleBuilder: widget.customStyleBuilder); result.add(Directionality( textDirection: getDirectionOfNode(node), child: editableTextBlock)); + + clearIndents = false; } else { throw StateError('Unreachable.'); } diff --git a/lib/src/widgets/style_widgets/number_point.dart b/lib/src/widgets/style_widgets/number_point.dart index 1dbf2261..54d5ebc9 100644 --- a/lib/src/widgets/style_widgets/number_point.dart +++ b/lib/src/widgets/style_widgets/number_point.dart @@ -31,6 +31,7 @@ class QuillNumberPoint extends StatelessWidget { int? level = 0; if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { indentLevelCounts.clear(); + indentLevelCounts[0] = 1; return Container( alignment: AlignmentDirectional.topEnd, width: width, diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 8ece7cf5..09b2af7c 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -68,6 +68,7 @@ class EditableTextBlock extends StatelessWidget { required this.linkActionPicker, required this.cursorCont, required this.indentLevelCounts, + required this.clearIndents, required this.onCheckboxTap, required this.readOnly, this.onLaunchUrl, @@ -91,6 +92,7 @@ class EditableTextBlock extends StatelessWidget { final CustomStyleBuilder? customStyleBuilder; final CursorCont cursorCont; final Map indentLevelCounts; + final bool clearIndents; final Function(int, bool) onCheckboxTap; final bool readOnly; @@ -107,7 +109,7 @@ class EditableTextBlock extends StatelessWidget { decoration: _getDecorationForBlock(block, defaultStyles) ?? const BoxDecoration(), contentPadding: contentPadding, - children: _buildChildren(context, indentLevelCounts)); + children: _buildChildren(context, indentLevelCounts, clearIndents)); } BoxDecoration? _getDecorationForBlock( @@ -122,11 +124,14 @@ class EditableTextBlock extends StatelessWidget { return null; } - List _buildChildren( - BuildContext context, Map indentLevelCounts) { + List _buildChildren(BuildContext context, + Map indentLevelCounts, bool clearIndents) { final defaultStyles = QuillStyles.getStyles(context, false); final count = block.children.length; final children = []; + if (clearIndents) { + indentLevelCounts.clear(); + } var index = 0; for (final line in Iterable.castFrom(block.children)) { index++; From 871b05e2372c54b1fd8b9b92594ef9158eb1ff05 Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 6 Apr 2023 07:47:17 -0700 Subject: [PATCH 126/204] Upgrade to 7.1.0 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfc19ba4..a4e7e9bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.0] +* Fix ordered list numeration with several lists in document. + # [7.0.9] * Use const constructor for EmbedBuilder. diff --git a/pubspec.yaml b/pubspec.yaml index 8c90b6d2..76970dbc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.9 +version: 7.1.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 01271b0cd974f1c6d31effac183ec59627d37b9d Mon Sep 17 00:00:00 2001 From: spChief Date: Fri, 7 Apr 2023 10:22:31 +0700 Subject: [PATCH 127/204] customLinkPrefixes parameter - makes possible to open links with custom protocol (deeplinks) (#1164) --- lib/src/widgets/editor.dart | 8 ++++++++ lib/src/widgets/raw_editor.dart | 8 ++++++-- lib/src/widgets/text_block.dart | 3 +++ lib/src/widgets/text_line.dart | 4 +++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index e733c0e0..d808552d 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -182,6 +182,7 @@ class QuillEditor extends StatefulWidget { this.customShortcuts, this.customActions, this.detectWordBoundary = true, + this.customLinkPrefixes = const [], Key? key}) : super(key: key); @@ -401,6 +402,12 @@ class QuillEditor extends StatefulWidget { final bool detectWordBoundary; + /// Additional list if links prefixes, which must not be prepended + /// with "https://" when [LinkMenuAction.launch] happened + /// + /// Useful for deeplinks + final List customLinkPrefixes; + @override QuillEditorState createState() => QuillEditorState(); } @@ -498,6 +505,7 @@ class QuillEditorState extends State onImagePaste: widget.onImagePaste, customShortcuts: widget.customShortcuts, customActions: widget.customActions, + customLinkPrefixes: widget.customLinkPrefixes, ); final editor = I18n( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index f27fd604..36af8938 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -78,7 +78,8 @@ class RawEditor extends StatefulWidget { this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.customStyleBuilder, this.floatingCursorDisabled = false, - this.onImagePaste}) + this.onImagePaste, + this.customLinkPrefixes = const []}) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'), assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'), assert(maxHeight == null || minHeight == null || maxHeight >= minHeight, @@ -250,6 +251,7 @@ class RawEditor extends StatefulWidget { final LinkActionPickerDelegate linkActionPickerDelegate; final CustomStyleBuilder? customStyleBuilder; final bool floatingCursorDisabled; + final List customLinkPrefixes; @override State createState() => RawEditorState(); @@ -800,7 +802,8 @@ class RawEditorState extends EditorState clearIndents: clearIndents, onCheckboxTap: _handleCheckboxTap, readOnly: widget.readOnly, - customStyleBuilder: widget.customStyleBuilder); + customStyleBuilder: widget.customStyleBuilder, + customLinkPrefixes: widget.customLinkPrefixes); result.add(Directionality( textDirection: getDirectionOfNode(node), child: editableTextBlock)); @@ -824,6 +827,7 @@ class RawEditorState extends EditorState controller: controller, linkActionPicker: _linkActionPicker, onLaunchUrl: widget.onLaunchUrl, + customLinkPrefixes: widget.customLinkPrefixes, ); final editableTextLine = EditableTextLine( node, diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 09b2af7c..b61ad9cf 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -73,6 +73,7 @@ class EditableTextBlock extends StatelessWidget { required this.readOnly, this.onLaunchUrl, this.customStyleBuilder, + this.customLinkPrefixes = const [], Key? key}); final Block block; @@ -95,6 +96,7 @@ class EditableTextBlock extends StatelessWidget { final bool clearIndents; final Function(int, bool) onCheckboxTap; final bool readOnly; + final List customLinkPrefixes; @override Widget build(BuildContext context) { @@ -148,6 +150,7 @@ class EditableTextBlock extends StatelessWidget { controller: controller, linkActionPicker: linkActionPicker, onLaunchUrl: onLaunchUrl, + customLinkPrefixes: customLinkPrefixes, ), _getIndentWidth(), _getSpacingForLine(line, index, count, defaultStyles), diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index b7174a2f..22623f4d 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -41,6 +41,7 @@ class TextLine extends StatefulWidget { required this.linkActionPicker, this.textDirection, this.customStyleBuilder, + this.customLinkPrefixes = const [], Key? key, }) : super(key: key); @@ -53,6 +54,7 @@ class TextLine extends StatefulWidget { final CustomStyleBuilder? customStyleBuilder; final ValueChanged? onLaunchUrl; final LinkActionPicker linkActionPicker; + final List customLinkPrefixes; @override State createState() => _TextLineState(); @@ -430,7 +432,7 @@ class _TextLineState extends State { launchUrl ??= _launchUrl; link = link.trim(); - if (!linkPrefixes + if (!(widget.customLinkPrefixes + linkPrefixes) .any((linkPrefix) => link!.toLowerCase().startsWith(linkPrefix))) { link = 'https://$link'; } From 160006c4de9d8ac0bcef6dc4668c68f464fed20b Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 6 Apr 2023 20:24:05 -0700 Subject: [PATCH 128/204] Upgrade to 7.1.1 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4e7e9bd..d659f50d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.1] +* customLinkPrefixes parameter - makes possible to open links with custom protoco. + # [7.1.0] * Fix ordered list numeration with several lists in document. diff --git a/pubspec.yaml b/pubspec.yaml index 76970dbc..4b9b4358 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.0 +version: 7.1.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 2ea29d56a14b72d2da94eadee992a305b2e49649 Mon Sep 17 00:00:00 2001 From: spChief Date: Fri, 7 Apr 2023 14:56:31 +0700 Subject: [PATCH 129/204] fix non scrollable editor exception, when tapped under content (#1167) --- lib/src/widgets/editor.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index d808552d..4ddbfcae 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -1794,7 +1794,10 @@ class RenderEditableContainerBox extends RenderBox dy += child.size.height; child = childAfter(child); } - throw StateError('No child at offset $offset.'); + + // this case possible, when editor not scrollable, + // but minHeight > content height and tap was under content + return lastChild!; } @override From d517847d8a04536ee305840818312f76a662ce53 Mon Sep 17 00:00:00 2001 From: X Code Date: Fri, 7 Apr 2023 09:45:34 -0700 Subject: [PATCH 130/204] Upgrade to 7.1.2 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d659f50d..cd8acc3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.2] +* Fix non scrollable editor exception, when tapped under content. + # [7.1.1] * customLinkPrefixes parameter - makes possible to open links with custom protoco. diff --git a/pubspec.yaml b/pubspec.yaml index 4b9b4358..2dc6d2ff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.1 +version: 7.1.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From d62be3d12aca0110b20ec0cbcbeeeeebe4721dab Mon Sep 17 00:00:00 2001 From: Pwiz Date: Sun, 9 Apr 2023 21:26:45 +0800 Subject: [PATCH 131/204] fix ios cursor bug when word.length==1 (#1168) --- lib/src/widgets/editor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 4ddbfcae..e1f6d220 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -1135,7 +1135,7 @@ class RenderEditor extends RenderEditableContainerBox start: localWord.start + nodeOffset, end: localWord.end + nodeOffset, ); - if (position.offset - word.start <= 1) { + if (position.offset - word.start <= 1 && word.end != position.offset) { _handleSelectionChange( TextSelection.collapsed(offset: word.start), cause, From 351bcb95c862ba5ce70de09e93ff8224aa8def56 Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 9 Apr 2023 09:04:20 -0700 Subject: [PATCH 132/204] Upgrade to 7.1.3 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd8acc3b..dd28ddeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.3] +* Fix ios cursor bug when word.length==1. + # [7.1.2] * Fix non scrollable editor exception, when tapped under content. diff --git a/pubspec.yaml b/pubspec.yaml index 2dc6d2ff..7adb8ef7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.2 +version: 7.1.3 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 871b41354429231d0f332f4c1496b612056f1fed Mon Sep 17 00:00:00 2001 From: bohdanudreambit Date: Tue, 11 Apr 2023 17:25:29 +0300 Subject: [PATCH 133/204] fix inserting tab character in lists (#1171) --- lib/src/widgets/raw_editor.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 36af8938..4adb058e 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -665,18 +665,22 @@ class RawEditorState extends EditorState return insertTabCharacter(); } - if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty) { - return insertTabCharacter(); - } - final parentBlock = parent; if (parentBlock.style.containsKey(Attribute.ol.key) || parentBlock.style.containsKey(Attribute.ul.key) || parentBlock.style.containsKey(Attribute.checked.key)) { + if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty + && controller.selection.base.offset > node.documentOffset) { + return insertTabCharacter(); + } controller.indentSelection(!event.isShiftPressed); return KeyEventResult.handled; } + if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty) { + return insertTabCharacter(); + } + return insertTabCharacter(); } From e90e6eb70dc5922bbc1246247a214e507f6995f1 Mon Sep 17 00:00:00 2001 From: X Code Date: Tue, 11 Apr 2023 08:13:01 -0700 Subject: [PATCH 134/204] Upgrade to 7.1.4 --- CHANGELOG.md | 3 +++ lib/src/widgets/raw_editor.dart | 5 +++-- pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd28ddeb..6d24d17b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.4] +* Fix inserting tab character in lists. + # [7.1.3] * Fix ios cursor bug when word.length==1. diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 4adb058e..9531cbde 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -669,8 +669,9 @@ class RawEditorState extends EditorState if (parentBlock.style.containsKey(Attribute.ol.key) || parentBlock.style.containsKey(Attribute.ul.key) || parentBlock.style.containsKey(Attribute.checked.key)) { - if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty - && controller.selection.base.offset > node.documentOffset) { + if (node.isNotEmpty && + (node.first as leaf.Text).value.isNotEmpty && + controller.selection.base.offset > node.documentOffset) { return insertTabCharacter(); } controller.indentSelection(!event.isShiftPressed); diff --git a/pubspec.yaml b/pubspec.yaml index 7adb8ef7..3a11ea64 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.3 +version: 7.1.4 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 539357bc63fe349884837ed7cceaaad792b513f7 Mon Sep 17 00:00:00 2001 From: BambinoUA <45417992+bambinoua@users.noreply.github.com> Date: Sat, 15 Apr 2023 08:17:04 +0300 Subject: [PATCH 135/204] Add tooltips for toolbar buttons. (#1175) * Add `UtilityWidgets` * Implement tooltips for toolbar buttons * Add `tooltip` to `QuillCustomButton` * Add `ToolbarButtons` * Update `EmbedButtonBuilder` with `tooltip` * Implement tooltips in `QuillToolbar` * Add tooltip property to embed buttons (for future) * Update version of flutter_quill in pubspec.yaml * Restore `EmbedButtonBuilder` * Update english translations * Implement translations for tooltips * Update translations for `en-us` * Make `tooltips` nullable to hide tooltips. * Remove `tooltips` property from `QuillToolbar` --- .../lib/embeds/toolbar/camera_button.dart | 2 + .../lib/embeds/toolbar/formula_button.dart | 2 + .../lib/embeds/toolbar/image_button.dart | 2 + .../lib/embeds/toolbar/video_button.dart | 2 + flutter_quill_extensions/pubspec.yaml | 6 +- .../models/themes/quill_custom_button.dart | 9 +- lib/src/translations/toolbar.i18n.dart | 52 +++++++++++ lib/src/utils/widgets.dart | 11 +++ lib/src/widgets/toolbar.dart | 80 +++++++++++++++++ .../widgets/toolbar/clear_format_button.dart | 3 + lib/src/widgets/toolbar/color_button.dart | 3 + lib/src/widgets/toolbar/enum.dart | 30 +++++++ lib/src/widgets/toolbar/history_button.dart | 3 + lib/src/widgets/toolbar/indent_button.dart | 3 + .../widgets/toolbar/link_style_button.dart | 3 + .../toolbar/quill_font_family_button.dart | 34 +++++--- .../toolbar/quill_font_size_button.dart | 34 +++++--- .../widgets/toolbar/quill_icon_button.dart | 33 ++++--- lib/src/widgets/toolbar/search_button.dart | 3 + .../toolbar/select_alignment_button.dart | 87 +++++++++++-------- .../toolbar/select_header_style_button.dart | 62 +++++++------ .../toolbar/toggle_check_list_button.dart | 26 +++--- .../widgets/toolbar/toggle_style_button.dart | 26 +++--- 23 files changed, 387 insertions(+), 129 deletions(-) create mode 100644 lib/src/utils/widgets.dart create mode 100644 lib/src/widgets/toolbar/enum.dart diff --git a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart index bb906f30..202d97b1 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart @@ -19,6 +19,7 @@ class CameraButton extends StatelessWidget { this.webVideoPickImpl, this.cameraPickSettingSelector, this.iconTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -42,6 +43,7 @@ class CameraButton extends StatelessWidget { final MediaPickSettingSelector? cameraPickSettingSelector; final QuillIconTheme? iconTheme; + final String? tooltip; @override Widget build(BuildContext context) { diff --git a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart index fb0ab679..70c4987f 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart @@ -9,6 +9,7 @@ class FormulaButton extends StatelessWidget { this.fillColor, this.iconTheme, this.dialogTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -23,6 +24,7 @@ class FormulaButton extends StatelessWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; + final String? tooltip; @override Widget build(BuildContext context) { diff --git a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart index 5cc51aff..8cad519c 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart @@ -17,6 +17,7 @@ class ImageButton extends StatelessWidget { this.mediaPickSettingSelector, this.iconTheme, this.dialogTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -38,6 +39,7 @@ class ImageButton extends StatelessWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; + final String? tooltip; @override Widget build(BuildContext context) { diff --git a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart index e6193622..30e0dbf7 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart @@ -17,6 +17,7 @@ class VideoButton extends StatelessWidget { this.mediaPickSettingSelector, this.iconTheme, this.dialogTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -38,6 +39,7 @@ class VideoButton extends StatelessWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; + final String? tooltip; @override Widget build(BuildContext context) { diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 5ca94b5c..e0e1269d 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.0.2 + flutter_quill: ^7.1.4 image_picker: ^0.8.5+3 photo_view: ^0.14.0 @@ -23,10 +23,6 @@ dependencies: string_validator: ^1.0.0 url_launcher: ^6.1.9 -# dependency_overrides: -# flutter_quill: -# path: ../ - dev_dependencies: flutter_test: sdk: flutter diff --git a/lib/src/models/themes/quill_custom_button.dart b/lib/src/models/themes/quill_custom_button.dart index 0dbac618..4ea4e4f5 100644 --- a/lib/src/models/themes/quill_custom_button.dart +++ b/lib/src/models/themes/quill_custom_button.dart @@ -1,11 +1,18 @@ import 'package:flutter/material.dart'; class QuillCustomButton { - const QuillCustomButton({this.icon, this.onTap}); + const QuillCustomButton({ + this.icon, + this.onTap, + this.tooltip, + }); ///The icon widget final IconData? icon; ///The function when the icon is tapped final VoidCallback? onTap; + + /// The button tooltip. + final String? tooltip; } diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 2abcfb21..cb6c7853 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -35,6 +35,32 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', }, 'en_us': { 'Paste a link': 'Paste a link', @@ -68,6 +94,32 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', }, 'ar': { 'Paste a link': 'نسخ الرابط', diff --git a/lib/src/utils/widgets.dart b/lib/src/utils/widgets.dart new file mode 100644 index 00000000..1b560e84 --- /dev/null +++ b/lib/src/utils/widgets.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +/// Provides utiulity widgets. +abstract class UtilityWidgets { + /// Conditionally wraps the [child] with [Tooltip] widget if [message] + /// is not null and not empty. + static Widget maybeTooltip({required Widget child, String? message}) => + (message ?? '').isNotEmpty + ? Tooltip(message: message!, child: child) + : child; +} diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 58171ad1..08c7c6fe 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -12,6 +12,7 @@ import 'embeds.dart'; import 'toolbar/arrow_indicated_button_list.dart'; import 'toolbar/clear_format_button.dart'; import 'toolbar/color_button.dart'; +import 'toolbar/enum.dart'; import 'toolbar/history_button.dart'; import 'toolbar/indent_button.dart'; import 'toolbar/link_style_button.dart'; @@ -117,6 +118,20 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// Is called after whatever logic the button performs has run. VoidCallback? afterButtonPressed, + ///Map of tooltips for toolbar buttons + /// + ///The example is: + ///```dart + /// tooltips = { + /// ToolbarButtons.undo: 'Undo', + /// ToolbarButtons.redo: 'Redo', + /// } + /// + ///``` + /// + /// To disable tooltips just pass empty map as well. + Map? tooltips, + /// The locale to use for the editor toolbar, defaults to system locale /// More at https://github.com/singerdmx/flutter-quill#translation Locale? locale, @@ -172,6 +187,39 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { 'Clear'.i18n: 'Clear' }; + //default button tooltips + final buttonTooltips = tooltips ?? + { + ToolbarButtons.undo: 'Undo'.i18n, + ToolbarButtons.redo: 'Redo'.i18n, + ToolbarButtons.fontFamily: 'Font family'.i18n, + ToolbarButtons.fontSize: 'Font size'.i18n, + ToolbarButtons.bold: 'Bold'.i18n, + ToolbarButtons.italic: 'Italic'.i18n, + ToolbarButtons.small: 'Small'.i18n, + ToolbarButtons.underline: 'Underline'.i18n, + ToolbarButtons.strikeThrough: 'Strike through'.i18n, + ToolbarButtons.inlineCode: 'Inline code'.i18n, + ToolbarButtons.color: 'Font color'.i18n, + ToolbarButtons.backgroundColor: 'Background color'.i18n, + ToolbarButtons.clearFormat: 'Clear format'.i18n, + ToolbarButtons.leftAlignment: 'Align left'.i18n, + ToolbarButtons.centerAlignment: 'Align center'.i18n, + ToolbarButtons.rightAlignment: 'Align right'.i18n, + ToolbarButtons.justifyAlignment: 'Justify win width'.i18n, + ToolbarButtons.direction: 'Text direction'.i18n, + ToolbarButtons.headerStyle: 'Header style'.i18n, + ToolbarButtons.listNumbers: 'Numbered list'.i18n, + ToolbarButtons.listBullets: 'Bullet list'.i18n, + ToolbarButtons.listChecks: 'Checked list'.i18n, + ToolbarButtons.codeBlock: 'Code block'.i18n, + ToolbarButtons.quote: 'Quote'.i18n, + ToolbarButtons.indentIncrease: 'Increase indent'.i18n, + ToolbarButtons.indentDecrease: 'Decrease indent'.i18n, + ToolbarButtons.link: 'Insert URL'.i18n, + ToolbarButtons.search: 'Search'.i18n, + }; + return QuillToolbar( key: key, axis: axis, @@ -189,6 +237,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { HistoryButton( icon: Icons.undo_outlined, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.undo], controller: controller, undo: true, iconTheme: iconTheme, @@ -198,6 +247,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { HistoryButton( icon: Icons.redo_outlined, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.redo], controller: controller, undo: false, iconTheme: iconTheme, @@ -207,6 +257,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { QuillFontFamilyButton( iconTheme: iconTheme, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.fontFamily], attribute: Attribute.font, controller: controller, items: [ @@ -231,6 +282,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { QuillFontSizeButton( iconTheme: iconTheme, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.fontSize], attribute: Attribute.size, controller: controller, items: [ @@ -255,6 +307,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.bold, icon: Icons.format_bold, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.bold], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -264,6 +317,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.italic, icon: Icons.format_italic, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.italic], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -273,6 +327,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.small, icon: Icons.format_size, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.small], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -282,6 +337,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.underline, icon: Icons.format_underline, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.underline], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -291,6 +347,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.strikeThrough, icon: Icons.format_strikethrough, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.strikeThrough], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -300,6 +357,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.inlineCode, icon: Icons.code, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.inlineCode], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -308,6 +366,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ColorButton( icon: Icons.color_lens, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.color], controller: controller, background: false, iconTheme: iconTheme, @@ -317,6 +376,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ColorButton( icon: Icons.format_color_fill, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.backgroundColor], controller: controller, background: true, iconTheme: iconTheme, @@ -326,6 +386,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ClearFormatButton( icon: Icons.format_clear, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.clearFormat], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -344,6 +405,13 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showAlignmentButtons) SelectAlignmentButton( controller: controller, + tooltips: Map.of(buttonTooltips) + ..removeWhere((key, value) => ![ + ToolbarButtons.leftAlignment, + ToolbarButtons.centerAlignment, + ToolbarButtons.rightAlignment, + ToolbarButtons.justifyAlignment, + ].contains(key)), iconSize: toolbarIconSize, iconTheme: iconTheme, showLeftAlignment: showLeftAlignment, @@ -355,6 +423,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDirection) ToggleStyleButton( attribute: Attribute.rtl, + tooltip: buttonTooltips[ToolbarButtons.direction], controller: controller, icon: Icons.format_textdirection_r_to_l, iconSize: toolbarIconSize, @@ -370,6 +439,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { _dividerOnAxis(axis), if (showHeaderStyle) SelectHeaderStyleButton( + tooltip: buttonTooltips[ToolbarButtons.headerStyle], controller: controller, axis: axis, iconSize: toolbarIconSize, @@ -386,6 +456,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showListNumbers) ToggleStyleButton( attribute: Attribute.ol, + tooltip: buttonTooltips[ToolbarButtons.listNumbers], controller: controller, icon: Icons.format_list_numbered, iconSize: toolbarIconSize, @@ -395,6 +466,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showListBullets) ToggleStyleButton( attribute: Attribute.ul, + tooltip: buttonTooltips[ToolbarButtons.listBullets], controller: controller, icon: Icons.format_list_bulleted, iconSize: toolbarIconSize, @@ -404,6 +476,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showListCheck) ToggleCheckListButton( attribute: Attribute.unchecked, + tooltip: buttonTooltips[ToolbarButtons.listChecks], controller: controller, icon: Icons.check_box, iconSize: toolbarIconSize, @@ -413,6 +486,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showCodeBlock) ToggleStyleButton( attribute: Attribute.codeBlock, + tooltip: buttonTooltips[ToolbarButtons.codeBlock], controller: controller, icon: Icons.code, iconSize: toolbarIconSize, @@ -426,6 +500,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showQuote) ToggleStyleButton( attribute: Attribute.blockQuote, + tooltip: buttonTooltips[ToolbarButtons.quote], controller: controller, icon: Icons.format_quote, iconSize: toolbarIconSize, @@ -436,6 +511,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { IndentButton( icon: Icons.format_indent_increase, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.indentIncrease], controller: controller, isIncrease: true, iconTheme: iconTheme, @@ -445,6 +521,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { IndentButton( icon: Icons.format_indent_decrease, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.indentDecrease], controller: controller, isIncrease: false, iconTheme: iconTheme, @@ -454,6 +531,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { _dividerOnAxis(axis), if (showLink) LinkStyleButton( + tooltip: buttonTooltips[ToolbarButtons.link], controller: controller, iconSize: toolbarIconSize, iconTheme: iconTheme, @@ -464,6 +542,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { SearchButton( icon: Icons.search, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.search], controller: controller, iconTheme: iconTheme, dialogTheme: dialogTheme, @@ -477,6 +556,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { hoverElevation: 0, size: toolbarIconSize * kIconButtonFactor, icon: Icon(customButton.icon, size: toolbarIconSize), + tooltip: customButton.tooltip, borderRadius: iconTheme?.borderRadius ?? 2, onPressed: customButton.onTap, afterPressed: afterButtonPressed, diff --git a/lib/src/widgets/toolbar/clear_format_button.dart b/lib/src/widgets/toolbar/clear_format_button.dart index f601bd28..14610232 100644 --- a/lib/src/widgets/toolbar/clear_format_button.dart +++ b/lib/src/widgets/toolbar/clear_format_button.dart @@ -12,6 +12,7 @@ class ClearFormatButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -22,6 +23,7 @@ class ClearFormatButton extends StatefulWidget { final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ClearFormatButtonState createState() => _ClearFormatButtonState(); @@ -36,6 +38,7 @@ class _ClearFormatButtonState extends State { final fillColor = widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor; return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/color_button.dart b/lib/src/widgets/toolbar/color_button.dart index 087ae94f..60f0a591 100644 --- a/lib/src/widgets/toolbar/color_button.dart +++ b/lib/src/widgets/toolbar/color_button.dart @@ -21,6 +21,7 @@ class ColorButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -30,6 +31,7 @@ class ColorButton extends StatefulWidget { final QuillController controller; final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ColorButtonState createState() => _ColorButtonState(); @@ -119,6 +121,7 @@ class _ColorButtonState extends State { : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor); return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/enum.dart b/lib/src/widgets/toolbar/enum.dart new file mode 100644 index 00000000..197bea56 --- /dev/null +++ b/lib/src/widgets/toolbar/enum.dart @@ -0,0 +1,30 @@ +enum ToolbarButtons { + undo, + redo, + fontFamily, + fontSize, + bold, + italic, + small, + underline, + strikeThrough, + inlineCode, + color, + backgroundColor, + clearFormat, + centerAlignment, + leftAlignment, + rightAlignment, + justifyAlignment, + direction, + headerStyle, + listNumbers, + listBullets, + listChecks, + codeBlock, + quote, + indentIncrease, + indentDecrease, + link, + search, +} diff --git a/lib/src/widgets/toolbar/history_button.dart b/lib/src/widgets/toolbar/history_button.dart index a3abc7f2..6d3c29ad 100644 --- a/lib/src/widgets/toolbar/history_button.dart +++ b/lib/src/widgets/toolbar/history_button.dart @@ -12,6 +12,7 @@ class HistoryButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -21,6 +22,7 @@ class HistoryButton extends StatefulWidget { final QuillController controller; final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _HistoryButtonState createState() => _HistoryButtonState(); @@ -41,6 +43,7 @@ class _HistoryButtonState extends State { _setIconColor(); }); return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * 1.77, diff --git a/lib/src/widgets/toolbar/indent_button.dart b/lib/src/widgets/toolbar/indent_button.dart index 129ef8b1..1ce83e99 100644 --- a/lib/src/widgets/toolbar/indent_button.dart +++ b/lib/src/widgets/toolbar/indent_button.dart @@ -12,6 +12,7 @@ class IndentButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -22,6 +23,7 @@ class IndentButton extends StatefulWidget { final VoidCallback? afterButtonPressed; final QuillIconTheme? iconTheme; + final String? tooltip; @override _IndentButtonState createState() => _IndentButtonState(); @@ -37,6 +39,7 @@ class _IndentButtonState extends State { final iconFillColor = widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor; return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * 1.77, diff --git a/lib/src/widgets/toolbar/link_style_button.dart b/lib/src/widgets/toolbar/link_style_button.dart index f48a15e4..a0865941 100644 --- a/lib/src/widgets/toolbar/link_style_button.dart +++ b/lib/src/widgets/toolbar/link_style_button.dart @@ -17,6 +17,7 @@ class LinkStyleButton extends StatefulWidget { this.iconTheme, this.dialogTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -26,6 +27,7 @@ class LinkStyleButton extends StatefulWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _LinkStyleButtonState createState() => _LinkStyleButtonState(); @@ -63,6 +65,7 @@ class _LinkStyleButtonState extends State { final isToggled = _getLinkAttributeValue() != null; final pressedHandler = () => _openLinkDialog(context); return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/quill_font_family_button.dart b/lib/src/widgets/toolbar/quill_font_family_button.dart index 88b3dda4..bdddc832 100644 --- a/lib/src/widgets/toolbar/quill_font_family_button.dart +++ b/lib/src/widgets/toolbar/quill_font_family_button.dart @@ -4,6 +4,7 @@ import '../../models/documents/attribute.dart'; import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; import '../../translations/toolbar.i18n.dart'; +import '../../utils/widgets.dart'; import '../controller.dart'; class QuillFontFamilyButton extends StatefulWidget { @@ -19,6 +20,7 @@ class QuillFontFamilyButton extends StatefulWidget { this.highlightElevation = 1, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -33,6 +35,7 @@ class QuillFontFamilyButton extends StatefulWidget { final Attribute attribute; final QuillController controller; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState(); @@ -88,20 +91,23 @@ class _QuillFontFamilyButtonState extends State { Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81), - child: RawMaterialButton( - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), - fillColor: widget.fillColor, - elevation: 0, - hoverElevation: widget.hoverElevation, - highlightElevation: widget.hoverElevation, - onPressed: () { - _showMenu(); - widget.afterButtonPressed?.call(); - }, - child: _buildContent(context), + child: UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: RawMaterialButton( + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), + fillColor: widget.fillColor, + elevation: 0, + hoverElevation: widget.hoverElevation, + highlightElevation: widget.hoverElevation, + onPressed: () { + _showMenu(); + widget.afterButtonPressed?.call(); + }, + child: _buildContent(context), + ), ), ); } diff --git a/lib/src/widgets/toolbar/quill_font_size_button.dart b/lib/src/widgets/toolbar/quill_font_size_button.dart index da6fa187..f82175c2 100644 --- a/lib/src/widgets/toolbar/quill_font_size_button.dart +++ b/lib/src/widgets/toolbar/quill_font_size_button.dart @@ -5,6 +5,7 @@ import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; import '../../translations/toolbar.i18n.dart'; import '../../utils/font.dart'; +import '../../utils/widgets.dart'; import '../controller.dart'; class QuillFontSizeButton extends StatefulWidget { @@ -20,6 +21,7 @@ class QuillFontSizeButton extends StatefulWidget { this.highlightElevation = 1, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -34,6 +36,7 @@ class QuillFontSizeButton extends StatefulWidget { final Attribute attribute; final QuillController controller; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _QuillFontSizeButtonState createState() => _QuillFontSizeButtonState(); @@ -89,20 +92,23 @@ class _QuillFontSizeButtonState extends State { Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81), - child: RawMaterialButton( - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), - fillColor: widget.fillColor, - elevation: 0, - hoverElevation: widget.hoverElevation, - highlightElevation: widget.hoverElevation, - onPressed: () { - _showMenu(); - widget.afterButtonPressed?.call(); - }, - child: _buildContent(context), + child: UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: RawMaterialButton( + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), + fillColor: widget.fillColor, + elevation: 0, + hoverElevation: widget.hoverElevation, + highlightElevation: widget.hoverElevation, + onPressed: () { + _showMenu(); + widget.afterButtonPressed?.call(); + }, + child: _buildContent(context), + ), ), ); } diff --git a/lib/src/widgets/toolbar/quill_icon_button.dart b/lib/src/widgets/toolbar/quill_icon_button.dart index 7714187c..86c5b30b 100644 --- a/lib/src/widgets/toolbar/quill_icon_button.dart +++ b/lib/src/widgets/toolbar/quill_icon_button.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../../utils/widgets.dart'; + class QuillIconButton extends StatelessWidget { const QuillIconButton({ required this.onPressed, @@ -10,6 +12,7 @@ class QuillIconButton extends StatelessWidget { this.hoverElevation = 1, this.highlightElevation = 1, this.borderRadius = 2, + this.tooltip, Key? key, }) : super(key: key); @@ -21,24 +24,28 @@ class QuillIconButton extends StatelessWidget { final double hoverElevation; final double highlightElevation; final double borderRadius; + final String? tooltip; @override Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints.tightFor(width: size, height: size), - child: RawMaterialButton( - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(borderRadius)), - fillColor: fillColor, - elevation: 0, - hoverElevation: hoverElevation, - highlightElevation: hoverElevation, - onPressed: () { - onPressed?.call(); - afterPressed?.call(); - }, - child: icon, + child: UtilityWidgets.maybeTooltip( + message: tooltip, + child: RawMaterialButton( + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius)), + fillColor: fillColor, + elevation: 0, + hoverElevation: hoverElevation, + highlightElevation: hoverElevation, + onPressed: () { + onPressed?.call(); + afterPressed?.call(); + }, + child: icon, + ), ), ); } diff --git a/lib/src/widgets/toolbar/search_button.dart b/lib/src/widgets/toolbar/search_button.dart index b9436bf1..9233cf45 100644 --- a/lib/src/widgets/toolbar/search_button.dart +++ b/lib/src/widgets/toolbar/search_button.dart @@ -15,6 +15,7 @@ class SearchButton extends StatelessWidget { this.iconTheme, this.dialogTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -27,6 +28,7 @@ class SearchButton extends StatelessWidget { final QuillDialogTheme? dialogTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override Widget build(BuildContext context) { @@ -37,6 +39,7 @@ class SearchButton extends StatelessWidget { iconTheme?.iconUnselectedFillColor ?? (fillColor ?? theme.canvasColor); return QuillIconButton( + tooltip: tooltip, icon: Icon(icon, size: iconSize, color: iconColor), highlightElevation: 0, hoverElevation: 0, diff --git a/lib/src/widgets/toolbar/select_alignment_button.dart b/lib/src/widgets/toolbar/select_alignment_button.dart index 596b095d..4d22b2cf 100644 --- a/lib/src/widgets/toolbar/select_alignment_button.dart +++ b/lib/src/widgets/toolbar/select_alignment_button.dart @@ -4,8 +4,10 @@ import 'package:flutter/material.dart'; import '../../models/documents/attribute.dart'; import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; +import '../../utils/widgets.dart'; import '../controller.dart'; import '../toolbar.dart'; +import 'enum.dart'; class SelectAlignmentButton extends StatefulWidget { const SelectAlignmentButton({ @@ -17,6 +19,7 @@ class SelectAlignmentButton extends StatefulWidget { this.showRightAlignment, this.showJustifyAlignment, this.afterButtonPressed, + this.tooltips = const {}, Key? key, }) : super(key: key); @@ -29,6 +32,7 @@ class SelectAlignmentButton extends StatefulWidget { final bool? showRightAlignment; final bool? showJustifyAlignment; final VoidCallback? afterButtonPressed; + final Map tooltips; @override _SelectAlignmentButtonState createState() => _SelectAlignmentButtonState(); @@ -74,6 +78,16 @@ class _SelectAlignmentButtonState extends State { if (widget.showRightAlignment!) Attribute.rightAlignment.value!, if (widget.showJustifyAlignment!) Attribute.justifyAlignment.value!, ]; + final _valueToButtons = { + if (widget.showLeftAlignment!) + Attribute.leftAlignment: ToolbarButtons.leftAlignment, + if (widget.showCenterAlignment!) + Attribute.centerAlignment: ToolbarButtons.centerAlignment, + if (widget.showRightAlignment!) + Attribute.rightAlignment: ToolbarButtons.rightAlignment, + if (widget.showJustifyAlignment!) + Attribute.justifyAlignment: ToolbarButtons.justifyAlignment, + }; final theme = Theme.of(context); @@ -93,40 +107,45 @@ class _SelectAlignmentButtonState extends State { width: widget.iconSize * kIconButtonFactor, height: widget.iconSize * kIconButtonFactor, ), - child: RawMaterialButton( - hoverElevation: 0, - highlightElevation: 0, - elevation: 0, - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - widget.iconTheme?.borderRadius ?? 2)), - fillColor: _valueToText[_value] == _valueString[index] - ? (widget.iconTheme?.iconSelectedFillColor ?? - Theme.of(context).primaryColor) - : (widget.iconTheme?.iconUnselectedFillColor ?? - theme.canvasColor), - onPressed: () { - _valueAttribute[index] == Attribute.leftAlignment - ? widget.controller - .formatSelection(Attribute.clone(Attribute.align, null)) - : widget.controller.formatSelection(_valueAttribute[index]); - widget.afterButtonPressed?.call(); - }, - child: Icon( - _valueString[index] == Attribute.leftAlignment.value - ? Icons.format_align_left - : _valueString[index] == Attribute.centerAlignment.value - ? Icons.format_align_center - : _valueString[index] == Attribute.rightAlignment.value - ? Icons.format_align_right - : Icons.format_align_justify, - size: widget.iconSize, - color: _valueToText[_value] == _valueString[index] - ? (widget.iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) - : (widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), + child: UtilityWidgets.maybeTooltip( + message: widget.tooltips[_valueToButtons[_valueAttribute[index]]], + child: RawMaterialButton( + hoverElevation: 0, + highlightElevation: 0, + elevation: 0, + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + widget.iconTheme?.borderRadius ?? 2)), + fillColor: _valueToText[_value] == _valueString[index] + ? (widget.iconTheme?.iconSelectedFillColor ?? + Theme.of(context).primaryColor) + : (widget.iconTheme?.iconUnselectedFillColor ?? + theme.canvasColor), + onPressed: () { + _valueAttribute[index] == Attribute.leftAlignment + ? widget.controller.formatSelection( + Attribute.clone(Attribute.align, null)) + : widget.controller + .formatSelection(_valueAttribute[index]); + widget.afterButtonPressed?.call(); + }, + child: Icon( + _valueString[index] == Attribute.leftAlignment.value + ? Icons.format_align_left + : _valueString[index] == Attribute.centerAlignment.value + ? Icons.format_align_center + : _valueString[index] == + Attribute.rightAlignment.value + ? Icons.format_align_right + : Icons.format_align_justify, + size: widget.iconSize, + color: _valueToText[_value] == _valueString[index] + ? (widget.iconTheme?.iconSelectedColor ?? + theme.primaryIconTheme.color) + : (widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color), + ), ), ), ), diff --git a/lib/src/widgets/toolbar/select_header_style_button.dart b/lib/src/widgets/toolbar/select_header_style_button.dart index f27998b8..986abc22 100644 --- a/lib/src/widgets/toolbar/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/select_header_style_button.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import '../../models/documents/attribute.dart'; import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; +import '../../utils/widgets.dart'; import '../controller.dart'; import '../toolbar.dart'; @@ -20,6 +21,7 @@ class SelectHeaderStyleButton extends StatefulWidget { Attribute.h3, ], this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -29,6 +31,7 @@ class SelectHeaderStyleButton extends StatefulWidget { final QuillIconTheme? iconTheme; final List attributes; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _SelectHeaderStyleButtonState createState() => @@ -79,34 +82,37 @@ class _SelectHeaderStyleButtonState extends State { width: widget.iconSize * kIconButtonFactor, height: widget.iconSize * kIconButtonFactor, ), - child: RawMaterialButton( - hoverElevation: 0, - highlightElevation: 0, - elevation: 0, - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), - fillColor: isSelected - ? (widget.iconTheme?.iconSelectedFillColor ?? - Theme.of(context).primaryColor) - : (widget.iconTheme?.iconUnselectedFillColor ?? - theme.canvasColor), - onPressed: () { - final _attribute = _selectedAttribute == attribute - ? Attribute.header - : attribute; - widget.controller.formatSelection(_attribute); - widget.afterButtonPressed?.call(); - }, - child: Text( - _valueToText[attribute] ?? '', - style: style.copyWith( - color: isSelected - ? (widget.iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) - : (widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), + child: UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: RawMaterialButton( + hoverElevation: 0, + highlightElevation: 0, + elevation: 0, + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + widget.iconTheme?.borderRadius ?? 2)), + fillColor: isSelected + ? (widget.iconTheme?.iconSelectedFillColor ?? + Theme.of(context).primaryColor) + : (widget.iconTheme?.iconUnselectedFillColor ?? + theme.canvasColor), + onPressed: () { + final _attribute = _selectedAttribute == attribute + ? Attribute.header + : attribute; + widget.controller.formatSelection(_attribute); + widget.afterButtonPressed?.call(); + }, + child: Text( + _valueToText[attribute] ?? '', + style: style.copyWith( + color: isSelected + ? (widget.iconTheme?.iconSelectedColor ?? + theme.primaryIconTheme.color) + : (widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color), + ), ), ), ), diff --git a/lib/src/widgets/toolbar/toggle_check_list_button.dart b/lib/src/widgets/toolbar/toggle_check_list_button.dart index 037c47cd..6912916b 100644 --- a/lib/src/widgets/toolbar/toggle_check_list_button.dart +++ b/lib/src/widgets/toolbar/toggle_check_list_button.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../models/documents/attribute.dart'; import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; +import '../../utils/widgets.dart'; import '../controller.dart'; import '../toolbar.dart'; @@ -16,6 +17,7 @@ class ToggleCheckListButton extends StatefulWidget { this.childBuilder = defaultToggleStyleButtonBuilder, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -32,6 +34,7 @@ class ToggleCheckListButton extends StatefulWidget { final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ToggleCheckListButtonState createState() => _ToggleCheckListButtonState(); @@ -91,16 +94,19 @@ class _ToggleCheckListButtonState extends State { @override Widget build(BuildContext context) { - return widget.childBuilder( - context, - Attribute.unchecked, - widget.icon, - widget.fillColor, - _isToggled, - _toggleAttribute, - widget.afterButtonPressed, - widget.iconSize, - widget.iconTheme, + return UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: widget.childBuilder( + context, + Attribute.unchecked, + widget.icon, + widget.fillColor, + _isToggled, + _toggleAttribute, + widget.afterButtonPressed, + widget.iconSize, + widget.iconTheme, + ), ); } diff --git a/lib/src/widgets/toolbar/toggle_style_button.dart b/lib/src/widgets/toolbar/toggle_style_button.dart index 176b96b0..1eb0eb57 100644 --- a/lib/src/widgets/toolbar/toggle_style_button.dart +++ b/lib/src/widgets/toolbar/toggle_style_button.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../models/documents/attribute.dart'; import '../../models/documents/style.dart'; import '../../models/themes/quill_icon_theme.dart'; +import '../../utils/widgets.dart'; import '../controller.dart'; import '../toolbar.dart'; @@ -28,6 +29,7 @@ class ToggleStyleButton extends StatefulWidget { this.childBuilder = defaultToggleStyleButtonBuilder, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -46,6 +48,7 @@ class ToggleStyleButton extends StatefulWidget { final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ToggleStyleButtonState createState() => _ToggleStyleButtonState(); @@ -65,16 +68,19 @@ class _ToggleStyleButtonState extends State { @override Widget build(BuildContext context) { - return widget.childBuilder( - context, - widget.attribute, - widget.icon, - widget.fillColor, - _isToggled, - _toggleAttribute, - widget.afterButtonPressed, - widget.iconSize, - widget.iconTheme, + return UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: widget.childBuilder( + context, + widget.attribute, + widget.icon, + widget.fillColor, + _isToggled, + _toggleAttribute, + widget.afterButtonPressed, + widget.iconSize, + widget.iconTheme, + ), ); } From f7e1a50df1ac306e829dec88a70338a31e8603f1 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Fri, 14 Apr 2023 22:29:34 -0700 Subject: [PATCH 136/204] Upgrade to 7.1.5 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d24d17b..3eb12046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.5] +* Add tooltips for toolbar buttons. + # [7.1.4] * Fix inserting tab character in lists. diff --git a/pubspec.yaml b/pubspec.yaml index 3a11ea64..c6d50420 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.4 +version: 7.1.5 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 3f7437b863aee2cd2329d7fb57151b1623f1e067 Mon Sep 17 00:00:00 2001 From: bohdanudreambit Date: Sat, 15 Apr 2023 19:21:09 +0300 Subject: [PATCH 137/204] add enableUnfocusOnTapOutside field to RawEditor and Editor widgets (#1177) --- lib/src/widgets/editor.dart | 5 +++++ lib/src/widgets/raw_editor.dart | 3 +++ 2 files changed, 8 insertions(+) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index e1f6d220..2a1bc5e3 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -182,6 +182,7 @@ class QuillEditor extends StatefulWidget { this.customShortcuts, this.customActions, this.detectWordBoundary = true, + this.enableUnfocusOnTapOutside = true, this.customLinkPrefixes = const [], Key? key}) : super(key: key); @@ -246,6 +247,9 @@ class QuillEditor extends StatefulWidget { /// Defaults to `false`. Cannot be `null`. final bool autoFocus; + /// Whether focus should be revoked on tap outside the editor. + final bool enableUnfocusOnTapOutside; + /// Whether to show cursor. /// /// The cursor refers to the blinking caret when the editor is focused. @@ -506,6 +510,7 @@ class QuillEditorState extends State customShortcuts: widget.customShortcuts, customActions: widget.customActions, customLinkPrefixes: widget.customLinkPrefixes, + enableUnfocusOnTapOutside: widget.enableUnfocusOnTapOutside, ); final editor = I18n( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 9531cbde..64059983 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -72,6 +72,7 @@ class RawEditor extends StatefulWidget { this.customActions, this.expands = false, this.autoFocus = false, + this.enableUnfocusOnTapOutside = true, this.keyboardAppearance = Brightness.light, this.enableInteractiveSelection = true, this.scrollPhysics, @@ -95,6 +96,7 @@ class RawEditor extends StatefulWidget { final ScrollController scrollController; final bool scrollable; final double scrollBottomInset; + final bool enableUnfocusOnTapOutside; /// Additional space around the editor contents. final EdgeInsetsGeometry padding; @@ -494,6 +496,7 @@ class RawEditorState extends EditorState maxHeight: widget.maxHeight ?? double.infinity); return TextFieldTapRegion( + enabled: widget.enableUnfocusOnTapOutside, onTapOutside: _defaultOnTapOutside, child: QuillStyles( data: _styles!, From c3e410c2a2ff5b6b26fbbd82b604ec86c3068a7e Mon Sep 17 00:00:00 2001 From: Cheryl Date: Sat, 15 Apr 2023 10:16:15 -0700 Subject: [PATCH 138/204] Upgrade to 7.1.6 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eb12046..bf092c36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.6] +* Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets. + # [7.1.5] * Add tooltips for toolbar buttons. diff --git a/pubspec.yaml b/pubspec.yaml index c6d50420..208761ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.5 +version: 7.1.6 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 431efc1eda5f1a3e72fb7cd26282b7b37e0a4a44 Mon Sep 17 00:00:00 2001 From: BambinoUA <45417992+bambinoua@users.noreply.github.com> Date: Mon, 17 Apr 2023 17:13:15 +0300 Subject: [PATCH 139/204] Toolbar tweaks (#1179) --- .../lib/embeds/toolbar/camera_button.dart | 1 + .../lib/embeds/toolbar/formula_button.dart | 1 + .../lib/embeds/toolbar/image_button.dart | 1 + .../lib/embeds/toolbar/video_button.dart | 1 + .../lib/flutter_quill_extensions.dart | 8 ++ flutter_quill_extensions/pubspec.yaml | 2 +- lib/flutter_quill.dart | 1 + lib/src/utils/font.dart | 3 +- lib/src/widgets/toolbar.dart | 135 ++++++++++-------- .../toolbar/quill_font_family_button.dart | 57 ++++++-- .../toolbar/quill_font_size_button.dart | 60 ++++++-- .../toolbar/select_alignment_button.dart | 6 +- 12 files changed, 186 insertions(+), 90 deletions(-) diff --git a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart index 202d97b1..5ec8e28e 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart @@ -55,6 +55,7 @@ class CameraButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart index 70c4987f..5c7c5684 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart @@ -36,6 +36,7 @@ class FormulaButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart index 8cad519c..d05d851d 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart @@ -51,6 +51,7 @@ class ImageButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart index 30e0dbf7..e5c1ab73 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart @@ -51,6 +51,7 @@ class VideoButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/flutter_quill_extensions.dart b/flutter_quill_extensions/lib/flutter_quill_extensions.dart index 7ef38328..12b3bd82 100644 --- a/flutter_quill_extensions/lib/flutter_quill_extensions.dart +++ b/flutter_quill_extensions/lib/flutter_quill_extensions.dart @@ -32,6 +32,10 @@ class FlutterQuillEmbeds { bool showVideoButton = true, bool showCameraButton = true, bool showFormulaButton = false, + String? imageButtonTooltip, + String? videoButtonTooltip, + String? cameraButtonTooltip, + String? formulaButtonTooltip, OnImagePickCallback? onImagePickCallback, OnVideoPickCallback? onVideoPickCallback, MediaPickSettingSelector? mediaPickSettingSelector, @@ -45,6 +49,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => ImageButton( icon: Icons.image, iconSize: toolbarIconSize, + tooltip: imageButtonTooltip, controller: controller, onImagePickCallback: onImagePickCallback, filePickImpl: filePickImpl, @@ -57,6 +62,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton( icon: Icons.movie_creation, iconSize: toolbarIconSize, + tooltip: videoButtonTooltip, controller: controller, onVideoPickCallback: onVideoPickCallback, filePickImpl: filePickImpl, @@ -70,6 +76,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton( icon: Icons.photo_camera, iconSize: toolbarIconSize, + tooltip: cameraButtonTooltip, controller: controller, onImagePickCallback: onImagePickCallback, onVideoPickCallback: onVideoPickCallback, @@ -83,6 +90,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => FormulaButton( icon: Icons.functions, iconSize: toolbarIconSize, + tooltip: formulaButtonTooltip, controller: controller, iconTheme: iconTheme, dialogTheme: dialogTheme, diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index e0e1269d..ba650ad9 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.4 + flutter_quill: ^7.1.6 image_picker: ^0.8.5+3 photo_view: ^0.14.0 diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index aefc566c..bfc666f1 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -25,3 +25,4 @@ export 'src/widgets/embeds.dart'; export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction; export 'src/widgets/style_widgets/style_widgets.dart'; export 'src/widgets/toolbar.dart'; +export 'src/widgets/toolbar/enum.dart'; diff --git a/lib/src/utils/font.dart b/lib/src/utils/font.dart index 4962a9a2..1e996e10 100644 --- a/lib/src/utils/font.dart +++ b/lib/src/utils/font.dart @@ -1,5 +1,6 @@ dynamic getFontSize(dynamic sizeValue) { - if (sizeValue is String && ['small', 'large', 'huge'].contains(sizeValue)) { + if (sizeValue is String && + ['small', 'normal', 'large', 'huge'].contains(sizeValue)) { return sizeValue; } diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 08c7c6fe..d5a5c93a 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -6,7 +6,6 @@ import '../models/themes/quill_custom_button.dart'; import '../models/themes/quill_dialog_theme.dart'; import '../models/themes/quill_icon_theme.dart'; import '../translations/toolbar.i18n.dart'; -import '../utils/font.dart'; import 'controller.dart'; import 'embeds.dart'; import 'toolbar/arrow_indicated_button_list.dart'; @@ -30,32 +29,39 @@ export 'toolbar/color_button.dart'; export 'toolbar/history_button.dart'; export 'toolbar/indent_button.dart'; export 'toolbar/link_style_button.dart'; +export 'toolbar/quill_font_family_button.dart'; export 'toolbar/quill_font_size_button.dart'; export 'toolbar/quill_icon_button.dart'; +export 'toolbar/search_button.dart'; export 'toolbar/select_alignment_button.dart'; export 'toolbar/select_header_style_button.dart'; export 'toolbar/toggle_check_list_button.dart'; export 'toolbar/toggle_style_button.dart'; -// The default size of the icon of a button. +/// The default size of the icon of a button. const double kDefaultIconSize = 18; -// The factor of how much larger the button is in relation to the icon. +/// The factor of how much larger the button is in relation to the icon. const double kIconButtonFactor = 1.77; +/// The horizontal margin between the contents of each toolbar section. +const double kToolbarSectionSpacing = 4; + class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { const QuillToolbar({ required this.children, this.axis = Axis.horizontal, - this.toolbarSize = 36, + this.toolbarSize = kDefaultIconSize * 2, + this.toolbarSectionSpacing = kToolbarSectionSpacing, this.toolbarIconAlignment = WrapAlignment.center, this.toolbarIconCrossAlignment = WrapCrossAlignment.center, - this.toolbarSectionSpacing = 4, this.multiRowsDisplay = true, this.color, this.customButtons = const [], this.locale, VoidCallback? afterButtonPressed, + this.sectionDividerColor, + this.sectionDividerSpace, Key? key, }) : super(key: key); @@ -63,9 +69,10 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { required QuillController controller, Axis axis = Axis.horizontal, double toolbarIconSize = kDefaultIconSize, - double toolbarSectionSpacing = 4, + double toolbarSectionSpacing = kToolbarSectionSpacing, WrapAlignment toolbarIconAlignment = WrapAlignment.center, WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center, + bool multiRowsDisplay = true, bool showDividers = true, bool showFontFamily = true, bool showFontSize = true, @@ -93,7 +100,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { bool showLink = true, bool showUndo = true, bool showRedo = true, - bool multiRowsDisplay = true, bool showDirection = false, bool showSearchButton = true, List customButtons = const [], @@ -138,6 +144,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// The color of the toolbar Color? color, + + /// The color of the toolbar section divider + Color? sectionDividerColor, + + /// The space occupied by toolbar divider + double? sectionDividerSpace, Key? key, }) { final isButtonGroupShown = [ @@ -260,21 +272,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { tooltip: buttonTooltips[ToolbarButtons.fontFamily], attribute: Attribute.font, controller: controller, - items: [ - for (MapEntry fontFamily in fontFamilies.entries) - PopupMenuItem( - key: ValueKey(fontFamily.key), - value: fontFamily.value, - child: Text(fontFamily.key.toString(), - style: TextStyle( - color: - fontFamily.value == 'Clear' ? Colors.red : null)), - ), - ], - onSelected: (newFont) { - controller.formatSelection(Attribute.fromKeyValue( - 'font', newFont == 'Clear' ? null : newFont)); - }, rawItemsMap: fontFamilies, afterButtonPressed: afterButtonPressed, ), @@ -285,20 +282,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { tooltip: buttonTooltips[ToolbarButtons.fontSize], attribute: Attribute.size, controller: controller, - items: [ - for (MapEntry fontSize in fontSizes.entries) - PopupMenuItem( - key: ValueKey(fontSize.key), - value: fontSize.value, - child: Text(fontSize.key.toString(), - style: TextStyle( - color: fontSize.value == '0' ? Colors.red : null)), - ), - ], - onSelected: (newSize) { - controller.formatSelection(Attribute.fromKeyValue( - 'size', newSize == '0' ? null : getFontSize(newSize))); - }, rawItemsMap: fontSizes, afterButtonPressed: afterButtonPressed, ), @@ -401,7 +384,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showAlignmentButtons) SelectAlignmentButton( controller: controller, @@ -436,7 +420,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showHeaderStyle) SelectHeaderStyleButton( tooltip: buttonTooltips[ToolbarButtons.headerStyle], @@ -452,7 +437,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showListNumbers) ToggleStyleButton( attribute: Attribute.ol, @@ -496,7 +482,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDividers && isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showQuote) ToggleStyleButton( attribute: Attribute.blockQuote, @@ -528,7 +515,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { afterButtonPressed: afterButtonPressed, ), if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showLink) LinkStyleButton( tooltip: buttonTooltips[ToolbarButtons.link], @@ -549,7 +537,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { afterButtonPressed: afterButtonPressed, ), if (customButtons.isNotEmpty) - if (showDividers) _dividerOnAxis(axis), + if (showDividers) + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), for (var customButton in customButtons) QuillIconButton( highlightElevation: 0, @@ -565,22 +555,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ); } - static Widget _dividerOnAxis(Axis axis) { - if (axis == Axis.horizontal) { - return const VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey, - ); - } else { - return const Divider( - indent: 12, - endIndent: 12, - color: Colors.grey, - ); - } - } - final List children; final Axis axis; final double toolbarSize; @@ -602,6 +576,15 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// List of custom buttons final List customButtons; + /// The color to use when painting the toolbar section divider. + /// + /// If this is null, then the [DividerThemeData.color] is used. If that is + /// also null, then [ThemeData.dividerColor] is used. + final Color? sectionDividerColor; + + /// The space occupied by toolbar section divider. + final double? sectionDividerSpace; + @override Size get preferredSize => axis == Axis.horizontal ? Size.fromHeight(toolbarSize) @@ -634,3 +617,39 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ); } } + +class AxisDivider extends StatelessWidget { + const AxisDivider( + this.axis, { + Key? key, + this.color, + this.space, + }) : super(key: key); + + const AxisDivider.horizontal({Color? color, double? space}) + : this(Axis.horizontal, color: color, space: space); + + const AxisDivider.vertical({Color? color, double? space}) + : this(Axis.vertical, color: color, space: space); + + final Axis axis; + final Color? color; + final double? space; + + @override + Widget build(BuildContext context) { + return axis == Axis.horizontal + ? Divider( + height: space, + color: color, + indent: 12, + endIndent: 12, + ) + : VerticalDivider( + width: space, + color: color, + indent: 12, + endIndent: 12, + ); + } +} diff --git a/lib/src/widgets/toolbar/quill_font_family_button.dart b/lib/src/widgets/toolbar/quill_font_family_button.dart index bdddc832..d214bf64 100644 --- a/lib/src/widgets/toolbar/quill_font_family_button.dart +++ b/lib/src/widgets/toolbar/quill_font_family_button.dart @@ -9,11 +9,11 @@ import '../controller.dart'; class QuillFontFamilyButton extends StatefulWidget { const QuillFontFamilyButton({ - required this.items, required this.rawItemsMap, required this.attribute, required this.controller, - required this.onSelected, + @Deprecated('It is not required because of `rawItemsMap`') this.items, + this.onSelected, this.iconSize = 40, this.fillColor, this.hoverElevation = 1, @@ -21,6 +21,11 @@ class QuillFontFamilyButton extends StatefulWidget { this.iconTheme, this.afterButtonPressed, this.tooltip, + this.padding, + this.style, + this.width, + this.renderFontFamilies = true, + this.alignment, Key? key, }) : super(key: key); @@ -28,14 +33,20 @@ class QuillFontFamilyButton extends StatefulWidget { final Color? fillColor; final double hoverElevation; final double highlightElevation; - final List> items; + @Deprecated('It is not required because of `rawItemsMap`') + final List>? items; final Map rawItemsMap; - final ValueChanged onSelected; + final ValueChanged? onSelected; final QuillIconTheme? iconTheme; final Attribute attribute; final QuillController controller; final VoidCallback? afterButtonPressed; final String? tooltip; + final EdgeInsetsGeometry? padding; + final TextStyle? style; + final double? width; + final bool renderFontFamilies; + final AlignmentGeometry? alignment; @override _QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState(); @@ -90,7 +101,10 @@ class _QuillFontFamilyButtonState extends State { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81), + constraints: BoxConstraints.tightFor( + height: widget.iconSize * 1.81, + width: widget.width, + ), child: UtilityWidgets.maybeTooltip( message: widget.tooltip, child: RawMaterialButton( @@ -127,7 +141,20 @@ class _QuillFontFamilyButtonState extends State { showMenu( context: context, elevation: 4, - items: widget.items, + items: [ + for (MapEntry fontFamily in widget.rawItemsMap.entries) + PopupMenuItem( + key: ValueKey(fontFamily.key), + value: fontFamily.value, + child: Text( + fontFamily.key.toString(), + style: TextStyle( + fontFamily: + widget.renderFontFamilies ? fontFamily.value : null, + color: fontFamily.value == 'Clear' ? Colors.red : null), + ), + ), + ], position: position, shape: popupMenuTheme.shape, color: popupMenuTheme.color, @@ -140,7 +167,9 @@ class _QuillFontFamilyButtonState extends State { setState(() { _currentValue = keyName ?? _defaultDisplayText; if (keyName != null) { - widget.onSelected(newValue); + widget.controller.formatSelection(Attribute.fromKeyValue( + 'font', newValue == 'Clear' ? null : newValue)); + widget.onSelected?.call(newValue); } }); }); @@ -148,16 +177,18 @@ class _QuillFontFamilyButtonState extends State { Widget _buildContent(BuildContext context) { final theme = Theme.of(context); - return Padding( - padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + return Container( + alignment: widget.alignment ?? Alignment.center, + padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(_currentValue, - style: TextStyle( - fontSize: widget.iconSize / 1.15, - color: widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color)), + style: widget.style ?? + TextStyle( + fontSize: widget.iconSize / 1.15, + color: widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color)), const SizedBox(width: 3), Icon(Icons.arrow_drop_down, size: widget.iconSize / 1.15, diff --git a/lib/src/widgets/toolbar/quill_font_size_button.dart b/lib/src/widgets/toolbar/quill_font_size_button.dart index f82175c2..83d39747 100644 --- a/lib/src/widgets/toolbar/quill_font_size_button.dart +++ b/lib/src/widgets/toolbar/quill_font_size_button.dart @@ -10,11 +10,11 @@ import '../controller.dart'; class QuillFontSizeButton extends StatefulWidget { const QuillFontSizeButton({ - required this.items, required this.rawItemsMap, required this.attribute, required this.controller, - required this.onSelected, + this.onSelected, + @Deprecated('It is not required because of `rawItemsMap`') this.items, this.iconSize = 40, this.fillColor, this.hoverElevation = 1, @@ -22,21 +22,33 @@ class QuillFontSizeButton extends StatefulWidget { this.iconTheme, this.afterButtonPressed, this.tooltip, + this.padding, + this.style, + this.width, + this.initialValue, + this.alignment, Key? key, - }) : super(key: key); + }) : assert(rawItemsMap.length > 0), + super(key: key); final double iconSize; final Color? fillColor; final double hoverElevation; final double highlightElevation; - final List> items; + @Deprecated('It is not required because of `rawItemsMap`') + final List>? items; final Map rawItemsMap; - final ValueChanged onSelected; + final ValueChanged? onSelected; final QuillIconTheme? iconTheme; final Attribute attribute; final QuillController controller; final VoidCallback? afterButtonPressed; final String? tooltip; + final EdgeInsetsGeometry? padding; + final TextStyle? style; + final double? width; + final String? initialValue; + final AlignmentGeometry? alignment; @override _QuillFontSizeButtonState createState() => _QuillFontSizeButtonState(); @@ -50,7 +62,7 @@ class _QuillFontSizeButtonState extends State { @override void initState() { super.initState(); - _currentValue = _defaultDisplayText = 'Size'.i18n; + _currentValue = _defaultDisplayText = widget.initialValue ?? 'Size'.i18n; widget.controller.addListener(_didChangeEditingValue); } @@ -91,7 +103,10 @@ class _QuillFontSizeButtonState extends State { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81), + constraints: BoxConstraints.tightFor( + height: widget.iconSize * 1.81, + width: widget.width, + ), child: UtilityWidgets.maybeTooltip( message: widget.tooltip, child: RawMaterialButton( @@ -128,7 +143,18 @@ class _QuillFontSizeButtonState extends State { showMenu( context: context, elevation: 4, - items: widget.items, + items: [ + for (MapEntry fontSize in widget.rawItemsMap.entries) + PopupMenuItem( + key: ValueKey(fontSize.key), + value: fontSize.value, + child: Text( + fontSize.key.toString(), + style: + TextStyle(color: fontSize.value == '0' ? Colors.red : null), + ), + ), + ], position: position, shape: popupMenuTheme.shape, color: popupMenuTheme.color, @@ -141,7 +167,9 @@ class _QuillFontSizeButtonState extends State { setState(() { _currentValue = keyName ?? _defaultDisplayText; if (keyName != null) { - widget.onSelected(newValue); + widget.controller.formatSelection(Attribute.fromKeyValue( + 'size', newValue == '0' ? null : getFontSize(newValue))); + widget.onSelected?.call(newValue); } }); }); @@ -149,16 +177,18 @@ class _QuillFontSizeButtonState extends State { Widget _buildContent(BuildContext context) { final theme = Theme.of(context); - return Padding( - padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + return Container( + alignment: widget.alignment ?? Alignment.center, + padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(_currentValue, - style: TextStyle( - fontSize: widget.iconSize / 1.15, - color: widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color)), + style: widget.style ?? + TextStyle( + fontSize: widget.iconSize / 1.15, + color: widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color)), const SizedBox(width: 3), Icon(Icons.arrow_drop_down, size: widget.iconSize / 1.15, diff --git a/lib/src/widgets/toolbar/select_alignment_button.dart b/lib/src/widgets/toolbar/select_alignment_button.dart index 4d22b2cf..922b30fc 100644 --- a/lib/src/widgets/toolbar/select_alignment_button.dart +++ b/lib/src/widgets/toolbar/select_alignment_button.dart @@ -20,6 +20,7 @@ class SelectAlignmentButton extends StatefulWidget { this.showJustifyAlignment, this.afterButtonPressed, this.tooltips = const {}, + this.padding, Key? key, }) : super(key: key); @@ -33,6 +34,7 @@ class SelectAlignmentButton extends StatefulWidget { final bool? showJustifyAlignment; final VoidCallback? afterButtonPressed; final Map tooltips; + final EdgeInsetsGeometry? padding; @override _SelectAlignmentButtonState createState() => _SelectAlignmentButtonState(); @@ -100,8 +102,8 @@ class _SelectAlignmentButtonState extends State { mainAxisSize: MainAxisSize.min, children: List.generate(buttonCount, (index) { return Padding( - // ignore: prefer_const_constructors - padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), + padding: widget.padding ?? + const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), child: ConstrainedBox( constraints: BoxConstraints.tightFor( width: widget.iconSize * kIconButtonFactor, From 4617d79b64059002b0a2d130d1a0fc3e5e275002 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Mon, 17 Apr 2023 09:23:24 -0700 Subject: [PATCH 140/204] Upgrade to 7.1.7 --- CHANGELOG.md | 3 +++ flutter_quill_extensions/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf092c36..4a4dc3a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.7] +* Toolbar tweaks. + # [7.1.6] * Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets. diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index ba650ad9..cd2b2ee8 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.6 + flutter_quill: ^7.1.7 image_picker: ^0.8.5+3 photo_view: ^0.14.0 diff --git a/pubspec.yaml b/pubspec.yaml index 208761ac..94ff3915 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.6 +version: 7.1.7 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 752e1a746bccadbb9f16b09f54e145612791e588 Mon Sep 17 00:00:00 2001 From: BambinoUA <45417992+bambinoua@users.noreply.github.com> Date: Tue, 18 Apr 2023 11:46:15 +0300 Subject: [PATCH 141/204] Update pubspec.yaml (#1181) --- flutter_quill_extensions/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index cd2b2ee8..2bb092ce 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: 0.2.0 +version: 0.2.1 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions From 6d9b43f909cc609cd98fe03db412e2c41c384d3e Mon Sep 17 00:00:00 2001 From: BambinoUA <45417992+bambinoua@users.noreply.github.com> Date: Tue, 18 Apr 2023 19:04:19 +0300 Subject: [PATCH 142/204] Dropdown tweaks (#1182) --- lib/src/utils/widgets.dart | 10 +++ .../toolbar/quill_font_family_button.dart | 63 ++++++++++++++----- .../toolbar/quill_font_size_button.dart | 42 +++++++++---- 3 files changed, 88 insertions(+), 27 deletions(-) diff --git a/lib/src/utils/widgets.dart b/lib/src/utils/widgets.dart index 1b560e84..c27777f0 100644 --- a/lib/src/utils/widgets.dart +++ b/lib/src/utils/widgets.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +typedef WidgetWrapper = Widget Function(Widget child); + /// Provides utiulity widgets. abstract class UtilityWidgets { /// Conditionally wraps the [child] with [Tooltip] widget if [message] @@ -8,4 +10,12 @@ abstract class UtilityWidgets { (message ?? '').isNotEmpty ? Tooltip(message: message!, child: child) : child; + + /// Conditionally wraps the [child] with [wrapper] widget if [enabled] + /// is true. + static Widget maybeWidget( + {required WidgetWrapper wrapper, + required Widget child, + bool enabled = false}) => + enabled ? wrapper(child) : child; } diff --git a/lib/src/widgets/toolbar/quill_font_family_button.dart b/lib/src/widgets/toolbar/quill_font_family_button.dart index d214bf64..8108c200 100644 --- a/lib/src/widgets/toolbar/quill_font_family_button.dart +++ b/lib/src/widgets/toolbar/quill_font_family_button.dart @@ -25,9 +25,16 @@ class QuillFontFamilyButton extends StatefulWidget { this.style, this.width, this.renderFontFamilies = true, - this.alignment, + this.initialValue, + this.labelOverflow = TextOverflow.visible, + this.overrideTooltipByFontFamily = false, + this.itemHeight, + this.itemPadding, + this.defaultItemColor = Colors.red, Key? key, - }) : super(key: key); + }) : assert(rawItemsMap.length > 0), + assert(initialValue == null || initialValue.length > 0), + super(key: key); final double iconSize; final Color? fillColor; @@ -46,7 +53,12 @@ class QuillFontFamilyButton extends StatefulWidget { final TextStyle? style; final double? width; final bool renderFontFamilies; - final AlignmentGeometry? alignment; + final String? initialValue; + final TextOverflow labelOverflow; + final bool overrideTooltipByFontFamily; + final double? itemHeight; + final EdgeInsets? itemPadding; + final Color? defaultItemColor; @override _QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState(); @@ -60,7 +72,7 @@ class _QuillFontFamilyButtonState extends State { @override void initState() { super.initState(); - _currentValue = _defaultDisplayText = 'Font'.i18n; + _currentValue = _defaultDisplayText = widget.initialValue ?? 'Font'.i18n; widget.controller.addListener(_didChangeEditingValue); } @@ -105,8 +117,18 @@ class _QuillFontFamilyButtonState extends State { height: widget.iconSize * 1.81, width: widget.width, ), - child: UtilityWidgets.maybeTooltip( - message: widget.tooltip, + child: UtilityWidgets.maybeWidget( + enabled: (widget.tooltip ?? '').isNotEmpty || + widget.overrideTooltipByFontFamily, + wrapper: (child) { + var effectiveTooltip = widget.tooltip ?? ''; + if (widget.overrideTooltipByFontFamily) { + effectiveTooltip = effectiveTooltip.isNotEmpty + ? '$effectiveTooltip: $_currentValue' + : '${'Font'.i18n}: $_currentValue'; + } + return Tooltip(message: effectiveTooltip, child: child); + }, child: RawMaterialButton( visualDensity: VisualDensity.compact, shape: RoundedRectangleBorder( @@ -146,12 +168,16 @@ class _QuillFontFamilyButtonState extends State { PopupMenuItem( key: ValueKey(fontFamily.key), value: fontFamily.value, + height: widget.itemHeight ?? kMinInteractiveDimension, + padding: widget.itemPadding, child: Text( fontFamily.key.toString(), style: TextStyle( - fontFamily: - widget.renderFontFamilies ? fontFamily.value : null, - color: fontFamily.value == 'Clear' ? Colors.red : null), + fontFamily: widget.renderFontFamilies ? fontFamily.value : null, + color: fontFamily.value == 'Clear' + ? widget.defaultItemColor + : null, + ), ), ), ], @@ -177,18 +203,27 @@ class _QuillFontFamilyButtonState extends State { Widget _buildContent(BuildContext context) { final theme = Theme.of(context); - return Container( - alignment: widget.alignment ?? Alignment.center, + final hasFinalWidth = widget.width != null; + return Padding( padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(_currentValue, + UtilityWidgets.maybeWidget( + enabled: hasFinalWidth, + wrapper: (child) => Expanded(child: child), + child: Text( + _currentValue, + maxLines: 1, + overflow: widget.labelOverflow, style: widget.style ?? TextStyle( fontSize: widget.iconSize / 1.15, color: widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color)), + theme.iconTheme.color), + ), + ), const SizedBox(width: 3), Icon(Icons.arrow_drop_down, size: widget.iconSize / 1.15, diff --git a/lib/src/widgets/toolbar/quill_font_size_button.dart b/lib/src/widgets/toolbar/quill_font_size_button.dart index 83d39747..e445c0ae 100644 --- a/lib/src/widgets/toolbar/quill_font_size_button.dart +++ b/lib/src/widgets/toolbar/quill_font_size_button.dart @@ -26,9 +26,13 @@ class QuillFontSizeButton extends StatefulWidget { this.style, this.width, this.initialValue, - this.alignment, + this.labelOverflow = TextOverflow.visible, + this.itemHeight, + this.itemPadding, + this.defaultItemColor = Colors.red, Key? key, }) : assert(rawItemsMap.length > 0), + assert(initialValue == null || initialValue.length > 0), super(key: key); final double iconSize; @@ -48,7 +52,10 @@ class QuillFontSizeButton extends StatefulWidget { final TextStyle? style; final double? width; final String? initialValue; - final AlignmentGeometry? alignment; + final TextOverflow labelOverflow; + final double? itemHeight; + final EdgeInsets? itemPadding; + final Color? defaultItemColor; @override _QuillFontSizeButtonState createState() => _QuillFontSizeButtonState(); @@ -148,10 +155,13 @@ class _QuillFontSizeButtonState extends State { PopupMenuItem( key: ValueKey(fontSize.key), value: fontSize.value, + height: widget.itemHeight ?? kMinInteractiveDimension, + padding: widget.itemPadding, child: Text( fontSize.key.toString(), - style: - TextStyle(color: fontSize.value == '0' ? Colors.red : null), + style: TextStyle( + color: fontSize.value == '0' ? widget.defaultItemColor : null, + ), ), ), ], @@ -177,18 +187,24 @@ class _QuillFontSizeButtonState extends State { Widget _buildContent(BuildContext context) { final theme = Theme.of(context); - return Container( - alignment: widget.alignment ?? Alignment.center, + final hasFinalWidth = widget.width != null; + return Padding( padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(_currentValue, - style: widget.style ?? - TextStyle( - fontSize: widget.iconSize / 1.15, - color: widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color)), + UtilityWidgets.maybeWidget( + enabled: hasFinalWidth, + wrapper: (child) => Expanded(child: child), + child: Text(_currentValue, + overflow: widget.labelOverflow, + style: widget.style ?? + TextStyle( + fontSize: widget.iconSize / 1.15, + color: widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color)), + ), const SizedBox(width: 3), Icon(Icons.arrow_drop_down, size: widget.iconSize / 1.15, From f2a1a1f45ac90d539cbf84f87a33a13d5085f155 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Tue, 18 Apr 2023 09:10:18 -0700 Subject: [PATCH 143/204] Upgrade to 7.1.8 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4dc3a8..a745d7b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.8] +* Dropdown tweaks. + # [7.1.7] * Toolbar tweaks. diff --git a/pubspec.yaml b/pubspec.yaml index 94ff3915..38a3d504 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.7 +version: 7.1.8 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 411f911dfb66e64e5810321155539d54611e2f02 Mon Sep 17 00:00:00 2001 From: BambinoUA <130981115+MacDeveloper1@users.noreply.github.com> Date: Thu, 20 Apr 2023 16:53:07 +0200 Subject: [PATCH 144/204] Editor tweaks (#1185) --- lib/src/models/documents/attribute.dart | 38 +- lib/src/models/themes/quill_dialog_theme.dart | 102 +++- lib/src/translations/toolbar.i18n.dart | 8 + lib/src/widgets/editor.dart | 102 ++-- lib/src/widgets/raw_editor.dart | 326 +++++++++---- lib/src/widgets/toolbar.dart | 1 + .../widgets/toolbar/link_style_button2.dart | 447 ++++++++++++++++++ pubspec.yaml | 2 +- 8 files changed, 871 insertions(+), 155 deletions(-) create mode 100644 lib/src/widgets/toolbar/link_style_button2.dart diff --git a/lib/src/models/documents/attribute.dart b/lib/src/models/documents/attribute.dart index 2ec7d7f2..9bf75396 100644 --- a/lib/src/models/documents/attribute.dart +++ b/lib/src/models/documents/attribute.dart @@ -42,6 +42,8 @@ class Attribute { Attribute.style.key: Attribute.style, Attribute.token.key: Attribute.token, Attribute.script.key: Attribute.script, + Attribute.image.key: Attribute.image, + Attribute.video.key: Attribute.video, }); static const BoldAttribute bold = BoldAttribute(); @@ -90,7 +92,7 @@ class Attribute { static const TokenAttribute token = TokenAttribute(''); - static const ScriptAttribute script = ScriptAttribute(''); + static final ScriptAttribute script = ScriptAttribute(null); static const String mobileWidth = 'mobileWidth'; @@ -100,6 +102,10 @@ class Attribute { static const String mobileAlignment = 'mobileAlignment'; + static const ImageAttribute image = ImageAttribute(null); + + static const VideoAttribute video = VideoAttribute(null); + static final Set inlineKeys = { Attribute.bold.key, Attribute.italic.key, @@ -138,6 +144,11 @@ class Attribute { Attribute.blockQuote.key, }); + static final Set embedKeys = { + Attribute.image.key, + Attribute.video.key, + }; + static const Attribute h1 = HeaderAttribute(level: 1); static const Attribute h2 = HeaderAttribute(level: 2); @@ -346,7 +357,26 @@ class TokenAttribute extends Attribute { } // `script` is supposed to be inline attribute but it is not supported yet -class ScriptAttribute extends Attribute { - const ScriptAttribute(String val) - : super('script', AttributeScope.IGNORE, val); +class ScriptAttribute extends Attribute { + ScriptAttribute(ScriptAttributes? val) + : super('script', AttributeScope.IGNORE, val?.value); +} + +enum ScriptAttributes { + sup('super'), + sub('sup'); + + const ScriptAttributes(this.value); + + final String value; +} + +class ImageAttribute extends Attribute { + const ImageAttribute(String? url) + : super('image', AttributeScope.EMBEDS, url); +} + +class VideoAttribute extends Attribute { + const VideoAttribute(String? url) + : super('video', AttributeScope.EMBEDS, url); } diff --git a/lib/src/models/themes/quill_dialog_theme.dart b/lib/src/models/themes/quill_dialog_theme.dart index 795d35d5..47d732b9 100644 --- a/lib/src/models/themes/quill_dialog_theme.dart +++ b/lib/src/models/themes/quill_dialog_theme.dart @@ -1,8 +1,19 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -class QuillDialogTheme { - QuillDialogTheme( - {this.labelTextStyle, this.inputTextStyle, this.dialogBackgroundColor}); +/// Used to configure the dialog's look and feel. +class QuillDialogTheme with Diagnosticable { + const QuillDialogTheme({ + this.labelTextStyle, + this.inputTextStyle, + this.dialogBackgroundColor, + this.shape, + this.buttonStyle, + this.linkDialogConstraints, + this.imageDialogConstraints, + this.isWrappable = false, + this.runSpacing = 8.0, + }) : assert(runSpacing >= 0); ///The text style to use for the label shown in the link-input dialog final TextStyle? labelTextStyle; @@ -10,6 +21,89 @@ class QuillDialogTheme { ///The text style to use for the input text shown in the link-input dialog final TextStyle? inputTextStyle; - ///The background color for the [LinkDialog()] + ///The background color for the Quill dialog final Color? dialogBackgroundColor; + + /// The shape of this dialog's border. + /// + /// Defines the dialog's [Material.shape]. + /// + /// The default shape is a [RoundedRectangleBorder] with a radius of 4.0 + final ShapeBorder? shape; + + /// Constrains for [LinkStyleDialog]. + final BoxConstraints? linkDialogConstraints; + + /// Constrains for [EmbedImageDialog]. + final BoxConstraints? imageDialogConstraints; + + /// Customizes this button's appearance. + final ButtonStyle? buttonStyle; + + /// Whether dialog's children are wrappred with [Wrap] instead of [Row]. + final bool isWrappable; + + /// How much space to place between the runs themselves in the cross axis. + /// + /// Make sense if [isWrappable] is `true`. + /// + /// Defaults to 0.0. + final double runSpacing; + + QuillDialogTheme copyWith({ + TextStyle? labelTextStyle, + TextStyle? inputTextStyle, + Color? dialogBackgroundColor, + ShapeBorder? shape, + ButtonStyle? buttonStyle, + BoxConstraints? linkDialogConstraints, + BoxConstraints? imageDialogConstraints, + bool? isWrappable, + double? runSpacing, + }) { + return QuillDialogTheme( + labelTextStyle: labelTextStyle ?? this.labelTextStyle, + inputTextStyle: inputTextStyle ?? this.inputTextStyle, + dialogBackgroundColor: + dialogBackgroundColor ?? this.dialogBackgroundColor, + shape: shape ?? this.shape, + buttonStyle: buttonStyle ?? this.buttonStyle, + linkDialogConstraints: + linkDialogConstraints ?? this.linkDialogConstraints, + imageDialogConstraints: + imageDialogConstraints ?? this.imageDialogConstraints, + isWrappable: isWrappable ?? this.isWrappable, + runSpacing: runSpacing ?? this.runSpacing, + ); + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is QuillDialogTheme && + other.labelTextStyle == labelTextStyle && + other.inputTextStyle == inputTextStyle && + other.dialogBackgroundColor == dialogBackgroundColor && + other.shape == shape && + other.buttonStyle == buttonStyle && + other.linkDialogConstraints == linkDialogConstraints && + other.imageDialogConstraints == imageDialogConstraints && + other.isWrappable == isWrappable && + other.runSpacing == runSpacing; + } + + @override + int get hashCode => Object.hash( + labelTextStyle, + inputTextStyle, + dialogBackgroundColor, + shape, + buttonStyle, + linkDialogConstraints, + imageDialogConstraints, + isWrappable, + runSpacing, + ); } diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index cb6c7853..507d6ebd 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -61,6 +61,10 @@ extension Localization on String { 'Increase indent': 'Increase indent', 'Decrease indent': 'Decrease indent', 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'en_us': { 'Paste a link': 'Paste a link', @@ -120,6 +124,10 @@ extension Localization on String { 'Increase indent': 'Increase indent', 'Decrease indent': 'Decrease indent', 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'ar': { 'Paste a link': 'نسخ الرابط', diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 2a1bc5e3..1c360ead 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -15,6 +15,7 @@ import '../models/documents/nodes/container.dart' as container_node; import '../models/documents/nodes/leaf.dart'; import '../models/documents/style.dart'; import '../models/structs/offset_value.dart'; +import '../models/themes/quill_dialog_theme.dart'; import '../utils/platform.dart'; import 'box.dart'; import 'controller.dart'; @@ -143,49 +144,50 @@ abstract class RenderAbstractEditor implements TextLayoutMetrics { } class QuillEditor extends StatefulWidget { - const QuillEditor( - {required this.controller, - required this.focusNode, - required this.scrollController, - required this.scrollable, - required this.padding, - required this.autoFocus, - required this.readOnly, - required this.expands, - this.showCursor, - this.paintCursorAboveText, - this.placeholder, - this.enableInteractiveSelection = true, - this.enableSelectionToolbar = true, - this.scrollBottomInset = 0, - this.minHeight, - this.maxHeight, - this.maxContentWidth, - this.customStyles, - this.textCapitalization = TextCapitalization.sentences, - this.keyboardAppearance = Brightness.light, - this.scrollPhysics, - this.onLaunchUrl, - this.onTapDown, - this.onTapUp, - this.onSingleLongTapStart, - this.onSingleLongTapMoveUpdate, - this.onSingleLongTapEnd, - this.embedBuilders, - this.unknownEmbedBuilder, - this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, - this.customStyleBuilder, - this.locale, - this.floatingCursorDisabled = false, - this.textSelectionControls, - this.onImagePaste, - this.customShortcuts, - this.customActions, - this.detectWordBoundary = true, - this.enableUnfocusOnTapOutside = true, - this.customLinkPrefixes = const [], - Key? key}) - : super(key: key); + const QuillEditor({ + required this.controller, + required this.focusNode, + required this.scrollController, + required this.scrollable, + required this.padding, + required this.autoFocus, + required this.readOnly, + required this.expands, + this.showCursor, + this.paintCursorAboveText, + this.placeholder, + this.enableInteractiveSelection = true, + this.enableSelectionToolbar = true, + this.scrollBottomInset = 0, + this.minHeight, + this.maxHeight, + this.maxContentWidth, + this.customStyles, + this.textCapitalization = TextCapitalization.sentences, + this.keyboardAppearance = Brightness.light, + this.scrollPhysics, + this.onLaunchUrl, + this.onTapDown, + this.onTapUp, + this.onSingleLongTapStart, + this.onSingleLongTapMoveUpdate, + this.onSingleLongTapEnd, + this.embedBuilders, + this.unknownEmbedBuilder, + this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, + this.customStyleBuilder, + this.locale, + this.floatingCursorDisabled = false, + this.textSelectionControls, + this.onImagePaste, + this.customShortcuts, + this.customActions, + this.detectWordBoundary = true, + this.enableUnfocusOnTapOutside = true, + this.customLinkPrefixes = const [], + this.dialogTheme, + Key? key, + }) : super(key: key); factory QuillEditor.basic({ required QuillController controller, @@ -302,6 +304,7 @@ class QuillEditor extends StatefulWidget { /// horizontally centered. This is mostly useful on devices with wide screens. final double? maxContentWidth; + /// Allows to override [DefaultStyles]. final DefaultStyles? customStyles; /// Whether this editor's height will be sized to fill its parent. @@ -401,7 +404,14 @@ class QuillEditor extends StatefulWidget { /// Returns the url of the image if the image should be inserted. final Future Function(Uint8List imageBytes)? onImagePaste; - final Map? customShortcuts; + /// Contains user-defined shortcuts map. + /// + /// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#shortcuts] + final Map? customShortcuts; + + /// Contains user-defined actions. + /// + /// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#actions] final Map>? customActions; final bool detectWordBoundary; @@ -412,6 +422,9 @@ class QuillEditor extends StatefulWidget { /// Useful for deeplinks final List customLinkPrefixes; + /// Configures the dialog theme. + final QuillDialogTheme? dialogTheme; + @override QuillEditorState createState() => QuillEditorState(); } @@ -511,6 +524,7 @@ class QuillEditorState extends State customActions: widget.customActions, customLinkPrefixes: widget.customLinkPrefixes, enableUnfocusOnTapOutside: widget.enableUnfocusOnTapOutside, + dialogTheme: widget.dialogTheme, ); final editor = I18n( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 64059983..b8218f8b 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -2,10 +2,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math' as math; -// ignore: unnecessary_import -import 'dart:typed_data'; import 'dart:ui' as ui hide TextStyle; +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -24,6 +23,7 @@ import '../models/documents/nodes/node.dart'; import '../models/documents/style.dart'; import '../models/structs/offset_value.dart'; import '../models/structs/vertical_spacing.dart'; +import '../models/themes/quill_dialog_theme.dart'; import '../utils/cast.dart'; import '../utils/delta.dart'; import '../utils/embeds.dart'; @@ -42,46 +42,48 @@ import 'raw_editor/raw_editor_state_text_input_client_mixin.dart'; import 'text_block.dart'; import 'text_line.dart'; import 'text_selection.dart'; +import 'toolbar/link_style_button2.dart'; import 'toolbar/search_dialog.dart'; class RawEditor extends StatefulWidget { - const RawEditor( - {required this.controller, - required this.focusNode, - required this.scrollController, - required this.scrollBottomInset, - required this.cursorStyle, - required this.selectionColor, - required this.selectionCtrls, - required this.embedBuilder, - Key? key, - this.scrollable = true, - this.padding = EdgeInsets.zero, - this.readOnly = false, - this.placeholder, - this.onLaunchUrl, - this.contextMenuBuilder = defaultContextMenuBuilder, - this.showSelectionHandles = false, - bool? showCursor, - this.textCapitalization = TextCapitalization.none, - this.maxHeight, - this.minHeight, - this.maxContentWidth, - this.customStyles, - this.customShortcuts, - this.customActions, - this.expands = false, - this.autoFocus = false, - this.enableUnfocusOnTapOutside = true, - this.keyboardAppearance = Brightness.light, - this.enableInteractiveSelection = true, - this.scrollPhysics, - this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, - this.customStyleBuilder, - this.floatingCursorDisabled = false, - this.onImagePaste, - this.customLinkPrefixes = const []}) - : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'), + const RawEditor({ + required this.controller, + required this.focusNode, + required this.scrollController, + required this.scrollBottomInset, + required this.cursorStyle, + required this.selectionColor, + required this.selectionCtrls, + required this.embedBuilder, + Key? key, + this.scrollable = true, + this.padding = EdgeInsets.zero, + this.readOnly = false, + this.placeholder, + this.onLaunchUrl, + this.contextMenuBuilder = defaultContextMenuBuilder, + this.showSelectionHandles = false, + bool? showCursor, + this.textCapitalization = TextCapitalization.none, + this.maxHeight, + this.minHeight, + this.maxContentWidth, + this.customStyles, + this.customShortcuts, + this.customActions, + this.expands = false, + this.autoFocus = false, + this.enableUnfocusOnTapOutside = true, + this.keyboardAppearance = Brightness.light, + this.enableInteractiveSelection = true, + this.scrollPhysics, + this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, + this.customStyleBuilder, + this.floatingCursorDisabled = false, + this.onImagePaste, + this.customLinkPrefixes = const [], + this.dialogTheme, + }) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'), assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'), assert(maxHeight == null || minHeight == null || maxHeight >= minHeight, 'maxHeight cannot be null'), @@ -190,6 +192,7 @@ class RawEditor extends StatefulWidget { /// horizontally centered. This is mostly useful on devices with wide screens. final double? maxContentWidth; + /// Allows to override [DefaultStyles]. final DefaultStyles? customStyles; /// Whether this widget's height will be sized to fill its parent. @@ -245,7 +248,14 @@ class RawEditor extends StatefulWidget { final Future Function(Uint8List imageBytes)? onImagePaste; - final Map? customShortcuts; + /// Contains user-defined shortcuts map. + /// + /// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#shortcuts] + final Map? customShortcuts; + + /// Contains user-defined actions. + /// + /// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#actions] final Map>? customActions; /// Builder function for embeddable objects. @@ -255,6 +265,9 @@ class RawEditor extends StatefulWidget { final bool floatingCursorDisabled; final List customLinkPrefixes; + /// Configures the dialog theme. + final QuillDialogTheme? dialogTheme; + @override State createState() => RawEditorState(); } @@ -495,78 +508,148 @@ class RawEditorState extends EditorState minHeight: widget.minHeight ?? 0.0, maxHeight: widget.maxHeight ?? double.infinity); + final isMacOS = Theme.of(context).platform == TargetPlatform.macOS; + return TextFieldTapRegion( enabled: widget.enableUnfocusOnTapOutside, onTapOutside: _defaultOnTapOutside, child: QuillStyles( data: _styles!, child: Shortcuts( - shortcuts: { + shortcuts: mergeMaps({ // shortcuts added for Desktop platforms. - LogicalKeySet(LogicalKeyboardKey.escape): - const HideSelectionToolbarIntent(), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyZ): - const UndoTextIntent(SelectionChangedCause.keyboard), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyY): - const RedoTextIntent(SelectionChangedCause.keyboard), + const SingleActivator( + LogicalKeyboardKey.escape, + ): const HideSelectionToolbarIntent(), + SingleActivator( + LogicalKeyboardKey.keyZ, + control: !isMacOS, + meta: isMacOS, + ): const UndoTextIntent(SelectionChangedCause.keyboard), + SingleActivator( + LogicalKeyboardKey.keyY, + control: !isMacOS, + meta: isMacOS, + ): const RedoTextIntent(SelectionChangedCause.keyboard), // Selection formatting. - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyB): - const ToggleTextStyleIntent(Attribute.bold), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyU): - const ToggleTextStyleIntent(Attribute.underline), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyI): - const ToggleTextStyleIntent(Attribute.italic), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyS): - const ToggleTextStyleIntent(Attribute.strikeThrough), - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.backquote): - const ToggleTextStyleIntent(Attribute.inlineCode), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyL): - const ToggleTextStyleIntent(Attribute.ul), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyO): - const ToggleTextStyleIntent(Attribute.ol), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyB): - const ToggleTextStyleIntent(Attribute.blockQuote), - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.tilde): - const ToggleTextStyleIntent(Attribute.codeBlock), - // Indent - LogicalKeySet(LogicalKeyboardKey.control, - LogicalKeyboardKey.bracketRight): - const IndentSelectionIntent(true), - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.bracketLeft): - const IndentSelectionIntent(false), - - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF): - const OpenSearchIntent(), - - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.digit1): - const ApplyHeaderIntent(Attribute.h1), - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.digit2): - const ApplyHeaderIntent(Attribute.h2), - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.digit3): - const ApplyHeaderIntent(Attribute.h3), - LogicalKeySet( - LogicalKeyboardKey.control, LogicalKeyboardKey.digit0): - const ApplyHeaderIntent(Attribute.header), - - LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyL): const ApplyCheckListIntent(), - - if (widget.customShortcuts != null) ...widget.customShortcuts!, - }, + SingleActivator( + LogicalKeyboardKey.keyB, + control: !isMacOS, + meta: isMacOS, + ): const ToggleTextStyleIntent(Attribute.bold), + SingleActivator( + LogicalKeyboardKey.keyU, + control: !isMacOS, + meta: isMacOS, + ): const ToggleTextStyleIntent(Attribute.underline), + SingleActivator( + LogicalKeyboardKey.keyI, + control: !isMacOS, + meta: isMacOS, + ): const ToggleTextStyleIntent(Attribute.italic), + SingleActivator( + LogicalKeyboardKey.keyS, + control: !isMacOS, + meta: isMacOS, + shift: true, + ): const ToggleTextStyleIntent(Attribute.strikeThrough), + SingleActivator( + LogicalKeyboardKey.backquote, + control: !isMacOS, + meta: isMacOS, + ): const ToggleTextStyleIntent(Attribute.inlineCode), + SingleActivator( + LogicalKeyboardKey.tilde, + control: !isMacOS, + meta: isMacOS, + shift: true, + ): const ToggleTextStyleIntent(Attribute.codeBlock), + SingleActivator( + LogicalKeyboardKey.keyB, + control: !isMacOS, + meta: isMacOS, + shift: true, + ): const ToggleTextStyleIntent(Attribute.blockQuote), + SingleActivator( + LogicalKeyboardKey.keyK, + control: !isMacOS, + meta: isMacOS, + ): const ApplyLinkIntent(), + + // Lists + SingleActivator( + LogicalKeyboardKey.keyL, + control: !isMacOS, + meta: isMacOS, + shift: true, + ): const ToggleTextStyleIntent(Attribute.ul), + SingleActivator( + LogicalKeyboardKey.keyO, + control: !isMacOS, + meta: isMacOS, + shift: true, + ): const ToggleTextStyleIntent(Attribute.ol), + SingleActivator( + LogicalKeyboardKey.keyC, + control: !isMacOS, + meta: isMacOS, + shift: true, + ): const ApplyCheckListIntent(), + + // Indents + SingleActivator( + LogicalKeyboardKey.keyM, + control: !isMacOS, + meta: isMacOS, + ): const IndentSelectionIntent(true), + SingleActivator( + LogicalKeyboardKey.keyM, + control: !isMacOS, + meta: isMacOS, + shift: true, + ): const IndentSelectionIntent(false), + + // Headers + SingleActivator( + LogicalKeyboardKey.digit1, + control: !isMacOS, + meta: isMacOS, + ): const ApplyHeaderIntent(Attribute.h1), + SingleActivator( + LogicalKeyboardKey.digit2, + control: !isMacOS, + meta: isMacOS, + ): const ApplyHeaderIntent(Attribute.h2), + SingleActivator( + LogicalKeyboardKey.digit3, + control: !isMacOS, + meta: isMacOS, + ): const ApplyHeaderIntent(Attribute.h3), + SingleActivator( + LogicalKeyboardKey.digit0, + control: !isMacOS, + meta: isMacOS, + ): const ApplyHeaderIntent(Attribute.header), + + SingleActivator( + LogicalKeyboardKey.keyG, + control: !isMacOS, + meta: isMacOS, + ): const InsertEmbedIntent(Attribute.image), + + SingleActivator( + LogicalKeyboardKey.keyF, + control: !isMacOS, + meta: isMacOS, + ): const OpenSearchIntent(), + }, { + ...?widget.customShortcuts + }), child: Actions( - actions: { - ..._actions, - if (widget.customActions != null) ...widget.customActions!, - }, + actions: mergeMaps>(_actions, { + ...?widget.customActions, + }), child: Focus( focusNode: widget.focusNode, onKey: _onKey, @@ -1570,11 +1653,13 @@ class RawEditorState extends EditorState RedoTextIntent: _makeOverridable(_RedoKeyboardAction(this)), OpenSearchIntent: _openSearchAction, + // Selection Formatting ToggleTextStyleIntent: _formatSelectionAction, IndentSelectionIntent: _indentSelectionAction, ApplyHeaderIntent: _applyHeaderAction, ApplyCheckListIntent: _applyCheckListAction, + ApplyLinkIntent: ApplyLinkAction(this) }; @override @@ -2490,6 +2575,43 @@ class _ApplyCheckListAction extends Action { bool get isActionEnabled => true; } +class ApplyLinkIntent extends Intent { + const ApplyLinkIntent(); +} + +class ApplyLinkAction extends Action { + ApplyLinkAction(this.state); + + final RawEditorState state; + + @override + Object? invoke(ApplyLinkIntent intent) async { + final initialTextLink = QuillTextLink.prepare(state.controller); + + final textLink = await showDialog( + context: state.context, + builder: (context) { + return LinkStyleDialog( + text: initialTextLink.text, + link: initialTextLink.link, + dialogTheme: state.widget.dialogTheme, + ); + }, + ); + + if (textLink != null) { + textLink.submit(state.controller); + } + return null; + } +} + +class InsertEmbedIntent extends Intent { + const InsertEmbedIntent(this.type); + + final Attribute type; +} + /// Signature for a widget builder that builds a context menu for the given /// [RawEditorState]. /// diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index d5a5c93a..f28eaee9 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -29,6 +29,7 @@ export 'toolbar/color_button.dart'; export 'toolbar/history_button.dart'; export 'toolbar/indent_button.dart'; export 'toolbar/link_style_button.dart'; +export 'toolbar/link_style_button2.dart'; export 'toolbar/quill_font_family_button.dart'; export 'toolbar/quill_font_size_button.dart'; export 'toolbar/quill_icon_button.dart'; diff --git a/lib/src/widgets/toolbar/link_style_button2.dart b/lib/src/widgets/toolbar/link_style_button2.dart new file mode 100644 index 00000000..ad73152a --- /dev/null +++ b/lib/src/widgets/toolbar/link_style_button2.dart @@ -0,0 +1,447 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/link.dart'; + +import '../../../extensions.dart'; +import '../../../translations.dart'; +import '../../models/documents/attribute.dart'; +import '../../models/themes/quill_dialog_theme.dart'; +import '../../models/themes/quill_icon_theme.dart'; +import '../../utils/widgets.dart'; +import '../controller.dart'; +import '../link.dart'; +import '../toolbar.dart'; + +/// Alternative version of [LinkStyleButton]. This widget has more customization +/// and uses dialog similar to one which is used on [http://quilljs.com]. +class LinkStyleButton2 extends StatefulWidget { + const LinkStyleButton2({ + required this.controller, + this.icon, + this.iconSize = kDefaultIconSize, + this.iconTheme, + this.dialogTheme, + this.afterButtonPressed, + this.tooltip, + this.constraints, + this.addLinkLabel, + this.editLinkLabel, + this.linkColor, + this.childrenSpacing = 16.0, + this.autovalidateMode = AutovalidateMode.disabled, + this.validationMessage, + this.buttonSize, + Key? key, + }) : assert(addLinkLabel == null || addLinkLabel.length > 0), + assert(editLinkLabel == null || editLinkLabel.length > 0), + assert(childrenSpacing > 0), + assert(validationMessage == null || validationMessage.length > 0), + super(key: key); + + final QuillController controller; + final IconData? icon; + final double iconSize; + final QuillIconTheme? iconTheme; + final QuillDialogTheme? dialogTheme; + final VoidCallback? afterButtonPressed; + final String? tooltip; + + /// The constrains for dialog. + final BoxConstraints? constraints; + + /// The text of label in link add mode. + final String? addLinkLabel; + + /// The text of label in link edit mode. + final String? editLinkLabel; + + /// The color of URL. + final Color? linkColor; + + /// The margin between child widgets in the dialog. + final double childrenSpacing; + + final AutovalidateMode autovalidateMode; + final String? validationMessage; + + /// The size of dialog buttons. + final Size? buttonSize; + + @override + State createState() => _LinkStyleButton2State(); +} + +class _LinkStyleButton2State extends State { + @override + void dispose() { + super.dispose(); + widget.controller.removeListener(_didChangeSelection); + } + + @override + void initState() { + super.initState(); + widget.controller.addListener(_didChangeSelection); + } + + @override + void didUpdateWidget(covariant LinkStyleButton2 oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.controller != widget.controller) { + oldWidget.controller.removeListener(_didChangeSelection); + widget.controller.addListener(_didChangeSelection); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final isToggled = _getLinkAttributeValue() != null; + return QuillIconButton( + tooltip: widget.tooltip, + highlightElevation: 0, + hoverElevation: 0, + size: widget.iconSize * kIconButtonFactor, + icon: Icon( + widget.icon ?? Icons.link, + size: widget.iconSize, + color: isToggled + ? (widget.iconTheme?.iconSelectedColor ?? + theme.primaryIconTheme.color) + : (widget.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color), + ), + fillColor: isToggled + ? (widget.iconTheme?.iconSelectedFillColor ?? + Theme.of(context).primaryColor) + : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor), + borderRadius: widget.iconTheme?.borderRadius ?? 2, + onPressed: _openLinkDialog, + afterPressed: widget.afterButtonPressed, + ); + } + + Future _openLinkDialog() async { + final initialTextLink = QuillTextLink.prepare(widget.controller); + + final textLink = await showDialog( + context: context, + builder: (_) => LinkStyleDialog( + dialogTheme: widget.dialogTheme, + text: initialTextLink.text, + link: initialTextLink.link, + constraints: widget.constraints, + addLinkLabel: widget.addLinkLabel, + editLinkLabel: widget.editLinkLabel, + linkColor: widget.linkColor, + childrenSpacing: widget.childrenSpacing, + autovalidateMode: widget.autovalidateMode, + validationMessage: widget.validationMessage, + buttonSize: widget.buttonSize, + ), + ); + + if (textLink != null) { + textLink.submit(widget.controller); + } + } + + String? _getLinkAttributeValue() { + return widget.controller + .getSelectionStyle() + .attributes[Attribute.link.key] + ?.value; + } + + void _didChangeSelection() { + setState(() {}); + } +} + +class LinkStyleDialog extends StatefulWidget { + const LinkStyleDialog({ + Key? key, + this.text, + this.link, + this.dialogTheme, + this.constraints, + this.contentPadding = + const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + this.addLinkLabel, + this.editLinkLabel, + this.linkColor, + this.childrenSpacing = 16.0, + this.autovalidateMode = AutovalidateMode.disabled, + this.validationMessage, + this.buttonSize, + }) : assert(addLinkLabel == null || addLinkLabel.length > 0), + assert(editLinkLabel == null || editLinkLabel.length > 0), + assert(childrenSpacing > 0), + assert(validationMessage == null || validationMessage.length > 0), + super(key: key); + + final String? text; + final String? link; + final QuillDialogTheme? dialogTheme; + + /// The constrains for dialog. + final BoxConstraints? constraints; + + /// The padding for content of dialog. + final EdgeInsetsGeometry contentPadding; + + /// The text of label in link add mode. + final String? addLinkLabel; + + /// The text of label in link edit mode. + final String? editLinkLabel; + + /// The color of URL. + final Color? linkColor; + + /// The margin between child widgets in the dialog. + final double childrenSpacing; + + final AutovalidateMode autovalidateMode; + final String? validationMessage; + + /// The size of dialog buttons. + final Size? buttonSize; + + @override + State createState() => _LinkStyleDialogState(); +} + +class _LinkStyleDialogState extends State { + late final TextEditingController _linkController; + + late String _link; + late String _text; + + late bool _isEditMode; + + @override + void dispose() { + _linkController.dispose(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + _link = widget.link ?? ''; + _text = widget.text ?? ''; + _isEditMode = _link.isNotEmpty; + _linkController = TextEditingController.fromValue( + TextEditingValue( + text: _isEditMode ? _link : '', + selection: _isEditMode + ? TextSelection(baseOffset: 0, extentOffset: _link.length) + : const TextSelection.collapsed(offset: 0), + ), + ); + } + + @override + Widget build(BuildContext context) { + final constraints = widget.constraints ?? + widget.dialogTheme?.linkDialogConstraints ?? + () { + final mediaQuery = MediaQuery.of(context); + final maxWidth = + kIsWeb ? mediaQuery.size.width / 4 : mediaQuery.size.width - 80; + return BoxConstraints(maxWidth: maxWidth, maxHeight: 80); + }(); + + final buttonStyle = widget.buttonSize != null + ? Theme.of(context) + .elevatedButtonTheme + .style + ?.copyWith(fixedSize: MaterialStatePropertyAll(widget.buttonSize)) + : widget.dialogTheme?.buttonStyle; + + final isWrappable = widget.dialogTheme?.isWrappable ?? false; + + final children = _isEditMode + ? [ + Text(widget.editLinkLabel ?? 'Visit link'.i18n), + UtilityWidgets.maybeWidget( + enabled: !isWrappable, + wrapper: (child) => Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: child, + ), + ), + child: Padding( + padding: + EdgeInsets.symmetric(horizontal: widget.childrenSpacing), + child: Link( + uri: Uri.parse(_linkController.text), + builder: (context, followLink) { + return TextButton( + onPressed: followLink, + style: TextButton.styleFrom( + backgroundColor: Colors.transparent, + ), + child: Text( + widget.link!, + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: widget.dialogTheme?.inputTextStyle?.copyWith( + color: widget.linkColor ?? Colors.blue, + decoration: TextDecoration.underline, + ), + ), + ); + }, + ), + ), + ), + ElevatedButton( + onPressed: () { + setState(() { + _isEditMode = !_isEditMode; + }); + }, + style: buttonStyle, + child: Text('Edit'.i18n), + ), + Padding( + padding: EdgeInsets.only(left: widget.childrenSpacing), + child: ElevatedButton( + onPressed: _removeLink, + style: buttonStyle, + child: Text('Remove'.i18n), + ), + ), + ] + : [ + Text(widget.addLinkLabel ?? 'Enter link'.i18n), + UtilityWidgets.maybeWidget( + enabled: !isWrappable, + wrapper: (child) => Expanded( + child: child, + ), + child: Padding( + padding: + EdgeInsets.symmetric(horizontal: widget.childrenSpacing), + child: TextFormField( + controller: _linkController, + style: widget.dialogTheme?.inputTextStyle, + keyboardType: TextInputType.url, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelStyle: widget.dialogTheme?.labelTextStyle, + ), + autofocus: true, + autovalidateMode: widget.autovalidateMode, + validator: _validateLink, + onChanged: _linkChanged, + ), + ), + ), + ElevatedButton( + onPressed: _canPress() ? _applyLink : null, + style: buttonStyle, + child: Text('Apply'.i18n), + ), + ]; + + return Dialog( + backgroundColor: widget.dialogTheme?.dialogBackgroundColor, + shape: widget.dialogTheme?.shape ?? + DialogTheme.of(context).shape ?? + RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + child: ConstrainedBox( + constraints: constraints, + child: Padding( + padding: widget.contentPadding, + child: isWrappable + ? Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: widget.dialogTheme?.runSpacing ?? 0.0, + children: children, + ) + : Row( + children: children, + ), + ), + ), + ); + } + + void _linkChanged(String value) { + setState(() { + _link = value; + }); + } + + bool _canPress() => _validateLink(_link) == null; + + String? _validateLink(String? value) { + if ((value?.isEmpty ?? false) || + !AutoFormatMultipleLinksRule.linkRegExp.hasMatch(value!)) { + return widget.validationMessage ?? 'That is not a valid URL'; + } + + return null; + } + + void _applyLink() => + Navigator.pop(context, QuillTextLink(_text.trim(), _link.trim())); + + void _removeLink() => + Navigator.pop(context, QuillTextLink(_text.trim(), null)); +} + +/// Contains information about text URL. +class QuillTextLink { + QuillTextLink( + this.text, + this.link, + ); + + final String text; + final String? link; + + static QuillTextLink prepare(QuillController controller) { + final link = + controller.getSelectionStyle().attributes[Attribute.link.key]?.value; + final index = controller.selection.start; + + var text; + if (link != null) { + // text should be the link's corresponding text, not selection + final leaf = controller.document.querySegmentLeafNode(index).leaf; + if (leaf != null) { + text = leaf.toPlainText(); + } + } + + final len = controller.selection.end - index; + text ??= len == 0 ? '' : controller.document.getPlainText(index, len); + + return QuillTextLink(text, link); + } + + void submit(QuillController controller) { + var index = controller.selection.start; + var length = controller.selection.end - index; + final linkValue = + controller.getSelectionStyle().attributes[Attribute.link.key]?.value; + + if (linkValue != null) { + // text should be the link's corresponding text, not selection + final leaf = controller.document.querySegmentLeafNode(index).leaf; + if (leaf != null) { + final range = getLinkRange(leaf); + index = range.start; + length = range.end - range.start; + } + } + controller + ..replaceText(index, length, text, null) + ..formatText(index, text.length, LinkAttribute(link)); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 38a3d504..a9a108c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" dependencies: From af1c087fa9d05d82be76a2049eca7d12fe1b2d4e Mon Sep 17 00:00:00 2001 From: BambinoUA <45417992+bambinoua@users.noreply.github.com> Date: Thu, 20 Apr 2023 21:01:05 +0300 Subject: [PATCH 145/204] Update `CHANGELOG.md`, `flutter_quill` version to 7.1.9 and `flutter_quill_extension` version to 0.2.1+1 (#1186) --- CHANGELOG.md | 920 +++++++++++++++++--------- flutter_quill_extensions/pubspec.yaml | 6 +- pubspec.yaml | 2 +- 3 files changed, 621 insertions(+), 307 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a745d7b9..0f4f80e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,188 +1,272 @@ +# [7.1.9] + +- Editor tweaks PR from [bambinoua](https://github.com/bambinoua). + - Shortcuts now working in Mac OS + - QuillDialogTheme is extended with new properties buttonStyle, linkDialogConstraints, imageDialogConstraints, isWrappable, runSpacing, + - Added LinkStyleButton2 with new LinkStyleDialog (similar to Quill implementation + - Conditinally use Row or Wrap for dialog's children. + - Update minimum Dart SDK version to 2.17.0 to use enum extensions. + - Use merging shortcuts and actions correclty (if the key combination is the same) + + # [7.1.8] -* Dropdown tweaks. +- Dropdown tweaks + - Add itemHeight, itemPadding, defaultItemColor for customization of dropdown items. + - Remove alignment property as useless. + - Fix bugs with max width when width property is null. + # [7.1.7] -* Toolbar tweaks. + +- Toolbar tweaks. + - Implement tooltips for embed CameraButton, VideoButton, FormulaButton, ImageButton. + - Extends customization for SelectAlignmentButton, QuillFontFamilyButton, QuillFontSizeButton adding padding, text style, alignment, width. + - Add renderFontFamilies to QuillFontFamilyButton to show font faces in dropdown. + - Add AxisDivider and its named constructors for for use in parent project. + - Export ToolbarButtons enum to allow specify tooltips for SelectAlignmentButton. + - Export QuillFontFamilyButton, SearchButton as they were not exported before. + - Deprecate items property in QuillFontFamilyButton, QuillFontSizeButton as the it can be built usinr rawItemsMap. + - Make onSelection QuillFontFamilyButton, QuillFontSizeButton omittable as no need to execute callback outside if controller is passed to widget. + +Now the package is more friendly for web projects. + # [7.1.6] -* Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets. + +- Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets. # [7.1.5] -* Add tooltips for toolbar buttons. + +- Add tooltips for toolbar buttons. # [7.1.4] -* Fix inserting tab character in lists. + +- Fix inserting tab character in lists. # [7.1.3] -* Fix ios cursor bug when word.length==1. + +- Fix ios cursor bug when word.length==1. # [7.1.2] -* Fix non scrollable editor exception, when tapped under content. + +- Fix non scrollable editor exception, when tapped under content. # [7.1.1] -* customLinkPrefixes parameter - makes possible to open links with custom protoco. + +- customLinkPrefixes parameter - makes possible to open links with custom protoco. # [7.1.0] -* Fix ordered list numeration with several lists in document. + +- Fix ordered list numeration with several lists in document. # [7.0.9] -* Use const constructor for EmbedBuilder. + +- Use const constructor for EmbedBuilder. # [7.0.8] -* Fix IME position bug with scroller. + +- Fix IME position bug with scroller. # [7.0.7] -* Add TextFieldTapRegion for contextMenu. + +- Add TextFieldTapRegion for contextMenu. # [7.0.6] -* Fix line style loss on new line from non string. + +- Fix line style loss on new line from non string. # [7.0.5] -* Fix IME position bug for Mac and Windows. -* Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac. + +- Fix IME position bug for Mac and Windows. +- Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac. # [7.0.4] -* Have text selection span full line height for uneven sized text. + +- Have text selection span full line height for uneven sized text. # [7.0.3] -* Fix ordered list numeration for lists with more than one level of list. + +- Fix ordered list numeration for lists with more than one level of list. # [7.0.2] -* Allow widgets to override widget span properties. + +- Allow widgets to override widget span properties. # [7.0.1] -* Update i18n_extension dependency to version 8.0.0. + +- Update i18n_extension dependency to version 8.0.0. # [7.0.0] -* Breaking change: Tuples are no longer used. They have been replaced with a number of data classes. + +- Breaking change: Tuples are no longer used. They have been replaced with a number of data classes. # [6.4.4] -* Increased compatibility with Flutter widget tests. + +- Increased compatibility with Flutter widget tests. # [6.4.3] -* Update dependencies (collection: 1.17.0, flutter_keyboard_visibility: 5.4.0, quiver: 3.2.1, tuple: 2.0.1, url_launcher: 6.1.9, characters: 1.2.1, i18n_extension: 7.0.0, device_info_plus: 8.1.0) + +- Update dependencies (collection: 1.17.0, flutter_keyboard_visibility: 5.4.0, quiver: 3.2.1, tuple: 2.0.1, url_launcher: 6.1.9, characters: 1.2.1, i18n_extension: 7.0.0, device_info_plus: 8.1.0) # [6.4.2] -* Replace `buildToolbar` with `contextMenuBuilder`. + +- Replace `buildToolbar` with `contextMenuBuilder`. # [6.4.1] -* Control the detect word boundary behaviour. + +- Control the detect word boundary behaviour. # [6.4.0] -* Use `axis` to make the toolbar vertical. -* Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis. -* Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`. + +- Use `axis` to make the toolbar vertical. +- Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis. +- Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`. # [6.3.5] -* Ability to add custom shortcuts. + +- Ability to add custom shortcuts. # [6.3.4] -* Update clipboard status prior to showing selected text overlay. + +- Update clipboard status prior to showing selected text overlay. # [6.3.3] -* Fixed handling of mac intents. + +- Fixed handling of mac intents. # [6.3.2] -* Added `unknownEmbedBuilder` to QuillEditor. -* Fix error style when input chinese japanese or korean. + +- Added `unknownEmbedBuilder` to QuillEditor. +- Fix error style when input chinese japanese or korean. # [6.3.1] -* Add color property to the basic factory function. + +- Add color property to the basic factory function. # [6.3.0] -* Support Flutter 3.7. + +- Support Flutter 3.7. # [6.2.2] -* Fix: nextLine getter null where no assertion. + +- Fix: nextLine getter null where no assertion. # [6.2.1] -* Revert "Align numerical and bullet lists along with text content". + +- Revert "Align numerical and bullet lists along with text content". # [6.2.0] -* Align numerical and bullet lists along with text content. + +- Align numerical and bullet lists along with text content. # [6.1.12] -* Apply i18n for default font dropdown option labels corresponding to 'Clear'. + +- Apply i18n for default font dropdown option labels corresponding to 'Clear'. # [6.1.11] -* Remove iOS hack for delaying focus calculation. + +- Remove iOS hack for delaying focus calculation. # [6.1.10] -* Delay focus calculation for iOS. + +- Delay focus calculation for iOS. # [6.1.9] -* Bump keyboard show up wait to 1 sec. + +- Bump keyboard show up wait to 1 sec. # [6.1.8] -* Recalculate focus when showing keyboard. + +- Recalculate focus when showing keyboard. # [6.1.7] -* Add czech localizations. + +- Add czech localizations. # [6.1.6] -* Upgrade i18n_extension to 6.0.0. + +- Upgrade i18n_extension to 6.0.0. # [6.1.5] -* Fix formatting exception. + +- Fix formatting exception. # [6.1.4] -* Add double quotes validation. + +- Add double quotes validation. # [6.1.3] -* Revert "fix order list numbering (#988)". + +- Revert "fix order list numbering (#988)". # [6.1.2] -* Add typing shortcuts. + +- Add typing shortcuts. # [6.1.1] -* Fix order list numbering. + +- Fix order list numbering. # [6.1.0] -* Add keyboard shortcuts for editor actions. + +- Add keyboard shortcuts for editor actions. # [6.0.10] -* Upgrade device info plus to ^7.0.0. + +- Upgrade device info plus to ^7.0.0. # [6.0.9] -* Don't throw showAutocorrectionPromptRect not implemented. The function is called with every keystroke as a user is typing. + +- Don't throw showAutocorrectionPromptRect not implemented. The function is called with every keystroke as a user is typing. # [6.0.8+1] -* Fixes null pointer when setting documents. + +- Fixes null pointer when setting documents. # [6.0.8] -* Make QuillController.document mutable. + +- Make QuillController.document mutable. # [6.0.7] -* Allow disabling of selection toolbar. + +- Allow disabling of selection toolbar. # [6.0.6+1] -* Revert 6.0.6. + +- Revert 6.0.6. # [6.0.6] -* Fix wrong custom embed key. + +- Fix wrong custom embed key. # [6.0.5] -* Fixes toolbar buttons stealing focus from editor. + +- Fixes toolbar buttons stealing focus from editor. # [6.0.4] -* Bug fix for Type 'Uint8List' not found. + +- Bug fix for Type 'Uint8List' not found. # [6.0.3] -* Add ability to paste images. + +- Add ability to paste images. # [6.0.2] -* Address Dart Analysis issues. + +- Address Dart Analysis issues. # [6.0.1] -* Changed translation country code (zh_HK -> zh_hk) to lower case, which is required for i18n_extension used in flutter_quill. -* Add localization in example's main to demonstrate translation. -* Issue [Windows] selection's copy / paste tool bar not shown #861: add selection's copy / paste toolbar, escape to hide toolbar, mouse right click to show toolbar, ctrl-Y / ctrl-Z to undo / redo. -* Image and video displayed in Windows platform caused screen flickering while selecting text, a sample_data_nomedia.json asset is added for Desktop to demonstrate the added features. -* Known issue: keyboard action sometimes causes exception mentioned in Flutter's issue #106475 ([Windows] Keyboard shortcuts stop working after modifier key repeat flutter/flutter#106475). -* Know issue: user needs to click the editor to get focus before toolbar is able to display. + +- Changed translation country code (zh_HK -> zh_hk) to lower case, which is required for i18n_extension used in flutter_quill. +- Add localization in example's main to demonstrate translation. +- Issue [Windows] selection's copy / paste tool bar not shown #861: add selection's copy / paste toolbar, escape to hide toolbar, mouse right click to show toolbar, ctrl-Y / ctrl-Z to undo / redo. +- Image and video displayed in Windows platform caused screen flickering while selecting text, a sample_data_nomedia.json asset is added for Desktop to demonstrate the added features. +- Known issue: keyboard action sometimes causes exception mentioned in Flutter's issue #106475 ([Windows] Keyboard shortcuts stop working after modifier key repeat flutter/flutter#106475). +- Know issue: user needs to click the editor to get focus before toolbar is able to display. # [6.0.0] BREAKING CHANGE -* Removed embed (image, video & formula) blocks from the package to reduce app size. + +- Removed embed (image, video & formula) blocks from the package to reduce app size. These blocks have been moved to the package `flutter_quill_extensions`, migrate by filling the `embedBuilders` and `embedButtons` parameters as follows: @@ -200,699 +284,929 @@ QuillToolbar.basic( ); ``` - # [5.4.2] -* Upgrade i18n_extension. + +- Upgrade i18n_extension. # [5.4.1] -* Update German Translation. + +- Update German Translation. # [5.4.0] -* Added Formula Button (for maths support). + +- Added Formula Button (for maths support). # [5.3.2] -* Add more font family. + +- Add more font family. # [5.3.1] -* Enable search when text is not empty. + +- Enable search when text is not empty. # [5.3.0] -* Added search function. + +- Added search function. # [5.2.11] -* Remove default small color. + +- Remove default small color. # [5.2.10] -* Don't wrap the QuillEditor's child in the EditorTextSelectionGestureDetector if selection is disabled. + +- Don't wrap the QuillEditor's child in the EditorTextSelectionGestureDetector if selection is disabled. # [5.2.9] -* Added option to modify SelectHeaderStyleButton options. -* Added option to click again on h1, h2, h3 button to go back to normal. + +- Added option to modify SelectHeaderStyleButton options. +- Added option to click again on h1, h2, h3 button to go back to normal. # [5.2.8] -* Remove tooltip for LinkStyleButton. -* Make link match regex case insensitive. + +- Remove tooltip for LinkStyleButton. +- Make link match regex case insensitive. # [5.2.7] -* Add locale to QuillEditor.basic. + +- Add locale to QuillEditor.basic. # [5.2.6] -* Fix keyboard pops up when resizing the image. + +- Fix keyboard pops up when resizing the image. # [5.2.5] -* Upgrade youtube_player_flutter_quill to 8.2.2. + +- Upgrade youtube_player_flutter_quill to 8.2.2. # [5.2.4] -* Upgrade youtube_player_flutter_quill to 8.2.1. + +- Upgrade youtube_player_flutter_quill to 8.2.1. # [5.2.3] -* Flutter Quill Doesn't Work On iOS 16 or Xcode 14 Betas (Stored properties cannot be marked potentially unavailable with '@available'). + +- Flutter Quill Doesn't Work On iOS 16 or Xcode 14 Betas (Stored properties cannot be marked potentially unavailable with '@available'). # [5.2.2] -* Fix Web Unsupported operation: Platform._operatingSystem error. + +- Fix Web Unsupported operation: Platform.\_operatingSystem error. # [5.2.1] -* Rename QuillCustomIcon to QuillCustomButton. + +- Rename QuillCustomIcon to QuillCustomButton. # [5.2.0] -* Support font family selection. + +- Support font family selection. # [5.1.1] -* Update README. + +- Update README. # [5.1.0] -* Added CustomBlockEmbed and customElementsEmbedBuilder. + +- Added CustomBlockEmbed and customElementsEmbedBuilder. # [5.0.5] -* Upgrade device_info_plus to 4.0.0. + +- Upgrade device_info_plus to 4.0.0. # [5.0.4] -* Added onVideoInit callback for video documents. + +- Added onVideoInit callback for video documents. # [5.0.3] -* Update dependencies. + +- Update dependencies. # [5.0.2] -* Keep cursor position on checkbox tap. + +- Keep cursor position on checkbox tap. # [5.0.1] -* Fix static analysis errors. + +- Fix static analysis errors. # [5.0.0] -* Flutter 3.0.0 support. + +- Flutter 3.0.0 support. # [4.2.3] -* Ignore color:inherit and convert double to int for level. + +- Ignore color:inherit and convert double to int for level. # [4.2.2] -* Add clear option to font size dropdown. + +- Add clear option to font size dropdown. # [4.2.1] -* Refactor font size dropdown. + +- Refactor font size dropdown. # [4.2.0] -* Ensure selectionOverlay is available for showToolbar. + +- Ensure selectionOverlay is available for showToolbar. # [4.1.9] -* Using properly iconTheme colors. + +- Using properly iconTheme colors. # [4.1.8] -* Update font size dropdown. + +- Update font size dropdown. # [4.1.7] -* Convert FontSize to a Map to allow for named Font Size. + +- Convert FontSize to a Map to allow for named Font Size. # [4.1.6] -* Update quill_dropdown_button.dart. + +- Update quill_dropdown_button.dart. # [4.1.5] -* Add Font Size dropdown to the toolbar. + +- Add Font Size dropdown to the toolbar. # [4.1.4] -* New borderRadius for iconTheme. + +- New borderRadius for iconTheme. # [4.1.3] -* Fix selection handles show/hide after paste, backspace, copy. + +- Fix selection handles show/hide after paste, backspace, copy. # [4.1.2] -* Add full support for hardware keyboards (Chromebook, Android tablets, etc) that don't alter screen UI. + +- Add full support for hardware keyboards (Chromebook, Android tablets, etc) that don't alter screen UI. # [4.1.1] -* Added textSelectionControls field in QuillEditor. + +- Added textSelectionControls field in QuillEditor. # [4.1.0] -* Added Node to linkActionPickerDelegate. + +- Added Node to linkActionPickerDelegate. # [4.0.12] -* Add Persian(fa) language. + +- Add Persian(fa) language. # [4.0.11] -* Fix cut selection error in multi-node line. + +- Fix cut selection error in multi-node line. # [4.0.10] -* Fix vertical caret position bug. + +- Fix vertical caret position bug. # [4.0.9] -* Request keyboard focus when no child is found. + +- Request keyboard focus when no child is found. # [4.0.8] -* Fix blank lines do not display when --web-renderer=html. + +- Fix blank lines do not display when --web-renderer=html. # [4.0.7] -* Refactor getPlainText (better handling of blank lines and lines with multiple markups. + +- Refactor getPlainText (better handling of blank lines and lines with multiple markups. # [4.0.6] -* Bug fix for copying text with new lines. + +- Bug fix for copying text with new lines. # [4.0.5] -* Fixed casting null to Tuple2 when link dialog is dismissed without any input (e.g. barrier dismissed). + +- Fixed casting null to Tuple2 when link dialog is dismissed without any input (e.g. barrier dismissed). # [4.0.4] -* Bug fix for text direction rtl. + +- Bug fix for text direction rtl. # [4.0.3] -* Support text direction rtl. + +- Support text direction rtl. # [4.0.2] -* Clear toggled style on selection change. + +- Clear toggled style on selection change. # [4.0.1] -* Fix copy/cut/paste/selectAll not working. + +- Fix copy/cut/paste/selectAll not working. # [4.0.0] -* Upgrade for Flutter 2.10. + +- Upgrade for Flutter 2.10. # [3.9.11] -* Added Indonesian translation. + +- Added Indonesian translation. # [3.9.10] -* Fix for undoing a modification ending with an indented line. + +- Fix for undoing a modification ending with an indented line. # [3.9.9] -* iOS: Save image whose filename does not end with image file extension. + +- iOS: Save image whose filename does not end with image file extension. # [3.9.8] -* Added Urdu translation. + +- Added Urdu translation. # [3.9.7] -* Fix for clicking on the Link button without any text on a new line crashes. + +- Fix for clicking on the Link button without any text on a new line crashes. # [3.9.6] -* Apply locale to QuillEditor(contents). + +- Apply locale to QuillEditor(contents). # [3.9.5] -* Fix image pasting. + +- Fix image pasting. # [3.9.4] -* Hiding dialog after selecting action for image. + +- Hiding dialog after selecting action for image. # [3.9.3] -* Update ImageResizer for Android. + +- Update ImageResizer for Android. # [3.9.2] -* Copy image with its style. + +- Copy image with its style. # [3.9.1] -* Support resizing image. + +- Support resizing image. # [3.9.0] -* Image menu options for copy/remove. + +- Image menu options for copy/remove. # [3.8.8] -* Update set textEditingValue. + +- Update set textEditingValue. # [3.8.7] -* Fix checkbox not toggled correctly in toolbar button. + +- Fix checkbox not toggled correctly in toolbar button. # [3.8.6] -* Fix cursor position changes when checking/unchecking the checkbox. + +- Fix cursor position changes when checking/unchecking the checkbox. # [3.8.5] -* Fix _handleDragUpdate in _TextSelectionHandleOverlayState. + +- Fix \_handleDragUpdate in \_TextSelectionHandleOverlayState. # [3.8.4] -* Fix link dialog layout. + +- Fix link dialog layout. # [3.8.3] -* Fix for errors on a non scrollable editor. + +- Fix for errors on a non scrollable editor. # [3.8.2] -* Fix certain keys not working on web when editor is a child of a scroll view. + +- Fix certain keys not working on web when editor is a child of a scroll view. # [3.8.1] -* Refactor _QuillEditorState to QuillEditorState. + +- Refactor \_QuillEditorState to QuillEditorState. # [3.8.0] -* Support pasting with format. + +- Support pasting with format. # [3.7.3] -* Fix selection overlay for collapsed selection. + +- Fix selection overlay for collapsed selection. # [3.7.2] -* Reverted Embed toPlainText change. + +- Reverted Embed toPlainText change. # [3.7.1] -* Change Embed toPlainText to be empty string. + +- Change Embed toPlainText to be empty string. # [3.7.0] -* Replace Toolbar showHistory group with individual showRedo and showUndo. + +- Replace Toolbar showHistory group with individual showRedo and showUndo. # [3.6.5] -* Update Link dialogue for image/video. + +- Update Link dialogue for image/video. # [3.6.4] -* Link dialogue TextInputType.multiline. + +- Link dialogue TextInputType.multiline. # [3.6.3] -* Bug fix for link button text selection. + +- Bug fix for link button text selection. # [3.6.2] -* Improve link button. + +- Improve link button. # [3.6.1] -* Remove SnackBar 'What is entered is not a link'. + +- Remove SnackBar 'What is entered is not a link'. # [3.6.0] -* Allow link button to enter text. + +- Allow link button to enter text. # [3.5.3] -* Change link button behavior. + +- Change link button behavior. # [3.5.2] -* Bug fix for embed. + +- Bug fix for embed. # [3.5.1] -* Bug fix for platform util. + +- Bug fix for platform util. # [3.5.0] -* Removed redundant classes. + +- Removed redundant classes. # [3.4.4] -* Add more translations. + +- Add more translations. # [3.4.3] -* Preset link from attributes. + +- Preset link from attributes. # [3.4.2] -* Fix launch link edit mode. + +- Fix launch link edit mode. # [3.4.1] -* Placeholder effective in scrollable. + +- Placeholder effective in scrollable. # [3.4.0] -* Option to save image in read-only mode. + +- Option to save image in read-only mode. # [3.3.1] -* Pass any specified key in QuillEditor constructor to super. + +- Pass any specified key in QuillEditor constructor to super. # [3.3.0] -* Fixed Style toggle issue. + +- Fixed Style toggle issue. # [3.2.1] -* Added new translations. + +- Added new translations. # [3.2.0] -* Support multiple links insertion on the go. + +- Support multiple links insertion on the go. # [3.1.1] -* Add selection completed callback. + +- Add selection completed callback. # [3.1.0] -* Fixed image ontap functionality. + +- Fixed image ontap functionality. # [3.0.4] -* Add maxContentWidth constraint to editor. + +- Add maxContentWidth constraint to editor. # [3.0.3] -* Do not show caret on screen when the editor is not focused. + +- Do not show caret on screen when the editor is not focused. # [3.0.2] -* Fix launch link for read-only mode. + +- Fix launch link for read-only mode. # [3.0.1] -* Handle null value of Attribute.link. + +- Handle null value of Attribute.link. # [3.0.0] -* Launch link improvements. -* Removed QuillSimpleViewer. + +- Launch link improvements. +- Removed QuillSimpleViewer. # [2.5.2] -* Skip image when pasting. + +- Skip image when pasting. # [2.5.1] -* Bug fix for Desktop `Shift` + `Click` support. + +- Bug fix for Desktop `Shift` + `Click` support. # [2.5.0] -* Update checkbox list. + +- Update checkbox list. # [2.4.1] -* Desktop selection improvements. + +- Desktop selection improvements. # [2.4.0] -* Improve inline code style. + +- Improve inline code style. # [2.3.3] -* Improves selection rects to have consistent height regardless of individual segment text styles. + +- Improves selection rects to have consistent height regardless of individual segment text styles. # [2.3.2] -* Allow disabling floating cursor. + +- Allow disabling floating cursor. # [2.3.1] -* Preserve last newline character on delete. + +- Preserve last newline character on delete. # [2.3.0] -* Massive changes to support flutter 2.8. + +- Massive changes to support flutter 2.8. # [2.2.2] -* iOS - floating cursor. + +- iOS - floating cursor. # [2.2.1] -* Bug fix for imports supporting flutter 2.8. + +- Bug fix for imports supporting flutter 2.8. # [2.2.0] -* Support flutter 2.8. + +- Support flutter 2.8. # [2.1.1] -* Add methods of clearing editor and moving cursor. + +- Add methods of clearing editor and moving cursor. # [2.1.0] -* Add delete handler. + +- Add delete handler. # [2.0.23] -* Support custom replaceText handler. + +- Support custom replaceText handler. # [2.0.22] -* Fix attribute compare and fix font size parsing. + +- Fix attribute compare and fix font size parsing. # [2.0.21] -* Handle click on embed object. + +- Handle click on embed object. # [2.0.20] -* Improved UX/UI of Image widget. + +- Improved UX/UI of Image widget. # [2.0.19] -* When uploading a video, applying indicator. + +- When uploading a video, applying indicator. # [2.0.18] -* Make toolbar dividers optional. + +- Make toolbar dividers optional. # [2.0.17] -* Allow alignment of the toolbar icons to match WrapAlignment. + +- Allow alignment of the toolbar icons to match WrapAlignment. # [2.0.16] -* Add hide / show alignment buttons. + +- Add hide / show alignment buttons. # [2.0.15] -* Implement change cursor to SystemMouseCursors.click when hovering a link styled text. + +- Implement change cursor to SystemMouseCursors.click when hovering a link styled text. # [2.0.14] -* Enable customize the checkbox widget using DefaultListBlockStyle style. + +- Enable customize the checkbox widget using DefaultListBlockStyle style. # [2.0.13] -* Improve the scrolling performance by reducing the repaint areas. + +- Improve the scrolling performance by reducing the repaint areas. # [2.0.12] -* Fix the selection effect can't be seen as the textLine with background color. + +- Fix the selection effect can't be seen as the textLine with background color. # [2.0.11] -* Fix visibility of text selection handlers on scroll. + +- Fix visibility of text selection handlers on scroll. # [2.0.10] -* cursorConnt.color notify the text_line to repaint if it was disposed. + +- cursorConnt.color notify the text_line to repaint if it was disposed. # [2.0.9] -* Improve UX when trying to add a link. + +- Improve UX when trying to add a link. # [2.0.8] -* Adding translations to the toolbar. + +- Adding translations to the toolbar. # [2.0.7] -* Added theming options for toolbar icons and LinkDialog. + +- Added theming options for toolbar icons and LinkDialog. # [2.0.6] -* Avoid runtime error when placed inside TabBarView. + +- Avoid runtime error when placed inside TabBarView. # [2.0.5] -* Support inline code formatting. + +- Support inline code formatting. # [2.0.4] -* Enable history shortcuts for desktop. + +- Enable history shortcuts for desktop. # [2.0.3] -* Fix cursor when line contains image. + +- Fix cursor when line contains image. # [2.0.2] -* Address KeyboardListener class name conflict. + +- Address KeyboardListener class name conflict. # [2.0.1] -* Upgrade flutter_colorpicker to 0.5.0. + +- Upgrade flutter_colorpicker to 0.5.0. # [2.0.0] -* Text Alignment functions + Block Format standards. + +- Text Alignment functions + Block Format standards. # [1.9.6] -* Support putting QuillEditor inside a Scrollable view. + +- Support putting QuillEditor inside a Scrollable view. # [1.9.5] -* Skip image when pasting. + +- Skip image when pasting. # [1.9.4] -* Bug fix for cursor position when tapping at the end of line with image(s). + +- Bug fix for cursor position when tapping at the end of line with image(s). # [1.9.3] -* Bug fix when line only contains one image. + +- Bug fix when line only contains one image. # [1.9.2] -* Support for building custom inline styles. + +- Support for building custom inline styles. # [1.9.1] -* Cursor jumps to the most appropriate offset to display selection. + +- Cursor jumps to the most appropriate offset to display selection. # [1.9.0] -* Support inline image. + +- Support inline image. # [1.8.3] -* Updated quill_delta. + +- Updated quill_delta. # [1.8.2] -* Support mobile image alignment. + +- Support mobile image alignment. # [1.8.1] -* Support mobile custom size image. + +- Support mobile custom size image. # [1.8.0] -* Support entering link for image/video. + +- Support entering link for image/video. # [1.7.3] -* Bumps photo_view version. + +- Bumps photo_view version. # [1.7.2] -* Fix static analysis error. + +- Fix static analysis error. # [1.7.1] -* Support Youtube video. + +- Support Youtube video. # [1.7.0] -* Support video. + +- Support video. # [1.6.4] -* Bug fix for clear format button. + +- Bug fix for clear format button. # [1.6.3] -* Fixed dragging right handle scrolling issue. + +- Fixed dragging right handle scrolling issue. # [1.6.2] -* Fixed the position of the selection status drag handle. + +- Fixed the position of the selection status drag handle. # [1.6.1] -* Upgrade image_picker and flutter_colorpicker. + +- Upgrade image_picker and flutter_colorpicker. # [1.6.0] -* Support Multi Row Toolbar. + +- Support Multi Row Toolbar. # [1.5.0] -* Remove file_picker dependency. + +- Remove file_picker dependency. # [1.4.1] -* Remove filesystem_picker dependency. + +- Remove filesystem_picker dependency. # [1.4.0] -* Remove path_provider dependency. + +- Remove path_provider dependency. # [1.3.4] -* Add option to paintCursorAboveText. + +- Add option to paintCursorAboveText. # [1.3.3] -* Upgrade file_picker version. + +- Upgrade file_picker version. # [1.3.2] -* Fix copy/paste bug. + +- Fix copy/paste bug. # [1.3.1] -* New logo. + +- New logo. # [1.3.0] -* Support flutter 2.2.0. + +- Support flutter 2.2.0. # [1.2.2] -* Checkbox supports tapping. + +- Checkbox supports tapping. # [1.2.1] -* Indented position not holding while editing. + +- Indented position not holding while editing. # [1.2.0] -* Fix image button cancel causes crash. + +- Fix image button cancel causes crash. # [1.1.8] -* Fix height of empty line bug. + +- Fix height of empty line bug. # [1.1.7] -* Fix text selection in read-only mode. + +- Fix text selection in read-only mode. # [1.1.6] -* Remove universal_html dependency. + +- Remove universal_html dependency. # [1.1.5] -* Enable "Select", "Select All" and "Copy" in read-only mode. + +- Enable "Select", "Select All" and "Copy" in read-only mode. # [1.1.4] -* Fix text selection issue. + +- Fix text selection issue. # [1.1.3] -* Update example folder. + +- Update example folder. # [1.1.2] -* Add pedantic. + +- Add pedantic. # [1.1.1] -* Base64 image support. + +- Base64 image support. # [1.1.0] -* Support null safety. + +- Support null safety. # [1.0.9] -* Web support for raw editor and keyboard listener. + +- Web support for raw editor and keyboard listener. # [1.0.8] -* Support token attribute. + +- Support token attribute. # [1.0.7] -* Fix crash on web (dart:io). + +- Fix crash on web (dart:io). # [1.0.6] -* Add desktop support - WINDOWS, MACOS and LINUX. + +- Add desktop support - WINDOWS, MACOS and LINUX. # [1.0.5] -* Bug fix: Can not insert newline when Bold is toggled ON. + +- Bug fix: Can not insert newline when Bold is toggled ON. # [1.0.4] -* Upgrade photo_view to ^0.11.0. + +- Upgrade photo_view to ^0.11.0. # [1.0.3] -* Fix issue that text is not displayed while typing [WEB]. + +- Fix issue that text is not displayed while typing [WEB]. # [1.0.2] -* Update toolbar in sample home page. + +- Update toolbar in sample home page. # [1.0.1] -* Fix static analysis errors. + +- Fix static analysis errors. # [1.0.0] -* Support flutter 2.0. + +- Support flutter 2.0. # [1.0.0-dev.2] -* Improve link handling for tel, mailto and etc. + +- Improve link handling for tel, mailto and etc. # [1.0.0-dev.1] -* Upgrade prerelease SDK & Bump for master. + +- Upgrade prerelease SDK & Bump for master. # [0.3.5] -* Fix for cursor focus issues when keyboard is on. + +- Fix for cursor focus issues when keyboard is on. # [0.3.4] -* Improve link handling for tel, mailto and etc. + +- Improve link handling for tel, mailto and etc. # [0.3.3] -* More fix on cursor focus issue when keyboard is on. + +- More fix on cursor focus issue when keyboard is on. # [0.3.2] -* Fix cursor focus issue when keyboard is on. + +- Fix cursor focus issue when keyboard is on. # [0.3.1] -* cursor focus when keyboard is on. + +- cursor focus when keyboard is on. # [0.3.0] -* Line Height calculated based on font size. + +- Line Height calculated based on font size. # [0.2.12] -* Support placeholder. + +- Support placeholder. # [0.2.11] -* Fix static analysis error. + +- Fix static analysis error. # [0.2.10] -* Update TextInputConfiguration autocorrect to true in stable branch. + +- Update TextInputConfiguration autocorrect to true in stable branch. # [0.2.9] -* Update TextInputConfiguration autocorrect to true. + +- Update TextInputConfiguration autocorrect to true. # [0.2.8] -* Support display local image besides network image in stable branch. + +- Support display local image besides network image in stable branch. # [0.2.7] -* Support display local image besides network image. + +- Support display local image besides network image. # [0.2.6] -* Fix cursor after pasting. + +- Fix cursor after pasting. # [0.2.5] -* Toggle text/background color button in toolbar. + +- Toggle text/background color button in toolbar. # [0.2.4] -* Support the use of custom icon size in toolbar. + +- Support the use of custom icon size in toolbar. # [0.2.3] -* Support custom styles and image on local device storage without uploading. + +- Support custom styles and image on local device storage without uploading. # [0.2.2] -* Update git repo. + +- Update git repo. # [0.2.1] -* Fix static analysis error. + +- Fix static analysis error. # [0.2.0] -* Add checked/unchecked list button in toolbar. + +- Add checked/unchecked list button in toolbar. # [0.1.8] -* Support font and size attributes. + +- Support font and size attributes. # [0.1.7] -* Support checked/unchecked list. + +- Support checked/unchecked list. # [0.1.6] -* Fix getExtentEndpointForSelection. + +- Fix getExtentEndpointForSelection. # [0.1.5] -* Support text alignment. + +- Support text alignment. # [0.1.4] -* Handle url with trailing spaces. + +- Handle url with trailing spaces. # [0.1.3] -* Handle cursor position change when undo/redo. + +- Handle cursor position change when undo/redo. # [0.1.2] -* Handle more text colors. + +- Handle more text colors. # [0.1.1] -* Fix cursor issue when undo. + +- Fix cursor issue when undo. # [0.1.0] -* Fix insert image. + +- Fix insert image. # [0.0.9] -* Handle rgba color. + +- Handle rgba color. # [0.0.8] -* Fix launching url. + +- Fix launching url. # [0.0.7] -* Handle multiple image inserts. + +- Handle multiple image inserts. # [0.0.6] -* More toolbar functionality. + +- More toolbar functionality. # [0.0.5] -* Update example. + +- Update example. # [0.0.4] -* Update example. + +- Update example. # [0.0.3] -* Update home page meta data. + +- Update home page meta data. # [0.0.2] -* Support image upload and launch url in read-only mode. + +- Support image upload and launch url in read-only mode. # [0.0.1] -* Rich text editor based on Quill Delta. + +- Rich text editor based on Quill Delta. diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 2bb092ce..befe2296 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -1,18 +1,18 @@ name: flutter_quill_extensions description: Embed extensions for flutter_quill including image, video, formula and etc. -version: 0.2.1 +version: 0.2.1+1 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.7 + flutter_quill: ^7.1.8 image_picker: ^0.8.5+3 photo_view: ^0.14.0 diff --git a/pubspec.yaml b/pubspec.yaml index a9a108c7..569616c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.8 +version: 7.1.9 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 7c94129c88dd9f04f9f65ef9039f5500462e3313 Mon Sep 17 00:00:00 2001 From: X Code Date: Thu, 20 Apr 2023 19:38:31 -0700 Subject: [PATCH 146/204] Update pubspec.yaml --- flutter_quill_extensions/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index befe2296..05f699fb 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.8 + flutter_quill: ^7.1.9 image_picker: ^0.8.5+3 photo_view: ^0.14.0 From dba00d8812cd374e1e6f13990d5e59551412491f Mon Sep 17 00:00:00 2001 From: Jonathan Salmon Date: Fri, 21 Apr 2023 14:13:08 +0100 Subject: [PATCH 147/204] Update changelog for flutter_quill_extensions --- flutter_quill_extensions/CHANGELOG.md | 4 ++++ flutter_quill_extensions/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index 442fb43d..00824281 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0 + +* Added support for adding custom tooltips to toolbar buttons + ## 0.2.0 * Allow widgets to override widget span properties [b7951b0](https://github.com/singerdmx/flutter-quill/commit/b7951b02c9086ea42e7aad6d78e6c9b0297562e5) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 05f699fb..1d7d7606 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: 0.2.1+1 +version: 0.3.0 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions From d83cc0cf4cadbe7bdeae090ae6cf5352a5928b31 Mon Sep 17 00:00:00 2001 From: BambinoUA <130981115+MacDeveloper1@users.noreply.github.com> Date: Fri, 21 Apr 2023 18:28:19 +0200 Subject: [PATCH 148/204] Image embedding tweaks (#1187) * Update `QuillDialogTheme` * Add `ImageAttribute` and `VideoAttribute` * Add `LinkStyleDialog` * Update `RawEditor` with new actions * Update translations * Add translations * Restore original `LinkStyleButton` * Update dart SDK to 2.17.0 * Update `ScriptAttribute` and `ScriptAttributes` * Add `LinkStyleButton2` * Implement `ApplyLinkAction` for `Ctrl+K` * Use `ShortcutActivator` instead of `LogicalKeySet` * Use `ShortcutActivator` instead of `LogicalKeySet` * Update `QuillDialogTheme` with `buttonStyle` * Implement `dialogTheme` for `QuillEditor` * Update `QuillDialogTheme` with dialog constraints * Pass `dialogTheme` to `RawEditor` * Merge `customShorcuts` and `customActions` * Implement `constrains` and `buttonStyle` * Update `QuillDialogTheme` with `isWrappable` * Update `LinkStyleDialog` to use `Wrap` conditionally * Add `ImageButton2` * Update `QuillDialogTheme` with padding properties * Update `QuillDialogTheme` * Export `UtilityWidgets` via flutter_quill.extensions * Minor change * Minor change * Update `QuillDialogTheme` * Update translations * Add `MediaButton` * Add `ImageEmbedBuilderWeb` * Update `flutter_quill`' version * Update `flutter_quill_extensions` * Update CHANGELOG.md --- CHANGELOG.md | 8 +- .../lib/embeds/builders.dart | 38 ++ .../lib/embeds/embed_types.dart | 26 + .../lib/embeds/toolbar/media_button.dart | 452 ++++++++++++++++++ .../lib/embeds/widgets/image.dart | 2 +- .../lib/flutter_quill_extensions.dart | 118 ++--- .../lib/shims/dart_ui_fake.dart | 23 + .../lib/shims/dart_ui_real.dart | 1 + flutter_quill_extensions/pubspec.yaml | 13 +- lib/extensions.dart | 1 + lib/src/models/themes/quill_dialog_theme.dart | 32 +- lib/src/translations/toolbar.i18n.dart | 2 + .../widgets/toolbar/link_style_button2.dart | 1 - pubspec.yaml | 2 +- 14 files changed, 645 insertions(+), 74 deletions(-) create mode 100644 flutter_quill_extensions/lib/embeds/toolbar/media_button.dart create mode 100644 flutter_quill_extensions/lib/shims/dart_ui_fake.dart create mode 100644 flutter_quill_extensions/lib/shims/dart_ui_real.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f4f80e3..094eee38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# [7.1.10] +- Image embedding tweaks + - Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working. + - Implement image insert for web (image as base64) + # [7.1.9] - Editor tweaks PR from [bambinoua](https://github.com/bambinoua). @@ -8,14 +13,12 @@ - Update minimum Dart SDK version to 2.17.0 to use enum extensions. - Use merging shortcuts and actions correclty (if the key combination is the same) - # [7.1.8] - Dropdown tweaks - Add itemHeight, itemPadding, defaultItemColor for customization of dropdown items. - Remove alignment property as useless. - Fix bugs with max width when width property is null. - # [7.1.7] - Toolbar tweaks. @@ -30,7 +33,6 @@ Now the package is more friendly for web projects. - # [7.1.6] - Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets. diff --git a/flutter_quill_extensions/lib/embeds/builders.dart b/flutter_quill_extensions/lib/embeds/builders.dart index 4888da9f..cf04a462 100644 --- a/flutter_quill_extensions/lib/embeds/builders.dart +++ b/flutter_quill_extensions/lib/embeds/builders.dart @@ -7,7 +7,10 @@ import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/translations.dart'; import 'package:gallery_saver/gallery_saver.dart'; import 'package:math_keyboard/math_keyboard.dart'; +import 'package:universal_html/html.dart' as html; +import '../shims/dart_ui_fake.dart' + if (dart.library.html) '../shims/dart_ui_real.dart' as ui; import 'utils.dart'; import 'widgets/image.dart'; import 'widgets/image_resizer.dart'; @@ -145,6 +148,41 @@ class ImageEmbedBuilder extends EmbedBuilder { } } +class ImageEmbedBuilderWeb extends EmbedBuilder { + ImageEmbedBuilderWeb({this.constraints}) + : assert(kIsWeb, 'ImageEmbedBuilderWeb is only for web platform'); + + final BoxConstraints? constraints; + + @override + String get key => BlockEmbed.imageType; + + @override + Widget build( + BuildContext context, + QuillController controller, + Embed node, + bool readOnly, + bool inline, + ) { + final imageUrl = node.value.data; + + ui.platformViewRegistry.registerViewFactory(imageUrl, (viewId) { + return html.ImageElement() + ..src = imageUrl + ..style.height = 'auto' + ..style.width = 'auto'; + }); + + return ConstrainedBox( + constraints: constraints ?? BoxConstraints.loose(const Size(200, 200)), + child: HtmlElementView( + viewType: imageUrl, + ), + ); + } +} + class VideoEmbedBuilder extends EmbedBuilder { VideoEmbedBuilder({this.onVideoInit}); diff --git a/flutter_quill_extensions/lib/embeds/embed_types.dart b/flutter_quill_extensions/lib/embeds/embed_types.dart index 814b77b6..6a48f066 100644 --- a/flutter_quill_extensions/lib/embeds/embed_types.dart +++ b/flutter_quill_extensions/lib/embeds/embed_types.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; @@ -18,3 +19,28 @@ enum MediaPickSetting { Camera, Video, } + +typedef MediaFileUrl = String; +typedef MediaFilePicker = Future Function(QuillMediaType mediaType); +typedef MediaPickedCallback = Future Function(QuillFile file); + +enum QuillMediaType { image, video } + +extension QuillMediaTypeX on QuillMediaType { + bool get isImage => this == QuillMediaType.image; + bool get isVideo => this == QuillMediaType.video; +} + +/// Represents a file data which returned by file picker. +class QuillFile { + QuillFile({ + required this.name, + this.path = '', + Uint8List? bytes, + }) : assert(name.isNotEmpty), + bytes = bytes ?? Uint8List(0); + + final String name; + final String path; + final Uint8List bytes; +} diff --git a/flutter_quill_extensions/lib/embeds/toolbar/media_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/media_button.dart new file mode 100644 index 00000000..837ca825 --- /dev/null +++ b/flutter_quill_extensions/lib/embeds/toolbar/media_button.dart @@ -0,0 +1,452 @@ +//import 'dart:io'; +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_quill/extensions.dart'; +import 'package:flutter_quill/flutter_quill.dart' hide Text; +import 'package:flutter_quill/translations.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../embed_types.dart'; + +/// Widget which combines [ImageButton] and [VideButton] widgets. This widget +/// has more customization and uses dialog similar to one which is used +/// on [http://quilljs.com]. +class MediaButton extends StatelessWidget { + const MediaButton({ + required this.controller, + required this.icon, + this.type = QuillMediaType.image, + this.iconSize = kDefaultIconSize, + this.fillColor, + this.mediaFilePicker = _defaultMediaPicker, + this.onMediaPickedCallback, + this.iconTheme, + this.dialogTheme, + this.tooltip, + this.childrenSpacing = 16.0, + this.labelText, + this.hintText, + this.submitButtonText, + this.submitButtonSize, + this.galleryButtonText, + this.linkButtonText, + this.autovalidateMode = AutovalidateMode.disabled, + Key? key, + this.validationMessage, + }) : assert(type == QuillMediaType.image, + 'Video selection is not supported yet'), + super(key: key); + + final QuillController controller; + final IconData icon; + final double iconSize; + final Color? fillColor; + final QuillMediaType type; + final QuillIconTheme? iconTheme; + final QuillDialogTheme? dialogTheme; + final String? tooltip; + final MediaFilePicker mediaFilePicker; + final MediaPickedCallback? onMediaPickedCallback; + + /// The margin between child widgets in the dialog. + final double childrenSpacing; + + /// The text of label in link add mode. + final String? labelText; + + /// The hint text for link [TextField]. + final String? hintText; + + /// The text of the submit button. + final String? submitButtonText; + + /// The size of dialog buttons. + final Size? submitButtonSize; + + /// The text of the gallery button [MediaSourceSelectorDialog]. + final String? galleryButtonText; + + /// The text of the link button [MediaSourceSelectorDialog]. + final String? linkButtonText; + + final AutovalidateMode autovalidateMode; + final String? validationMessage; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final iconColor = iconTheme?.iconUnselectedColor ?? theme.iconTheme.color; + final iconFillColor = + iconTheme?.iconUnselectedFillColor ?? fillColor ?? theme.canvasColor; + + return QuillIconButton( + icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, + highlightElevation: 0, + hoverElevation: 0, + size: iconSize * 1.77, + fillColor: iconFillColor, + borderRadius: iconTheme?.borderRadius ?? 2, + onPressed: () => _onPressedHandler(context), + ); + } + + Future _onPressedHandler(BuildContext context) async { + if (onMediaPickedCallback != null) { + final mediaSource = await showDialog( + context: context, + builder: (_) => MediaSourceSelectorDialog( + dialogTheme: dialogTheme, + galleryButtonText: galleryButtonText, + linkButtonText: linkButtonText, + ), + ); + if (mediaSource != null) { + if (mediaSource == MediaPickSetting.Gallery) { + await _pickImage(); + } else { + _inputLink(context); + } + } + } else { + _inputLink(context); + } + } + + Future _pickImage() async { + if (!(kIsWeb || isMobile() || isDesktop())) { + throw UnsupportedError( + 'Unsupported target platform: ${defaultTargetPlatform.name}'); + } + + final mediaFileUrl = await _pickMediaFileUrl(); + + if (mediaFileUrl != null) { + final index = controller.selection.baseOffset; + final length = controller.selection.extentOffset - index; + controller.replaceText( + index, length, BlockEmbed.image(mediaFileUrl), null); + } + } + + Future _pickMediaFileUrl() async { + final mediaFile = await mediaFilePicker(type); + return mediaFile != null ? onMediaPickedCallback?.call(mediaFile) : null; + } + + void _inputLink(BuildContext context) { + showDialog( + context: context, + builder: (_) => MediaLinkDialog( + dialogTheme: dialogTheme, + labelText: labelText, + hintText: hintText, + buttonText: submitButtonText, + buttonSize: submitButtonSize, + childrenSpacing: childrenSpacing, + autovalidateMode: autovalidateMode, + validationMessage: validationMessage, + ), + ).then(_linkSubmitted); + } + + void _linkSubmitted(String? value) { + if (value != null && value.isNotEmpty) { + final index = controller.selection.baseOffset; + final length = controller.selection.extentOffset - index; + final data = + type.isImage ? BlockEmbed.image(value) : BlockEmbed.video(value); + controller.replaceText(index, length, data, null); + } + } +} + +/// Provides a dialog for input link to media resource. +class MediaLinkDialog extends StatefulWidget { + const MediaLinkDialog({ + Key? key, + this.link, + this.dialogTheme, + this.childrenSpacing = 16.0, + this.labelText, + this.hintText, + this.buttonText, + this.buttonSize, + this.autovalidateMode = AutovalidateMode.disabled, + this.validationMessage, + }) : assert(childrenSpacing > 0), + super(key: key); + + final String? link; + final QuillDialogTheme? dialogTheme; + + /// The margin between child widgets in the dialog. + final double childrenSpacing; + + /// The text of label in link add mode. + final String? labelText; + + /// The hint text for link [TextField]. + final String? hintText; + + /// The text of the submit button. + final String? buttonText; + + /// The size of dialog buttons. + final Size? buttonSize; + + final AutovalidateMode autovalidateMode; + final String? validationMessage; + + @override + State createState() => _MediaLinkDialogState(); +} + +class _MediaLinkDialogState extends State { + final _linkFocus = FocusNode(); + final _linkController = TextEditingController(); + + @override + void dispose() { + _linkFocus.dispose(); + _linkController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final constraints = widget.dialogTheme?.linkDialogConstraints ?? + () { + final mediaQuery = MediaQuery.of(context); + final maxWidth = + kIsWeb ? mediaQuery.size.width / 4 : mediaQuery.size.width - 80; + return BoxConstraints(maxWidth: maxWidth, maxHeight: 80); + }(); + + final buttonStyle = widget.buttonSize != null + ? Theme.of(context) + .elevatedButtonTheme + .style + ?.copyWith(fixedSize: MaterialStatePropertyAll(widget.buttonSize)) + : widget.dialogTheme?.buttonStyle; + + final isWrappable = widget.dialogTheme?.isWrappable ?? false; + + final children = [ + Text(widget.labelText ?? 'Enter media'.i18n), + UtilityWidgets.maybeWidget( + enabled: !isWrappable, + wrapper: (child) => Expanded( + child: child, + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: widget.childrenSpacing), + child: TextFormField( + controller: _linkController, + focusNode: _linkFocus, + style: widget.dialogTheme?.inputTextStyle, + keyboardType: TextInputType.url, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + labelStyle: widget.dialogTheme?.labelTextStyle, + hintText: widget.hintText, + ), + autofocus: true, + autovalidateMode: widget.autovalidateMode, + validator: _validateLink, + onChanged: _linkChanged, + ), + ), + ), + ElevatedButton( + onPressed: _canPress() ? _submitLink : null, + style: buttonStyle, + child: Text(widget.buttonText ?? 'Ok'.i18n), + ), + ]; + + return Dialog( + backgroundColor: widget.dialogTheme?.dialogBackgroundColor, + shape: widget.dialogTheme?.shape ?? + DialogTheme.of(context).shape ?? + RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + child: ConstrainedBox( + constraints: constraints, + child: Padding( + padding: + widget.dialogTheme?.linkDialogPadding ?? const EdgeInsets.all(16), + child: isWrappable + ? Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: widget.dialogTheme?.runSpacing ?? 0.0, + children: children, + ) + : Row( + children: children, + ), + ), + ), + ); + } + + bool _canPress() => _validateLink(_linkController.text) == null; + + void _linkChanged(String value) { + setState(() { + _linkController.text = value; + }); + } + + void _submitLink() => Navigator.pop(context, _linkController.text); + + String? _validateLink(String? value) { + if ((value?.isEmpty ?? false) || + !AutoFormatMultipleLinksRule.linkRegExp.hasMatch(value!)) { + return widget.validationMessage ?? 'That is not a valid URL'; + } + + return null; + } +} + +/// Media souce selector. +class MediaSourceSelectorDialog extends StatelessWidget { + const MediaSourceSelectorDialog({ + Key? key, + this.dialogTheme, + this.galleryButtonText, + this.linkButtonText, + }) : super(key: key); + + final QuillDialogTheme? dialogTheme; + + /// The text of the gallery button [MediaSourceSelectorDialog]. + final String? galleryButtonText; + + /// The text of the link button [MediaSourceSelectorDialog]. + final String? linkButtonText; + + @override + Widget build(BuildContext context) { + final constraints = dialogTheme?.mediaSelectorDialogConstraints ?? + () { + final mediaQuery = MediaQuery.of(context); + double maxWidth, maxHeight; + if (kIsWeb) { + maxWidth = mediaQuery.size.width / 7; + maxHeight = mediaQuery.size.height / 7; + } else { + maxWidth = mediaQuery.size.width - 80; + maxHeight = maxWidth / 2; + } + return BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight); + }(); + + final shape = dialogTheme?.shape ?? + DialogTheme.of(context).shape ?? + RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)); + + return Dialog( + backgroundColor: dialogTheme?.dialogBackgroundColor, + shape: shape, + child: ConstrainedBox( + constraints: constraints, + child: Padding( + padding: dialogTheme?.mediaSelectorDialogPadding ?? + const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: TextButtonWithIcon( + icon: Icons.collections, + label: galleryButtonText ?? 'Gallery'.i18n, + onPressed: () => + Navigator.pop(context, MediaPickSetting.Gallery), + ), + ), + const SizedBox(width: 10), + Expanded( + child: TextButtonWithIcon( + icon: Icons.link, + label: linkButtonText ?? 'Link'.i18n, + onPressed: () => + Navigator.pop(context, MediaPickSetting.Link), + ), + ) + ], + ), + ), + ), + ); + } +} + +class TextButtonWithIcon extends StatelessWidget { + const TextButtonWithIcon({ + required this.label, + required this.icon, + required this.onPressed, + this.textStyle, + Key? key, + }) : super(key: key); + + final String label; + final IconData icon; + final VoidCallback onPressed; + final TextStyle? textStyle; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1; + final gap = scale <= 1 ? 8.0 : lerpDouble(8, 4, math.min(scale - 1, 1))!; + final buttonStyle = TextButtonTheme.of(context).style; + final shape = buttonStyle?.shape?.resolve({}) ?? + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4))); + return Material( + shape: shape, + textStyle: textStyle ?? + theme.textButtonTheme.style?.textStyle?.resolve({}) ?? + theme.textTheme.labelLarge, + elevation: buttonStyle?.elevation?.resolve({}) ?? 0, + child: InkWell( + customBorder: shape, + onTap: onPressed, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon), + SizedBox(height: gap), + Flexible(child: Text(label)), + ], + ), + ), + ), + ); + } +} + +/// Default file picker. +Future _defaultMediaPicker(QuillMediaType mediaType) async { + final pickedFile = mediaType.isImage + ? await ImagePicker().pickImage(source: ImageSource.gallery) + : await ImagePicker().pickVideo(source: ImageSource.gallery); + + if (pickedFile != null) { + return QuillFile( + name: pickedFile.name, + path: pickedFile.path, + bytes: await pickedFile.readAsBytes(), + ); + } + + return null; +} diff --git a/flutter_quill_extensions/lib/embeds/widgets/image.dart b/flutter_quill_extensions/lib/embeds/widgets/image.dart index d4df2a4c..658c5050 100644 --- a/flutter_quill_extensions/lib/embeds/widgets/image.dart +++ b/flutter_quill_extensions/lib/embeds/widgets/image.dart @@ -21,7 +21,7 @@ String getImageStyleString(QuillController controller) { final String? s = controller .getAllSelectionStyles() .firstWhere((s) => s.attributes.containsKey(Attribute.style.key), - orElse: () => Style()) + orElse: Style.new) .attributes[Attribute.style.key] ?.value; return s ?? ''; diff --git a/flutter_quill_extensions/lib/flutter_quill_extensions.dart b/flutter_quill_extensions/lib/flutter_quill_extensions.dart index 12b3bd82..fdbc54b2 100644 --- a/flutter_quill_extensions/lib/flutter_quill_extensions.dart +++ b/flutter_quill_extensions/lib/flutter_quill_extensions.dart @@ -15,18 +15,24 @@ export 'embeds/toolbar/camera_button.dart'; export 'embeds/toolbar/formula_button.dart'; export 'embeds/toolbar/image_button.dart'; export 'embeds/toolbar/image_video_utils.dart'; +export 'embeds/toolbar/media_button.dart'; export 'embeds/toolbar/video_button.dart'; export 'embeds/utils.dart'; class FlutterQuillEmbeds { - static List builders( - {void Function(GlobalKey videoContainerKey)? onVideoInit}) => + static List builders({ + void Function(GlobalKey videoContainerKey)? onVideoInit, + }) => [ ImageEmbedBuilder(), VideoEmbedBuilder(onVideoInit: onVideoInit), FormulaEmbedBuilder(), ]; + static List webBuilders() => [ + ImageEmbedBuilderWeb(), + ]; + static List buttons({ bool showImageButton = true, bool showVideoButton = true, @@ -43,58 +49,58 @@ class FlutterQuillEmbeds { FilePickImpl? filePickImpl, WebImagePickImpl? webImagePickImpl, WebVideoPickImpl? webVideoPickImpl, - }) { - return [ - if (showImageButton) - (controller, toolbarIconSize, iconTheme, dialogTheme) => ImageButton( - icon: Icons.image, - iconSize: toolbarIconSize, - tooltip: imageButtonTooltip, - controller: controller, - onImagePickCallback: onImagePickCallback, - filePickImpl: filePickImpl, - webImagePickImpl: webImagePickImpl, - mediaPickSettingSelector: mediaPickSettingSelector, - iconTheme: iconTheme, - dialogTheme: dialogTheme, - ), - if (showVideoButton) - (controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton( - icon: Icons.movie_creation, - iconSize: toolbarIconSize, - tooltip: videoButtonTooltip, - controller: controller, - onVideoPickCallback: onVideoPickCallback, - filePickImpl: filePickImpl, - webVideoPickImpl: webImagePickImpl, - mediaPickSettingSelector: mediaPickSettingSelector, - iconTheme: iconTheme, - dialogTheme: dialogTheme, - ), - if ((onImagePickCallback != null || onVideoPickCallback != null) && - showCameraButton) - (controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton( - icon: Icons.photo_camera, - iconSize: toolbarIconSize, - tooltip: cameraButtonTooltip, - controller: controller, - onImagePickCallback: onImagePickCallback, - onVideoPickCallback: onVideoPickCallback, - filePickImpl: filePickImpl, - webImagePickImpl: webImagePickImpl, - webVideoPickImpl: webVideoPickImpl, - cameraPickSettingSelector: cameraPickSettingSelector, - iconTheme: iconTheme, - ), - if (showFormulaButton) - (controller, toolbarIconSize, iconTheme, dialogTheme) => FormulaButton( - icon: Icons.functions, - iconSize: toolbarIconSize, - tooltip: formulaButtonTooltip, - controller: controller, - iconTheme: iconTheme, - dialogTheme: dialogTheme, - ) - ]; - } + }) => + [ + if (showImageButton) + (controller, toolbarIconSize, iconTheme, dialogTheme) => ImageButton( + icon: Icons.image, + iconSize: toolbarIconSize, + tooltip: imageButtonTooltip, + controller: controller, + onImagePickCallback: onImagePickCallback, + filePickImpl: filePickImpl, + webImagePickImpl: webImagePickImpl, + mediaPickSettingSelector: mediaPickSettingSelector, + iconTheme: iconTheme, + dialogTheme: dialogTheme, + ), + if (showVideoButton) + (controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton( + icon: Icons.movie_creation, + iconSize: toolbarIconSize, + tooltip: videoButtonTooltip, + controller: controller, + onVideoPickCallback: onVideoPickCallback, + filePickImpl: filePickImpl, + webVideoPickImpl: webImagePickImpl, + mediaPickSettingSelector: mediaPickSettingSelector, + iconTheme: iconTheme, + dialogTheme: dialogTheme, + ), + if ((onImagePickCallback != null || onVideoPickCallback != null) && + showCameraButton) + (controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton( + icon: Icons.photo_camera, + iconSize: toolbarIconSize, + tooltip: cameraButtonTooltip, + controller: controller, + onImagePickCallback: onImagePickCallback, + onVideoPickCallback: onVideoPickCallback, + filePickImpl: filePickImpl, + webImagePickImpl: webImagePickImpl, + webVideoPickImpl: webVideoPickImpl, + cameraPickSettingSelector: cameraPickSettingSelector, + iconTheme: iconTheme, + ), + if (showFormulaButton) + (controller, toolbarIconSize, iconTheme, dialogTheme) => + FormulaButton( + icon: Icons.functions, + iconSize: toolbarIconSize, + tooltip: formulaButtonTooltip, + controller: controller, + iconTheme: iconTheme, + dialogTheme: dialogTheme, + ) + ]; } diff --git a/flutter_quill_extensions/lib/shims/dart_ui_fake.dart b/flutter_quill_extensions/lib/shims/dart_ui_fake.dart new file mode 100644 index 00000000..baaf9ebd --- /dev/null +++ b/flutter_quill_extensions/lib/shims/dart_ui_fake.dart @@ -0,0 +1,23 @@ +// ignore_for_file: avoid_classes_with_only_static_members, camel_case_types, lines_longer_than_80_chars + +import 'package:universal_html/html.dart' as html; + +// Fake interface for the logic that this package needs from (web-only) dart:ui. +// This is conditionally exported so the analyzer sees these methods as available. + +typedef PlatroformViewFactory = html.Element Function(int viewId); + +/// Shim for web_ui engine.PlatformViewRegistry +/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L62 +class platformViewRegistry { + /// Shim for registerViewFactory + /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L72 + static dynamic registerViewFactory( + String viewTypeId, PlatroformViewFactory viewFactory) {} +} + +/// Shim for web_ui engine.AssetManager +/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/assets.dart#L12 +class webOnlyAssetManager { + static dynamic getAssetUrl(String asset) {} +} diff --git a/flutter_quill_extensions/lib/shims/dart_ui_real.dart b/flutter_quill_extensions/lib/shims/dart_ui_real.dart new file mode 100644 index 00000000..69c06ee2 --- /dev/null +++ b/flutter_quill_extensions/lib/shims/dart_ui_real.dart @@ -0,0 +1 @@ +export 'dart:ui'; diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 1d7d7606..48ac50ac 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: 0.3.0 +version: 0.3.1 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions @@ -12,15 +12,18 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.9 + flutter_quill: ^7.1.10 - image_picker: ^0.8.5+3 + file_picker: ^5.2.10 + image_picker: ^0.8.7+3 photo_view: ^0.14.0 - video_player: ^2.4.2 - youtube_player_flutter: ^8.1.1 + video_player: ^2.6.1 + youtube_player_flutter: ^8.1.2 gallery_saver: ^2.3.2 math_keyboard: ^0.1.8 string_validator: ^1.0.0 + universal_ui: ^0.0.8 + universal_html: ^2.2.1 url_launcher: ^6.1.9 dev_dependencies: diff --git a/lib/extensions.dart b/lib/extensions.dart index 12e961c2..b7c2b0af 100644 --- a/lib/extensions.dart +++ b/lib/extensions.dart @@ -4,3 +4,4 @@ export 'src/models/documents/nodes/leaf.dart' hide Text; export 'src/models/rules/insert.dart'; export 'src/utils/platform.dart'; export 'src/utils/string.dart'; +export 'src/utils/widgets.dart'; diff --git a/lib/src/models/themes/quill_dialog_theme.dart b/lib/src/models/themes/quill_dialog_theme.dart index 47d732b9..1552c5f5 100644 --- a/lib/src/models/themes/quill_dialog_theme.dart +++ b/lib/src/models/themes/quill_dialog_theme.dart @@ -10,7 +10,9 @@ class QuillDialogTheme with Diagnosticable { this.shape, this.buttonStyle, this.linkDialogConstraints, - this.imageDialogConstraints, + this.linkDialogPadding = const EdgeInsets.all(16), + this.mediaSelectorDialogConstraints, + this.mediaSelectorDialogPadding = const EdgeInsets.all(16), this.isWrappable = false, this.runSpacing = 8.0, }) : assert(runSpacing >= 0); @@ -34,8 +36,14 @@ class QuillDialogTheme with Diagnosticable { /// Constrains for [LinkStyleDialog]. final BoxConstraints? linkDialogConstraints; - /// Constrains for [EmbedImageDialog]. - final BoxConstraints? imageDialogConstraints; + /// The padding for content of [LinkStyleDialog]. + final EdgeInsetsGeometry linkDialogPadding; + + /// Constrains for [MediaSourceSelectorDialog]. + final BoxConstraints? mediaSelectorDialogConstraints; + + /// The padding for content of [MediaSourceSelectorDialog]. + final EdgeInsetsGeometry mediaSelectorDialogPadding; /// Customizes this button's appearance. final ButtonStyle? buttonStyle; @@ -57,7 +65,9 @@ class QuillDialogTheme with Diagnosticable { ShapeBorder? shape, ButtonStyle? buttonStyle, BoxConstraints? linkDialogConstraints, + EdgeInsetsGeometry? linkDialogPadding, BoxConstraints? imageDialogConstraints, + EdgeInsetsGeometry? mediaDialogPadding, bool? isWrappable, double? runSpacing, }) { @@ -70,8 +80,11 @@ class QuillDialogTheme with Diagnosticable { buttonStyle: buttonStyle ?? this.buttonStyle, linkDialogConstraints: linkDialogConstraints ?? this.linkDialogConstraints, - imageDialogConstraints: - imageDialogConstraints ?? this.imageDialogConstraints, + linkDialogPadding: linkDialogPadding ?? this.linkDialogPadding, + mediaSelectorDialogConstraints: + imageDialogConstraints ?? mediaSelectorDialogConstraints, + mediaSelectorDialogPadding: + mediaDialogPadding ?? mediaSelectorDialogPadding, isWrappable: isWrappable ?? this.isWrappable, runSpacing: runSpacing ?? this.runSpacing, ); @@ -89,7 +102,10 @@ class QuillDialogTheme with Diagnosticable { other.shape == shape && other.buttonStyle == buttonStyle && other.linkDialogConstraints == linkDialogConstraints && - other.imageDialogConstraints == imageDialogConstraints && + other.linkDialogPadding == linkDialogPadding && + other.mediaSelectorDialogConstraints == + mediaSelectorDialogConstraints && + other.mediaSelectorDialogPadding == mediaSelectorDialogPadding && other.isWrappable == isWrappable && other.runSpacing == runSpacing; } @@ -102,7 +118,9 @@ class QuillDialogTheme with Diagnosticable { shape, buttonStyle, linkDialogConstraints, - imageDialogConstraints, + linkDialogPadding, + mediaSelectorDialogConstraints, + mediaSelectorDialogPadding, isWrappable, runSpacing, ); diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 507d6ebd..305e0152 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -63,6 +63,7 @@ extension Localization on String { 'Insert URL': 'Insert URL', 'Visit link': 'Visit link', 'Enter link': 'Enter link', + 'Enter media': 'Enter media', 'Edit': 'Edit', 'Apply': 'Apply', }, @@ -126,6 +127,7 @@ extension Localization on String { 'Insert URL': 'Insert URL', 'Visit link': 'Visit link', 'Enter link': 'Enter link', + 'Enter media': 'Enter media', 'Edit': 'Edit', 'Apply': 'Apply', }, diff --git a/lib/src/widgets/toolbar/link_style_button2.dart b/lib/src/widgets/toolbar/link_style_button2.dart index ad73152a..42aaff11 100644 --- a/lib/src/widgets/toolbar/link_style_button2.dart +++ b/lib/src/widgets/toolbar/link_style_button2.dart @@ -7,7 +7,6 @@ import '../../../translations.dart'; import '../../models/documents/attribute.dart'; import '../../models/themes/quill_dialog_theme.dart'; import '../../models/themes/quill_icon_theme.dart'; -import '../../utils/widgets.dart'; import '../controller.dart'; import '../link.dart'; import '../toolbar.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 569616c4..24e9ed14 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.9 +version: 7.1.10 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From fe8102e45627526330110eebd132e41e405abc27 Mon Sep 17 00:00:00 2001 From: bohdanudreambit Date: Mon, 24 Apr 2023 16:22:20 +0300 Subject: [PATCH 149/204] add inserting indents for lines for selected text (#1188) --- CHANGELOG.md | 3 +++ lib/src/widgets/raw_editor.dart | 22 +++++++++++++++++----- pubspec.yaml | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 094eee38..40e96aa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.11] +- Add inserting indents for lines of list if text is selected + # [7.1.10] - Image embedding tweaks - Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working. diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index b8218f8b..4cbf4193 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -675,17 +675,16 @@ class RawEditorState extends EditorState if (event is! RawKeyDownEvent) { return KeyEventResult.ignored; } + // Handle indenting blocks when pressing the tab key. + if (event.logicalKey == LogicalKeyboardKey.tab) { + return _handleTabKey(event); + } // Don't handle key if there is an active selection. if (controller.selection.baseOffset != controller.selection.extentOffset) { return KeyEventResult.ignored; } - // Handle indenting blocks when pressing the tab key. - if (event.logicalKey == LogicalKeyboardKey.tab) { - return _handleTabKey(event); - } - // Handle inserting lists when space is pressed following // a list initiating phrase. if (event.logicalKey == LogicalKeyboardKey.space) { @@ -736,6 +735,19 @@ class RawEditorState extends EditorState return KeyEventResult.handled; } + if (controller.selection.baseOffset != controller.selection.extentOffset) { + if (child.node == null || child.node!.parent == null) { + return KeyEventResult.handled; + } + final parentBlock = child.node!.parent!; + if (parentBlock.style.containsKey(Attribute.ol.key) || + parentBlock.style.containsKey(Attribute.ul.key) || + parentBlock.style.containsKey(Attribute.checked.key)) { + controller.indentSelection(!event.isShiftPressed); + } + return KeyEventResult.handled; + } + if (child.node == null) { return insertTabCharacter(); } diff --git a/pubspec.yaml b/pubspec.yaml index 24e9ed14..7d497da1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.10 +version: 7.1.11 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 06cafef8c99d8ecddeb9223a897b506ecf4fb0a4 Mon Sep 17 00:00:00 2001 From: jiangchong Date: Fri, 28 Apr 2023 23:54:14 +0800 Subject: [PATCH 150/204] add superscript and subscript styles (#1194) --- lib/src/models/documents/attribute.dart | 11 +- lib/src/translations/toolbar.i18n.dart | 895 ++++++++++++++++++ lib/src/widgets/default_styles.dart | 9 + lib/src/widgets/text_line.dart | 8 + lib/src/widgets/toolbar.dart | 27 + lib/src/widgets/toolbar/enum.dart | 2 + .../widgets/toolbar/toggle_style_button.dart | 2 +- 7 files changed, 951 insertions(+), 3 deletions(-) diff --git a/lib/src/models/documents/attribute.dart b/lib/src/models/documents/attribute.dart index 9bf75396..b1df087f 100644 --- a/lib/src/models/documents/attribute.dart +++ b/lib/src/models/documents/attribute.dart @@ -19,6 +19,8 @@ class Attribute { static final Map _registry = LinkedHashMap.of({ Attribute.bold.key: Attribute.bold, + Attribute.subscript.key: Attribute.subscript, + Attribute.superscript.key: Attribute.superscript, Attribute.italic.key: Attribute.italic, Attribute.small.key: Attribute.small, Attribute.underline.key: Attribute.underline, @@ -48,6 +50,10 @@ class Attribute { static const BoldAttribute bold = BoldAttribute(); + static final ScriptAttribute subscript = ScriptAttribute(ScriptAttributes.sub); + + static final ScriptAttribute superscript = ScriptAttribute(ScriptAttributes.sup); + static const ItalicAttribute italic = ItalicAttribute(); static const SmallAttribute small = SmallAttribute(); @@ -108,6 +114,8 @@ class Attribute { static final Set inlineKeys = { Attribute.bold.key, + Attribute.subscript.key, + Attribute.superscript.key, Attribute.italic.key, Attribute.small.key, Attribute.underline.key, @@ -356,10 +364,9 @@ class TokenAttribute extends Attribute { const TokenAttribute(String val) : super('token', AttributeScope.IGNORE, val); } -// `script` is supposed to be inline attribute but it is not supported yet class ScriptAttribute extends Attribute { ScriptAttribute(ScriptAttributes? val) - : super('script', AttributeScope.IGNORE, val?.value); + : super('script', AttributeScope.INLINE, val?.value); } enum ScriptAttributes { diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 305e0152..c2136dc0 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -40,6 +40,8 @@ extension Localization on String { 'Font family': 'Font family', 'Font size': 'Font size', 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', 'Italic': 'Italic', 'Underline': 'Underline', 'Strike through': 'Strike through', @@ -104,6 +106,8 @@ extension Localization on String { 'Font family': 'Font family', 'Font size': 'Font size', 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', 'Italic': 'Italic', 'Underline': 'Underline', 'Strike through': 'Strike through', @@ -163,6 +167,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'da': { 'Paste a link': 'Indsæt link', @@ -196,6 +233,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'de': { 'Paste a link': 'Link hinzufügen', @@ -230,6 +300,39 @@ extension Localization on String { 'Next': 'Nächster', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'fr': { 'Paste a link': 'Coller un lien', @@ -263,6 +366,39 @@ extension Localization on String { 'Next': 'Suivant', 'Camera': 'Caméra', 'Video': 'Vidéo', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'zh_cn': { 'Paste a link': '粘贴链接', @@ -296,6 +432,39 @@ extension Localization on String { 'Next': '下一个', 'Camera': '拍照', 'Video': '录像', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'jp': { 'Paste a link': 'リンクをペースト', @@ -329,6 +498,39 @@ extension Localization on String { 'Next': '次へ', 'Camera': 'カメラ', 'Video': 'ビデオ', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'ko': { 'Paste a link': '링크를 붙여넣어 주세요.', @@ -362,6 +564,39 @@ extension Localization on String { 'Next': '다음', 'Camera': '카메라', 'Video': '비디오', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'ru': { 'Paste a link': 'Вставить ссылку', @@ -395,6 +630,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'es': { 'Paste a link': 'Pega un enlace', @@ -429,6 +697,39 @@ extension Localization on String { 'Next': 'Siguiente', 'Camera': 'Cámara', 'Video': 'Vídeo', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'tr': { 'Paste a link': 'Bağlantıyı Yapıştır', @@ -462,6 +763,39 @@ extension Localization on String { 'Next': 'Devam', 'Camera': 'Kamera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'uk': { 'Paste a link': 'Вставити посилання', @@ -495,6 +829,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'pt': { 'Paste a link': 'Colar um link', @@ -529,6 +896,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'pt_br': { 'Paste a link': 'Colar um link', @@ -563,6 +963,39 @@ extension Localization on String { 'Next': 'Próximo', 'Camera': 'Camera', 'Video': 'Vídeo', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'pl': { 'Paste a link': 'Wklej link', @@ -597,6 +1030,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'vi': { 'Paste a link': 'Chèn liên kết', @@ -631,6 +1097,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'ur': { 'Paste a link': 'لنک پیسٹ کریں', @@ -664,6 +1163,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'id': { 'Paste a link': 'Tempel tautan', @@ -697,6 +1229,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'no': { 'Paste a link': 'Lim inn lenke', @@ -730,6 +1295,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'fa': { 'Paste a link': 'جایگذاری لینک', @@ -763,6 +1361,39 @@ extension Localization on String { 'Next': 'بعدی', 'Camera': 'دوربین', 'Video': 'ویدیو', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'hi': { 'Paste a link': 'लिंक पेस्ट करें', @@ -796,6 +1427,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'nl': { 'Paste a link': 'Plak een link', @@ -829,6 +1493,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'zh_hk': { 'Paste a link': '貼上連結', @@ -862,6 +1559,39 @@ extension Localization on String { 'Next': '下一個', 'Camera': '相機', 'Video': '錄影', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'sr': { 'Paste a link': 'Nalepi vezu', @@ -895,6 +1625,39 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'cs': { 'Paste a link': 'Vložte odkaz', @@ -928,6 +1691,39 @@ extension Localization on String { 'Next': 'Další', 'Camera': 'Kamera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'he': { 'Paste a link': 'הדבק את הלינק', @@ -961,6 +1757,39 @@ extension Localization on String { 'Next': 'הבא', 'Camera': 'מצלמה', 'Video': 'וידאו', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'ms': { 'Paste a link': 'Tampal Pautan', @@ -995,6 +1824,39 @@ extension Localization on String { 'Next': 'Seterusnya', 'Camera': 'Kamera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, 'it': { 'Paste a link': 'Incolla un collegamento', @@ -1029,6 +1891,39 @@ extension Localization on String { 'Next': 'Succ', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', + 'Visit link': 'Visit link', + 'Enter link': 'Enter link', + 'Enter media': 'Enter media', + 'Edit': 'Edit', + 'Apply': 'Apply', }, }; diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index f9fef5d0..5ca0e95b 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'dart:ui'; import '../models/documents/attribute.dart'; import '../models/documents/style.dart'; @@ -141,6 +142,8 @@ class DefaultStyles { this.h3, this.paragraph, this.bold, + this.subscript, + this.superscript, this.italic, this.small, this.underline, @@ -165,6 +168,8 @@ class DefaultStyles { final DefaultTextBlockStyle? h3; final DefaultTextBlockStyle? paragraph; final TextStyle? bold; + final TextStyle? subscript; + final TextStyle? superscript; final TextStyle? italic; final TextStyle? small; final TextStyle? underline; @@ -244,6 +249,8 @@ class DefaultStyles { paragraph: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), const VerticalSpacing(0, 0), null), bold: const TextStyle(fontWeight: FontWeight.bold), + subscript: TextStyle(fontFeatures: [FontFeature.subscripts()]), + superscript: TextStyle(fontFeatures: [FontFeature.superscripts()]), italic: const TextStyle(fontStyle: FontStyle.italic), small: const TextStyle(fontSize: 12), underline: const TextStyle(decoration: TextDecoration.underline), @@ -317,6 +324,8 @@ class DefaultStyles { h3: other.h3 ?? h3, paragraph: other.paragraph ?? paragraph, bold: other.bold ?? bold, + subscript: other.subscript ?? subscript, + superscript: other.superscript ?? superscript, italic: other.italic ?? italic, small: other.small ?? small, underline: other.underline ?? underline, diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 22623f4d..86c35da8 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -352,6 +352,14 @@ class _TextLineState extends State { } }); + if (nodeStyle.containsKey(Attribute.script.key)) { + if (nodeStyle.attributes.values.contains(Attribute.subscript)) { + res = _merge(res, defaultStyles.subscript!); + }else if (nodeStyle.attributes.values.contains(Attribute.superscript)) { + res = _merge(res, defaultStyles.superscript!); + } + } + if (nodeStyle.containsKey(Attribute.inlineCode.key)) { res = _merge(res, defaultStyles.inlineCode!.styleFor(lineStyle)); } diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index f28eaee9..bc08c027 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -103,6 +103,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { bool showRedo = true, bool showDirection = false, bool showSearchButton = true, + bool showSubscript = true, + bool showSuperscript = true, List customButtons = const [], ///Map of font sizes in string @@ -208,6 +210,8 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ToolbarButtons.fontFamily: 'Font family'.i18n, ToolbarButtons.fontSize: 'Font size'.i18n, ToolbarButtons.bold: 'Bold'.i18n, + ToolbarButtons.subscript: 'Subscript'.i18n, + ToolbarButtons.superscript: 'Superscript'.i18n, ToolbarButtons.italic: 'Italic'.i18n, ToolbarButtons.small: 'Small'.i18n, ToolbarButtons.underline: 'Underline'.i18n, @@ -296,6 +300,29 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, ), + + if (showSubscript) + ToggleStyleButton( + attribute: Attribute.subscript, + icon: Icons.subscript, + iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.subscript], + controller: controller, + iconTheme: iconTheme, + afterButtonPressed: afterButtonPressed, + ), + + if (showSuperscript) + ToggleStyleButton( + attribute: Attribute.superscript, + icon: Icons.superscript, + iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.superscript], + controller: controller, + iconTheme: iconTheme, + afterButtonPressed: afterButtonPressed, + ), + if (showItalicButton) ToggleStyleButton( attribute: Attribute.italic, diff --git a/lib/src/widgets/toolbar/enum.dart b/lib/src/widgets/toolbar/enum.dart index 197bea56..e6719e50 100644 --- a/lib/src/widgets/toolbar/enum.dart +++ b/lib/src/widgets/toolbar/enum.dart @@ -4,6 +4,8 @@ enum ToolbarButtons { fontFamily, fontSize, bold, + subscript, + superscript, italic, small, underline, diff --git a/lib/src/widgets/toolbar/toggle_style_button.dart b/lib/src/widgets/toolbar/toggle_style_button.dart index 1eb0eb57..6800cc7b 100644 --- a/lib/src/widgets/toolbar/toggle_style_button.dart +++ b/lib/src/widgets/toolbar/toggle_style_button.dart @@ -105,7 +105,7 @@ class _ToggleStyleButtonState extends State { } bool _getIsToggled(Map attrs) { - if (widget.attribute.key == Attribute.list.key) { + if (widget.attribute.key == Attribute.list.key || widget.attribute.key == Attribute.script.key) { final attribute = attrs[widget.attribute.key]; if (attribute == null) { return false; From ed546e1aa65969da1437b6ab73a1bce9d2412cb2 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Fri, 28 Apr 2023 09:49:25 -0700 Subject: [PATCH 151/204] Upgrade to 7.1.12 --- CHANGELOG.md | 5 ++++- flutter_quill_extensions/pubspec.yaml | 2 +- lib/src/models/documents/attribute.dart | 6 ++++-- lib/src/widgets/text_line.dart | 2 +- lib/src/widgets/toolbar.dart | 3 --- lib/src/widgets/toolbar/toggle_style_button.dart | 3 ++- pubspec.yaml | 2 +- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e96aa8..eed8effb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ +# [7.1.12] +- Add superscript and subscript styles. + # [7.1.11] -- Add inserting indents for lines of list if text is selected +- Add inserting indents for lines of list if text is selected. # [7.1.10] - Image embedding tweaks diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 48ac50ac..0d29645d 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.10 + flutter_quill: ^7.1.12 file_picker: ^5.2.10 image_picker: ^0.8.7+3 diff --git a/lib/src/models/documents/attribute.dart b/lib/src/models/documents/attribute.dart index b1df087f..6c48e59e 100644 --- a/lib/src/models/documents/attribute.dart +++ b/lib/src/models/documents/attribute.dart @@ -50,9 +50,11 @@ class Attribute { static const BoldAttribute bold = BoldAttribute(); - static final ScriptAttribute subscript = ScriptAttribute(ScriptAttributes.sub); + static final ScriptAttribute subscript = + ScriptAttribute(ScriptAttributes.sub); - static final ScriptAttribute superscript = ScriptAttribute(ScriptAttributes.sup); + static final ScriptAttribute superscript = + ScriptAttribute(ScriptAttributes.sup); static const ItalicAttribute italic = ItalicAttribute(); diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 86c35da8..28e70e2c 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -355,7 +355,7 @@ class _TextLineState extends State { if (nodeStyle.containsKey(Attribute.script.key)) { if (nodeStyle.attributes.values.contains(Attribute.subscript)) { res = _merge(res, defaultStyles.subscript!); - }else if (nodeStyle.attributes.values.contains(Attribute.superscript)) { + } else if (nodeStyle.attributes.values.contains(Attribute.superscript)) { res = _merge(res, defaultStyles.superscript!); } } diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index bc08c027..e4da8149 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -300,7 +300,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, ), - if (showSubscript) ToggleStyleButton( attribute: Attribute.subscript, @@ -311,7 +310,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, ), - if (showSuperscript) ToggleStyleButton( attribute: Attribute.superscript, @@ -322,7 +320,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, ), - if (showItalicButton) ToggleStyleButton( attribute: Attribute.italic, diff --git a/lib/src/widgets/toolbar/toggle_style_button.dart b/lib/src/widgets/toolbar/toggle_style_button.dart index 6800cc7b..53099743 100644 --- a/lib/src/widgets/toolbar/toggle_style_button.dart +++ b/lib/src/widgets/toolbar/toggle_style_button.dart @@ -105,7 +105,8 @@ class _ToggleStyleButtonState extends State { } bool _getIsToggled(Map attrs) { - if (widget.attribute.key == Attribute.list.key || widget.attribute.key == Attribute.script.key) { + if (widget.attribute.key == Attribute.list.key || + widget.attribute.key == Attribute.script.key) { final attribute = attrs[widget.attribute.key]; if (attribute == null) { return false; diff --git a/pubspec.yaml b/pubspec.yaml index 7d497da1..316c44ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.11 +version: 7.1.12 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 8da8201c5d23979369cfa4054899666414c062f9 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Fri, 28 Apr 2023 09:50:49 -0700 Subject: [PATCH 152/204] Fix import order --- lib/src/widgets/default_styles.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index 5ca0e95b..60eda59b 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -1,6 +1,7 @@ -import 'package:flutter/material.dart'; import 'dart:ui'; +import 'package:flutter/material.dart'; + import '../models/documents/attribute.dart'; import '../models/documents/style.dart'; import '../models/structs/vertical_spacing.dart'; From 8fe0e0004bac5c41e5885e742f37ef738bee4b1a Mon Sep 17 00:00:00 2001 From: Cheryl Date: Fri, 28 Apr 2023 09:52:09 -0700 Subject: [PATCH 153/204] Format code --- lib/src/widgets/default_styles.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index 60eda59b..c2d6e878 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -250,8 +250,9 @@ class DefaultStyles { paragraph: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), const VerticalSpacing(0, 0), null), bold: const TextStyle(fontWeight: FontWeight.bold), - subscript: TextStyle(fontFeatures: [FontFeature.subscripts()]), - superscript: TextStyle(fontFeatures: [FontFeature.superscripts()]), + subscript: const TextStyle(fontFeatures: [FontFeature.subscripts()]), + superscript: + const TextStyle(fontFeatures: [FontFeature.superscripts()]), italic: const TextStyle(fontStyle: FontStyle.italic), small: const TextStyle(fontSize: 12), underline: const TextStyle(decoration: TextDecoration.underline), From 744d0b6c394bee497021afbc672ce1ef57e25be1 Mon Sep 17 00:00:00 2001 From: jiangchong Date: Sat, 29 Apr 2023 02:47:18 +0800 Subject: [PATCH 154/204] add custom recognizer (#1196) --- lib/src/widgets/delegate.dart | 2 ++ lib/src/widgets/editor.dart | 3 +++ lib/src/widgets/raw_editor.dart | 3 +++ lib/src/widgets/text_line.dart | 43 +++++++++++++++++++++++++-------- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index fa5dbe1f..a5b842a5 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -14,6 +14,8 @@ typedef EmbedsBuilder = EmbedBuilder Function(Embed node); typedef CustomStyleBuilder = TextStyle Function(Attribute attribute); +typedef CustomRecognizerBuilder = GestureRecognizer? Function(Attribute attribute); + /// Delegate interface for the [EditorTextSelectionGestureDetectorBuilder]. /// /// The interface is usually implemented by textfield implementations wrapping diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 1c360ead..906b67fb 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -176,6 +176,7 @@ class QuillEditor extends StatefulWidget { this.unknownEmbedBuilder, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.customStyleBuilder, + this.customRecognizerBuilder, this.locale, this.floatingCursorDisabled = false, this.textSelectionControls, @@ -372,6 +373,7 @@ class QuillEditor extends StatefulWidget { final Iterable? embedBuilders; final EmbedBuilder? unknownEmbedBuilder; final CustomStyleBuilder? customStyleBuilder; + final CustomRecognizerBuilder? customRecognizerBuilder; /// The locale to use for the editor toolbar, defaults to system locale /// More https://github.com/singerdmx/flutter-quill#translation @@ -518,6 +520,7 @@ class QuillEditorState extends State embedBuilder: _getEmbedBuilder, linkActionPickerDelegate: widget.linkActionPickerDelegate, customStyleBuilder: widget.customStyleBuilder, + customRecognizerBuilder: widget.customRecognizerBuilder, floatingCursorDisabled: widget.floatingCursorDisabled, onImagePaste: widget.onImagePaste, customShortcuts: widget.customShortcuts, diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 4cbf4193..89831d96 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -79,6 +79,7 @@ class RawEditor extends StatefulWidget { this.scrollPhysics, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.customStyleBuilder, + this.customRecognizerBuilder, this.floatingCursorDisabled = false, this.onImagePaste, this.customLinkPrefixes = const [], @@ -262,6 +263,7 @@ class RawEditor extends StatefulWidget { final EmbedsBuilder embedBuilder; final LinkActionPickerDelegate linkActionPickerDelegate; final CustomStyleBuilder? customStyleBuilder; + final CustomRecognizerBuilder? customRecognizerBuilder; final bool floatingCursorDisabled; final List customLinkPrefixes; @@ -925,6 +927,7 @@ class RawEditorState extends EditorState textDirection: _textDirection, embedBuilder: widget.embedBuilder, customStyleBuilder: widget.customStyleBuilder, + customRecognizerBuilder: widget.customRecognizerBuilder, styles: _styles!, readOnly: widget.readOnly, controller: controller, diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 28e70e2c..5e83837b 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -41,6 +41,7 @@ class TextLine extends StatefulWidget { required this.linkActionPicker, this.textDirection, this.customStyleBuilder, + this.customRecognizerBuilder, this.customLinkPrefixes = const [], Key? key, }) : super(key: key); @@ -52,6 +53,7 @@ class TextLine extends StatefulWidget { final bool readOnly; final QuillController controller; final CustomStyleBuilder? customStyleBuilder; + final CustomRecognizerBuilder? customRecognizerBuilder; final ValueChanged? onLaunchUrl; final LinkActionPicker linkActionPicker; final List customLinkPrefixes; @@ -313,12 +315,14 @@ class _TextLineState extends State { final isLink = nodeStyle.containsKey(Attribute.link.key) && nodeStyle.attributes[Attribute.link.key]!.value != null; + final recognizer = _getRecognizer(node, isLink); + return TextSpan( text: textNode.value, style: _getInlineTextStyle( textNode, defaultStyles, nodeStyle, lineStyle, isLink), - recognizer: isLink && canLaunchLinks ? _getRecognizer(node) : null, - mouseCursor: isLink && canLaunchLinks ? SystemMouseCursors.click : null, + recognizer: recognizer, + mouseCursor: (recognizer != null) ? SystemMouseCursors.click : null, ); } @@ -406,19 +410,38 @@ class _TextLineState extends State { return res; } - GestureRecognizer _getRecognizer(Node segment) { + GestureRecognizer? _getRecognizer(Node segment, bool isLink) { if (_linkRecognizers.containsKey(segment)) { return _linkRecognizers[segment]!; } - if (isDesktop() || widget.readOnly) { - _linkRecognizers[segment] = TapGestureRecognizer() - ..onTap = () => _tapNodeLink(segment); - } else { - _linkRecognizers[segment] = LongPressGestureRecognizer() - ..onLongPress = () => _longPressLink(segment); + if (widget.customRecognizerBuilder != null) { + final textNode = segment as leaf.Text; + final nodeStyle = textNode.style; + + nodeStyle.attributes.forEach((key, value) { + final recognizer = widget.customRecognizerBuilder!.call(value); + if (recognizer != null) { + _linkRecognizers[segment] = recognizer; + return; + } + }); + } + + if (_linkRecognizers.containsKey(segment)) { + return _linkRecognizers[segment]!; + } + + if (isLink && canLaunchLinks) { + if (isDesktop() || widget.readOnly) { + _linkRecognizers[segment] = TapGestureRecognizer() + ..onTap = () => _tapNodeLink(segment); + } else { + _linkRecognizers[segment] = LongPressGestureRecognizer() + ..onLongPress = () => _longPressLink(segment); + } } - return _linkRecognizers[segment]!; + return _linkRecognizers[segment]; } Future _launchUrl(String url) async { From f4c2449f8650faa28f40adc900e0245484bc04cc Mon Sep 17 00:00:00 2001 From: Cheryl Date: Fri, 28 Apr 2023 21:57:58 -0700 Subject: [PATCH 155/204] Upgrade to 7.1.13 --- CHANGELOG.md | 3 +++ lib/src/widgets/delegate.dart | 3 ++- pubspec.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eed8effb..42c9713d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.13] +- Add custom recognizer. + # [7.1.12] - Add superscript and subscript styles. diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index a5b842a5..41ae8762 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -14,7 +14,8 @@ typedef EmbedsBuilder = EmbedBuilder Function(Embed node); typedef CustomStyleBuilder = TextStyle Function(Attribute attribute); -typedef CustomRecognizerBuilder = GestureRecognizer? Function(Attribute attribute); +typedef CustomRecognizerBuilder = GestureRecognizer? Function( + Attribute attribute); /// Delegate interface for the [EditorTextSelectionGestureDetectorBuilder]. /// diff --git a/pubspec.yaml b/pubspec.yaml index 316c44ce..f44f24bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.12 +version: 7.1.13 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 89b06c07e9608a9222196b33375a48c2f42a1c0d Mon Sep 17 00:00:00 2001 From: bohdanudreambit Date: Tue, 2 May 2023 17:34:12 +0300 Subject: [PATCH 156/204] Add indents change for multiline selection (#1195) --- lib/src/models/documents/document.dart | 6 ++++ lib/src/models/documents/nodes/line.dart | 38 +++++++++++++++++++++++ lib/src/models/structs/offset_value.dart | 3 +- lib/src/widgets/controller.dart | 39 ++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart index 1b4d5aef..597d9025 100644 --- a/lib/src/models/documents/document.dart +++ b/lib/src/models/documents/document.dart @@ -170,6 +170,12 @@ class Document { return (res.node as Line).collectAllStyles(res.offset, len); } + /// Returns all styles for any character within the specified text range. + List> collectAllStylesWithOffset(int index, int len) { + final res = queryChild(index); + return (res.node as Line).collectAllStylesWithOffsets(res.offset, len); + } + /// Returns plain text within the specified text range. String getPlainText(int index, int len) { final res = queryChild(index); diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index a8cca75c..e87451f1 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/line.dart @@ -458,6 +458,44 @@ class Line extends Container { return result; } + /// Returns all styles for any character within the specified text range. + List> collectAllStylesWithOffsets( + int offset, + int len, { + int beg = 0, + }) { + final local = math.min(length - offset, len); + final result = >[]; + + final data = queryChild(offset, true); + var node = data.node as Leaf?; + if (node != null) { + var pos = 0; + pos = node.length - data.offset; + result.add(OffsetValue(node.documentOffset, node.style, node.length)); + while (!node!.isLast && pos < local) { + node = node.next as Leaf; + result.add(OffsetValue(node.documentOffset, node.style, node.length)); + pos += node.length; + } + } + + result.add(OffsetValue(documentOffset, style, length)); + if (parent is Block) { + final block = parent as Block; + result.add(OffsetValue(block.documentOffset, block.style, block.length)); + } + + final remaining = len - local; + if (remaining > 0 && nextLine != null) { + final rest = + nextLine!.collectAllStylesWithOffsets(0, remaining, beg: local); + result.addAll(rest); + } + + return result; + } + /// Returns plain text within the specified text range. String getPlainText(int offset, int len) { final plainText = StringBuffer(); diff --git a/lib/src/models/structs/offset_value.dart b/lib/src/models/structs/offset_value.dart index 0f9e558b..58275458 100644 --- a/lib/src/models/structs/offset_value.dart +++ b/lib/src/models/structs/offset_value.dart @@ -1,5 +1,6 @@ class OffsetValue { - OffsetValue(this.offset, this.value); + OffsetValue(this.offset, this.value, [this.length]); final int offset; + final int? length; final T value; } diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index 30396377..529e393f 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -101,6 +101,14 @@ class QuillController extends ChangeNotifier { // Increases or decreases the indent of the current selection by 1. void indentSelection(bool isIncrease) { + if (selection.isCollapsed) { + _indentSelectionFormat(isIncrease); + } else { + _indentSelectionEachLine(isIncrease); + } + } + + void _indentSelectionFormat(bool isIncrease) { final indent = getSelectionStyle().attributes[Attribute.indent.key]; if (indent == null) { if (isIncrease) { @@ -119,6 +127,37 @@ class QuillController extends ChangeNotifier { formatSelection(Attribute.getIndentLevel(indent.value - 1)); } + void _indentSelectionEachLine(bool isIncrease) { + final styles = document.collectAllStylesWithOffset( + selection.start, + selection.end - selection.start, + ); + for (final style in styles) { + final indent = style.value.attributes[Attribute.indent.key]; + final formatIndex = math.max(style.offset, selection.start); + final formatLength = math.min( + style.offset + (style.length ?? 0), + selection.end, + ) - style.offset; + Attribute? formatAttribute; + if (indent == null) { + if (isIncrease) { + formatAttribute = Attribute.indentL1; + } + } else if (indent.value == 1 && !isIncrease) { + formatAttribute = Attribute.clone(Attribute.indentL1, null); + } else if (isIncrease) { + formatAttribute = Attribute.getIndentLevel(indent.value + 1); + } else { + formatAttribute = Attribute.getIndentLevel(indent.value - 1); + } + if (formatAttribute != null) { + document.format(formatIndex, formatLength, formatAttribute); + } + } + notifyListeners(); + } + /// Returns all styles for each node within selection List> getAllIndividualSelectionStyles() { final styles = document.collectAllIndividualStyles( From 8a4a980d6a94c604bbb0afd2a64ea91d13fef306 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Tue, 2 May 2023 12:34:45 -0700 Subject: [PATCH 157/204] Upgrade to 7.1.14 --- CHANGELOG.md | 3 +++ lib/src/models/documents/nodes/line.dart | 2 +- lib/src/widgets/controller.dart | 7 ++++--- pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42c9713d..e97089e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.14] +- Add indents change for multiline selection. + # [7.1.13] - Add custom recognizer. diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index e87451f1..42306edb 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/line.dart @@ -489,7 +489,7 @@ class Line extends Container { final remaining = len - local; if (remaining > 0 && nextLine != null) { final rest = - nextLine!.collectAllStylesWithOffsets(0, remaining, beg: local); + nextLine!.collectAllStylesWithOffsets(0, remaining, beg: local); result.addAll(rest); } diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index 529e393f..48bf136d 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -136,9 +136,10 @@ class QuillController extends ChangeNotifier { final indent = style.value.attributes[Attribute.indent.key]; final formatIndex = math.max(style.offset, selection.start); final formatLength = math.min( - style.offset + (style.length ?? 0), - selection.end, - ) - style.offset; + style.offset + (style.length ?? 0), + selection.end, + ) - + style.offset; Attribute? formatAttribute; if (indent == null) { if (isIncrease) { diff --git a/pubspec.yaml b/pubspec.yaml index f44f24bd..43a4ec74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.13 +version: 7.1.14 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 99bd4e85b4456503dcc5c5e7aeb2fec709762333 Mon Sep 17 00:00:00 2001 From: Jonathan Salmon Date: Tue, 2 May 2023 22:36:08 +0100 Subject: [PATCH 158/204] Reverted redundant dependency edits and released embeddings 0.3.1 --- flutter_quill_extensions/CHANGELOG.md | 5 +++++ flutter_quill_extensions/pubspec.yaml | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index 00824281..b741b21a 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.1 +* Image embedding tweaks + * Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working. + * Implement image insert for web (image as base64) + ## 0.3.0 * Added support for adding custom tooltips to toolbar buttons diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 0d29645d..0f3ccc9c 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -14,11 +14,10 @@ dependencies: flutter_quill: ^7.1.12 - file_picker: ^5.2.10 - image_picker: ^0.8.7+3 + image_picker: ^0.8.5+3 photo_view: ^0.14.0 - video_player: ^2.6.1 - youtube_player_flutter: ^8.1.2 + video_player: ^2.4.2 + youtube_player_flutter: ^8.1.1 gallery_saver: ^2.3.2 math_keyboard: ^0.1.8 string_validator: ^1.0.0 From 16443de08543883b5fb41ab25769b942c2f8b57c Mon Sep 17 00:00:00 2001 From: Jonathan Salmon Date: Tue, 2 May 2023 22:44:26 +0100 Subject: [PATCH 159/204] Removed unnused package `universal_ui` --- flutter_quill_extensions/pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 0f3ccc9c..12ab6b52 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -21,7 +21,6 @@ dependencies: gallery_saver: ^2.3.2 math_keyboard: ^0.1.8 string_validator: ^1.0.0 - universal_ui: ^0.0.8 universal_html: ^2.2.1 url_launcher: ^6.1.9 From c0ed80b7a4e44fef9b77378091d7388cfb593e76 Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Thu, 4 May 2023 22:35:18 +0800 Subject: [PATCH 160/204] Update zh_cn, zh_hk and jp translation (#1198) --- lib/src/translations/toolbar.i18n.dart | 264 ++++++++++++------------- 1 file changed, 132 insertions(+), 132 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index c2136dc0..5eb2b344 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -432,39 +432,105 @@ extension Localization on String { 'Next': '下一个', 'Camera': '拍照', 'Video': '录像', - 'Undo': 'Undo', - 'Redo': 'Redo', - 'Font family': 'Font family', - 'Font size': 'Font size', - 'Bold': 'Bold', - 'Subscript': 'Subscript', - 'Superscript': 'Superscript', - 'Italic': 'Italic', - 'Underline': 'Underline', - 'Strike through': 'Strike through', - 'Inline code': 'Inline code', - 'Font color': 'Font color', - 'Background color': 'Background color', - 'Clear format': 'Clear format', - 'Align left': 'Align left', - 'Align center': 'Align center', - 'Align right': 'Align right', - 'Justify win width': 'Justify win width', - 'Text direction': 'Text direction', - 'Header style': 'Header style', - 'Numbered list': 'Numbered list', - 'Bullet list': 'Bullet list', - 'Checked list': 'Checked list', - 'Code block': 'Code block', - 'Quote': 'Quote', - 'Increase indent': 'Increase indent', - 'Decrease indent': 'Decrease indent', - 'Insert URL': 'Insert URL', - 'Visit link': 'Visit link', - 'Enter link': 'Enter link', - 'Enter media': 'Enter media', - 'Edit': 'Edit', - 'Apply': 'Apply', + 'Undo': '撤销', + 'Redo': '重做', + 'Font family': '字体', + 'Font size': '字号', + 'Bold': '粗体', + 'Subscript': '下标', + 'Superscript': '上标', + 'Italic': '斜体', + 'Underline': '下划线', + 'Strike through': '删除线', + 'Inline code': '内联代码', + 'Font color': '字体颜色', + 'Background color': '背景颜色', + 'Clear format': '清除格式', + 'Align left': '左对齐', + 'Align center': '居中对齐', + 'Align right': '右对齐', + 'Justify win width': '两端对齐', + 'Text direction': '文本方向', + 'Header style': '标题样式', + 'Numbered list': '有序列表', + 'Bullet list': '无序列表', + 'Checked list': '任务列表', + 'Code block': '代码块', + 'Quote': '引言', + 'Increase indent': '增加缩进', + 'Decrease indent': '减少缩进', + 'Insert URL': '插入链接', + 'Visit link': '访问链接', + 'Enter link': '输入链接', + 'Enter media': '输入媒体', + 'Edit': '编辑', + 'Apply': '应用', + }, + 'zh_hk': { + 'Paste a link': '貼上連結', + 'Ok': '確定', + 'Select Color': '選擇顏色', + 'Gallery': '圖片庫', + 'Link': '連結', + 'Please first select some text to transform into a link.': + '請選擇文字以轉換為連結。', + 'Open': '開啓', + 'Copy': '複製', + 'Remove': '移除', + 'Save': '儲存', + 'Zoom': '放大', + 'Saved': '已儲存', + 'Text': '文字', + 'What is entered is not a link': '輸入的不是連結', + 'Resize': '變更大小', + 'Width': '寛', + 'Height': '高', + 'Size': '大小', + 'Small': '小', + 'Large': '大', + 'Huge': '超大', + 'Clear': '清除', + 'Font': '字型', + 'Search': '搜尋', + 'matches': '符合', + 'showing match': '顯示符合', + 'Prev': '上一個', + 'Next': '下一個', + 'Camera': '相機', + 'Video': '錄影', + 'Undo': '撤銷', + 'Redo': '重做', + 'Font family': '字體', + 'Font size': '字號', + 'Bold': '粗體', + 'Subscript': '下標', + 'Superscript': '上標', + 'Italic': '斜體', + 'Underline': '下劃線', + 'Strike through': '刪除線', + 'Inline code': '內聯代碼', + 'Font color': '字體顏色', + 'Background color': '背景顏色', + 'Clear format': '清除格式', + 'Align left': '左對齊', + 'Align center': '居中對齊', + 'Align right': '右對齊', + 'Justify win width': '兩端對齊', + 'Text direction': '文本方向', + 'Header style': '標題樣式', + 'Numbered list': '有序列表', + 'Bullet list': '無序列表', + 'Checked list': '任務列表', + 'Code block': '代碼塊', + 'Quote': '引言', + 'Increase indent': '增加縮進', + 'Decrease indent': '減少縮進', + 'Insert URL': '插入鏈接', + 'Visit link': '訪問鏈接', + 'Enter link': '輸入鏈接', + 'Enter media': '輸入媒體', + 'Edit': '編輯', + 'Apply': '應用', }, 'jp': { 'Paste a link': 'リンクをペースト', @@ -498,39 +564,39 @@ extension Localization on String { 'Next': '次へ', 'Camera': 'カメラ', 'Video': 'ビデオ', - 'Undo': 'Undo', - 'Redo': 'Redo', - 'Font family': 'Font family', - 'Font size': 'Font size', - 'Bold': 'Bold', - 'Subscript': 'Subscript', - 'Superscript': 'Superscript', - 'Italic': 'Italic', - 'Underline': 'Underline', - 'Strike through': 'Strike through', - 'Inline code': 'Inline code', - 'Font color': 'Font color', - 'Background color': 'Background color', - 'Clear format': 'Clear format', - 'Align left': 'Align left', - 'Align center': 'Align center', - 'Align right': 'Align right', - 'Justify win width': 'Justify win width', - 'Text direction': 'Text direction', - 'Header style': 'Header style', - 'Numbered list': 'Numbered list', - 'Bullet list': 'Bullet list', - 'Checked list': 'Checked list', - 'Code block': 'Code block', - 'Quote': 'Quote', - 'Increase indent': 'Increase indent', - 'Decrease indent': 'Decrease indent', - 'Insert URL': 'Insert URL', - 'Visit link': 'Visit link', - 'Enter link': 'Enter link', - 'Enter media': 'Enter media', - 'Edit': 'Edit', - 'Apply': 'Apply', + 'Undo': '取り消し', + 'Redo': 'やり直し', + 'Font family': 'フォントファミリー', + 'Font size': 'フォントサイズ', + 'Bold': '太字', + 'Subscript': '下付き', + 'Superscript': '上付き', + 'Italic': '斜体', + 'Underline': '下線', + 'Strike through': '取り消し線', + 'Inline code': 'インラインコード', + 'Font color': 'フォントカラー', + 'Background color': 'ベースカラー', + 'Clear format': 'クリアフォーマット', + 'Align left': '左揃え', + 'Align center': 'センターアライメント', + 'Align right': '右揃え', + 'Justify win width': '両端揃え', + 'Text direction': '文字方向', + 'Header style': 'タイトルスタイル', + 'Numbered list': '順序付きリスト', + 'Bullet list': '順序無しリスト', + 'Checked list': 'チェックボックス', + 'Code block': 'コード', + 'Quote': '引用', + 'Increase indent': 'インデントを増やす', + 'Decrease indent': 'インデントを減らす', + 'Insert URL': 'ハイパーリンクを挿入', + 'Visit link': 'ハイパーリンクを訪問', + 'Enter link': 'ハイパーリンクを輸入', + 'Enter media': 'ミディアムを輸入', + 'Edit': '編集', + 'Apply': '応用', }, 'ko': { 'Paste a link': '링크를 붙여넣어 주세요.', @@ -1527,72 +1593,6 @@ extension Localization on String { 'Edit': 'Edit', 'Apply': 'Apply', }, - 'zh_hk': { - 'Paste a link': '貼上連結', - 'Ok': '確定', - 'Select Color': '選擇顏色', - 'Gallery': '圖片庫', - 'Link': '連結', - 'Please first select some text to transform into a link.': - '請選擇文字以轉換為連結。', - 'Open': '開啓', - 'Copy': '複製', - 'Remove': '移除', - 'Save': '儲存', - 'Zoom': '放大', - 'Saved': '已儲存', - 'Text': '文字', - 'What is entered is not a link': '輸入的不是連結', - 'Resize': '變更大小', - 'Width': '寛', - 'Height': '高', - 'Size': '大小', - 'Small': '小', - 'Large': '大', - 'Huge': '超大', - 'Clear': '清除', - 'Font': '字型', - 'Search': '搜尋', - 'matches': '符合', - 'showing match': '顯示符合', - 'Prev': '上一個', - 'Next': '下一個', - 'Camera': '相機', - 'Video': '錄影', - 'Undo': 'Undo', - 'Redo': 'Redo', - 'Font family': 'Font family', - 'Font size': 'Font size', - 'Bold': 'Bold', - 'Subscript': 'Subscript', - 'Superscript': 'Superscript', - 'Italic': 'Italic', - 'Underline': 'Underline', - 'Strike through': 'Strike through', - 'Inline code': 'Inline code', - 'Font color': 'Font color', - 'Background color': 'Background color', - 'Clear format': 'Clear format', - 'Align left': 'Align left', - 'Align center': 'Align center', - 'Align right': 'Align right', - 'Justify win width': 'Justify win width', - 'Text direction': 'Text direction', - 'Header style': 'Header style', - 'Numbered list': 'Numbered list', - 'Bullet list': 'Bullet list', - 'Checked list': 'Checked list', - 'Code block': 'Code block', - 'Quote': 'Quote', - 'Increase indent': 'Increase indent', - 'Decrease indent': 'Decrease indent', - 'Insert URL': 'Insert URL', - 'Visit link': 'Visit link', - 'Enter link': 'Enter link', - 'Enter media': 'Enter media', - 'Edit': 'Edit', - 'Apply': 'Apply', - }, 'sr': { 'Paste a link': 'Nalepi vezu', 'Ok': 'OK', From d4aaea239f5827307181647fcbf1fd4d5357edd2 Mon Sep 17 00:00:00 2001 From: jiangchong Date: Fri, 5 May 2023 13:17:26 +0800 Subject: [PATCH 161/204] Add node params to custom recognizer function (#1199) --- lib/src/widgets/delegate.dart | 2 +- lib/src/widgets/text_line.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index 41ae8762..de92d8eb 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -15,7 +15,7 @@ typedef EmbedsBuilder = EmbedBuilder Function(Embed node); typedef CustomStyleBuilder = TextStyle Function(Attribute attribute); typedef CustomRecognizerBuilder = GestureRecognizer? Function( - Attribute attribute); + Attribute attribute, Leaf leaf); /// Delegate interface for the [EditorTextSelectionGestureDetectorBuilder]. /// diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 5e83837b..f4dbc899 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -420,7 +420,7 @@ class _TextLineState extends State { final nodeStyle = textNode.style; nodeStyle.attributes.forEach((key, value) { - final recognizer = widget.customRecognizerBuilder!.call(value); + final recognizer = widget.customRecognizerBuilder!.call(value, segment); if (recognizer != null) { _linkRecognizers[segment] = recognizer; return; From 6614030ae571e63ff2bec9f64ba0630eae006c9f Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Sun, 7 May 2023 16:34:21 +0100 Subject: [PATCH 162/204] Fix direction of toolbar dividers (#1202) --- CHANGELOG.md | 3 +++ flutter_quill_extensions/pubspec.yaml | 2 +- lib/src/widgets/toolbar.dart | 25 ++++++++++--------------- pubspec.yaml | 2 +- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e97089e9..76c289ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.15] +- Fixed a bug introduced in 7.1.7 where each section in `QuillToolbar` was displayed on its own line. + # [7.1.14] - Add indents change for multiline selection. diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 12ab6b52..a2f9129a 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.12 + flutter_quill: ^7.1.15 image_picker: ^0.8.5+3 photo_view: ^0.14.0 diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index e4da8149..c8a534c6 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -409,7 +409,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - AxisDivider(axis, + _AxisDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showAlignmentButtons) SelectAlignmentButton( @@ -445,7 +445,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - AxisDivider(axis, + _AxisDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showHeaderStyle) SelectHeaderStyleButton( @@ -462,7 +462,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - AxisDivider(axis, + _AxisDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showListNumbers) ToggleStyleButton( @@ -507,7 +507,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDividers && isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5])) - AxisDivider(axis, + _AxisDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showQuote) ToggleStyleButton( @@ -540,7 +540,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { afterButtonPressed: afterButtonPressed, ), if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) - AxisDivider(axis, + _AxisDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showLink) LinkStyleButton( @@ -563,7 +563,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ), if (customButtons.isNotEmpty) if (showDividers) - AxisDivider(axis, + _AxisDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), for (var customButton in customButtons) QuillIconButton( @@ -643,27 +643,22 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { } } -class AxisDivider extends StatelessWidget { - const AxisDivider( +class _AxisDivider extends StatelessWidget { + const _AxisDivider( this.axis, { Key? key, this.color, this.space, }) : super(key: key); - const AxisDivider.horizontal({Color? color, double? space}) - : this(Axis.horizontal, color: color, space: space); - - const AxisDivider.vertical({Color? color, double? space}) - : this(Axis.vertical, color: color, space: space); - final Axis axis; final Color? color; final double? space; @override Widget build(BuildContext context) { - return axis == Axis.horizontal + // Vertical toolbar requires horizontal divider, and vice versa + return axis == Axis.vertical ? Divider( height: space, color: color, diff --git a/pubspec.yaml b/pubspec.yaml index 43a4ec74..b76d6bd0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.14 +version: 7.1.15 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From ccdb88fb9acac91b51f187c9f35f5c1da6f8d1f7 Mon Sep 17 00:00:00 2001 From: jiangchong Date: Mon, 8 May 2023 18:46:08 +0800 Subject: [PATCH 163/204] Fix subscript (#1204) --- CHANGELOG.md | 3 +++ flutter_quill_extensions/pubspec.yaml | 2 +- lib/src/models/documents/attribute.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c289ec..f51ff476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.16] +- Fixed subscript key from 'sup' to 'sub'. + # [7.1.15] - Fixed a bug introduced in 7.1.7 where each section in `QuillToolbar` was displayed on its own line. diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index a2f9129a..7a04bfde 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.15 + flutter_quill: ^7.1.16 image_picker: ^0.8.5+3 photo_view: ^0.14.0 diff --git a/lib/src/models/documents/attribute.dart b/lib/src/models/documents/attribute.dart index 6c48e59e..c8d40360 100644 --- a/lib/src/models/documents/attribute.dart +++ b/lib/src/models/documents/attribute.dart @@ -373,7 +373,7 @@ class ScriptAttribute extends Attribute { enum ScriptAttributes { sup('super'), - sub('sup'); + sub('sub'); const ScriptAttributes(this.value); diff --git a/pubspec.yaml b/pubspec.yaml index b76d6bd0..c4b0e341 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.15 +version: 7.1.16 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From aab7ee8f6173e616d3c3221e1f0332e7646dc6f9 Mon Sep 17 00:00:00 2001 From: MacDeveloper1 <130981115+MacDeveloper1@users.noreply.github.com> Date: Mon, 8 May 2023 17:14:40 +0200 Subject: [PATCH 164/204] Rename `_AxisDivider` to `QuillDivider` and make it public (#1205) --- lib/src/widgets/toolbar.dart | 33 +++++++++++++++++++++++++-------- pubspec.yaml | 2 +- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index c8a534c6..f1cb3d96 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -409,7 +409,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _AxisDivider(axis, + QuillDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showAlignmentButtons) SelectAlignmentButton( @@ -445,7 +445,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _AxisDivider(axis, + QuillDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showHeaderStyle) SelectHeaderStyleButton( @@ -462,7 +462,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _AxisDivider(axis, + QuillDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showListNumbers) ToggleStyleButton( @@ -507,7 +507,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDividers && isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5])) - _AxisDivider(axis, + QuillDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showQuote) ToggleStyleButton( @@ -540,7 +540,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { afterButtonPressed: afterButtonPressed, ), if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) - _AxisDivider(axis, + QuillDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), if (showLink) LinkStyleButton( @@ -563,7 +563,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ), if (customButtons.isNotEmpty) if (showDividers) - _AxisDivider(axis, + QuillDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), for (var customButton in customButtons) QuillIconButton( @@ -643,16 +643,33 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { } } -class _AxisDivider extends StatelessWidget { - const _AxisDivider( +/// The divider which is used for separation of buttons in the toolbar. +/// +/// It can be used outside of this package, for example when user does not use +/// [QuillToolbar.basic] and compose toolbat's children on its own. +class QuillDivider extends StatelessWidget { + const QuillDivider( this.axis, { Key? key, this.color, this.space, }) : super(key: key); + /// Provides a horizonal divider for vertical toolbar. + const QuillDivider.horizontal({Color? color, double? space}) + : this(Axis.horizontal, color: color, space: space); + + /// Provides a horizonal divider for horizontal toolbar. + const QuillDivider.vertical({Color? color, double? space}) + : this(Axis.vertical, color: color, space: space); + + /// The axis along which the toolbar is. final Axis axis; + + /// The color to use when painting this divider's line. final Color? color; + + /// The divider's space (width or height) depending of [axis]. final double? space; @override diff --git a/pubspec.yaml b/pubspec.yaml index c4b0e341..324dbc80 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.16 +version: 7.1.16+1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 1149d03199fd25e27e54ad6555319d5eee9ad6b6 Mon Sep 17 00:00:00 2001 From: shenjingfs Date: Tue, 9 May 2023 21:22:46 +0800 Subject: [PATCH 165/204] Fix custom text style of lists not work (#1207) --- lib/src/widgets/text_line.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index f4dbc899..f38cbebf 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -282,7 +282,7 @@ class _TextLineState extends State { toMerge = defaultStyles.quote!.style; } else if (block == Attribute.codeBlock) { toMerge = defaultStyles.code!.style; - } else if (block == Attribute.list) { + } else if (block?.key == Attribute.list.key) { toMerge = defaultStyles.lists!.style; } From 8758c2d9529c5601bd66ac6466f42f6d5dad8612 Mon Sep 17 00:00:00 2001 From: francksoudan <112876604+francksoudan@users.noreply.github.com> Date: Wed, 10 May 2023 16:03:35 +0200 Subject: [PATCH 166/204] 7.1.17 release: updates dependencies (#1208) --- CHANGELOG.md | 3 +++ flutter_quill_extensions/pubspec.yaml | 2 +- pubspec.yaml | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f51ff476..46b9b977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.17] +- Updates `device_info_plus` to version 9.0.0 to benefit from AGP 8 (see [changelog#900](https://pub.dev/packages/device_info_plus/changelog#900)). + # [7.1.16] - Fixed subscript key from 'sup' to 'sub'. diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 7a04bfde..f03c97c7 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.16 + flutter_quill: ^7.1.17 image_picker: ^0.8.5+3 photo_view: ^0.14.0 diff --git a/pubspec.yaml b/pubspec.yaml index 324dbc80..0a369e47 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.16+1 +version: 7.1.17+1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill @@ -21,7 +21,7 @@ dependencies: characters: ^1.2.1 diff_match_patch: ^0.4.1 i18n_extension: ^8.0.0 - device_info_plus: ^8.1.0 + device_info_plus: ^9.0.0 platform: ^3.1.0 pasteboard: ^0.2.0 From 100566104847bff564d1cdb8e37073370e1f6e4b Mon Sep 17 00:00:00 2001 From: Mmisiek Date: Thu, 11 May 2023 07:35:57 -0700 Subject: [PATCH 167/204] Fixed issues from last Flutter update. (#1210) --- lib/src/widgets/delegate.dart | 3 ++- lib/src/widgets/raw_editor.dart | 2 ++ lib/src/widgets/text_selection.dart | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index de92d8eb..988473bd 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -312,7 +312,8 @@ class EditorTextSelectionGestureDetectorBuilder { /// which triggers this callback./lib/src/material/text_field.dart @protected void onDragSelectionUpdate( - DragStartDetails startDetails, DragUpdateDetails updateDetails) { + //DragStartDetails startDetails, + DragUpdateDetails updateDetails) { renderEditor!.extendSelection(updateDetails.globalPosition, cause: SelectionChangedCause.drag); } diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 89831d96..7d60a051 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -325,6 +325,8 @@ class RawEditorState extends EditorState TextDirection get _textDirection => Directionality.of(context); + void insertContent(KeyboardInsertedContent content) {} + /// Returns the [ContextMenuButtonItem]s representing the buttons in this /// platform's default selection menu for [RawEditor]. /// diff --git a/lib/src/widgets/text_selection.dart b/lib/src/widgets/text_selection.dart index ad1398e0..688505e2 100644 --- a/lib/src/widgets/text_selection.dart +++ b/lib/src/widgets/text_selection.dart @@ -713,7 +713,7 @@ class EditorTextSelectionGestureDetector extends StatefulWidget { /// The frequency of calls is throttled to avoid excessive text layout /// operations in text fields. The throttling is controlled by the constant /// [_kDragSelectionUpdateThrottle]. - final DragSelectionUpdateCallback? onDragSelectionUpdate; + final GestureDragUpdateCallback? onDragSelectionUpdate; /// Called when a mouse that was previously dragging is released. final GestureDragEndCallback? onDragSelectionEnd; @@ -857,7 +857,8 @@ class _EditorTextSelectionGestureDetectorState assert(_lastDragUpdateDetails != null); if (widget.onDragSelectionUpdate != null) { widget.onDragSelectionUpdate!( - _lastDragStartDetails!, _lastDragUpdateDetails!); + //_lastDragStartDetails!, + _lastDragUpdateDetails!); } _dragUpdateThrottleTimer = null; _lastDragUpdateDetails = null; From 0a3fe3f63c4636c68de1e44523eae3b9de787dcb Mon Sep 17 00:00:00 2001 From: Cheryl Date: Thu, 11 May 2023 12:51:46 -0700 Subject: [PATCH 168/204] Upgrade to 7.1.18 --- CHANGELOG.md | 5 ++++- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b9b977..a688982f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -# [7.1.17] +# [7.1.18] +- Support flutter latest version. + +# [7.1.17+1] - Updates `device_info_plus` to version 9.0.0 to benefit from AGP 8 (see [changelog#900](https://pub.dev/packages/device_info_plus/changelog#900)). # [7.1.16] diff --git a/pubspec.yaml b/pubspec.yaml index 0a369e47..88c1e2b3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.17+1 +version: 7.1.18 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 3b43baf229e4db8fe72bb7a7faa82773c8cca5b0 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Thu, 11 May 2023 12:53:31 -0700 Subject: [PATCH 169/204] Fix analysis error --- lib/src/widgets/raw_editor.dart | 3 ++- lib/src/widgets/text_block.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 7d60a051..b80aa1b2 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -325,6 +325,7 @@ class RawEditorState extends EditorState TextDirection get _textDirection => Directionality.of(context); + @override void insertContent(KeyboardInsertedContent content) {} /// Returns the [ContextMenuButtonItem]s representing the buttons in this @@ -1711,7 +1712,7 @@ class RawEditorState extends EditorState } class _Editor extends MultiChildRenderObjectWidget { - _Editor({ + const _Editor({ required Key key, required List children, required this.document, diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index b61ad9cf..f30ccea0 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -596,7 +596,7 @@ class RenderEditableTextBlock extends RenderEditableContainerBox } class _EditableBlock extends MultiChildRenderObjectWidget { - _EditableBlock( + const _EditableBlock( {required this.block, required this.textDirection, required this.padding, diff --git a/pubspec.yaml b/pubspec.yaml index 88c1e2b3..479b2115 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.17.0 <4.0.0" flutter: ">=3.0.0" dependencies: From c35170dcd45a4976237c3f2136a5c4e51049e240 Mon Sep 17 00:00:00 2001 From: Jon Salmon <26483285+Jon-Salmon@users.noreply.github.com> Date: Sat, 13 May 2023 00:38:15 +0100 Subject: [PATCH 170/204] Allow use of latest i18n dependencies (#1215) --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 479b2115..4c4b023a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: pedantic: ^1.11.1 characters: ^1.2.1 diff_match_patch: ^0.4.1 - i18n_extension: ^8.0.0 + i18n_extension: ">=8.0.0 <10.0.0" device_info_plus: ^9.0.0 platform: ^3.1.0 pasteboard: ^0.2.0 From e06c3f32f66193c533dd4c0c966bb4ffe6e37f5e Mon Sep 17 00:00:00 2001 From: rodrigo-itao <66178504+rodrigo-itao@users.noreply.github.com> Date: Wed, 17 May 2023 13:42:34 -0300 Subject: [PATCH 171/204] Update pubspec.yaml (#1221) --- flutter_quill_extensions/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index f03c97c7..2c132290 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: video_player: ^2.4.2 youtube_player_flutter: ^8.1.1 gallery_saver: ^2.3.2 - math_keyboard: ^0.1.8 + math_keyboard: ^0.2.0 string_validator: ^1.0.0 universal_html: ^2.2.1 url_launcher: ^6.1.9 From 5ae6e824fa677d0d6ba5dac9035c803125bb5390 Mon Sep 17 00:00:00 2001 From: Firas Abd Alrahman Date: Wed, 17 May 2023 21:05:22 +0300 Subject: [PATCH 172/204] Fix Rtl leading alignment problem (#1222) --- lib/src/widgets/raw_editor.dart | 2 +- lib/src/widgets/text_line.dart | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index b80aa1b2..444c49dd 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -891,7 +891,7 @@ class RawEditorState extends EditorState final editableTextBlock = EditableTextBlock( block: node, controller: controller, - textDirection: _textDirection, + textDirection: getDirectionOfNode(node), scrollBottomInset: widget.scrollBottomInset, verticalSpacing: _getVerticalSpacingForBlock(node, _styles), textSelection: controller.selection, diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index f38cbebf..467fddb2 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -1082,9 +1082,16 @@ class RenderEditableTextLine extends RenderEditableBox { @override void paint(PaintingContext context, Offset offset) { if (_leading != null) { - final parentData = _leading!.parentData as BoxParentData; - final effectiveOffset = offset + parentData.offset; - context.paintChild(_leading!, effectiveOffset); + if (textDirection == TextDirection.ltr) { + final parentData = _leading!.parentData as BoxParentData; + final effectiveOffset = offset + parentData.offset; + context.paintChild(_leading!, effectiveOffset); + } else { + final parentData = _leading!.parentData as BoxParentData; + final effectiveOffset = offset + parentData.offset; + context.paintChild(_leading!, + Offset(size.width - _leading!.size.width, effectiveOffset.dy)); + } } if (_body != null) { From 5cd0785ce4ce976cc7fc283cd68664b9c4069938 Mon Sep 17 00:00:00 2001 From: Jonathan Salmon Date: Thu, 18 May 2023 02:23:17 +0100 Subject: [PATCH 173/204] flutter_quill_extensions release 0.3.2 --- flutter_quill_extensions/CHANGELOG.md | 3 +++ flutter_quill_extensions/pubspec.yaml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index b741b21a..3bb1abe4 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.3.2 +* Updated dependencies to support intl 0.18 + ## 0.3.1 * Image embedding tweaks * Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working. diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 2c132290..a5f88c0d 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: 0.3.1 +version: 0.3.2 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions @@ -19,7 +19,7 @@ dependencies: video_player: ^2.4.2 youtube_player_flutter: ^8.1.1 gallery_saver: ^2.3.2 - math_keyboard: ^0.2.0 + math_keyboard: ">=0.1.8 <0.3.0" string_validator: ^1.0.0 universal_html: ^2.2.1 url_launcher: ^6.1.9 From 140e77bedf1fe79c795cc2a6d4cb12740229f7f9 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Wed, 17 May 2023 19:00:51 -0700 Subject: [PATCH 174/204] Upgrade to 7.1.19 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a688982f..9f3c96b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.19] +- Fix Rtl leading alignment problem. + # [7.1.18] - Support flutter latest version. diff --git a/pubspec.yaml b/pubspec.yaml index 4c4b023a..1122854b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.18 +version: 7.1.19 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 494be80045b5a5d8dbcc15d19212337a6c5e4ca3 Mon Sep 17 00:00:00 2001 From: Jonathan Salmon Date: Thu, 18 May 2023 03:27:36 +0100 Subject: [PATCH 175/204] Updated supported dart versions in flutter_quill_extensions --- flutter_quill_extensions/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index a5f88c0d..c58b0d24 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.17.0 <4.0.0" flutter: ">=3.0.0" dependencies: From 99d60bc1b7da2a2bdc904c6921348959e8d7e780 Mon Sep 17 00:00:00 2001 From: Abdurrahman Adel <47070231+Abdurrahman98XX@users.noreply.github.com> Date: Sun, 21 May 2023 12:14:29 +0300 Subject: [PATCH 176/204] Update Arabic language of the toolbar.i18n.dart (#1225) --- lib/src/translations/toolbar.i18n.dart | 110 +++++++++++++------------ 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 5eb2b344..77c0bd67 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -139,67 +139,69 @@ extension Localization on String { 'Paste a link': 'نسخ الرابط', 'Ok': 'نعم', 'Select Color': 'اختار اللون', - 'Gallery': 'الصور', + 'Gallery': 'المعرض', 'Link': 'الرابط', 'Please first select some text to transform into a link.': 'يرجى اختيار نص للتحويل إلى رابط', 'Open': 'فتح', - 'Copy': 'ينسخ', + 'Copy': 'نسخ', 'Remove': 'إزالة', - 'Save': 'يحفظ', + 'Save': 'حفظ', 'Zoom': 'تكبير', - 'Saved': 'أنقذ', - 'Text': 'Text', - 'What is entered is not a link': 'What is entered is not a link', - 'Resize': 'Resize', - 'Width': 'Width', - 'Height': 'Height', - 'Size': 'Size', - 'Small': 'Small', - 'Large': 'Large', - 'Huge': 'Huge', - 'Clear': 'Clear', - 'Font': 'Font', - 'Search': 'Search', - 'matches': 'matches', - 'showing match': 'showing match', - 'Prev': 'Prev', - 'Next': 'Next', - 'Camera': 'Camera', - 'Video': 'Video', - 'Undo': 'Undo', - 'Redo': 'Redo', - 'Font family': 'Font family', - 'Font size': 'Font size', - 'Bold': 'Bold', - 'Subscript': 'Subscript', - 'Superscript': 'Superscript', - 'Italic': 'Italic', - 'Underline': 'Underline', - 'Strike through': 'Strike through', - 'Inline code': 'Inline code', - 'Font color': 'Font color', - 'Background color': 'Background color', - 'Clear format': 'Clear format', - 'Align left': 'Align left', - 'Align center': 'Align center', - 'Align right': 'Align right', + 'Saved': 'تم الحفظ', + 'Text': 'نص', + 'What is entered is not a link': 'ما تم ادخاله ليس رابط', + 'Resize': 'تحجيم', + 'Width': 'عرض', + 'Height': 'ارتفاع', + 'Size': 'حجم', + 'Small': 'صغير', + 'Large': 'كبير', + 'Huge': 'ضخم', + 'Clear': 'تنظيف', + 'Font': 'خط', + 'Search': 'بحث', + 'matches': 'تطابق', + 'showing match': 'عرض التطابق', + 'Prev': 'سابق', + 'Next': 'تالي', + 'Camera': 'كاميرا', + 'Video': 'فيديو', + 'Undo': 'تراجع', + 'Redo': 'تقدم', + 'Font family': 'عائلة الخط', + 'Font size': 'حجم الخط', + 'Bold': 'عريض', + 'Subscript': 'نص سفلي', + 'Superscript': 'نص علوي', + 'Italic': 'مائل', + 'Underline': 'تحته خط', + 'Strike through': 'داخله خط', + 'Inline code': 'كود بوسط السطر', + 'Font color': 'لون الخط', + 'Background color': 'لون الخلفية', + 'Clear format': 'تنظيف التنسيق', + 'Align left': 'محاذاة اليسار', + 'Align center': 'محاذاة الوسط', + 'Align right': 'محاذاة اليمين', + // i think it should be 'Justify with width' + // it is wrong in all properties 'Justify win width': 'Justify win width', - 'Text direction': 'Text direction', - 'Header style': 'Header style', - 'Numbered list': 'Numbered list', - 'Bullet list': 'Bullet list', - 'Checked list': 'Checked list', - 'Code block': 'Code block', - 'Quote': 'Quote', - 'Increase indent': 'Increase indent', - 'Decrease indent': 'Decrease indent', - 'Insert URL': 'Insert URL', - 'Visit link': 'Visit link', - 'Enter link': 'Enter link', - 'Enter media': 'Enter media', - 'Edit': 'Edit', - 'Apply': 'Apply', + 'Text direction': 'اتجاه النص', + 'Header style': 'ستايل العنوان', + 'Numbered list': 'قائمة مرقمة', + 'Bullet list': 'قائمة منقطة', + 'Checked list': 'قائمة للمهام', + 'Code block': 'كود كامل', + 'Quote': 'اقتباس', + 'Increase indent': 'زيادة الهامش', + 'Decrease indent': 'تنقيص الهامش', + 'Insert URL': 'ادخل عنوان رابط', + 'Visit link': 'زيارة الرابط', + 'Enter link': 'ادخل رابط', + 'Enter media': 'ادخل وسائط', + 'Edit': 'تعديل', + 'Apply': 'تطبيق', }, 'da': { 'Paste a link': 'Indsæt link', From f56d5c36dc93d3f92d301b902674ea0d6024bd88 Mon Sep 17 00:00:00 2001 From: Firas Abd Alrahman Date: Wed, 24 May 2023 03:46:56 +0300 Subject: [PATCH 177/204] pass linestyle to embeded block (#1230) --- lib/src/widgets/embeds.dart | 1 + lib/src/widgets/text_line.dart | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/src/widgets/embeds.dart b/lib/src/widgets/embeds.dart index 8d72cd5b..565ee778 100644 --- a/lib/src/widgets/embeds.dart +++ b/lib/src/widgets/embeds.dart @@ -21,6 +21,7 @@ abstract class EmbedBuilder { leaf.Embed node, bool readOnly, bool inline, + TextStyle textStyle, ); } diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 467fddb2..ed77f79e 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -148,15 +148,10 @@ class _TextLineState extends State { final embedBuilder = widget.embedBuilder(embed); if (embedBuilder.expanded) { // Creates correct node for custom embed - + final lineStyle = _getLineStyle(widget.styles); return EmbedProxy( - embedBuilder.build( - context, - widget.controller, - embed, - widget.readOnly, - false, - ), + embedBuilder.build(context, widget.controller, embed, widget.readOnly, + false, lineStyle), ); } } @@ -208,6 +203,7 @@ class _TextLineState extends State { child, widget.readOnly, true, + lineStyle, ), ); final embed = embedBuilder.buildWidgetSpan(embedWidget); From a58ca0abbbc7dbfe012a7e786efc50536610d5c9 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Tue, 23 May 2023 18:00:39 -0700 Subject: [PATCH 178/204] Upgrade to 7.1.20 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3c96b0..a6b78d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.1.20] +- Pass linestyle to embedded block. + # [7.1.19] - Fix Rtl leading alignment problem. diff --git a/pubspec.yaml b/pubspec.yaml index 1122854b..e1562d97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.19 +version: 7.1.20 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 6c6217343d36c13fe9c9704b293d2848d742d999 Mon Sep 17 00:00:00 2001 From: Firas Abd Alrahman Date: Wed, 24 May 2023 07:39:19 +0300 Subject: [PATCH 179/204] keep style when creating embedded element (#1232) --- lib/src/widgets/text_line.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index ed77f79e..9358777a 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -193,7 +193,8 @@ class _TextLineState extends State { } // Creates correct node for custom embed if (child.value.type == BlockEmbed.customType) { - child = Embed(CustomBlockEmbed.fromJsonString(child.value.data)); + child = Embed(CustomBlockEmbed.fromJsonString(child.value.data)) + ..applyStyle(child.style); } final embedBuilder = widget.embedBuilder(child); final embedWidget = EmbedProxy( From fb890017695f4d4e40c4ae5d43bf4093bf6664b0 Mon Sep 17 00:00:00 2001 From: Adil Hanney Date: Wed, 24 May 2023 14:55:49 +0100 Subject: [PATCH 180/204] Scale leading widgets based on paragraph font size (#1226) --- CHANGELOG.md | 3 ++ example/windows/runner/Runner.rc | 10 ++--- flutter_quill_extensions/pubspec.yaml | 2 +- .../widgets/style_widgets/bullet_point.dart | 4 +- lib/src/widgets/text_block.dart | 41 +++++++++++-------- pubspec.yaml | 2 +- 6 files changed, 36 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b78d0f..ffbb02ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.0] +- Checkboxes, bullet points, and number points are now scaled based on the default paragraph font size. + # [7.1.20] - Pass linestyle to embedded block. diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc index 3ac0062f..a922e84e 100644 --- a/example/windows/runner/Runner.rc +++ b/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index c58b0d24..d57cd8bf 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: video_player: ^2.4.2 youtube_player_flutter: ^8.1.1 gallery_saver: ^2.3.2 - math_keyboard: ">=0.1.8 <0.3.0" + math_keyboard: ^0.2.0 string_validator: ^1.0.0 universal_html: ^2.2.1 url_launcher: ^6.1.9 diff --git a/lib/src/widgets/style_widgets/bullet_point.dart b/lib/src/widgets/style_widgets/bullet_point.dart index ee33c93a..8b5fce70 100644 --- a/lib/src/widgets/style_widgets/bullet_point.dart +++ b/lib/src/widgets/style_widgets/bullet_point.dart @@ -4,18 +4,20 @@ class QuillBulletPoint extends StatelessWidget { const QuillBulletPoint({ required this.style, required this.width, + this.padding = 0, Key? key, }) : super(key: key); final TextStyle style; final double width; + final double padding; @override Widget build(BuildContext context) { return Container( alignment: AlignmentDirectional.topEnd, width: width, - padding: const EdgeInsetsDirectional.only(end: 13), + padding: EdgeInsetsDirectional.only(end: padding), child: Text('•', style: style), ); } diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index f30ccea0..4adaac37 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -152,7 +152,7 @@ class EditableTextBlock extends StatelessWidget { onLaunchUrl: onLaunchUrl, customLinkPrefixes: customLinkPrefixes, ), - _getIndentWidth(), + _getIndentWidth(context), _getSpacingForLine(line, index, count, defaultStyles), textDirection, textSelection, @@ -170,45 +170,48 @@ class EditableTextBlock extends StatelessWidget { Widget? _buildLeading(BuildContext context, Line line, int index, Map indentLevelCounts, int count) { - final defaultStyles = QuillStyles.getStyles(context, false); + final defaultStyles = QuillStyles.getStyles(context, false)!; + final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; final attrs = line.style.attributes; + if (attrs[Attribute.list.key] == Attribute.ol) { return QuillNumberPoint( index: index, indentLevelCounts: indentLevelCounts, count: count, - style: defaultStyles!.leading!.style, + style: defaultStyles.leading!.style, attrs: attrs, - width: 32, - padding: 8, + width: fontSize * 2, + padding: fontSize / 2, ); } if (attrs[Attribute.list.key] == Attribute.ul) { return QuillBulletPoint( style: - defaultStyles!.leading!.style.copyWith(fontWeight: FontWeight.bold), - width: 32, + defaultStyles.leading!.style.copyWith(fontWeight: FontWeight.bold), + width: fontSize * 2, + padding: fontSize / 2, ); } if (attrs[Attribute.list.key] == Attribute.checked) { return CheckboxPoint( - size: 14, + size: fontSize, value: true, enabled: !readOnly, onChanged: (checked) => onCheckboxTap(line.documentOffset, checked), - uiBuilder: defaultStyles?.lists?.checkboxUIBuilder, + uiBuilder: defaultStyles.lists?.checkboxUIBuilder, ); } if (attrs[Attribute.list.key] == Attribute.unchecked) { return CheckboxPoint( - size: 14, + size: fontSize, value: false, enabled: !readOnly, onChanged: (checked) => onCheckboxTap(line.documentOffset, checked), - uiBuilder: defaultStyles?.lists?.checkboxUIBuilder, + uiBuilder: defaultStyles.lists?.checkboxUIBuilder, ); } @@ -217,35 +220,37 @@ class EditableTextBlock extends StatelessWidget { index: index, indentLevelCounts: indentLevelCounts, count: count, - style: defaultStyles!.code!.style + style: defaultStyles.code!.style .copyWith(color: defaultStyles.code!.style.color!.withOpacity(0.4)), - width: 32, + width: fontSize * 2, attrs: attrs, - padding: 16, + padding: fontSize, withDot: false, ); } return null; } - double _getIndentWidth() { + double _getIndentWidth(BuildContext context) { + final defaultStyles = QuillStyles.getStyles(context, false)!; + final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; final attrs = block.style.attributes; final indent = attrs[Attribute.indent.key]; var extraIndent = 0.0; if (indent != null && indent.value != null) { - extraIndent = 16.0 * indent.value; + extraIndent = fontSize * indent.value; } if (attrs.containsKey(Attribute.blockQuote.key)) { - return 16.0 + extraIndent; + return fontSize + extraIndent; } var baseIndent = 0.0; if (attrs.containsKey(Attribute.list.key) || attrs.containsKey(Attribute.codeBlock.key)) { - baseIndent = 32.0; + baseIndent = fontSize * 2; } return baseIndent + extraIndent; diff --git a/pubspec.yaml b/pubspec.yaml index e1562d97..d2ab913a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.1.20 +version: 7.2.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From df3afd3f37bf3e1b7124fb2b8f7292f7d8e6bdc0 Mon Sep 17 00:00:00 2001 From: MacDeveloper1 <130981115+MacDeveloper1@users.noreply.github.com> Date: Wed, 24 May 2023 15:56:19 +0200 Subject: [PATCH 181/204] Fix PR #1230 (#1233) --- example/lib/pages/home_page.dart | 1 + example/lib/universal_ui/universal_ui.dart | 2 ++ flutter_quill_extensions/CHANGELOG.md | 3 +++ flutter_quill_extensions/lib/embeds/builders.dart | 4 ++++ flutter_quill_extensions/pubspec.yaml | 4 ++-- 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 40449aeb..11baf428 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -502,6 +502,7 @@ class NotesEmbedBuilder extends EmbedBuilder { Embed node, bool readOnly, bool inline, + TextStyle textStyle, ) { final notes = NotesBlockEmbed(node.value.data).document; diff --git a/example/lib/universal_ui/universal_ui.dart b/example/lib/universal_ui/universal_ui.dart index 7b35f1f1..91344afb 100644 --- a/example/lib/universal_ui/universal_ui.dart +++ b/example/lib/universal_ui/universal_ui.dart @@ -38,6 +38,7 @@ class ImageEmbedBuilderWeb extends EmbedBuilder { Embed node, bool readOnly, bool inline, + TextStyle textStyle, ) { final imageUrl = node.value.data; if (isImageBase64(imageUrl)) { @@ -80,6 +81,7 @@ class VideoEmbedBuilderWeb extends EmbedBuilder { Embed node, bool readOnly, bool inline, + TextStyle textStyle, ) { var videoUrl = node.value.data; if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index 3bb1abe4..176876c7 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.3.3 +* Fix a prototype bug which was bring by [PR #1230](https://github.com/singerdmx/flutter-quill/pull/1230#issuecomment-1560597099) + ## 0.3.2 * Updated dependencies to support intl 0.18 diff --git a/flutter_quill_extensions/lib/embeds/builders.dart b/flutter_quill_extensions/lib/embeds/builders.dart index cf04a462..b16b5845 100644 --- a/flutter_quill_extensions/lib/embeds/builders.dart +++ b/flutter_quill_extensions/lib/embeds/builders.dart @@ -28,6 +28,7 @@ class ImageEmbedBuilder extends EmbedBuilder { base.Embed node, bool readOnly, bool inline, + TextStyle textStyle, ) { assert(!kIsWeb, 'Please provide image EmbedBuilder for Web'); @@ -164,6 +165,7 @@ class ImageEmbedBuilderWeb extends EmbedBuilder { Embed node, bool readOnly, bool inline, + TextStyle textStyle, ) { final imageUrl = node.value.data; @@ -198,6 +200,7 @@ class VideoEmbedBuilder extends EmbedBuilder { base.Embed node, bool readOnly, bool inline, + TextStyle textStyle, ) { assert(!kIsWeb, 'Please provide video EmbedBuilder for Web'); @@ -226,6 +229,7 @@ class FormulaEmbedBuilder extends EmbedBuilder { base.Embed node, bool readOnly, bool inline, + TextStyle textStyle, ) { assert(!kIsWeb, 'Please provide formula EmbedBuilder for Web'); diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index d57cd8bf..a1ce333f 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: 0.3.2 +version: 0.3.3 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.17 + flutter_quill: ^7.1.20 image_picker: ^0.8.5+3 photo_view: ^0.14.0 From c1bef0ae4f357a3d4e97041ea4b1ef4b0fb8b669 Mon Sep 17 00:00:00 2001 From: Richard Marshall Date: Thu, 25 May 2023 14:56:21 -0700 Subject: [PATCH 182/204] Add support for android keyboard content insertion (#1236) --- lib/src/widgets/editor.dart | 7 +++++++ lib/src/widgets/raw_editor.dart | 13 ++++++++++++- .../raw_editor_state_text_input_client_mixin.dart | 3 +++ pubspec.yaml | 2 +- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 906b67fb..c946e0b7 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -187,6 +187,7 @@ class QuillEditor extends StatefulWidget { this.enableUnfocusOnTapOutside = true, this.customLinkPrefixes = const [], this.dialogTheme, + this.contentInsertionConfiguration, Key? key, }) : super(key: key); @@ -427,6 +428,11 @@ class QuillEditor extends StatefulWidget { /// Configures the dialog theme. final QuillDialogTheme? dialogTheme; + /// Configuration of handler for media content inserted via the system input method. + /// + /// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html] + final ContentInsertionConfiguration? contentInsertionConfiguration; + @override QuillEditorState createState() => QuillEditorState(); } @@ -528,6 +534,7 @@ class QuillEditorState extends State customLinkPrefixes: widget.customLinkPrefixes, enableUnfocusOnTapOutside: widget.enableUnfocusOnTapOutside, dialogTheme: widget.dialogTheme, + contentInsertionConfiguration: widget.contentInsertionConfiguration, ); final editor = I18n( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 444c49dd..fbf5528c 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -84,6 +84,7 @@ class RawEditor extends StatefulWidget { this.onImagePaste, this.customLinkPrefixes = const [], this.dialogTheme, + this.contentInsertionConfiguration, }) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'), assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'), assert(maxHeight == null || minHeight == null || maxHeight >= minHeight, @@ -270,6 +271,11 @@ class RawEditor extends StatefulWidget { /// Configures the dialog theme. final QuillDialogTheme? dialogTheme; + /// Configuration of handler for media content inserted via the system input method. + /// + /// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html] + final ContentInsertionConfiguration? contentInsertionConfiguration; + @override State createState() => RawEditorState(); } @@ -326,7 +332,12 @@ class RawEditorState extends EditorState TextDirection get _textDirection => Directionality.of(context); @override - void insertContent(KeyboardInsertedContent content) {} + void insertContent(KeyboardInsertedContent content) { + assert(widget.contentInsertionConfiguration?.allowedMimeTypes + .contains(content.mimeType) ?? + false); + widget.contentInsertionConfiguration?.onContentInserted.call(content); + } /// Returns the [ContextMenuButtonItem]s representing the buttons in this /// platform's default selection menu for [RawEditor]. diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index e3b9ff5f..f3cec425 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -59,6 +59,9 @@ mixin RawEditorStateTextInputClientMixin on EditorState enableSuggestions: !widget.readOnly, keyboardAppearance: widget.keyboardAppearance, textCapitalization: widget.textCapitalization, + allowedMimeTypes: widget.contentInsertionConfiguration == null + ? const [] + : widget.contentInsertionConfiguration!.allowedMimeTypes, ), ); diff --git a/pubspec.yaml b/pubspec.yaml index d2ab913a..b6403a01 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ repository: https://github.com/singerdmx/flutter-quill environment: sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + flutter: ">=3.10.0" dependencies: flutter: From dbcc06a9d6743cafdd387765b6a38b06a31f8d01 Mon Sep 17 00:00:00 2001 From: Firas Abd Alrahman Date: Sun, 28 May 2023 02:51:12 +0300 Subject: [PATCH 183/204] enhance color picker, enter hex color and color palette option (#1234) --- lib/src/translations/toolbar.i18n.dart | 9 ++ lib/src/widgets/toolbar/color_button.dart | 152 +++++++++++++++++++--- 2 files changed, 145 insertions(+), 16 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 77c0bd67..2e0cdca0 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -68,6 +68,9 @@ extension Localization on String { 'Enter media': 'Enter media', 'Edit': 'Edit', 'Apply': 'Apply', + 'Hex': 'Hex', + 'Material': 'Material', + 'Color': 'Color', }, 'en_us': { 'Paste a link': 'Paste a link', @@ -134,6 +137,9 @@ extension Localization on String { 'Enter media': 'Enter media', 'Edit': 'Edit', 'Apply': 'Apply', + 'Hex': 'Hex', + 'Material': 'Material', + 'Color': 'Color', }, 'ar': { 'Paste a link': 'نسخ الرابط', @@ -202,6 +208,9 @@ extension Localization on String { 'Enter media': 'ادخل وسائط', 'Edit': 'تعديل', 'Apply': 'تطبيق', + 'Hex': 'Hex', + 'Material': 'Material', + 'Color': 'اللون', }, 'da': { 'Paste a link': 'Indsæt link', diff --git a/lib/src/widgets/toolbar/color_button.dart b/lib/src/widgets/toolbar/color_button.dart index 60f0a591..65139629 100644 --- a/lib/src/widgets/toolbar/color_button.dart +++ b/lib/src/widgets/toolbar/color_button.dart @@ -136,29 +136,149 @@ class _ColorButtonState extends State { } void _changeColor(BuildContext context, Color color) { - var hex = color.value.toRadixString(16); - if (hex.startsWith('ff')) { - hex = hex.substring(2); - } + var hex = colorToHex(color); hex = '#$hex'; widget.controller.formatSelection( widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex)); - Navigator.of(context).pop(); } void _showColorPicker() { - showDialog( + var pickerType = 'material'; + + var selectedColor = Colors.black; + + if (_isToggledColor) { + selectedColor = widget.background + ? hexToColor(_selectionStyle.attributes['background']?.value) + : hexToColor(_selectionStyle.attributes['color']?.value); + } + + final hexController = + TextEditingController(text: colorToHex(selectedColor)); + late void Function(void Function()) colorBoxSetState; + + showDialog( context: context, - builder: (context) => AlertDialog( - title: Text('Select Color'.i18n), - backgroundColor: Theme.of(context).canvasColor, - content: SingleChildScrollView( - child: MaterialPicker( - pickerColor: const Color(0x00000000), - onColorChanged: (color) => _changeColor(context, color), - ), - ), - ), + builder: (context) => StatefulBuilder(builder: (context, dlgSetState) { + return AlertDialog( + title: Text('Select Color'.i18n), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('OK'.i18n)), + ], + backgroundColor: Theme.of(context).canvasColor, + content: SizedBox( + height: 400, + child: Column( + children: [ + Row( + children: [ + TextButton( + onPressed: () { + dlgSetState(() { + pickerType = 'material'; + }); + }, + child: Text('Material'.i18n)), + TextButton( + onPressed: () { + dlgSetState(() { + pickerType = 'color'; + }); + }, + child: Text('Color'.i18n)), + ], + ), + Expanded( + child: Column(children: [ + if (pickerType == 'material') + MaterialPicker( + pickerColor: selectedColor, + onColorChanged: (color) { + _changeColor(context, color); + Navigator.of(context).pop(); + }, + ), + if (pickerType == 'color') + ColorPicker( + pickerColor: selectedColor, + onColorChanged: (color) { + _changeColor(context, color); + hexController.text = colorToHex(color); + selectedColor = color; + colorBoxSetState(() {}); + }, + ), + const SizedBox( + height: 10, + ), + Row( + children: [ + SizedBox( + width: 100, + height: 60, + child: TextFormField( + controller: hexController, + onChanged: (value) { + selectedColor = hexToColor(value); + _changeColor(context, selectedColor); + + colorBoxSetState(() {}); + }, + decoration: InputDecoration( + labelText: 'Hex'.i18n, + border: const OutlineInputBorder(), + ), + ), + ), + const SizedBox( + width: 10, + ), + StatefulBuilder(builder: (context, mcolorBoxSetState) { + colorBoxSetState = mcolorBoxSetState; + return Container( + width: 25, + height: 25, + decoration: BoxDecoration( + border: Border.all( + color: Colors.black45, + ), + color: selectedColor, + borderRadius: BorderRadius.circular(5), + ), + ); + }), + ], + ), + ])) + ], + ), + )); + }), ); } + + Color hexToColor(String? hexString) { + if (hexString == null) { + return Colors.black; + } + final hexRegex = RegExp(r'([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$'); + + hexString = hexString.replaceAll('#', ''); + if (!hexRegex.hasMatch(hexString)) { + return Colors.black; + } + + final buffer = StringBuffer(); + if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); + buffer.write(hexString); + return Color(int.tryParse(buffer.toString(), radix: 16) ?? 0xFF000000); + } + + String colorToHex(Color color) { + return color.value.toRadixString(16).padLeft(8, '0').toUpperCase(); + } } From f1b60d83b82453c9f4bd29df10444deb7133beed Mon Sep 17 00:00:00 2001 From: Cheryl Date: Sat, 27 May 2023 17:36:12 -0700 Subject: [PATCH 184/204] Upgrade to 7.2.1 --- CHANGELOG.md | 4 ++++ .../raw_editor/raw_editor_state_text_input_client_mixin.dart | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffbb02ed..639f46e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# [7.2.1] +- Add support for android keyboard content insertion. +- Enhance color picker, enter hex color and color palette option. + # [7.2.0] - Checkboxes, bullet points, and number points are now scaled based on the default paragraph font size. diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index f3cec425..fff4c5ac 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -60,8 +60,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState keyboardAppearance: widget.keyboardAppearance, textCapitalization: widget.textCapitalization, allowedMimeTypes: widget.contentInsertionConfiguration == null - ? const [] - : widget.contentInsertionConfiguration!.allowedMimeTypes, + ? const [] + : widget.contentInsertionConfiguration!.allowedMimeTypes, ), ); diff --git a/pubspec.yaml b/pubspec.yaml index b6403a01..f105b378 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.0 +version: 7.2.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 94da3cfd4687fa465fd480908d9ba3c2a9f0091e Mon Sep 17 00:00:00 2001 From: Cheryl Date: Sat, 27 May 2023 17:37:04 -0700 Subject: [PATCH 185/204] Fix lint error --- lib/src/widgets/editor.dart | 3 ++- lib/src/widgets/raw_editor.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index c946e0b7..aefa21dc 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -428,7 +428,8 @@ class QuillEditor extends StatefulWidget { /// Configures the dialog theme. final QuillDialogTheme? dialogTheme; - /// Configuration of handler for media content inserted via the system input method. + /// Configuration of handler for media content inserted via the system input + /// method. /// /// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html] final ContentInsertionConfiguration? contentInsertionConfiguration; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index fbf5528c..657c63f7 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -271,7 +271,8 @@ class RawEditor extends StatefulWidget { /// Configures the dialog theme. final QuillDialogTheme? dialogTheme; - /// Configuration of handler for media content inserted via the system input method. + /// Configuration of handler for media content inserted via the system input + /// method. /// /// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html] final ContentInsertionConfiguration? contentInsertionConfiguration; From 4dd4212b9ae0f4c2c84f213fe0ddd802c7d502db Mon Sep 17 00:00:00 2001 From: Oxana Kostikova Date: Tue, 30 May 2023 22:19:24 +0300 Subject: [PATCH 186/204] Add icon color to custom button (#1245) --- lib/src/models/themes/quill_custom_button.dart | 4 ++++ lib/src/widgets/toolbar.dart | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/models/themes/quill_custom_button.dart b/lib/src/models/themes/quill_custom_button.dart index 4ea4e4f5..791b7095 100644 --- a/lib/src/models/themes/quill_custom_button.dart +++ b/lib/src/models/themes/quill_custom_button.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; class QuillCustomButton { const QuillCustomButton({ this.icon, + this.iconColor, this.onTap, this.tooltip, }); @@ -10,6 +11,9 @@ class QuillCustomButton { ///The icon widget final IconData? icon; + ///The icon color; + final Color? iconColor; + ///The function when the icon is tapped final VoidCallback? onTap; diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index f1cb3d96..ed5f6a07 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -570,7 +570,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { highlightElevation: 0, hoverElevation: 0, size: toolbarIconSize * kIconButtonFactor, - icon: Icon(customButton.icon, size: toolbarIconSize), + icon: Icon( + customButton.icon, + size: toolbarIconSize, + color: customButton.iconColor, + ), tooltip: customButton.tooltip, borderRadius: iconTheme?.borderRadius ?? 2, onPressed: customButton.onTap, From 6399cca1d8ce6ba931403e201016d198ec910612 Mon Sep 17 00:00:00 2001 From: Simon Uzar <82913812+xGreatSoulx@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:34:44 -0300 Subject: [PATCH 187/204] Translated some texts in 'pt_br' (#1250) --- lib/src/translations/toolbar.i18n.dart | 68 +++++++++++++------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 2e0cdca0..7fc356e9 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -1034,45 +1034,45 @@ extension Localization on String { 'Clear': 'Limpar', 'Font': 'Fonte', 'Search': 'Buscar', - 'matches': 'matches', - 'showing match': 'showing match', + 'matches': 'resultado(s)', + 'showing match': 'mostrando resultado', 'Prev': 'Anterior', 'Next': 'Próximo', - 'Camera': 'Camera', + 'Camera': 'Câmera', 'Video': 'Vídeo', - 'Undo': 'Undo', - 'Redo': 'Redo', - 'Font family': 'Font family', - 'Font size': 'Font size', - 'Bold': 'Bold', - 'Subscript': 'Subscript', - 'Superscript': 'Superscript', - 'Italic': 'Italic', - 'Underline': 'Underline', - 'Strike through': 'Strike through', + 'Undo': 'Desfazer', + 'Redo': 'Refazer', + 'Font family': 'Fonte', + 'Font size': 'Tamanho da fonte', + 'Bold': 'Negrito', + 'Subscript': 'Subscrito', + 'Superscript': 'Sobrescrito', + 'Italic': 'Itálico', + 'Underline': 'Sublinhado', + 'Strike through': 'Tachado', 'Inline code': 'Inline code', - 'Font color': 'Font color', - 'Background color': 'Background color', - 'Clear format': 'Clear format', - 'Align left': 'Align left', - 'Align center': 'Align center', - 'Align right': 'Align right', - 'Justify win width': 'Justify win width', - 'Text direction': 'Text direction', - 'Header style': 'Header style', - 'Numbered list': 'Numbered list', - 'Bullet list': 'Bullet list', - 'Checked list': 'Checked list', + 'Font color': 'Cor da fonte', + 'Background color': 'Cor do fundo', + 'Clear format': 'Limpar formatação', + 'Align left': 'Texto à esquerda', + 'Align center': 'Centralizar', + 'Align right': 'Texto à direita', + 'Justify win width': 'Justificado', + 'Text direction': 'Direção do texto', + 'Header style': 'Estilo de cabeçalho', + 'Numbered list': 'Numeração', + 'Bullet list': 'Marcadores', + 'Checked list': 'Lista de verificação', 'Code block': 'Code block', - 'Quote': 'Quote', - 'Increase indent': 'Increase indent', - 'Decrease indent': 'Decrease indent', - 'Insert URL': 'Insert URL', - 'Visit link': 'Visit link', - 'Enter link': 'Enter link', - 'Enter media': 'Enter media', - 'Edit': 'Edit', - 'Apply': 'Apply', + 'Quote': 'Citação', + 'Increase indent': 'Aumentar recuo', + 'Decrease indent': 'Diminuir recuo', + 'Insert URL': 'Inserir URL', + 'Visit link': 'Visitar link', + 'Enter link': 'Inserir link', + 'Enter media': 'Inserir mídia', + 'Edit': 'Editar', + 'Apply': 'Aplicar', }, 'pl': { 'Paste a link': 'Wklej link', From 205be05e9aeb8b321fa7a4ddab25136f6188930f Mon Sep 17 00:00:00 2001 From: George Tian Date: Mon, 5 Jun 2023 22:17:38 +0800 Subject: [PATCH 188/204] Fix color picker dialog overflow (#1251) --- lib/src/widgets/toolbar/color_button.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/src/widgets/toolbar/color_button.dart b/lib/src/widgets/toolbar/color_button.dart index 65139629..41a57734 100644 --- a/lib/src/widgets/toolbar/color_button.dart +++ b/lib/src/widgets/toolbar/color_button.dart @@ -170,9 +170,9 @@ class _ColorButtonState extends State { child: Text('OK'.i18n)), ], backgroundColor: Theme.of(context).canvasColor, - content: SizedBox( - height: 400, + content: SingleChildScrollView( child: Column( + mainAxisSize: MainAxisSize.min, children: [ Row( children: [ @@ -192,8 +192,7 @@ class _ColorButtonState extends State { child: Text('Color'.i18n)), ], ), - Expanded( - child: Column(children: [ + Column(children: [ if (pickerType == 'material') MaterialPicker( pickerColor: selectedColor, @@ -253,7 +252,7 @@ class _ColorButtonState extends State { }), ], ), - ])) + ]) ], ), )); From 6fa057987363d15dc78eadaf5e71dd721bb3ea56 Mon Sep 17 00:00:00 2001 From: Dilanka Yapa <104094511+dilankayapagit@users.noreply.github.com> Date: Mon, 5 Jun 2023 19:47:57 +0530 Subject: [PATCH 189/204] Update pubspec.yaml (#1252) --- flutter_quill_extensions/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index a1ce333f..f1b77e8c 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.1.20 + flutter_quill: ^7.2.1 image_picker: ^0.8.5+3 photo_view: ^0.14.0 From 1124a1f26cea21a8d5839b15fc23ceb26a0ea1cd Mon Sep 17 00:00:00 2001 From: Richard Marshall Date: Tue, 6 Jun 2023 17:18:46 -0700 Subject: [PATCH 190/204] Prevent operations on stale editor state (#1256) --- lib/src/widgets/editor.dart | 3 ++ lib/src/widgets/raw_editor.dart | 44 ++++++++++++++----- ..._editor_state_text_input_client_mixin.dart | 3 +- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index aefa21dc..667d6d7e 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -46,6 +46,9 @@ abstract class EditorState extends State /// The floating cursor is animated to merge with the regular cursor. AnimationController get floatingCursorResetController; + /// Returns true if the editor has been marked as needing to be rebuilt. + bool get dirty; + bool showToolbar(); void requestKeyboard(); diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 657c63f7..8a669c0c 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -332,6 +332,10 @@ class RawEditorState extends EditorState TextDirection get _textDirection => Directionality.of(context); + @override + bool get dirty => _dirty; + bool _dirty = false; + @override void insertContent(KeyboardInsertedContent content) { assert(widget.contentInsertionConfiguration?.allowedMimeTypes @@ -855,6 +859,7 @@ class RawEditorState extends EditorState final currentSelection = controller.selection.copyWith(); final attribute = value ? Attribute.checked : Attribute.unchecked; + _markNeedsBuild(); controller ..ignoreFocusOnTextChange = true ..formatText(offset, 0, attribute) @@ -929,9 +934,11 @@ class RawEditorState extends EditorState clearIndents = false; } else { + _dirty = false; throw StateError('Unreachable.'); } } + _dirty = false; return result; } @@ -1170,6 +1177,17 @@ class RawEditorState extends EditorState _selectionOverlay?.updateForScroll(); } + /// Marks the editor as dirty and trigger a rebuild. + /// + /// When the editor is dirty methods that depend on the editor + /// state being in sync with the controller know they may be + /// operating on stale data. + void _markNeedsBuild() { + setState(() { + _dirty = true; + }); + } + void _didChangeTextEditingValue([bool ignoreFocus = false]) { if (kIsWeb) { _onChangeTextEditingValue(ignoreFocus); @@ -1184,10 +1202,9 @@ class RawEditorState extends EditorState } else { requestKeyboard(); if (mounted) { - setState(() { - // Use controller.value in build() - // Trigger build and updateChildren - }); + // Use controller.value in build() + // Mark widget as dirty and trigger build and updateChildren + _markNeedsBuild(); } } @@ -1222,10 +1239,9 @@ class RawEditorState extends EditorState _updateOrDisposeSelectionOverlayIfNeeded(); }); if (mounted) { - setState(() { - // Use controller.value in build() - // Trigger build and updateChildren - }); + // Use controller.value in build() + // Mark widget as dirty and trigger build and updateChildren + _markNeedsBuild(); } } @@ -1258,6 +1274,11 @@ class RawEditorState extends EditorState } void _handleFocusChanged() { + if (dirty) { + SchedulerBinding.instance + .addPostFrameCallback((_) => _handleFocusChanged()); + return; + } openOrCloseConnection(); _cursorCont.startOrStopCursorTimerIfNeeded(_hasFocus, controller.selection); _updateOrDisposeSelectionOverlayIfNeeded(); @@ -1272,10 +1293,9 @@ class RawEditorState extends EditorState void _onChangedClipboardStatus() { if (!mounted) return; - setState(() { - // Inform the widget that the value of clipboardStatus has changed. - // Trigger build and updateChildren - }); + // Inform the widget that the value of clipboardStatus has changed. + // Trigger build and updateChildren + _markNeedsBuild(); } Future _linkActionPicker(Node linkNode) async { diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index fff4c5ac..396a76e1 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -91,7 +91,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState void _updateCaretRectIfNeeded() { if (hasConnection) { - if (renderEditor.selection.isValid && + if (!dirty && + renderEditor.selection.isValid && renderEditor.selection.isCollapsed) { final currentTextPosition = TextPosition(offset: renderEditor.selection.baseOffset); From ebef9d7d95b941cc33451ccc099b761ab4daa667 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Tue, 6 Jun 2023 17:34:24 -0700 Subject: [PATCH 191/204] Upgrade to 7.2.2 --- CHANGELOG.md | 3 +++ lib/src/models/themes/quill_custom_button.dart | 2 +- lib/src/widgets/toolbar.dart | 2 +- pubspec.yaml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 639f46e8..40c907e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.2] +- Prevent operations on stale editor state. + # [7.2.1] - Add support for android keyboard content insertion. - Enhance color picker, enter hex color and color palette option. diff --git a/lib/src/models/themes/quill_custom_button.dart b/lib/src/models/themes/quill_custom_button.dart index 791b7095..bbb472f9 100644 --- a/lib/src/models/themes/quill_custom_button.dart +++ b/lib/src/models/themes/quill_custom_button.dart @@ -13,7 +13,7 @@ class QuillCustomButton { ///The icon color; final Color? iconColor; - + ///The function when the icon is tapped final VoidCallback? onTap; diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index ed5f6a07..fb01c4df 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -571,7 +571,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { hoverElevation: 0, size: toolbarIconSize * kIconButtonFactor, icon: Icon( - customButton.icon, + customButton.icon, size: toolbarIconSize, color: customButton.iconColor, ), diff --git a/pubspec.yaml b/pubspec.yaml index f105b378..de306d17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.1 +version: 7.2.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From ba6fa9c6f7f8b8704ce7f4d7f29bcc5160c57822 Mon Sep 17 00:00:00 2001 From: Pwiz Date: Fri, 9 Jun 2023 08:58:54 +0800 Subject: [PATCH 192/204] get pixel ratio from view (#1259) --- lib/src/widgets/editor.dart | 4 ++-- lib/src/widgets/raw_editor.dart | 2 +- lib/src/widgets/text_block.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 667d6d7e..52cf9a67 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -477,8 +477,8 @@ class QuillEditorState extends State selectionColor = selectionTheme.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40); cursorRadius ??= const Radius.circular(2); - cursorOffset = Offset( - iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0); + cursorOffset = + Offset(iOSHorizontalOffset / View.of(context).devicePixelRatio, 0); } else { textSelectionControls = materialTextSelectionControls; paintCursorAboveText = false; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 8a669c0c..9319bfab 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -968,7 +968,7 @@ class RawEditorState extends EditorState widget.selectionColor, widget.enableInteractiveSelection, _hasFocus, - MediaQuery.of(context).devicePixelRatio, + View.of(context).devicePixelRatio, _cursorCont); return editableTextLine; } diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 4adaac37..3906fb88 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -159,7 +159,7 @@ class EditableTextBlock extends StatelessWidget { color, enableInteractiveSelection, hasFocus, - MediaQuery.of(context).devicePixelRatio, + View.of(context).devicePixelRatio, cursorCont); final nodeTextDirection = getDirectionOfNode(line); children.add(Directionality( From d547ec5951af7d6771203b6886f80682e55ce2d0 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Thu, 8 Jun 2023 18:39:54 -0700 Subject: [PATCH 193/204] Upgrade to 7.2.3 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c907e3..84fab2bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.3] +- Get pixel ratio from view. + # [7.2.2] - Prevent operations on stale editor state. diff --git a/pubspec.yaml b/pubspec.yaml index de306d17..bba49aa1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.2 +version: 7.2.3 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From b883f727d80cb09b3ebbe61683e6ee473505f339 Mon Sep 17 00:00:00 2001 From: Richard Marshall Date: Mon, 12 Jun 2023 17:35:51 -0700 Subject: [PATCH 194/204] First pass of tests (#1261) * Initial set of tests for QuillController * Basic text input test for QuillEditor widget * Add test for Android keyboard image insertion * Add tests to validate fixes for #1189 * Export test utilites --- .gitignore | 1 + README.md | 16 ++ lib/flutter_quill_test.dart | 3 + lib/src/test/widget_tester_extension.dart | 60 +++++ test/bug_fix_test.dart | 60 +++++ test/widgets/controller_test.dart | 290 ++++++++++++++++++++++ test/widgets/editor_test.dart | 82 ++++++ 7 files changed, 512 insertions(+) create mode 100644 lib/flutter_quill_test.dart create mode 100644 lib/src/test/widget_tester_extension.dart create mode 100644 test/bug_fix_test.dart create mode 100644 test/widgets/controller_test.dart create mode 100644 test/widgets/editor_test.dart diff --git a/.gitignore b/.gitignore index 08c3c879..6b87759f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ .pub-cache/ .pub/ build/ +coverage/ # Android related **/android/**/gradle-wrapper.jar diff --git a/README.md b/README.md index d31e9a58..9fe48b01 100644 --- a/README.md +++ b/README.md @@ -391,6 +391,22 @@ tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server- It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) Typescript/Javascript package. +## Testing + +To aid in testing applications using the editor an extension to the flutter `WidgetTester` is provided which includes methods to simplify interacting with the editor in test cases. + +Import the test utilities in your test file: + +```dart +import 'package:flutter_quill/flutter_quill_test.dart'; +``` + +and then enter text using `quillEnterText`: + +```dart +await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); +``` + ## Sponsors diff --git a/lib/flutter_quill_test.dart b/lib/flutter_quill_test.dart new file mode 100644 index 00000000..988e4e82 --- /dev/null +++ b/lib/flutter_quill_test.dart @@ -0,0 +1,3 @@ +library flutter_quill_test; + +export 'src/test/widget_tester_extension.dart'; diff --git a/lib/src/test/widget_tester_extension.dart b/lib/src/test/widget_tester_extension.dart new file mode 100644 index 00000000..21bb75ab --- /dev/null +++ b/lib/src/test/widget_tester_extension.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../widgets/editor.dart'; +import '../widgets/raw_editor.dart'; + +/// Extends +extension QuillEnterText on WidgetTester { + /// Give the QuillEditor widget specified by [finder] the focus. + Future quillGiveFocus(Finder finder) { + return TestAsyncUtils.guard(() async { + final editor = state( + find.descendant( + of: finder, + matching: + find.byType(QuillEditor, skipOffstage: finder.skipOffstage), + matchRoot: true), + ); + editor.widget.focusNode.requestFocus(); + await pump(); + expect(editor.widget.focusNode.hasFocus, isTrue); + }); + } + + /// Give the QuillEditor widget specified by [finder] the focus and update its + /// editing value with [text], as if it had been provided by the onscreen + /// keyboard. + /// + /// The widget specified by [finder] must be a [QuillEditor] or have a + /// [QuillEditor] descendant. For example `find.byType(QuillEditor)`. + Future quillEnterText(Finder finder, String text) async { + return TestAsyncUtils.guard(() async { + await quillGiveFocus(finder); + await quillUpdateEditingValue(finder, text); + await idle(); + }); + } + + /// Update the text editing value of the QuillEditor widget specified by + /// [finder] with [text], as if it had been provided by the onscreen keyboard. + /// + /// The widget specified by [finder] must already have focus and be a + /// [QuillEditor] or have a [QuillEditor] descendant. For example + /// `find.byType(QuillEditor)`. + Future quillUpdateEditingValue(Finder finder, String text) async { + return TestAsyncUtils.guard(() async { + final editor = state( + find.descendant( + of: finder, + matching: find.byType(RawEditor, skipOffstage: finder.skipOffstage), + matchRoot: true), + ); + testTextInput.updateEditingValue(TextEditingValue( + text: text, + selection: TextSelection.collapsed( + offset: editor.textEditingValue.text.length))); + await idle(); + }); + } +} diff --git a/test/bug_fix_test.dart b/test/bug_fix_test.dart new file mode 100644 index 00000000..ecbcad2b --- /dev/null +++ b/test/bug_fix_test.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/flutter_quill_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Bug fix', () { + group('1189 - The provided text position is not in the current node', () { + late QuillController controller; + late QuillEditor editor; + + setUp(() { + controller = QuillController.basic(); + editor = QuillEditor.basic(controller: controller, readOnly: false); + }); + + tearDown(() { + controller.dispose(); + }); + + testWidgets('Refocus editor after controller clears document', + (tester) async { + await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + editor.focusNode.unfocus(); + await tester.pump(); + controller.clear(); + editor.focusNode.requestFocus(); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + + testWidgets('Refocus editor after removing block attribute', + (tester) async { + await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + controller.formatSelection(Attribute.ul); + editor.focusNode.unfocus(); + await tester.pump(); + controller.formatSelection(const ListAttribute(null)); + editor.focusNode.requestFocus(); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + + testWidgets('Tap checkbox in unfocused editor', (tester) async { + await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + controller.formatSelection(Attribute.unchecked); + editor.focusNode.unfocus(); + await tester.pump(); + await tester.tap(find.byType(CheckboxPoint)); + expect(tester.takeException(), isNull); + }); + }); + }); +} diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart new file mode 100644 index 00000000..047dfcae --- /dev/null +++ b/test/widgets/controller_test.dart @@ -0,0 +1,290 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const testDocumentContents = 'data'; + late QuillController controller; + + setUp(() { + controller = QuillController.basic() + ..compose(Delta()..insert(testDocumentContents), + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); + }); + + group('controller', () { + test('set document', () { + const replacementContents = 'replacement\n'; + final newDocument = + Document.fromDelta(Delta()..insert(replacementContents)); + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..document = newDocument; + expect(listenerCalled, isTrue); + expect(controller.document.toPlainText(), replacementContents); + }); + + test('getSelectionStyle', () { + controller + ..formatText(0, 5, Attribute.h1) + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), + ChangeSource.LOCAL); + + expect(controller.getSelectionStyle().values, [Attribute.h1]); + }); + + test('indentSelection with single line document', () { + var listenerCalled = false; + // With selection range + controller + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), + ChangeSource.LOCAL) + ..addListener(() { + listenerCalled = true; + }) + ..indentSelection(true); + expect(listenerCalled, isTrue); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller.indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL2]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, []); + + // With collapsed selection + controller + ..updateSelection( + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + ..indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller + ..updateSelection( + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + ..indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL2]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, []); + }); + + test('indentSelection with multiline document', () { + controller + ..compose(Delta()..insert('line1\nline2\nline3\n'), + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + // Indent first line + ..updateSelection( + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + ..indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + + // Indent first two lines + controller + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 11), + ChangeSource.LOCAL) + ..indentSelection(true); + + // Should have both L1 and L2 indent attributes in selection. + expect(controller.getAllSelectionStyles(), + contains(Style().put(Attribute.indentL1).put(Attribute.indentL2))); + + // Remaining lines should have no attributes. + controller.updateSelection( + TextSelection( + baseOffset: 12, + extentOffset: controller.document.toPlainText().length - 1), + ChangeSource.LOCAL); + expect(controller.getAllSelectionStyles(), everyElement(Style())); + }); + + test('getAllIndividualSelectionStyles', () { + controller.formatText(0, 2, Attribute.bold); + final result = controller.getAllIndividualSelectionStyles(); + expect(result.length, 1); + expect(result[0].offset, 0); + expect(result[0].value, Style().put(Attribute.bold)); + }); + + test('getPlainText', () { + controller.updateSelection( + const TextSelection(baseOffset: 0, extentOffset: 4), + ChangeSource.LOCAL); + + expect(controller.getPlainText(), testDocumentContents); + }); + + test('getAllSelectionStyles', () { + controller.formatText(0, 2, Attribute.bold); + expect(controller.getAllSelectionStyles(), + contains(Style().put(Attribute.bold))); + }); + + test('undo', () { + var listenerCalled = false; + controller.updateSelection( + const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); + + expect(controller.document.toDelta(), Delta()..insert('data\n')); + controller + ..addListener(() { + listenerCalled = true; + }) + ..undo(); + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('\n')); + }); + + test('redo', () { + var listenerCalled = false; + controller.updateSelection( + const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); + + expect(controller.document.toDelta(), Delta()..insert('data\n')); + controller.undo(); + expect(controller.document.toDelta(), Delta()..insert('\n')); + controller + ..addListener(() { + listenerCalled = true; + }) + ..redo(); + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('data\n')); + }); + test('clear', () { + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..clear(); + + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('\n')); + }); + + test('replaceText', () { + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..replaceText(1, 2, '11', const TextSelection.collapsed(offset: 0)); + + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('d11a\n')); + }); + + test('formatTextStyle', () { + var listenerCalled = false; + final style = Style().put(Attribute.bold).put(Attribute.italic); + controller + ..addListener(() { + listenerCalled = true; + }) + ..formatTextStyle(0, 2, style); + expect(listenerCalled, isTrue); + expect(controller.document.collectAllStyles(0, 2), contains(style)); + expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); + }); + + test('formatText', () { + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..formatText(0, 2, Attribute.bold); + expect(listenerCalled, isTrue); + expect(controller.document.collectAllStyles(0, 2), + contains(Style().put(Attribute.bold))); + expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); + }); + + test('formatSelection', () { + var listenerCalled = false; + controller + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 2), + ChangeSource.LOCAL) + ..addListener(() { + listenerCalled = true; + }) + ..formatSelection(Attribute.bold); + expect(listenerCalled, isTrue); + expect(controller.document.collectAllStyles(0, 2), + contains(Style().put(Attribute.bold))); + expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); + }); + + test('moveCursorToStart', () { + var listenerCalled = false; + controller + ..updateSelection( + const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL) + ..addListener(() { + listenerCalled = true; + }); + expect(controller.selection, const TextSelection.collapsed(offset: 4)); + + controller.moveCursorToStart(); + expect(listenerCalled, isTrue); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + }); + + test('moveCursorToPosition', () { + var listenerCalled = false; + controller.addListener(() { + listenerCalled = true; + }); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + + controller.moveCursorToPosition(2); + expect(listenerCalled, isTrue); + expect(controller.selection, const TextSelection.collapsed(offset: 2)); + }); + + test('moveCursorToEnd', () { + var listenerCalled = false; + controller.addListener(() { + listenerCalled = true; + }); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + + controller.moveCursorToEnd(); + expect(listenerCalled, isTrue); + expect(controller.selection, + TextSelection.collapsed(offset: controller.document.length - 1)); + }); + + test('updateSelection', () { + var listenerCalled = false; + const selection = TextSelection.collapsed(offset: 0); + controller + ..addListener(() { + listenerCalled = true; + }) + ..updateSelection(selection, ChangeSource.LOCAL); + + expect(listenerCalled, isTrue); + expect(controller.selection, selection); + }); + + test('compose', () { + var listenerCalled = false; + final originalContents = controller.document.toPlainText(); + controller + ..addListener(() { + listenerCalled = true; + }) + ..compose(Delta()..insert('test '), + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); + + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), + Delta()..insert('test $originalContents')); + }); + }); +} diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart new file mode 100644 index 00000000..3fd425fc --- /dev/null +++ b/test/widgets/editor_test.dart @@ -0,0 +1,82 @@ +import 'dart:convert' show jsonDecode; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/flutter_quill_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + late QuillController controller; + + setUp(() { + controller = QuillController.basic(); + }); + + tearDown(() { + controller.dispose(); + }); + + group('QuillEditor', () { + testWidgets('Keyboard entered text is stored in document', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: QuillEditor.basic(controller: controller, readOnly: false), + ), + ); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + expect(controller.document.toPlainText(), 'test\n'); + }); + + testWidgets('insertContent is handled correctly', (tester) async { + String? latestUri; + await tester.pumpWidget( + MaterialApp( + home: QuillEditor( + controller: controller, + focusNode: FocusNode(), + scrollController: ScrollController(), + scrollable: true, + padding: const EdgeInsets.all(0), + autoFocus: true, + readOnly: false, + expands: true, + contentInsertionConfiguration: ContentInsertionConfiguration( + onContentInserted: (content) { + latestUri = content.uri; + }, + allowedMimeTypes: const ['image/gif'], + ), + ), + ), + ); + await tester.tap(find.byType(QuillEditor)); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + await tester.idle(); + + const uri = + 'content://com.google.android.inputmethod.latin.fileprovider/test.gif'; + final messageBytes = + const JSONMessageCodec().encodeMessage({ + 'args': [ + -1, + 'TextInputAction.commitContent', + jsonDecode( + '{"mimeType": "image/gif", "data": [0,1,0,1,0,1,0,0,0], "uri": "$uri"}'), + ], + 'method': 'TextInputClient.performAction', + }); + + Object? error; + try { + await tester.binding.defaultBinaryMessenger + .handlePlatformMessage('flutter/textinput', messageBytes, (_) {}); + } catch (e) { + error = e; + } + expect(error, isNull); + expect(latestUri, equals(uri)); + }); + }); +} From b738cad4abc0e64932a92634346e92883bbc6bfa Mon Sep 17 00:00:00 2001 From: liam-duan <93059008+liam-duan@users.noreply.github.com> Date: Mon, 12 Jun 2023 22:36:51 -0600 Subject: [PATCH 195/204] Fixed keepStyleOnNewLine (#1262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The “keepStyleOnNewLine” parameter in the Controller class is no longer functional. --- lib/src/widgets/controller.dart | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/src/widgets/controller.dart b/lib/src/widgets/controller.dart index 48bf136d..aedd7015 100644 --- a/lib/src/widgets/controller.dart +++ b/lib/src/widgets/controller.dart @@ -255,14 +255,6 @@ class QuillController extends ChangeNotifier { } } - if (_keepStyleOnNewLine) { - final style = getSelectionStyle(); - final notInlineStyle = style.attributes.values.where((s) => !s.isInline); - toggledStyle = style.removeAll(notInlineStyle.toSet()); - } else { - toggledStyle = Style(); - } - if (textSelection != null) { if (delta == null || delta.isEmpty) { _updateSelection(textSelection, ChangeSource.LOCAL); @@ -400,7 +392,13 @@ class QuillController extends ChangeNotifier { _selection = selection.copyWith( baseOffset: math.min(selection.baseOffset, end), extentOffset: math.min(selection.extentOffset, end)); - toggledStyle = Style(); + if (_keepStyleOnNewLine) { + final style = getSelectionStyle(); + final notInlineStyle = style.attributes.values.where((s) => !s.isInline); + toggledStyle = style.removeAll(notInlineStyle.toSet()); + } else { + toggledStyle = Style(); + } onSelectionChanged?.call(textSelection); } From 0392a86c8935e4ba5c14e554a8bd830062e04c87 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Mon, 12 Jun 2023 21:43:35 -0700 Subject: [PATCH 196/204] Upgrade to 7.2.4 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84fab2bc..58f1e223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.4] +- Fixed keepStyleOnNewLine. + # [7.2.3] - Get pixel ratio from view. diff --git a/pubspec.yaml b/pubspec.yaml index bba49aa1..4ca8ee66 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.3 +version: 7.2.4 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 9d877478ad4ec6cc53880bb85de0525b6abdc900 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Mon, 12 Jun 2023 21:47:08 -0700 Subject: [PATCH 197/204] Revert "First pass of tests (#1261)" This reverts commit b883f727d80cb09b3ebbe61683e6ee473505f339. --- .gitignore | 1 - README.md | 16 -- lib/flutter_quill_test.dart | 3 - lib/src/test/widget_tester_extension.dart | 60 ----- test/bug_fix_test.dart | 60 ----- test/widgets/controller_test.dart | 290 ---------------------- test/widgets/editor_test.dart | 82 ------ 7 files changed, 512 deletions(-) delete mode 100644 lib/flutter_quill_test.dart delete mode 100644 lib/src/test/widget_tester_extension.dart delete mode 100644 test/bug_fix_test.dart delete mode 100644 test/widgets/controller_test.dart delete mode 100644 test/widgets/editor_test.dart diff --git a/.gitignore b/.gitignore index 6b87759f..08c3c879 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ .pub-cache/ .pub/ build/ -coverage/ # Android related **/android/**/gradle-wrapper.jar diff --git a/README.md b/README.md index 9fe48b01..d31e9a58 100644 --- a/README.md +++ b/README.md @@ -391,22 +391,6 @@ tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server- It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) Typescript/Javascript package. -## Testing - -To aid in testing applications using the editor an extension to the flutter `WidgetTester` is provided which includes methods to simplify interacting with the editor in test cases. - -Import the test utilities in your test file: - -```dart -import 'package:flutter_quill/flutter_quill_test.dart'; -``` - -and then enter text using `quillEnterText`: - -```dart -await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); -``` - ## Sponsors diff --git a/lib/flutter_quill_test.dart b/lib/flutter_quill_test.dart deleted file mode 100644 index 988e4e82..00000000 --- a/lib/flutter_quill_test.dart +++ /dev/null @@ -1,3 +0,0 @@ -library flutter_quill_test; - -export 'src/test/widget_tester_extension.dart'; diff --git a/lib/src/test/widget_tester_extension.dart b/lib/src/test/widget_tester_extension.dart deleted file mode 100644 index 21bb75ab..00000000 --- a/lib/src/test/widget_tester_extension.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../widgets/editor.dart'; -import '../widgets/raw_editor.dart'; - -/// Extends -extension QuillEnterText on WidgetTester { - /// Give the QuillEditor widget specified by [finder] the focus. - Future quillGiveFocus(Finder finder) { - return TestAsyncUtils.guard(() async { - final editor = state( - find.descendant( - of: finder, - matching: - find.byType(QuillEditor, skipOffstage: finder.skipOffstage), - matchRoot: true), - ); - editor.widget.focusNode.requestFocus(); - await pump(); - expect(editor.widget.focusNode.hasFocus, isTrue); - }); - } - - /// Give the QuillEditor widget specified by [finder] the focus and update its - /// editing value with [text], as if it had been provided by the onscreen - /// keyboard. - /// - /// The widget specified by [finder] must be a [QuillEditor] or have a - /// [QuillEditor] descendant. For example `find.byType(QuillEditor)`. - Future quillEnterText(Finder finder, String text) async { - return TestAsyncUtils.guard(() async { - await quillGiveFocus(finder); - await quillUpdateEditingValue(finder, text); - await idle(); - }); - } - - /// Update the text editing value of the QuillEditor widget specified by - /// [finder] with [text], as if it had been provided by the onscreen keyboard. - /// - /// The widget specified by [finder] must already have focus and be a - /// [QuillEditor] or have a [QuillEditor] descendant. For example - /// `find.byType(QuillEditor)`. - Future quillUpdateEditingValue(Finder finder, String text) async { - return TestAsyncUtils.guard(() async { - final editor = state( - find.descendant( - of: finder, - matching: find.byType(RawEditor, skipOffstage: finder.skipOffstage), - matchRoot: true), - ); - testTextInput.updateEditingValue(TextEditingValue( - text: text, - selection: TextSelection.collapsed( - offset: editor.textEditingValue.text.length))); - await idle(); - }); - } -} diff --git a/test/bug_fix_test.dart b/test/bug_fix_test.dart deleted file mode 100644 index ecbcad2b..00000000 --- a/test/bug_fix_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/flutter_quill_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('Bug fix', () { - group('1189 - The provided text position is not in the current node', () { - late QuillController controller; - late QuillEditor editor; - - setUp(() { - controller = QuillController.basic(); - editor = QuillEditor.basic(controller: controller, readOnly: false); - }); - - tearDown(() { - controller.dispose(); - }); - - testWidgets('Refocus editor after controller clears document', - (tester) async { - await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - editor.focusNode.unfocus(); - await tester.pump(); - controller.clear(); - editor.focusNode.requestFocus(); - await tester.pump(); - expect(tester.takeException(), isNull); - }); - - testWidgets('Refocus editor after removing block attribute', - (tester) async { - await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - controller.formatSelection(Attribute.ul); - editor.focusNode.unfocus(); - await tester.pump(); - controller.formatSelection(const ListAttribute(null)); - editor.focusNode.requestFocus(); - await tester.pump(); - expect(tester.takeException(), isNull); - }); - - testWidgets('Tap checkbox in unfocused editor', (tester) async { - await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - controller.formatSelection(Attribute.unchecked); - editor.focusNode.unfocus(); - await tester.pump(); - await tester.tap(find.byType(CheckboxPoint)); - expect(tester.takeException(), isNull); - }); - }); - }); -} diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart deleted file mode 100644 index 047dfcae..00000000 --- a/test/widgets/controller_test.dart +++ /dev/null @@ -1,290 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - const testDocumentContents = 'data'; - late QuillController controller; - - setUp(() { - controller = QuillController.basic() - ..compose(Delta()..insert(testDocumentContents), - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); - }); - - group('controller', () { - test('set document', () { - const replacementContents = 'replacement\n'; - final newDocument = - Document.fromDelta(Delta()..insert(replacementContents)); - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..document = newDocument; - expect(listenerCalled, isTrue); - expect(controller.document.toPlainText(), replacementContents); - }); - - test('getSelectionStyle', () { - controller - ..formatText(0, 5, Attribute.h1) - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), - ChangeSource.LOCAL); - - expect(controller.getSelectionStyle().values, [Attribute.h1]); - }); - - test('indentSelection with single line document', () { - var listenerCalled = false; - // With selection range - controller - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), - ChangeSource.LOCAL) - ..addListener(() { - listenerCalled = true; - }) - ..indentSelection(true); - expect(listenerCalled, isTrue); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller.indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL2]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, []); - - // With collapsed selection - controller - ..updateSelection( - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - ..indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller - ..updateSelection( - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - ..indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL2]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, []); - }); - - test('indentSelection with multiline document', () { - controller - ..compose(Delta()..insert('line1\nline2\nline3\n'), - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - // Indent first line - ..updateSelection( - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - ..indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - - // Indent first two lines - controller - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 11), - ChangeSource.LOCAL) - ..indentSelection(true); - - // Should have both L1 and L2 indent attributes in selection. - expect(controller.getAllSelectionStyles(), - contains(Style().put(Attribute.indentL1).put(Attribute.indentL2))); - - // Remaining lines should have no attributes. - controller.updateSelection( - TextSelection( - baseOffset: 12, - extentOffset: controller.document.toPlainText().length - 1), - ChangeSource.LOCAL); - expect(controller.getAllSelectionStyles(), everyElement(Style())); - }); - - test('getAllIndividualSelectionStyles', () { - controller.formatText(0, 2, Attribute.bold); - final result = controller.getAllIndividualSelectionStyles(); - expect(result.length, 1); - expect(result[0].offset, 0); - expect(result[0].value, Style().put(Attribute.bold)); - }); - - test('getPlainText', () { - controller.updateSelection( - const TextSelection(baseOffset: 0, extentOffset: 4), - ChangeSource.LOCAL); - - expect(controller.getPlainText(), testDocumentContents); - }); - - test('getAllSelectionStyles', () { - controller.formatText(0, 2, Attribute.bold); - expect(controller.getAllSelectionStyles(), - contains(Style().put(Attribute.bold))); - }); - - test('undo', () { - var listenerCalled = false; - controller.updateSelection( - const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); - - expect(controller.document.toDelta(), Delta()..insert('data\n')); - controller - ..addListener(() { - listenerCalled = true; - }) - ..undo(); - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('\n')); - }); - - test('redo', () { - var listenerCalled = false; - controller.updateSelection( - const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); - - expect(controller.document.toDelta(), Delta()..insert('data\n')); - controller.undo(); - expect(controller.document.toDelta(), Delta()..insert('\n')); - controller - ..addListener(() { - listenerCalled = true; - }) - ..redo(); - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('data\n')); - }); - test('clear', () { - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..clear(); - - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('\n')); - }); - - test('replaceText', () { - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..replaceText(1, 2, '11', const TextSelection.collapsed(offset: 0)); - - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('d11a\n')); - }); - - test('formatTextStyle', () { - var listenerCalled = false; - final style = Style().put(Attribute.bold).put(Attribute.italic); - controller - ..addListener(() { - listenerCalled = true; - }) - ..formatTextStyle(0, 2, style); - expect(listenerCalled, isTrue); - expect(controller.document.collectAllStyles(0, 2), contains(style)); - expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); - }); - - test('formatText', () { - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..formatText(0, 2, Attribute.bold); - expect(listenerCalled, isTrue); - expect(controller.document.collectAllStyles(0, 2), - contains(Style().put(Attribute.bold))); - expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); - }); - - test('formatSelection', () { - var listenerCalled = false; - controller - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 2), - ChangeSource.LOCAL) - ..addListener(() { - listenerCalled = true; - }) - ..formatSelection(Attribute.bold); - expect(listenerCalled, isTrue); - expect(controller.document.collectAllStyles(0, 2), - contains(Style().put(Attribute.bold))); - expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); - }); - - test('moveCursorToStart', () { - var listenerCalled = false; - controller - ..updateSelection( - const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL) - ..addListener(() { - listenerCalled = true; - }); - expect(controller.selection, const TextSelection.collapsed(offset: 4)); - - controller.moveCursorToStart(); - expect(listenerCalled, isTrue); - expect(controller.selection, const TextSelection.collapsed(offset: 0)); - }); - - test('moveCursorToPosition', () { - var listenerCalled = false; - controller.addListener(() { - listenerCalled = true; - }); - expect(controller.selection, const TextSelection.collapsed(offset: 0)); - - controller.moveCursorToPosition(2); - expect(listenerCalled, isTrue); - expect(controller.selection, const TextSelection.collapsed(offset: 2)); - }); - - test('moveCursorToEnd', () { - var listenerCalled = false; - controller.addListener(() { - listenerCalled = true; - }); - expect(controller.selection, const TextSelection.collapsed(offset: 0)); - - controller.moveCursorToEnd(); - expect(listenerCalled, isTrue); - expect(controller.selection, - TextSelection.collapsed(offset: controller.document.length - 1)); - }); - - test('updateSelection', () { - var listenerCalled = false; - const selection = TextSelection.collapsed(offset: 0); - controller - ..addListener(() { - listenerCalled = true; - }) - ..updateSelection(selection, ChangeSource.LOCAL); - - expect(listenerCalled, isTrue); - expect(controller.selection, selection); - }); - - test('compose', () { - var listenerCalled = false; - final originalContents = controller.document.toPlainText(); - controller - ..addListener(() { - listenerCalled = true; - }) - ..compose(Delta()..insert('test '), - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); - - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), - Delta()..insert('test $originalContents')); - }); - }); -} diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart deleted file mode 100644 index 3fd425fc..00000000 --- a/test/widgets/editor_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'dart:convert' show jsonDecode; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/flutter_quill_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - late QuillController controller; - - setUp(() { - controller = QuillController.basic(); - }); - - tearDown(() { - controller.dispose(); - }); - - group('QuillEditor', () { - testWidgets('Keyboard entered text is stored in document', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: QuillEditor.basic(controller: controller, readOnly: false), - ), - ); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - expect(controller.document.toPlainText(), 'test\n'); - }); - - testWidgets('insertContent is handled correctly', (tester) async { - String? latestUri; - await tester.pumpWidget( - MaterialApp( - home: QuillEditor( - controller: controller, - focusNode: FocusNode(), - scrollController: ScrollController(), - scrollable: true, - padding: const EdgeInsets.all(0), - autoFocus: true, - readOnly: false, - expands: true, - contentInsertionConfiguration: ContentInsertionConfiguration( - onContentInserted: (content) { - latestUri = content.uri; - }, - allowedMimeTypes: const ['image/gif'], - ), - ), - ), - ); - await tester.tap(find.byType(QuillEditor)); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - await tester.idle(); - - const uri = - 'content://com.google.android.inputmethod.latin.fileprovider/test.gif'; - final messageBytes = - const JSONMessageCodec().encodeMessage({ - 'args': [ - -1, - 'TextInputAction.commitContent', - jsonDecode( - '{"mimeType": "image/gif", "data": [0,1,0,1,0,1,0,0,0], "uri": "$uri"}'), - ], - 'method': 'TextInputClient.performAction', - }); - - Object? error; - try { - await tester.binding.defaultBinaryMessenger - .handlePlatformMessage('flutter/textinput', messageBytes, (_) {}); - } catch (e) { - error = e; - } - expect(error, isNull); - expect(latestUri, equals(uri)); - }); - }); -} From 0906f71b53cd6530905874cb72a3df00549e4f36 Mon Sep 17 00:00:00 2001 From: Benjamin Quinn Date: Tue, 13 Jun 2023 11:03:59 -0400 Subject: [PATCH 198/204] Always use text cursor for editor on desktop (#1264) --- example/lib/pages/home_page.dart | 128 +++++++++++++++---------------- lib/src/widgets/raw_editor.dart | 76 +++++++++--------- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 11baf428..9e3bcb02 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -158,76 +158,70 @@ class _HomePageState extends State { } Widget _buildWelcomeEditor(BuildContext context) { - Widget quillEditor = MouseRegion( - cursor: SystemMouseCursors.text, - child: QuillEditor( - controller: _controller!, - scrollController: ScrollController(), - scrollable: true, - focusNode: _focusNode, - autoFocus: false, - readOnly: false, - placeholder: 'Add content', - enableSelectionToolbar: isMobile(), - expands: false, - padding: EdgeInsets.zero, - onImagePaste: _onImagePaste, - onTapUp: (details, p1) { - return _onTripleClickSelection(); - }, - customStyles: DefaultStyles( - h1: DefaultTextBlockStyle( - const TextStyle( - fontSize: 32, - color: Colors.black, - height: 1.15, - fontWeight: FontWeight.w300, - ), - const VerticalSpacing(16, 0), - const VerticalSpacing(0, 0), - null), - sizeSmall: const TextStyle(fontSize: 9), - ), - embedBuilders: [ - ...FlutterQuillEmbeds.builders(), - NotesEmbedBuilder(addEditNote: _addEditNote) - ], + Widget quillEditor = QuillEditor( + controller: _controller!, + scrollController: ScrollController(), + scrollable: true, + focusNode: _focusNode, + autoFocus: false, + readOnly: false, + placeholder: 'Add content', + enableSelectionToolbar: isMobile(), + expands: false, + padding: EdgeInsets.zero, + onImagePaste: _onImagePaste, + onTapUp: (details, p1) { + return _onTripleClickSelection(); + }, + customStyles: DefaultStyles( + h1: DefaultTextBlockStyle( + const TextStyle( + fontSize: 32, + color: Colors.black, + height: 1.15, + fontWeight: FontWeight.w300, + ), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), + null), + sizeSmall: const TextStyle(fontSize: 9), ), + embedBuilders: [ + ...FlutterQuillEmbeds.builders(), + NotesEmbedBuilder(addEditNote: _addEditNote) + ], ); if (kIsWeb) { - quillEditor = MouseRegion( - cursor: SystemMouseCursors.text, - child: QuillEditor( - controller: _controller!, - scrollController: ScrollController(), - scrollable: true, - focusNode: _focusNode, - autoFocus: false, - readOnly: false, - placeholder: 'Add content', - expands: false, - padding: EdgeInsets.zero, - onTapUp: (details, p1) { - return _onTripleClickSelection(); - }, - customStyles: DefaultStyles( - h1: DefaultTextBlockStyle( - const TextStyle( - fontSize: 32, - color: Colors.black, - height: 1.15, - fontWeight: FontWeight.w300, - ), - const VerticalSpacing(16, 0), - const VerticalSpacing(0, 0), - null), - sizeSmall: const TextStyle(fontSize: 9), - ), - embedBuilders: [ - ...defaultEmbedBuildersWeb, - NotesEmbedBuilder(addEditNote: _addEditNote), - ]), - ); + quillEditor = QuillEditor( + controller: _controller!, + scrollController: ScrollController(), + scrollable: true, + focusNode: _focusNode, + autoFocus: false, + readOnly: false, + placeholder: 'Add content', + expands: false, + padding: EdgeInsets.zero, + onTapUp: (details, p1) { + return _onTripleClickSelection(); + }, + customStyles: DefaultStyles( + h1: DefaultTextBlockStyle( + const TextStyle( + fontSize: 32, + color: Colors.black, + height: 1.15, + fontWeight: FontWeight.w300, + ), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), + null), + sizeSmall: const TextStyle(fontSize: 9), + ), + embedBuilders: [ + ...defaultEmbedBuildersWeb, + NotesEmbedBuilder(addEditNote: _addEditNote), + ]); } var toolbar = QuillToolbar.basic( controller: _controller!, diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 9319bfab..48b65df0 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -461,23 +461,26 @@ class RawEditorState extends EditorState Widget child = CompositedTransformTarget( link: _toolbarLayerLink, child: Semantics( - child: _Editor( - key: _editorKey, - document: _doc, - selection: controller.selection, - hasFocus: _hasFocus, - scrollable: widget.scrollable, - cursorController: _cursorCont, - textDirection: _textDirection, - startHandleLayerLink: _startHandleLayerLink, - endHandleLayerLink: _endHandleLayerLink, - onSelectionChanged: _handleSelectionChanged, - onSelectionCompleted: _handleSelectionCompleted, - scrollBottomInset: widget.scrollBottomInset, - padding: widget.padding, - maxContentWidth: widget.maxContentWidth, - floatingCursorDisabled: widget.floatingCursorDisabled, - children: _buildChildren(_doc, context), + child: MouseRegion( + cursor: SystemMouseCursors.text, + child: _Editor( + key: _editorKey, + document: _doc, + selection: controller.selection, + hasFocus: _hasFocus, + scrollable: widget.scrollable, + cursorController: _cursorCont, + textDirection: _textDirection, + startHandleLayerLink: _startHandleLayerLink, + endHandleLayerLink: _endHandleLayerLink, + onSelectionChanged: _handleSelectionChanged, + onSelectionCompleted: _handleSelectionCompleted, + scrollBottomInset: widget.scrollBottomInset, + padding: widget.padding, + maxContentWidth: widget.maxContentWidth, + floatingCursorDisabled: widget.floatingCursorDisabled, + children: _buildChildren(_doc, context), + ), ), ), ); @@ -499,24 +502,27 @@ class RawEditorState extends EditorState physics: widget.scrollPhysics, viewportBuilder: (_, offset) => CompositedTransformTarget( link: _toolbarLayerLink, - child: _Editor( - key: _editorKey, - offset: offset, - document: _doc, - selection: controller.selection, - hasFocus: _hasFocus, - scrollable: widget.scrollable, - textDirection: _textDirection, - startHandleLayerLink: _startHandleLayerLink, - endHandleLayerLink: _endHandleLayerLink, - onSelectionChanged: _handleSelectionChanged, - onSelectionCompleted: _handleSelectionCompleted, - scrollBottomInset: widget.scrollBottomInset, - padding: widget.padding, - maxContentWidth: widget.maxContentWidth, - cursorController: _cursorCont, - floatingCursorDisabled: widget.floatingCursorDisabled, - children: _buildChildren(_doc, context), + child: MouseRegion( + cursor: SystemMouseCursors.text, + child: _Editor( + key: _editorKey, + offset: offset, + document: _doc, + selection: controller.selection, + hasFocus: _hasFocus, + scrollable: widget.scrollable, + textDirection: _textDirection, + startHandleLayerLink: _startHandleLayerLink, + endHandleLayerLink: _endHandleLayerLink, + onSelectionChanged: _handleSelectionChanged, + onSelectionCompleted: _handleSelectionCompleted, + scrollBottomInset: widget.scrollBottomInset, + padding: widget.padding, + maxContentWidth: widget.maxContentWidth, + cursorController: _cursorCont, + floatingCursorDisabled: widget.floatingCursorDisabled, + children: _buildChildren(_doc, context), + ), ), ), ), From 0fd790e157d9296fed2ce590294decc6992a561d Mon Sep 17 00:00:00 2001 From: Richard Marshall Date: Tue, 13 Jun 2023 11:32:22 -0700 Subject: [PATCH 199/204] First pass of tests and simple CI (#1265) --- .github/workflows/main.yml | 23 ++ .gitignore | 1 + README.md | 16 ++ lib/flutter_quill_test.dart | 3 + lib/src/test/widget_tester_extension.dart | 60 +++++ pubspec.yaml | 2 +- test/bug_fix_test.dart | 60 +++++ test/widgets/controller_test.dart | 290 ++++++++++++++++++++++ test/widgets/editor_test.dart | 82 ++++++ 9 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/main.yml create mode 100644 lib/flutter_quill_test.dart create mode 100644 lib/src/test/widget_tester_extension.dart create mode 100644 test/bug_fix_test.dart create mode 100644 test/widgets/controller_test.dart create mode 100644 test/widgets/editor_test.dart diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c9262307 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,23 @@ +name: flutter-quill CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - run: flutter --version + - run: flutter pub get + - run: flutter pub get -C flutter_quill_extensions + - run: flutter analyze + - run: flutter test + - run: flutter pub publish --dry-run diff --git a/.gitignore b/.gitignore index 08c3c879..6b87759f 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ .pub-cache/ .pub/ build/ +coverage/ # Android related **/android/**/gradle-wrapper.jar diff --git a/README.md b/README.md index d31e9a58..9fe48b01 100644 --- a/README.md +++ b/README.md @@ -391,6 +391,22 @@ tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server- It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) Typescript/Javascript package. +## Testing + +To aid in testing applications using the editor an extension to the flutter `WidgetTester` is provided which includes methods to simplify interacting with the editor in test cases. + +Import the test utilities in your test file: + +```dart +import 'package:flutter_quill/flutter_quill_test.dart'; +``` + +and then enter text using `quillEnterText`: + +```dart +await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); +``` + ## Sponsors diff --git a/lib/flutter_quill_test.dart b/lib/flutter_quill_test.dart new file mode 100644 index 00000000..988e4e82 --- /dev/null +++ b/lib/flutter_quill_test.dart @@ -0,0 +1,3 @@ +library flutter_quill_test; + +export 'src/test/widget_tester_extension.dart'; diff --git a/lib/src/test/widget_tester_extension.dart b/lib/src/test/widget_tester_extension.dart new file mode 100644 index 00000000..21bb75ab --- /dev/null +++ b/lib/src/test/widget_tester_extension.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../widgets/editor.dart'; +import '../widgets/raw_editor.dart'; + +/// Extends +extension QuillEnterText on WidgetTester { + /// Give the QuillEditor widget specified by [finder] the focus. + Future quillGiveFocus(Finder finder) { + return TestAsyncUtils.guard(() async { + final editor = state( + find.descendant( + of: finder, + matching: + find.byType(QuillEditor, skipOffstage: finder.skipOffstage), + matchRoot: true), + ); + editor.widget.focusNode.requestFocus(); + await pump(); + expect(editor.widget.focusNode.hasFocus, isTrue); + }); + } + + /// Give the QuillEditor widget specified by [finder] the focus and update its + /// editing value with [text], as if it had been provided by the onscreen + /// keyboard. + /// + /// The widget specified by [finder] must be a [QuillEditor] or have a + /// [QuillEditor] descendant. For example `find.byType(QuillEditor)`. + Future quillEnterText(Finder finder, String text) async { + return TestAsyncUtils.guard(() async { + await quillGiveFocus(finder); + await quillUpdateEditingValue(finder, text); + await idle(); + }); + } + + /// Update the text editing value of the QuillEditor widget specified by + /// [finder] with [text], as if it had been provided by the onscreen keyboard. + /// + /// The widget specified by [finder] must already have focus and be a + /// [QuillEditor] or have a [QuillEditor] descendant. For example + /// `find.byType(QuillEditor)`. + Future quillUpdateEditingValue(Finder finder, String text) async { + return TestAsyncUtils.guard(() async { + final editor = state( + find.descendant( + of: finder, + matching: find.byType(RawEditor, skipOffstage: finder.skipOffstage), + matchRoot: true), + ); + testTextInput.updateEditingValue(TextEditingValue( + text: text, + selection: TextSelection.collapsed( + offset: editor.textEditingValue.text.length))); + await idle(); + }); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 4ca8ee66..db62be2b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: platform: ^3.1.0 pasteboard: ^0.2.0 -dev_dependencies: + # Dependencies for testing utilities flutter_test: sdk: flutter diff --git a/test/bug_fix_test.dart b/test/bug_fix_test.dart new file mode 100644 index 00000000..ecbcad2b --- /dev/null +++ b/test/bug_fix_test.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/flutter_quill_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Bug fix', () { + group('1189 - The provided text position is not in the current node', () { + late QuillController controller; + late QuillEditor editor; + + setUp(() { + controller = QuillController.basic(); + editor = QuillEditor.basic(controller: controller, readOnly: false); + }); + + tearDown(() { + controller.dispose(); + }); + + testWidgets('Refocus editor after controller clears document', + (tester) async { + await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + editor.focusNode.unfocus(); + await tester.pump(); + controller.clear(); + editor.focusNode.requestFocus(); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + + testWidgets('Refocus editor after removing block attribute', + (tester) async { + await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + controller.formatSelection(Attribute.ul); + editor.focusNode.unfocus(); + await tester.pump(); + controller.formatSelection(const ListAttribute(null)); + editor.focusNode.requestFocus(); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + + testWidgets('Tap checkbox in unfocused editor', (tester) async { + await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + controller.formatSelection(Attribute.unchecked); + editor.focusNode.unfocus(); + await tester.pump(); + await tester.tap(find.byType(CheckboxPoint)); + expect(tester.takeException(), isNull); + }); + }); + }); +} diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart new file mode 100644 index 00000000..047dfcae --- /dev/null +++ b/test/widgets/controller_test.dart @@ -0,0 +1,290 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const testDocumentContents = 'data'; + late QuillController controller; + + setUp(() { + controller = QuillController.basic() + ..compose(Delta()..insert(testDocumentContents), + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); + }); + + group('controller', () { + test('set document', () { + const replacementContents = 'replacement\n'; + final newDocument = + Document.fromDelta(Delta()..insert(replacementContents)); + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..document = newDocument; + expect(listenerCalled, isTrue); + expect(controller.document.toPlainText(), replacementContents); + }); + + test('getSelectionStyle', () { + controller + ..formatText(0, 5, Attribute.h1) + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), + ChangeSource.LOCAL); + + expect(controller.getSelectionStyle().values, [Attribute.h1]); + }); + + test('indentSelection with single line document', () { + var listenerCalled = false; + // With selection range + controller + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), + ChangeSource.LOCAL) + ..addListener(() { + listenerCalled = true; + }) + ..indentSelection(true); + expect(listenerCalled, isTrue); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller.indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL2]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, []); + + // With collapsed selection + controller + ..updateSelection( + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + ..indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller + ..updateSelection( + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + ..indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL2]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + controller.indentSelection(false); + expect(controller.getSelectionStyle().values, []); + }); + + test('indentSelection with multiline document', () { + controller + ..compose(Delta()..insert('line1\nline2\nline3\n'), + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + // Indent first line + ..updateSelection( + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) + ..indentSelection(true); + expect(controller.getSelectionStyle().values, [Attribute.indentL1]); + + // Indent first two lines + controller + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 11), + ChangeSource.LOCAL) + ..indentSelection(true); + + // Should have both L1 and L2 indent attributes in selection. + expect(controller.getAllSelectionStyles(), + contains(Style().put(Attribute.indentL1).put(Attribute.indentL2))); + + // Remaining lines should have no attributes. + controller.updateSelection( + TextSelection( + baseOffset: 12, + extentOffset: controller.document.toPlainText().length - 1), + ChangeSource.LOCAL); + expect(controller.getAllSelectionStyles(), everyElement(Style())); + }); + + test('getAllIndividualSelectionStyles', () { + controller.formatText(0, 2, Attribute.bold); + final result = controller.getAllIndividualSelectionStyles(); + expect(result.length, 1); + expect(result[0].offset, 0); + expect(result[0].value, Style().put(Attribute.bold)); + }); + + test('getPlainText', () { + controller.updateSelection( + const TextSelection(baseOffset: 0, extentOffset: 4), + ChangeSource.LOCAL); + + expect(controller.getPlainText(), testDocumentContents); + }); + + test('getAllSelectionStyles', () { + controller.formatText(0, 2, Attribute.bold); + expect(controller.getAllSelectionStyles(), + contains(Style().put(Attribute.bold))); + }); + + test('undo', () { + var listenerCalled = false; + controller.updateSelection( + const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); + + expect(controller.document.toDelta(), Delta()..insert('data\n')); + controller + ..addListener(() { + listenerCalled = true; + }) + ..undo(); + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('\n')); + }); + + test('redo', () { + var listenerCalled = false; + controller.updateSelection( + const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); + + expect(controller.document.toDelta(), Delta()..insert('data\n')); + controller.undo(); + expect(controller.document.toDelta(), Delta()..insert('\n')); + controller + ..addListener(() { + listenerCalled = true; + }) + ..redo(); + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('data\n')); + }); + test('clear', () { + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..clear(); + + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('\n')); + }); + + test('replaceText', () { + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..replaceText(1, 2, '11', const TextSelection.collapsed(offset: 0)); + + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), Delta()..insert('d11a\n')); + }); + + test('formatTextStyle', () { + var listenerCalled = false; + final style = Style().put(Attribute.bold).put(Attribute.italic); + controller + ..addListener(() { + listenerCalled = true; + }) + ..formatTextStyle(0, 2, style); + expect(listenerCalled, isTrue); + expect(controller.document.collectAllStyles(0, 2), contains(style)); + expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); + }); + + test('formatText', () { + var listenerCalled = false; + controller + ..addListener(() { + listenerCalled = true; + }) + ..formatText(0, 2, Attribute.bold); + expect(listenerCalled, isTrue); + expect(controller.document.collectAllStyles(0, 2), + contains(Style().put(Attribute.bold))); + expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); + }); + + test('formatSelection', () { + var listenerCalled = false; + controller + ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 2), + ChangeSource.LOCAL) + ..addListener(() { + listenerCalled = true; + }) + ..formatSelection(Attribute.bold); + expect(listenerCalled, isTrue); + expect(controller.document.collectAllStyles(0, 2), + contains(Style().put(Attribute.bold))); + expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); + }); + + test('moveCursorToStart', () { + var listenerCalled = false; + controller + ..updateSelection( + const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL) + ..addListener(() { + listenerCalled = true; + }); + expect(controller.selection, const TextSelection.collapsed(offset: 4)); + + controller.moveCursorToStart(); + expect(listenerCalled, isTrue); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + }); + + test('moveCursorToPosition', () { + var listenerCalled = false; + controller.addListener(() { + listenerCalled = true; + }); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + + controller.moveCursorToPosition(2); + expect(listenerCalled, isTrue); + expect(controller.selection, const TextSelection.collapsed(offset: 2)); + }); + + test('moveCursorToEnd', () { + var listenerCalled = false; + controller.addListener(() { + listenerCalled = true; + }); + expect(controller.selection, const TextSelection.collapsed(offset: 0)); + + controller.moveCursorToEnd(); + expect(listenerCalled, isTrue); + expect(controller.selection, + TextSelection.collapsed(offset: controller.document.length - 1)); + }); + + test('updateSelection', () { + var listenerCalled = false; + const selection = TextSelection.collapsed(offset: 0); + controller + ..addListener(() { + listenerCalled = true; + }) + ..updateSelection(selection, ChangeSource.LOCAL); + + expect(listenerCalled, isTrue); + expect(controller.selection, selection); + }); + + test('compose', () { + var listenerCalled = false; + final originalContents = controller.document.toPlainText(); + controller + ..addListener(() { + listenerCalled = true; + }) + ..compose(Delta()..insert('test '), + const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); + + expect(listenerCalled, isTrue); + expect(controller.document.toDelta(), + Delta()..insert('test $originalContents')); + }); + }); +} diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart new file mode 100644 index 00000000..3fd425fc --- /dev/null +++ b/test/widgets/editor_test.dart @@ -0,0 +1,82 @@ +import 'dart:convert' show jsonDecode; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/flutter_quill_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + late QuillController controller; + + setUp(() { + controller = QuillController.basic(); + }); + + tearDown(() { + controller.dispose(); + }); + + group('QuillEditor', () { + testWidgets('Keyboard entered text is stored in document', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: QuillEditor.basic(controller: controller, readOnly: false), + ), + ); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + + expect(controller.document.toPlainText(), 'test\n'); + }); + + testWidgets('insertContent is handled correctly', (tester) async { + String? latestUri; + await tester.pumpWidget( + MaterialApp( + home: QuillEditor( + controller: controller, + focusNode: FocusNode(), + scrollController: ScrollController(), + scrollable: true, + padding: const EdgeInsets.all(0), + autoFocus: true, + readOnly: false, + expands: true, + contentInsertionConfiguration: ContentInsertionConfiguration( + onContentInserted: (content) { + latestUri = content.uri; + }, + allowedMimeTypes: const ['image/gif'], + ), + ), + ), + ); + await tester.tap(find.byType(QuillEditor)); + await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); + await tester.idle(); + + const uri = + 'content://com.google.android.inputmethod.latin.fileprovider/test.gif'; + final messageBytes = + const JSONMessageCodec().encodeMessage({ + 'args': [ + -1, + 'TextInputAction.commitContent', + jsonDecode( + '{"mimeType": "image/gif", "data": [0,1,0,1,0,1,0,0,0], "uri": "$uri"}'), + ], + 'method': 'TextInputClient.performAction', + }); + + Object? error; + try { + await tester.binding.defaultBinaryMessenger + .handlePlatformMessage('flutter/textinput', messageBytes, (_) {}); + } catch (e) { + error = e; + } + expect(error, isNull); + expect(latestUri, equals(uri)); + }); + }); +} From ba809c823439ffddf105cdb8f275af678c85c073 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Tue, 13 Jun 2023 12:49:25 -0700 Subject: [PATCH 200/204] Upgrade to 7.2.5 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f1e223..f0b10bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.5] +- Always use text cursor for editor on desktop. + # [7.2.4] - Fixed keepStyleOnNewLine. diff --git a/pubspec.yaml b/pubspec.yaml index db62be2b..432ba8ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.4 +version: 7.2.5 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From 3fb5b7a0a23e78383a024e7871fea6c43db7cff6 Mon Sep 17 00:00:00 2001 From: Richard Marshall Date: Tue, 13 Jun 2023 16:25:28 -0700 Subject: [PATCH 201/204] Style custom toolbar buttons like builtins (#1267) --- lib/src/widgets/toolbar.dart | 28 ++++++-------- lib/src/widgets/toolbar/custom_button.dart | 43 +++++++++++++++++++++ lib/src/widgets/toolbar/history_button.dart | 2 +- lib/src/widgets/toolbar/indent_button.dart | 2 +- lib/src/widgets/toolbar/search_button.dart | 2 +- test/bug_fix_test.dart | 35 +++++++++++++++++ 6 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 lib/src/widgets/toolbar/custom_button.dart diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index fb01c4df..91521fb0 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -11,13 +11,13 @@ import 'embeds.dart'; import 'toolbar/arrow_indicated_button_list.dart'; import 'toolbar/clear_format_button.dart'; import 'toolbar/color_button.dart'; +import 'toolbar/custom_button.dart'; import 'toolbar/enum.dart'; import 'toolbar/history_button.dart'; import 'toolbar/indent_button.dart'; import 'toolbar/link_style_button.dart'; import 'toolbar/quill_font_family_button.dart'; import 'toolbar/quill_font_size_button.dart'; -import 'toolbar/quill_icon_button.dart'; import 'toolbar/search_button.dart'; import 'toolbar/select_alignment_button.dart'; import 'toolbar/select_header_style_button.dart'; @@ -26,6 +26,7 @@ import 'toolbar/toggle_style_button.dart'; export 'toolbar/clear_format_button.dart'; export 'toolbar/color_button.dart'; +export 'toolbar/custom_button.dart'; export 'toolbar/history_button.dart'; export 'toolbar/indent_button.dart'; export 'toolbar/link_style_button.dart'; @@ -566,19 +567,14 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { QuillDivider(axis, color: sectionDividerColor, space: sectionDividerSpace), for (var customButton in customButtons) - QuillIconButton( - highlightElevation: 0, - hoverElevation: 0, - size: toolbarIconSize * kIconButtonFactor, - icon: Icon( - customButton.icon, - size: toolbarIconSize, - color: customButton.iconColor, - ), - tooltip: customButton.tooltip, - borderRadius: iconTheme?.borderRadius ?? 2, + CustomButton( onPressed: customButton.onTap, - afterPressed: afterButtonPressed, + icon: customButton.icon, + iconColor: customButton.iconColor, + iconSize: toolbarIconSize, + iconTheme: iconTheme, + afterButtonPressed: afterButtonPressed, + tooltip: customButton.tooltip, ), ], ); @@ -650,7 +646,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// The divider which is used for separation of buttons in the toolbar. /// /// It can be used outside of this package, for example when user does not use -/// [QuillToolbar.basic] and compose toolbat's children on its own. +/// [QuillToolbar.basic] and compose toolbar's children on its own. class QuillDivider extends StatelessWidget { const QuillDivider( this.axis, { @@ -659,11 +655,11 @@ class QuillDivider extends StatelessWidget { this.space, }) : super(key: key); - /// Provides a horizonal divider for vertical toolbar. + /// Provides a horizontal divider for vertical toolbar. const QuillDivider.horizontal({Color? color, double? space}) : this(Axis.horizontal, color: color, space: space); - /// Provides a horizonal divider for horizontal toolbar. + /// Provides a horizontal divider for horizontal toolbar. const QuillDivider.vertical({Color? color, double? space}) : this(Axis.vertical, color: color, space: space); diff --git a/lib/src/widgets/toolbar/custom_button.dart b/lib/src/widgets/toolbar/custom_button.dart new file mode 100644 index 00000000..614c79bc --- /dev/null +++ b/lib/src/widgets/toolbar/custom_button.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +import '../../models/themes/quill_icon_theme.dart'; +import '../toolbar.dart'; + +class CustomButton extends StatelessWidget { + const CustomButton({ + required this.onPressed, + required this.icon, + this.iconColor, + this.iconSize = kDefaultIconSize, + this.iconTheme, + this.afterButtonPressed, + this.tooltip, + Key? key, + }) : super(key: key); + + final VoidCallback? onPressed; + final IconData? icon; + final Color? iconColor; + final double iconSize; + final QuillIconTheme? iconTheme; + final VoidCallback? afterButtonPressed; + final String? tooltip; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + final iconColor = iconTheme?.iconUnselectedColor ?? theme.iconTheme.color; + return QuillIconButton( + highlightElevation: 0, + hoverElevation: 0, + size: iconSize * kIconButtonFactor, + icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, + borderRadius: iconTheme?.borderRadius ?? 2, + onPressed: onPressed, + afterPressed: afterButtonPressed, + fillColor: iconTheme?.iconUnselectedFillColor ?? theme.canvasColor, + ); + } +} diff --git a/lib/src/widgets/toolbar/history_button.dart b/lib/src/widgets/toolbar/history_button.dart index 6d3c29ad..909842c9 100644 --- a/lib/src/widgets/toolbar/history_button.dart +++ b/lib/src/widgets/toolbar/history_button.dart @@ -46,7 +46,7 @@ class _HistoryButtonState extends State { tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, - size: widget.iconSize * 1.77, + size: widget.iconSize * kIconButtonFactor, icon: Icon(widget.icon, size: widget.iconSize, color: _iconColor), fillColor: fillColor, borderRadius: widget.iconTheme?.borderRadius ?? 2, diff --git a/lib/src/widgets/toolbar/indent_button.dart b/lib/src/widgets/toolbar/indent_button.dart index 1ce83e99..89b57d4a 100644 --- a/lib/src/widgets/toolbar/indent_button.dart +++ b/lib/src/widgets/toolbar/indent_button.dart @@ -42,7 +42,7 @@ class _IndentButtonState extends State { tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, - size: widget.iconSize * 1.77, + size: widget.iconSize * kIconButtonFactor, icon: Icon(widget.icon, size: widget.iconSize, color: iconColor), fillColor: iconFillColor, borderRadius: widget.iconTheme?.borderRadius ?? 2, diff --git a/lib/src/widgets/toolbar/search_button.dart b/lib/src/widgets/toolbar/search_button.dart index 9233cf45..fc0dc79f 100644 --- a/lib/src/widgets/toolbar/search_button.dart +++ b/lib/src/widgets/toolbar/search_button.dart @@ -43,7 +43,7 @@ class SearchButton extends StatelessWidget { icon: Icon(icon, size: iconSize, color: iconColor), highlightElevation: 0, hoverElevation: 0, - size: iconSize * 1.77, + size: iconSize * kIconButtonFactor, fillColor: iconFillColor, borderRadius: iconTheme?.borderRadius ?? 2, onPressed: () => _onPressedHandler(context), diff --git a/test/bug_fix_test.dart b/test/bug_fix_test.dart index ecbcad2b..0689476e 100644 --- a/test/bug_fix_test.dart +++ b/test/bug_fix_test.dart @@ -5,6 +5,41 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('Bug fix', () { + group( + '1266 - QuillToolbar.basic() custom buttons do not have correct fill' + 'color set', () { + testWidgets('fillColor of custom buttons and builtin buttons match', + (tester) async { + const tooltip = 'custom button'; + + await tester.pumpWidget(MaterialApp( + home: QuillToolbar.basic( + showRedo: false, + controller: QuillController.basic(), + customButtons: [const QuillCustomButton(tooltip: tooltip)], + ))); + + final builtinFinder = find.descendant( + of: find.byType(HistoryButton), + matching: find.byType(QuillIconButton), + matchRoot: true); + expect(builtinFinder, findsOneWidget); + final builtinButton = + builtinFinder.evaluate().first.widget as QuillIconButton; + + final customFinder = find.descendant( + of: find.byType(QuillToolbar), + matching: find.byWidgetPredicate((widget) => + widget is QuillIconButton && widget.tooltip == tooltip), + matchRoot: true); + expect(customFinder, findsOneWidget); + final customButton = + customFinder.evaluate().first.widget as QuillIconButton; + + expect(customButton.fillColor, equals(builtinButton.fillColor)); + }); + }); + group('1189 - The provided text position is not in the current node', () { late QuillController controller; late QuillEditor editor; From a878088293c7e7fec601a9b369195958c23eb1e4 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Tue, 13 Jun 2023 16:32:26 -0700 Subject: [PATCH 202/204] Upgrade to 7.2.6 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b10bbe..6d511902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.6] +- Style custom toolbar buttons like builtins. + # [7.2.5] - Always use text cursor for editor on desktop. diff --git a/pubspec.yaml b/pubspec.yaml index 432ba8ae..f9b266dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.5 +version: 7.2.6 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill From e1fa84f5cf2c24d9367665eda0b9347ee62c1ded Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Mon, 19 Jun 2023 00:17:55 +0800 Subject: [PATCH 203/204] fixed language code of japan (#1270) --- README.md | 2 +- lib/src/translations/toolbar.i18n.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9fe48b01..3c3ca406 100644 --- a/README.md +++ b/README.md @@ -375,7 +375,7 @@ Currently, translations are available for these 27 locales: * `Locale('fa')` * `Locale('hi')` * `Locale('sr')` -* `Locale('jp')` +* `Locale('ja')` #### Contributing to translations diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 7fc356e9..be92116c 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -543,7 +543,7 @@ extension Localization on String { 'Edit': '編輯', 'Apply': '應用', }, - 'jp': { + 'ja': { 'Paste a link': 'リンクをペースト', 'Ok': '完了', 'Select Color': '色を選択', From 01b6191908f8a581928d06967d633ca714052fc2 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Sun, 18 Jun 2023 10:38:52 -0700 Subject: [PATCH 204/204] Upgrade to 7.2.7 --- CHANGELOG.md | 3 +++ pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d511902..f834734c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# [7.2.7] +- Fix language code of Japan. + # [7.2.6] - Style custom toolbar buttons like builtins. diff --git a/pubspec.yaml b/pubspec.yaml index f9b266dc..a603ba42 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.2.6 +version: 7.2.7 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill