diff --git a/.github/ISSUE_TEMPLATE/3_question.yml b/.github/ISSUE_TEMPLATE/3_question.yml index 509ae635..f21caa90 100644 --- a/.github/ISSUE_TEMPLATE/3_question.yml +++ b/.github/ISSUE_TEMPLATE/3_question.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - Thank you for using Flutter Quill! + If you have any suggestions or questions, feel free to share them in the [Discussions](https://github.com/singerdmx/flutter-quill/discussions) section. - type: checkboxes attributes: @@ -21,4 +21,4 @@ body: placeholder: | How do I save the images of Quill Editor? validations: - required: true \ No newline at end of file + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3ba13e0c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index dc9ad461..034fc634 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -74,25 +74,11 @@ jobs: id: release_tag run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - - name: 📑 Fetch release notes from Github API - id: fetch-release-notes-request - uses: fjogeleit/http-request-action@v1 - with: - url: https://api.github.com/repos/${{ github.repository }}/releases/tags/${{ steps.release_tag.outputs.tag }} - method: 'GET' - customHeaders: '{"Authorization": "${{ secrets.GITHUB_TOKEN }}"}' - preventFailureOnNoResponse: 'false' - - - name: ⚠️ Validate release notes response - run: | - releaseNotesResponse="${{ fromJson(steps.fetch-release-notes-request.outputs.response) }}" - if [[ -z "$releaseNotesResponse" || "$releaseNotesResponse" == "null" ]]; then - echo "Error: Release notes response is empty." - exit 1 - fi + - name: 📑 Fetch release notes from Github API and create a required file by the next step + run: dart ./scripts/create_version_content_from_github_release.dart "${{ github.repository }}" "${{ steps.release_tag.outputs.tag }}" - name: 📝 Update version and CHANGELOG for all the packages - run: dart ./scripts/update_package_version.dart ${{ steps.extract_version.outputs.VERSION }} '${{ fromJson(steps.fetch-release-notes-request.outputs.response).body }}' + run: dart ./scripts/update_package_version.dart ${{ steps.extract_version.outputs.VERSION }} - name: 💾 Commit updated version and CHANGELOG id: auto-commit-action diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bdf2ede..0064e681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,244 @@ All notable changes to this project will be documented in this file. +## 9.5.2 + +* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2 + +## 9.5.1 + +* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1 + +## 9.5.0 + +* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0 + +## 9.4.9 + +* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9 + +## 9.4.8 + +* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8 + +## 9.4.7 + +* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954 + +## New Contributors +* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7 + +## 9.4.6 + +* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938 +* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6 + +## 9.4.5 + +* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934 + +## New Contributors +* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5 + +## 9.4.4 + +* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4 + +## 9.4.3 + +* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926 + +## New Contributors +* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3 + +## 9.4.2 + +* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2 + +## 9.4.1 + +* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919 + +## New Contributors +* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1 + +## 9.4.0 + +This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x` + +- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`: + ```dart + QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( + searchButtonType: SearchButtonType.legacy, + ), + ) + ``` + While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future. + + If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget + + Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated) + +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b) + + +
+ +
+ Improved + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04) + +
+ + For the detailed changes, see #1904 + +- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911 + +- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call: + + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x` + + We initially wanted to publish `flutter_quill_super_clipboard` to allow: + - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins + - Using `flutter_quill_extensions` with optional `super_clipboard` + + To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above. + + Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated. + + The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by: + ```dart + // ignore: implementation_imports + import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; + ``` + + Then you can call: + ```dart + // ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; + ClipboardServiceProvider.setInstance(YourClipboardService()); +``` + + The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future. + + If you use the above imports, you might get **breaking changes** in **non-breaking change releases**. + +- Subscript and Superscript should now work for all languages and characters + + The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices. +We have removed the Apple font from the example + +- Allow pasting Markdown and HTML file content from the system to the editor + + Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor + Copying an HTML file, or HTML content from apps and websites is different than copying plain text. + + This is why this change requires `super_clipboard` implementation as this is platform-dependent: + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + as mentioned above. + + The following example for copying a Markdown file: + +
+ Markdown File Content + + ```md + + **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor + + You have two options: + + 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well + (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly + 1. Another 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 part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) + Typescript/Javascript package. + this package doesn't convert the HTML back to Quill Delta as far as we know + + ``` + +
+ +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e) + +
+ +
+ After + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788) + +
+ + Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot + however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info. + + ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9) + + For more details see #1915 + + Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. + + We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor + previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. + + Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions) + +## New Contributors +* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0 + ## 9.3.21 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 diff --git a/CHANGELOG_DATA.json b/CHANGELOG_DATA.json index 38551658..4c7e1bde 100644 --- a/CHANGELOG_DATA.json +++ b/CHANGELOG_DATA.json @@ -1 +1 @@ -{"9.3.21":"* fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.20...v9.3.21","9.3.20":"* Fix: Issue 1887 by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1892\r\n* fix: toolbar style change will be invalid when inputting more than 2 characters at a time by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1890\r\n\r\n## New Contributors\r\n* @crasowas made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1890\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.19...v9.3.20","9.3.19":"* Fix reported issues by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1886\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.18...v9.3.19","9.3.18":"* Fix: Undo/redo cursor position fixed by @Alspb in https://github.com/singerdmx/flutter-quill/pull/1885\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.17...v9.3.18","9.3.17":"* Update super_clipboard plugin to 0.8.15 to address [#1882](https://github.com/singerdmx/flutter-quill/issues/1882)\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.16...v9.3.17","9.3.16":"* Update `lint` dev package to 4.0.0\r\n* Require at least version 0.8.13 of the plugin\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.15...v9.3.16","9.3.15":"\r\n* Ci/automate updating the files by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1879\r\n* Updating outdated README.md and adding a few guidelines for CONTRIBUTING.md\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.14...v9.3.15","9.3.14":"* Chore/use original color picker package in [#1877](https://github.com/singerdmx/flutter-quill/pull/1877)","9.3.13":"* fix: `readOnlyMouseCursor` losing in construction function\n* Fix block multi-line selection style","9.3.12":"* Add `readOnlyMouseCursor` to config mouse cursor type","9.3.11":"* Fix typo in QuillHtmlConverter\n* Fix re-create checkbox","9.3.10":"* Support clipboard actions from the toolbar","9.3.9":"* fix: MD Parsing for multi space\n* fix: FontFamily and FontSize toolbars track the text selected in the editor\n* feat: Add checkBoxReadOnly property which can override readOnly for checkbox","9.3.8":"* fix: removed misleading parameters\n* fix: added missed translations for ru, es, de\n* added translations for Nepali Locale('ne', 'NP')","9.3.7":"* Fix for keyboard jumping when switching focus from a TextField\n* Toolbar button styling to reflect cursor position when running on desktops with keyboard to move care","9.3.6":"* Add SK and update CS locales [#1796](https://github.com/singerdmx/flutter-quill/pull/1796)\n* Fixes:\n * QuillIconTheme changes for FontFamily and FontSize buttons are not applied [#1797](https://github.com/singerdmx/flutter-quill/pull/1796)\n * Make the arrow_drop_down icons in the QuillToolbar the same size for all MenuAnchor buttons [#1799](https://github.com/singerdmx/flutter-quill/pull/1796)","9.3.5":"* Update the minimum version for the packages to support `device_info_plus` version 10.0.0 [#1783](https://github.com/singerdmx/flutter-quill/issues/1783)\n* Update the minimum version for `youtube_player_flutter` to new major version 9.0.0 in the `flutter_quill_extensions`","9.3.4":"* fix: multiline styling stuck/not working properly [#1782](https://github.com/singerdmx/flutter-quill/pull/1782)","9.3.3":"* Update `quill_html_converter` versions","9.3.2":"* Fix dispose of text painter [#1774](https://github.com/singerdmx/flutter-quill/pull/1774)","9.3.1":"* Require Flutter 3.19.0 as minimum version","9.3.0":"* **Breaking change**: `Document.fromHtml(html)` is now returns `Document` instead of `Delta`, use `DeltaX.fromHtml` to return `Delta`\n* Update old deprecated api from Flutter 3.19\n* Scribble scroll fix by @mtallenca in https://github.com/singerdmx/flutter-quill/pull/1745","9.2.14":"* feat: move cursor after inserting video/image\n* Apple pencil","9.2.13":"* Fix crash with inserting text from contextMenuButtonItems\n* Fix incorrect behaviour of context menu \n* fix: selection handles behaviour and unnessesary style assert\n* Update quill_fr.arb","9.2.12":"* Fix safari clipboard bug\n* Add the option to disable clipboard functionality","9.2.11":"* Fix a bug where it has problems with pasting text into the editor when the clipboard has styled text","9.2.10":"* Update example screenshots\n* Refactor `Container` to `QuillContainer` with backward compatibility\n* A workaround fix in history feature","9.2.9":"* Refactor the type of `Delta().toJson()` to be more clear type","9.2.8":"* feat: Export Container node as QuillContainer\n* fix web cursor position / height (don't use iOS logic)\n* Added Swedish translation","9.2.6":"* [fix selection.affinity always downstream after updateEditingValue](https://github.com/singerdmx/flutter-quill/pull/1682)\n* Bumb version of `super_clipboard`","9.2.5":"* Bumb version of `super_clipboard`","9.2.4":"* Use fixed version of intl","9.2.3":"* remove unncessary column in Flutter quill video embed block","9.2.2":"* Fix bug [#1627](https://github.com/singerdmx/flutter-quill/issues/1627)","9.2.1":"* Fix [bug](https://github.com/singerdmx/flutter-quill/issues/1119#issuecomment-1872605246) with font size button\n* Added ro RO translations\n* 📖 Update zh, zh_CN translations","9.2.0":"* Require minimum version `6.0.0` of `flutter_keyboard_visibility` to fix some build issues with Android Gradle Plugin 8.2.0\n* Add on image clicked in `flutter_quill_extensions` callback\n* Deprecate `globalIconSize` and `globalIconButtonFactor`, use `iconSize` and `iconButtonFactor` instead\n* Fix the `QuillToolbarSelectAlignmentButtons`","9.1.1":"* Require `super_clipboard` minimum version `0.8.1` to fix some bug with Linux build failure","9.1.1-dev":"* Fix bug [#1636](https://github.com/singerdmx/flutter-quill/issues/1636)\n* Fix a where you paste styled content (HTML) it always insert a new line at first even if the document is empty\n* Fix the font size button and migrate to `MenuAnchor`\n* The `defaultDisplayText` is no longer required in the font size and header dropdown buttons\n* Add pdf converter in a new package (`quill_pdf_converter`)","9.1.0":"* Fix the simple toolbar by add properties of `IconButton` and fix some buttons","9.1.0-dev.2":"* Fix the history buttons","9.1.0-dev.1":"* Bug fixes in the simple toolbar buttons","9.1.0-dev":"* **Breaking Change**: in the `QuillSimpleToolbar` Fix the `QuillIconTheme` by replacing all the properties with two properties of type `ButtonStyle`, use `IconButton.styleFrom()`","9.0.6":"* Fix bug in QuillToolbarSelectAlignmentButtons","9.0.5":"* You can now use most of the buttons without internal provider","9.0.4":"* Feature: [#1611](https://github.com/singerdmx/flutter-quill/issues/1611)\n* Export missing widgets","9.0.3":"* Flutter Quill Extensions:\n * Fix file image support for web image emebed builder","9.0.2":"* Remove unused properties in the `QuillToolbarSelectHeaderStyleDropdownButton`\n* Fix the `QuillSimpleToolbar` when `useMaterial3` is false, please upgrade to the latest version of flutter for better support","9.0.2-dev.3":"* Export `QuillSingleChildScrollView`","9.0.2-dev.2":"* Add the new translations for ru, uk arb files by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575)\n* Add a new dropdown button by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575)\n* Update the default style values by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575)\n* Fix bug [#1562](https://github.com/singerdmx/flutter-quill/issues/1562)\n* Fix the second bug of [#1480](https://github.com/singerdmx/flutter-quill/issues/1480)","9.0.2-dev.1":"* Add configurations for the new dropdown `QuillToolbarSelectHeaderStyleButton`, you can use the orignal one or this\n* Fix the [issue](https://github.com/singerdmx/flutter-quill/issues/1119) when enter is pressed, all font settings is lost","9.0.2-dev":"* **Breaking change** Remove the spacer widget, removed the controller option for each button\n* Add `toolbarRunSpacing` property to the simple toolbar","9.0.1":"* Fix default icon size","9.0.0":"* This version is quite stable but it's not how we wanted to be, because the lack of time and there are not too many maintainers active, we decided to publish it, we might make a new breaking changes verion","9.0.1-dev.1":"* Flutter Quill Extensions:\n * Update `QuillImageUtilities` and fixining some bugs","9.0.1-dev":"* Test new GitHub workflows","9.0.0-dev-10":"* Fix a bug of the improved pasting HTML contents contents into the editor","9.0.0-dev-9":"* Improves the new logic of pasting HTML contents into the Editor\n* Update `README.md` and the doc\n* Dispose the `QuillToolbarSelectHeaderStyleButton` state listener in `dispose`\n* Upgrade the font family button to material 3\n* Rework the font family and font size functionalities to change the font once and type all over the editor","9.0.0-dev-8":"* Better support for pasting HTML contents from external websites to the editor\n* The experimental support of converting the HTML from `quill_html_converter` is now built-in in the `flutter_quill` and removed from there (Breaking change for `quill_html_converter`)","9.0.0-dev-7":"* Fix a bug in chaning the background/font color of ol/ul list\n* Flutter Quill Extensions:\n * Fix link bug in the video url\n * Fix patterns","9.0.0-dev-6":"* Move the `child` from `QuillToolbarConfigurations` into `QuillToolbar` directly\n* Bug fixes\n* Add the ability to change the background and font color of the ol/ul elements dots and numbers\n* Flutter Quill Extensions:\n * **Breaking Change**: The `imageProviderBuilder`is now providing the context and image url","9.0.0-dev-5":"* The `QuillToolbar` is now accepting only `child` with no configurations so you can customize everything you wants, the `QuillToolbar.simple()` or `QuillSimpleToolbar` implements a simple toolbar that is based on `QuillToolbar`, you are free to use it but it just an example and not standard\n* Flutter Quill Extensions:\n * Improve the camera button","9.0.0-dev-4":"* The options parameter in all of the buttons is no longer required which can be useful to create custom toolbar with minimal efforts\n* Toolbar buttons fixes in both `flutter_quill` and `flutter_quill_extensions`\n* The `QuillProvider` has been dropped and no longer used, the providers will be used only internally from now on and we will not using them as much as possible","9.0.0-dev-3":"* Breaking Changes:\n * Rename `QuillToolbar` to `QuillSimpleToolbar`\n * Rename `QuillBaseToolbar` to `QuillToolbar`\n * Replace `pasteboard` with `rich_cliboard`\n* Fix a bug in the example when inserting an image from url\n* Flutter Quill Extensions:\n * Add support for copying the image to the system cliboard","9.0.0-dev-2":"* An attemp to fix CI automated publishing","9.0.0-dev-1":"* An attemp to fix CI automated publishing","9.0.0-dev":"* **Major Breaking change**: The `QuillProvider` is now optional, the `controller` parameter has been moved to the `QuillEditor` and `QuillToolbar` once again.\n* Flutter Quill Extensions;\n * **Breaking Change**: Completly change the way how the source code structured to more basic and simple way, organize folders and file names, if you use the library\nfrom `flutter_quill_extensions.dart` then there is nothing you need to do, but if you are using any other import then you need to re-imports\nembed, this won't affect how quill js work\n * Improvemenets to the image embed\n * Add support for `margin` for web\n * Add untranslated strings to the `quill_en.arb`","8.6.4":"* The default value of `keyboardAppearance` for the iOS will be the one from the App/System theme mode instead of always using the `Brightness.light`\n* Fix typos in `README.md`","8.6.3":"* Update the minimum flutter version to `3.16.0`","8.6.2":"* Restore use of alternative QuillToolbarLinkStyleButton2 widget","8.6.1":"* Temporary revert style bug fix","8.6.0":"* **Breaking Change** Support [Flutter 3.16](https://medium.com/flutter/whats-new-in-flutter-3-16-dba6cb1015d1), please upgrade to the latest stable version of flutter to use this update\n* **Breaking Change**: Remove Deprecated Fields\n* **Breaking Change**: Extract the shared things between `QuillToolbarConfigurations` and `QuillBaseToolbarConfigurations`\n* **Breaking Change**: You no longer need to use `QuillToolbarProvider` when using custom toolbar buttons, the example has been updated\n* Bug fixes","8.5.5":"* Now when opening dialogs by `QuillToolbar` you will not get an exception when you don't use `FlutterQuillLocalizations.delegate` in your `WidgetsApp`, `MaterialApp`, or `CupertinoApp`. The fix is for the `QuillToolbarSearchButton`, `QuillToolbarLinkStyleButton`, and `QuillToolbarColorButton` buttons","8.5.4":"* The `mobileWidth`, `mobileHeight`, `mobileMargin`, and `mobileAlignment` is now deprecated in `flutter_quill`, they are now defined in `flutter_quill_extensions`\n* Deprecate `replaceStyleStringWithSize` function which is in `string.dart`\n* Deprecate `alignment`, and `margin` as they don't conform to official Quill JS","8.5.3":"* Update doc\n* Update `README.md` and `CHANGELOG.md`\n* Fix typos\n* Use `immutable` when possible\n* Update `.pubignore`","8.5.2":"* Updated `README.md`.\n* Feature: Added the ability to include a custom callback when the `QuillToolbarColorButton` is pressed.\n* The `QuillToolbar` now implements `PreferredSizeWidget`, enabling usage in the AppBar, similar to `QuillBaseToolbar`.","8.5.1":"* Updated `README.md`.","8.5.0":"* Migrated to `flutter_localizations` for translations.\n* Fixed: Translated all previously untranslated localizations.\n* Fixed: Added translations for missing items.\n* Fixed: Introduced default Chinese fallback translation.\n* Removed: Unused parameters `items` in `QuillToolbarFontFamilyButtonOptions` and `QuillToolbarFontSizeButtonOptions`.\n* Updated: Documentation.","8.4.4":"* Updated `.pubignore` to ignore unnecessary files and folders.","8.4.3":"* Updated `CHANGELOG.md`.","8.4.2":"* **Breaking change**: Configuration for `QuillRawEditor` has been moved to a separate class. Additionally, `readOnly` has been renamed to `isReadOnly`. If using `QuillEditor`, no action is required.\n* Introduced the ability for developers to override `TextInputAction` in both `QuillRawEditor` and `QuillEditor`.\n* Enabled using `QuillRawEditor` without `QuillEditorProvider`.\n* Bug fixes.\n* Added image cropping implementation in the example.","8.4.1":"* Added `copyWith` in `OptionalSize` class.","8.4.0":"* **Breaking change**: Updated `QuillCustomButton` to use `QuillCustomButtonOptions`. Moved all properties from `QuillCustomButton` to `QuillCustomButtonOptions`, replacing `iconData` with `icon` widget for increased customization.\n* **Breaking change**: `customButtons` in `QuillToolbarConfigurations` is now of type `List`.\n* Bug fixes following the `8.0.0` update.\n* Updated `README.md`.\n* Improved platform checking.","8.3.0":"* Added `iconButtonFactor` property to `QuillToolbarBaseButtonOptions` for customizing button size relative to its icon size (defaults to `kIconButtonFactor`, consistent with previous releases).","8.2.6":"* Organized `QuillRawEditor` code.","8.2.5":"* Added `builder` property in `QuillEditorConfigurations`.","8.2.4":"* Adhered to Flutter best practices.\n* Fixed auto-focus bug.","8.2.3":"* Updated `README.md`.","8.2.2":"* Moved `flutter_quill_test` to a separate package: [flutter_quill_test](https://pub.dev/packages/flutter_quill_test).","8.2.1":"* Updated `README.md`.","8.2.0":"* Added the option to add configurations for `flutter_quill_extensions` using `extraConfigurations`.","8.1.11":"* Followed Dart best practices by using `lints` and removed `pedantic` and `platform` since they are not used.\n* Fixed text direction bug.\n* Updated `README.md`.","8.1.10":"* Secret for automated publishing to pub.dev.","8.1.9":"* Fixed automated publishing to pub.dev.","8.1.8":"* Fixed automated publishing to pub.dev.","8.1.7":"* Automated publishing to pub.dev.","8.1.6":"* Fixed compatibility with `integration_test` by downgrading the minimum version of the platform package to 3.1.0.","8.1.5":"* Reversed background/font color toolbar button icons.","8.1.4":"* Reversed background/font color toolbar button tooltips.","8.1.3":"* Moved images to screenshots instead of `README.md`.","8.1.2":"* Fixed a bug related to the regexp of the insert link dialog.\n* Required Dart 3 as the minimum version.\n* Code cleanup.\n* Added a spacer widget between each button in the `QuillToolbar`.","8.1.1":"* Fixed null error in line.dart #1487(https://github.com/singerdmx/flutter*quill/issues/1487).","8.1.0":"* Fixed a word typo of `mirgration` to `migration` in the readme & migration document.\n* Updated migration guide.\n* Removed property `enableUnfocusOnTapOutside` in `QuillEditor` configurations and added `isOnTapOutsideEnabled` instead.\n* Added a new callback called `onTapOutside` in the `QuillEditorConfigurations` to perform actions when tapping outside the editor.\n* Fixed a bug that caused the web platform to not unfocus the editor when tapping outside of it. To override this, please pass a value to the `onTapOutside` callback.\n* Removed the old property of `iconTheme`. Instead, pass `iconTheme` in the button options; you will find the `base` property inside it with `iconTheme`.","8.0.0":"* If you have migrated recently, don't be alarmed by this update; it adds documentation, a migration guide, and marks the version as a more stable release. Although there are breaking changes (as reported by some developers), the major version was not changed due to time constraints during development. A single property was also renamed from `code` to `codeBlock` in the `elements` of the new `QuillEditorConfigurations` class.\n* Updated the README for better readability.","7.10.2":"* Removed line numbers from code blocks by default. You can still enable this feature thanks to the new configurations in the `QuillEditor`. Find the `elementOptions` property and enable `enableLineNumbers`.","7.10.1":"* Fixed issues and utilized the new parameters.\n* No longer need to use `MaterialApp` for most toolbar button child builders.\n* Compatibility with [fresh_quill_extensions](https://pub.dev/packages/fresh_quill_extensions), a temporary alternative to [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions).\n* Updated most of the documentation in `README.md`.","7.10.0":"* **Breaking change**: `QuillToolbar.basic()` can be accessed directly from `QuillToolbar()`, and the old `QuillToolbar` can be accessed from `QuillBaseToolbar`.\n* Refactored Quill editor and toolbar configurations into a single class each.\n* After changing checkbox list values, the controller will not request keyboard focus by default.\n* Moved toolbar and editor configurations directly into the widget but still use inherited widgets internally.\n* Fixes to some code after the refactoring.","7.9.0":"* Buttons Improvemenets\n* Refactor all the button configurations that used in `QuillToolbar.basic()` but there are still few lefts\n* **Breaking change**: Remove some configurations from the QuillToolbar and move them to the new `QuillProvider`, please notice this is a development version and this might be changed in the next few days, the stable release will be ready in less than 3 weeks\n* Update `flutter_quill_extensions` and it will be published into pub.dev soon.\n* Allow you to customize the search dialog by custom callback with child builder","7.8.0":"* **Important note**: this is not test release yet, it works but need more test and changes and breaking changes, we don't have development version and it will help us if you try the latest version and report the issues in Github but if you want a stable version please use `7.4.16`. this refactoring process will not take long and should be done less than three weeks with the testing.\n* We managed to refactor most of the buttons configurations and customizations in the `QuillProvider`, only three lefts then will start on refactoring the toolbar configurations\n* Code improvemenets","7.7.0":"* **Breaking change**: We have mirgrated more buttons in the toolbar configurations, you can do change them in the `QuillProvider`\n* Important bug fixes","7.6.1":"* Bug fixes","7.6.0":"* **Breaking change**: To customize the buttons in the toolbar, you can do that in the `QuillProvider`","7.5.0":"* **Breaking change**: The widgets `QuillEditor` and `QuillToolbar` are no longer have controller parameter, instead you need to make sure in the widget tree you have wrapped them with `QuillProvider` widget and provide the controller and the require configurations","7.4.16":"* Update documentation and README.md","7.4.15":"* Custom style attrbuites for platforms other than mobile (alignment, margin, width, height)\n* Bug fixes and other improvemenets","7.4.14":"* Improve performance by reducing the number of widgets rebuilt by listening to media query for only the needed things, for example instead of using `MediaQuery.of(context).size`, now we are using `MediaQuery.sizeOf(context)`\n* Add MediaButton for picking the images only since the video one is not ready\n* A new feature which allows customizing the text selection in quill editor which is useful for custom theme design system for custom app widget","7.4.13":"* Fixed tab editing when in readOnly mode.","7.4.12":"* Update the minimum version of device_info_plus to 9.1.0.","7.4.11":"* Add sw locale.","7.4.10":"* Update translations.","7.4.9":"* Style recognition fixes.","7.4.8":"* Upgrade dependencies.","7.4.7":"* Add Vietnamese and German translations.","7.4.6":"* Fix more null errors in Leaf.retain [##1394](https://github.com/singerdmx/flutter-quill/issues/1394) and Line.delete [##1395](https://github.com/singerdmx/flutter-quill/issues/1395).","7.4.5":"* Fix null error in Container.insert [##1392](https://github.com/singerdmx/flutter-quill/issues/1392).","7.4.4":"* Fix extra padding on checklists [##1131](https://github.com/singerdmx/flutter-quill/issues/1131).","7.4.3":"* Fixed a space input error on iPad.","7.4.2":"* Fix bug with keepStyleOnNewLine for link.","7.4.1":"* Fix toolbar dividers condition.","7.4.0":"* Support Flutter version 3.13.0.","7.3.3":"* Updated Dependencies conflicting.","7.3.2":"* Added builder for custom button in _LinkDialog.","7.3.1":"* Added case sensitive and whole word search parameters.\n* Added wrap around.\n* Moved search dialog to the bottom in order not to override the editor and the text found.\n* Other minor search dialog enhancements.","7.3.0":"* Add default attributes to basic factory.","7.2.19":"* Feat/link regexp.","7.2.18":"* Fix paste block text in words apply same style.","7.2.17":"* Fix paste text mess up style.\n* Add support copy/cut block text.","7.2.16":"* Allow for custom context menu.","7.2.15":"* Add flutter_quill.delta library which only exposes Delta datatype.","7.2.14":"* Fix errors when the editor is used in the `screenshot` package.","7.2.13":"* Fix around image can't delete line break.","7.2.12":"* Add support for copy/cut select image and text together.","7.2.11":"* Add affinity for localPosition.","7.2.10":"* LINE._getPlainText queryChild inclusive=false.","7.2.9":"* Add toPlainText method to `EmbedBuilder`.","7.2.8":"* Add custom button widget in toolbar.","7.2.7":"* Fix language code of Japan.","7.2.6":"* Style custom toolbar buttons like builtins.","7.2.5":"* Always use text cursor for editor on desktop.","7.2.4":"* Fixed keepStyleOnNewLine.","7.2.3":"* Get pixel ratio from view.","7.2.2":"* Prevent operations on stale editor state.","7.2.1":"* Add support for android keyboard content insertion.\n* 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.","7.1.20":"* Pass linestyle to embedded block.","7.1.19":"* Fix Rtl leading alignment problem.","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":"* 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.","7.1.14":"* Add indents change for multiline selection.","7.1.13":"* Add custom recognizer.","7.1.12":"* Add superscript and subscript styles.","7.1.11":"* Add inserting indents for lines of list if text is selected.","7.1.10":"* Image embedding tweaks\n * Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working.\n * Implement image insert for web (image as base64)","7.1.9":"* Editor tweaks PR from bambinoua(https://github.com/bambinoua).\n * Shortcuts now working in Mac OS\n * QuillDialogTheme is extended with new properties buttonStyle, linkDialogConstraints, imageDialogConstraints, isWrappable, runSpacing,\n * Added LinkStyleButton2 with new LinkStyleDialog (similar to Quill implementation\n * Conditinally use Row or Wrap for dialog's children.\n * Update minimum Dart SDK version to 2.17.0 to use enum extensions.\n * Use merging shortcuts and actions correclty (if the key combination is the same)","7.1.8":"* Dropdown tweaks\n * Add itemHeight, itemPadding, defaultItemColor for customization of dropdown items.\n * Remove alignment property as useless.\n * Fix bugs with max width when width property is null.","7.1.7":"* Toolbar tweaks.\n * Implement tooltips for embed CameraButton, VideoButton, FormulaButton, ImageButton.\n * Extends customization for SelectAlignmentButton, QuillFontFamilyButton, QuillFontSizeButton adding padding, text style, alignment, width.\n * Add renderFontFamilies to QuillFontFamilyButton to show font faces in dropdown.\n * Add AxisDivider and its named constructors for for use in parent project.\n * Export ToolbarButtons enum to allow specify tooltips for SelectAlignmentButton.\n * Export QuillFontFamilyButton, SearchButton as they were not exported before.\n * Deprecate items property in QuillFontFamilyButton, QuillFontSizeButton as the it can be built usinr rawItemsMap.\n * Make onSelection QuillFontFamilyButton, QuillFontSizeButton omittable as no need to execute callback outside if controller is passed to widget.\n\nNow the package is more friendly for web projects.","7.1.6":"* Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets.","7.1.5":"* Add tooltips for toolbar buttons.","7.1.4":"* Fix inserting tab character in lists.","7.1.3":"* Fix ios cursor bug when word.length==1.","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.","7.1.0":"* Fix ordered list numeration with several lists in document.","7.0.9":"* Use const constructor for EmbedBuilder.","7.0.8":"* Fix IME position bug with scroller.","7.0.7":"* Add TextFieldTapRegion for contextMenu.","7.0.6":"* Fix line style loss on new line from non string.","7.0.5":"* Fix IME position bug for Mac and Windows.\n* 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.","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.","7.0.1":"* 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.","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)","6.4.2":"* Replace `buildToolbar` with `contextMenuBuilder`.","6.4.1":"* Control the detect word boundary behaviour.","6.4.0":"* Use `axis` to make the toolbar vertical.\n* Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis.\n* Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`.","6.3.5":"* Ability to add custom shortcuts.","6.3.4":"* Update clipboard status prior to showing selected text overlay.","6.3.3":"* Fixed handling of mac intents.","6.3.2":"* Added `unknownEmbedBuilder` to QuillEditor.\n* Fix error style when input chinese japanese or korean.","6.3.1":"* Add color property to the basic factory function.","6.3.0":"* Support Flutter 3.7.","6.2.2":"* Fix: nextLine getter null where no assertion.","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.","6.1.12":"* Apply i18n for default font dropdown option labels corresponding to 'Clear'.","6.1.11":"* Remove iOS hack for delaying focus calculation.","6.1.10":"* Delay focus calculation for iOS.","6.1.9":"* Bump keyboard show up wait to 1 sec.","6.1.8":"* Recalculate focus when showing keyboard.","6.1.7":"* Add czech localizations.","6.1.6":"* Upgrade i18n_extension to 6.0.0.","6.1.5":"* Fix formatting exception.","6.1.4":"* Add double quotes validation.","6.1.3":"* Revert \"fix order list numbering (##988)\".","6.1.2":"* Add typing shortcuts.","6.1.1":"* Fix order list numbering.","6.1.0":"* Add keyboard shortcuts for editor actions.","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.","6.0.8+1":"* Fixes null pointer when setting documents.","6.0.8":"* Make QuillController.document mutable.","6.0.7":"* Allow disabling of selection toolbar.","6.0.6+1":"* Revert 6.0.6.","6.0.6":"* Fix wrong custom embed key.","6.0.5":"* Fixes toolbar buttons stealing focus from editor.","6.0.4":"* Bug fix for Type 'Uint8List' not found.","6.0.3":"* Add ability to paste images.","6.0.2":"* 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.\n* Add localization in example's main to demonstrate translation.\n* 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.\n* 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.\n* 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).\n* 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.\n\nThese blocks have been moved to the package `flutter_quill_extensions`, migrate by filling the `embedBuilders` and `embedButtons` parameters as follows:\n\n```\nimport 'package:flutter_quill_extensions/flutter_quill_extensions.dart';\n\nQuillEditor.basic(\n controller: controller,\n embedBuilders: FlutterQuillEmbeds.builders(),\n);\n\nQuillToolbar.basic(\n controller: controller,\n embedButtons: FlutterQuillEmbeds.buttons(),\n);\n```","5.4.2":"* Upgrade i18n_extension.","5.4.1":"* Update German Translation.","5.4.0":"* Added Formula Button (for maths support).","5.3.2":"* Add more font family.","5.3.1":"* Enable search when text is not empty.","5.3.0":"* Added search function.","5.2.11":"* Remove default small color.","5.2.10":"* Don't wrap the QuillEditor's child in the EditorTextSelectionGestureDetector if selection is disabled.","5.2.9":"* Added option to modify SelectHeaderStyleButton options.\n* Added option to click again on h1, h2, h3 button to go back to normal.","5.2.8":"* Remove tooltip for LinkStyleButton.\n* Make link match regex case insensitive.","5.2.7":"* Add locale to QuillEditor.basic.","5.2.6":"* Fix keyboard pops up when resizing the image.","5.2.5":"* Upgrade youtube_player_flutter_quill to 8.2.2.","5.2.4":"* 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').","5.2.2":"* Fix Web Unsupported operation: Platform.\\_operatingSystem error.","5.2.1":"* Rename QuillCustomIcon to QuillCustomButton.","5.2.0":"* Support font family selection.","5.1.1":"* Update README.","5.1.0":"* Added CustomBlockEmbed and customElementsEmbedBuilder.","5.0.5":"* Upgrade device_info_plus to 4.0.0.","5.0.4":"* Added onVideoInit callback for video documents.","5.0.3":"* Update dependencies.","5.0.2":"* Keep cursor position on checkbox tap.","5.0.1":"* Fix static analysis errors.","5.0.0":"* Flutter 3.0.0 support.","4.2.3":"* Ignore color:inherit and convert double to int for level.","4.2.2":"* Add clear option to font size dropdown.","4.2.1":"* Refactor font size dropdown.","4.2.0":"* Ensure selectionOverlay is available for showToolbar.","4.1.9":"* Using properly iconTheme colors.","4.1.8":"* Update font size dropdown.","4.1.7":"* Convert FontSize to a Map to allow for named Font Size.","4.1.6":"* Update quill_dropdown_button.dart.","4.1.5":"* Add Font Size dropdown to the toolbar.","4.1.4":"* New borderRadius for iconTheme.","4.1.3":"* 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.","4.1.1":"* Added textSelectionControls field in QuillEditor.","4.1.0":"* Added Node to linkActionPickerDelegate.","4.0.12":"* Add Persian(fa) language.","4.0.11":"* Fix cut selection error in multi-node line.","4.0.10":"* Fix vertical caret position bug.","4.0.9":"* Request keyboard focus when no child is found.","4.0.8":"* 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.","4.0.6":"* 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).","4.0.4":"* Bug fix for text direction rtl.","4.0.3":"* Support text direction rtl.","4.0.2":"* Clear toggled style on selection change.","4.0.1":"* Fix copy/cut/paste/selectAll not working.","4.0.0":"* Upgrade for Flutter 2.10.","3.9.11":"* Added Indonesian translation.","3.9.10":"* 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.","3.9.8":"* Added Urdu translation.","3.9.7":"* Fix for clicking on the Link button without any text on a new line crashes.","3.9.6":"* Apply locale to QuillEditor(contents).","3.9.5":"* Fix image pasting.","3.9.4":"* Hiding dialog after selecting action for image.","3.9.3":"* Update ImageResizer for Android.","3.9.2":"* Copy image with its style.","3.9.1":"* Support resizing image.","3.9.0":"* Image menu options for copy/remove.","3.8.8":"* Update set textEditingValue.","3.8.7":"* Fix checkbox not toggled correctly in toolbar button.","3.8.6":"* Fix cursor position changes when checking/unchecking the checkbox.","3.8.5":"* Fix \\_handleDragUpdate in \\_TextSelectionHandleOverlayState.","3.8.4":"* Fix link dialog layout.","3.8.3":"* 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.","3.8.1":"* Refactor \\_QuillEditorState to QuillEditorState.","3.8.0":"* Support pasting with format.","3.7.3":"* Fix selection overlay for collapsed selection.","3.7.2":"* Reverted Embed toPlainText change.","3.7.1":"* Change Embed toPlainText to be empty string.","3.7.0":"* Replace Toolbar showHistory group with individual showRedo and showUndo.","3.6.5":"* Update Link dialogue for image/video.","3.6.4":"* Link dialogue TextInputType.multiline.","3.6.3":"* Bug fix for link button text selection.","3.6.2":"* Improve link button.","3.6.1":"* Remove SnackBar 'What is entered is not a link'.","3.6.0":"* Allow link button to enter text.","3.5.3":"* Change link button behavior.","3.5.2":"* Bug fix for embed.","3.5.1":"* Bug fix for platform util.","3.5.0":"* Removed redundant classes.","3.4.4":"* Add more translations.","3.4.3":"* Preset link from attributes.","3.4.2":"* Fix launch link edit mode.","3.4.1":"* Placeholder effective in scrollable.","3.4.0":"* Option to save image in read-only mode.","3.3.1":"* Pass any specified key in QuillEditor constructor to super.","3.3.0":"* Fixed Style toggle issue.","3.2.1":"* Added new translations.","3.2.0":"* Support multiple links insertion on the go.","3.1.1":"* Add selection completed callback.","3.1.0":"* Fixed image ontap functionality.","3.0.4":"* Add maxContentWidth constraint to editor.","3.0.3":"* Do not show caret on screen when the editor is not focused.","3.0.2":"* Fix launch link for read-only mode.","3.0.1":"* Handle null value of Attribute.link.","3.0.0":"* Launch link improvements.\n* Removed QuillSimpleViewer.","2.5.2":"* Skip image when pasting.","2.5.1":"* Bug fix for Desktop `Shift` + `Click` support.","2.5.0":"* Update checkbox list.","2.4.1":"* Desktop selection improvements.","2.4.0":"* Improve inline code style.","2.3.3":"* Improves selection rects to have consistent height regardless of individual segment text styles.","2.3.2":"* Allow disabling floating cursor.","2.3.1":"* Preserve last newline character on delete.","2.3.0":"* Massive changes to support flutter 2.8.","2.2.2":"* iOS - floating cursor.","2.2.1":"* Bug fix for imports supporting flutter 2.8.","2.2.0":"* Support flutter 2.8.","2.1.1":"* Add methods of clearing editor and moving cursor.","2.1.0":"* Add delete handler.","2.0.23":"* Support custom replaceText handler.","2.0.22":"* Fix attribute compare and fix font size parsing.","2.0.21":"* Handle click on embed object.","2.0.20":"* Improved UX/UI of Image widget.","2.0.19":"* When uploading a video, applying indicator.","2.0.18":"* Make toolbar dividers optional.","2.0.17":"* Allow alignment of the toolbar icons to match WrapAlignment.","2.0.16":"* Add hide / show alignment buttons.","2.0.15":"* Implement change cursor to SystemMouseCursors.click when hovering a link styled text.","2.0.14":"* Enable customize the checkbox widget using DefaultListBlockStyle style.","2.0.13":"* 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.","2.0.11":"* Fix visibility of text selection handlers on scroll.","2.0.10":"* cursorConnt.color notify the text_line to repaint if it was disposed.","2.0.9":"* Improve UX when trying to add a link.","2.0.8":"* Adding translations to the toolbar.","2.0.7":"* Added theming options for toolbar icons and LinkDialog.","2.0.6":"* Avoid runtime error when placed inside TabBarView.","2.0.5":"* Support inline code formatting.","2.0.4":"* Enable history shortcuts for desktop.","2.0.3":"* Fix cursor when line contains image.","2.0.2":"* Address KeyboardListener class name conflict.","2.0.1":"* Upgrade flutter_colorpicker to 0.5.0.","2.0.0":"* Text Alignment functions + Block Format standards.","1.9.6":"* Support putting QuillEditor inside a Scrollable view.","1.9.5":"* Skip image when pasting.","1.9.4":"* 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.","1.9.2":"* Support for building custom inline styles.","1.9.1":"* Cursor jumps to the most appropriate offset to display selection.","1.9.0":"* Support inline image.","1.8.3":"* Updated quill_delta.","1.8.2":"* Support mobile image alignment.","1.8.1":"* Support mobile custom size image.","1.8.0":"* Support entering link for image/video.","1.7.3":"* Bumps photo_view version.","1.7.2":"* Fix static analysis error.","1.7.1":"* Support Youtube video.","1.7.0":"* Support video.","1.6.4":"* Bug fix for clear format button.","1.6.3":"* Fixed dragging right handle scrolling issue.","1.6.2":"* Fixed the position of the selection status drag handle.","1.6.1":"* Upgrade image_picker and flutter_colorpicker.","1.6.0":"* Support Multi Row Toolbar.","1.5.0":"* Remove file_picker dependency.","1.4.1":"* Remove filesystem_picker dependency.","1.4.0":"* Remove path_provider dependency.","1.3.4":"* Add option to paintCursorAboveText.","1.3.3":"* Upgrade file_picker version.","1.3.2":"* Fix copy/paste bug.","1.3.1":"* New logo.","1.3.0":"* Support flutter 2.2.0.","1.2.2":"* Checkbox supports tapping.","1.2.1":"* Indented position not holding while editing.","1.2.0":"* Fix image button cancel causes crash.","1.1.8":"* Fix height of empty line bug.","1.1.7":"* Fix text selection in read-only mode.","1.1.6":"* Remove universal_html dependency.","1.1.5":"* Enable \"Select\", \"Select All\" and \"Copy\" in read-only mode.","1.1.4":"* Fix text selection issue.","1.1.3":"* Update example folder.","1.1.2":"* Add pedantic.","1.1.1":"* Base64 image support.","1.1.0":"* Support null safety.","1.0.9":"* Web support for raw editor and keyboard listener.","1.0.8":"* Support token attribute.","1.0.7":"* Fix crash on web (dart:io).","1.0.6":"* Add desktop support WINDOWS, MACOS and LINUX.","1.0.5":"* Bug fix: Can not insert newline when Bold is toggled ON.","1.0.4":"* Upgrade photo_view to ^0.11.0.","1.0.3":"* Fix issue that text is not displayed while typing WEB.","1.0.2":"* Update toolbar in sample home page.","1.0.1":"* Fix static analysis errors.","1.0.0":"* Support flutter 2.0.","1.0.0-dev.2":"* Improve link handling for tel, mailto and etc.","1.0.0-dev.1":"* Upgrade prerelease SDK & Bump for master.","0.3.5":"* Fix for cursor focus issues when keyboard is on.","0.3.4":"* Improve link handling for tel, mailto and etc.","0.3.3":"* More fix on cursor focus issue when keyboard is on.","0.3.2":"* Fix cursor focus issue when keyboard is on.","0.3.1":"* cursor focus when keyboard is on.","0.3.0":"* Line Height calculated based on font size.","0.2.12":"* Support placeholder.","0.2.11":"* Fix static analysis error.","0.2.10":"* Update TextInputConfiguration autocorrect to true in stable branch.","0.2.9":"* Update TextInputConfiguration autocorrect to true.","0.2.8":"* Support display local image besides network image in stable branch.","0.2.7":"* Support display local image besides network image.","0.2.6":"* Fix cursor after pasting.","0.2.5":"* Toggle text/background color button in toolbar.","0.2.4":"* Support the use of custom icon size in toolbar.","0.2.3":"* Support custom styles and image on local device storage without uploading.","0.2.2":"* Update git repo.","0.2.1":"* Fix static analysis error.","0.2.0":"* Add checked/unchecked list button in toolbar.","0.1.8":"* Support font and size attributes.","0.1.7":"* Support checked/unchecked list.","0.1.6":"* Fix getExtentEndpointForSelection.","0.1.5":"* Support text alignment.","0.1.4":"* Handle url with trailing spaces.","0.1.3":"* Handle cursor position change when undo/redo.","0.1.2":"* Handle more text colors.","0.1.1":"* Fix cursor issue when undo.","0.1.0":"* Fix insert image.","0.0.9":"* Handle rgba color.","0.0.8":"* Fix launching url.","0.0.7":"* Handle multiple image inserts.","0.0.6":"* More toolbar functionality.","0.0.5":"* Update example.","0.0.4":"* Update example.","0.0.3":"* Update home page meta data.","0.0.2":"* Support image upload and launch url in read-only mode.","0.0.1":"* Rich text editor based on Quill Delta."} \ No newline at end of file +{"9.5.2":"* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2","9.5.1":"* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1","9.5.0":"* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0","9.4.9":"* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9","9.4.8":"* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8","9.4.7":"* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954\r\n\r\n## New Contributors\r\n* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7","9.4.6":"* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938\r\n* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6","9.4.5":"* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934\r\n\r\n## New Contributors\r\n* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5","9.4.4":"* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4","9.4.3":"* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926\r\n\r\n## New Contributors\r\n* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3","9.4.2":"* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2","9.4.1":"* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919\r\n\r\n## New Contributors\r\n* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1","9.4.0":"This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x`\r\n\r\n- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`:\r\n ```dart\r\n QuillToolbar.simple(\r\n configurations: QuillSimpleToolbarConfigurations(\r\n searchButtonType: SearchButtonType.legacy,\r\n ),\r\n )\r\n ```\r\n While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future.\r\n\r\n If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget\r\n \r\n Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated)\r\n\r\n
\r\n Before\r\n \r\n ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b)\r\n \r\n \r\n
\r\n \r\n
\r\n Improved\r\n \r\n ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04)\r\n \r\n
\r\n \r\n For the detailed changes, see #1904\r\n\r\n- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911\r\n\r\n- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call:\r\n\r\n ```dart\r\n FlutterQuillExtensions.useSuperClipboardPlugin();\r\n ```\r\n Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x`\r\n \r\n We initially wanted to publish `flutter_quill_super_clipboard` to allow:\r\n - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins\r\n - Using `flutter_quill_extensions` with optional `super_clipboard`\r\n \r\n To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above.\r\n \r\n Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated.\r\n\r\n The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by:\r\n ```dart\r\n // ignore: implementation_imports\r\n import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart';\r\n ```\r\n\r\n Then you can call:\r\n ```dart\r\n // ignore: implementation_imports\r\nimport 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart';\r\n ClipboardServiceProvider.setInstance(YourClipboardService());\r\n```\r\n \r\n The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future.\r\n\r\n If you use the above imports, you might get **breaking changes** in **non-breaking change releases**.\r\n\r\n- Subscript and Superscript should now work for all languages and characters\r\n\r\n The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices.\r\nWe have removed the Apple font from the example\r\n\r\n- Allow pasting Markdown and HTML file content from the system to the editor\r\n\r\n Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor\r\n Copying an HTML file, or HTML content from apps and websites is different than copying plain text.\r\n\r\n This is why this change requires `super_clipboard` implementation as this is platform-dependent:\r\n ```dart\r\n FlutterQuillExtensions.useSuperClipboardPlugin();\r\n ```\r\n as mentioned above.\r\n \r\n The following example for copying a Markdown file:\r\n\r\n
\r\n Markdown File Content\r\n \r\n ```md\r\n \r\n **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor\r\n \r\n You have two options:\r\n \r\n 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well\r\n (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly\r\n 1. Another option is to use\r\n [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document\r\n to HTML.\r\n This package has full support for all Quill operations—including images, videos, formulas,\r\n tables, and mentions.\r\n Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter.\r\n It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html)\r\n Typescript/Javascript package.\r\n this package doesn't convert the HTML back to Quill Delta as far as we know \r\n \r\n ```\r\n\r\n
\r\n \r\n
\r\n Before\r\n \r\n ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e)\r\n \r\n
\r\n \r\n
\r\n After\r\n \r\n ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788)\r\n \r\n
\r\n \r\n Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot \r\n however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info.\r\n \r\n ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9)\r\n \r\n For more details see #1915\r\n \r\n Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. \r\n \r\n We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor \r\n previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides.\r\n\r\n Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions)\r\n\r\n## New Contributors\r\n* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0","9.3.21":"* fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.20...v9.3.21","9.3.20":"* Fix: Issue 1887 by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1892\r\n* fix: toolbar style change will be invalid when inputting more than 2 characters at a time by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1890\r\n\r\n## New Contributors\r\n* @crasowas made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1890\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.19...v9.3.20","9.3.19":"* Fix reported issues by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1886\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.18...v9.3.19","9.3.18":"* Fix: Undo/redo cursor position fixed by @Alspb in https://github.com/singerdmx/flutter-quill/pull/1885\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.17...v9.3.18","9.3.17":"* Update super_clipboard plugin to 0.8.15 to address [#1882](https://github.com/singerdmx/flutter-quill/issues/1882)\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.16...v9.3.17","9.3.16":"* Update `lint` dev package to 4.0.0\r\n* Require at least version 0.8.13 of the plugin\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.15...v9.3.16","9.3.15":"\r\n* Ci/automate updating the files by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1879\r\n* Updating outdated README.md and adding a few guidelines for CONTRIBUTING.md\r\n\r\n\r\n**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.14...v9.3.15","9.3.14":"* Chore/use original color picker package in [#1877](https://github.com/singerdmx/flutter-quill/pull/1877)","9.3.13":"* fix: `readOnlyMouseCursor` losing in construction function\n* Fix block multi-line selection style","9.3.12":"* Add `readOnlyMouseCursor` to config mouse cursor type","9.3.11":"* Fix typo in QuillHtmlConverter\n* Fix re-create checkbox","9.3.10":"* Support clipboard actions from the toolbar","9.3.9":"* fix: MD Parsing for multi space\n* fix: FontFamily and FontSize toolbars track the text selected in the editor\n* feat: Add checkBoxReadOnly property which can override readOnly for checkbox","9.3.8":"* fix: removed misleading parameters\n* fix: added missed translations for ru, es, de\n* added translations for Nepali Locale('ne', 'NP')","9.3.7":"* Fix for keyboard jumping when switching focus from a TextField\n* Toolbar button styling to reflect cursor position when running on desktops with keyboard to move care","9.3.6":"* Add SK and update CS locales [#1796](https://github.com/singerdmx/flutter-quill/pull/1796)\n* Fixes:\n * QuillIconTheme changes for FontFamily and FontSize buttons are not applied [#1797](https://github.com/singerdmx/flutter-quill/pull/1796)\n * Make the arrow_drop_down icons in the QuillToolbar the same size for all MenuAnchor buttons [#1799](https://github.com/singerdmx/flutter-quill/pull/1796)","9.3.5":"* Update the minimum version for the packages to support `device_info_plus` version 10.0.0 [#1783](https://github.com/singerdmx/flutter-quill/issues/1783)\n* Update the minimum version for `youtube_player_flutter` to new major version 9.0.0 in the `flutter_quill_extensions`","9.3.4":"* fix: multiline styling stuck/not working properly [#1782](https://github.com/singerdmx/flutter-quill/pull/1782)","9.3.3":"* Update `quill_html_converter` versions","9.3.2":"* Fix dispose of text painter [#1774](https://github.com/singerdmx/flutter-quill/pull/1774)","9.3.1":"* Require Flutter 3.19.0 as minimum version","9.3.0":"* **Breaking change**: `Document.fromHtml(html)` is now returns `Document` instead of `Delta`, use `DeltaX.fromHtml` to return `Delta`\n* Update old deprecated api from Flutter 3.19\n* Scribble scroll fix by @mtallenca in https://github.com/singerdmx/flutter-quill/pull/1745","9.2.14":"* feat: move cursor after inserting video/image\n* Apple pencil","9.2.13":"* Fix crash with inserting text from contextMenuButtonItems\n* Fix incorrect behaviour of context menu \n* fix: selection handles behaviour and unnessesary style assert\n* Update quill_fr.arb","9.2.12":"* Fix safari clipboard bug\n* Add the option to disable clipboard functionality","9.2.11":"* Fix a bug where it has problems with pasting text into the editor when the clipboard has styled text","9.2.10":"* Update example screenshots\n* Refactor `Container` to `QuillContainer` with backward compatibility\n* A workaround fix in history feature","9.2.9":"* Refactor the type of `Delta().toJson()` to be more clear type","9.2.8":"* feat: Export Container node as QuillContainer\n* fix web cursor position / height (don't use iOS logic)\n* Added Swedish translation","9.2.6":"* [fix selection.affinity always downstream after updateEditingValue](https://github.com/singerdmx/flutter-quill/pull/1682)\n* Bumb version of `super_clipboard`","9.2.5":"* Bumb version of `super_clipboard`","9.2.4":"* Use fixed version of intl","9.2.3":"* remove unncessary column in Flutter quill video embed block","9.2.2":"* Fix bug [#1627](https://github.com/singerdmx/flutter-quill/issues/1627)","9.2.1":"* Fix [bug](https://github.com/singerdmx/flutter-quill/issues/1119#issuecomment-1872605246) with font size button\n* Added ro RO translations\n* 📖 Update zh, zh_CN translations","9.2.0":"* Require minimum version `6.0.0` of `flutter_keyboard_visibility` to fix some build issues with Android Gradle Plugin 8.2.0\n* Add on image clicked in `flutter_quill_extensions` callback\n* Deprecate `globalIconSize` and `globalIconButtonFactor`, use `iconSize` and `iconButtonFactor` instead\n* Fix the `QuillToolbarSelectAlignmentButtons`","9.1.1":"* Require `super_clipboard` minimum version `0.8.1` to fix some bug with Linux build failure","9.1.1-dev":"* Fix bug [#1636](https://github.com/singerdmx/flutter-quill/issues/1636)\n* Fix a where you paste styled content (HTML) it always insert a new line at first even if the document is empty\n* Fix the font size button and migrate to `MenuAnchor`\n* The `defaultDisplayText` is no longer required in the font size and header dropdown buttons\n* Add pdf converter in a new package (`quill_pdf_converter`)","9.1.0":"* Fix the simple toolbar by add properties of `IconButton` and fix some buttons","9.1.0-dev.2":"* Fix the history buttons","9.1.0-dev.1":"* Bug fixes in the simple toolbar buttons","9.1.0-dev":"* **Breaking Change**: in the `QuillSimpleToolbar` Fix the `QuillIconTheme` by replacing all the properties with two properties of type `ButtonStyle`, use `IconButton.styleFrom()`","9.0.6":"* Fix bug in QuillToolbarSelectAlignmentButtons","9.0.5":"* You can now use most of the buttons without internal provider","9.0.4":"* Feature: [#1611](https://github.com/singerdmx/flutter-quill/issues/1611)\n* Export missing widgets","9.0.3":"* Flutter Quill Extensions:\n * Fix file image support for web image emebed builder","9.0.2":"* Remove unused properties in the `QuillToolbarSelectHeaderStyleDropdownButton`\n* Fix the `QuillSimpleToolbar` when `useMaterial3` is false, please upgrade to the latest version of flutter for better support","9.0.2-dev.3":"* Export `QuillSingleChildScrollView`","9.0.2-dev.2":"* Add the new translations for ru, uk arb files by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575)\n* Add a new dropdown button by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575)\n* Update the default style values by [#1575](https://github.com/singerdmx/flutter-quill/pull/1575)\n* Fix bug [#1562](https://github.com/singerdmx/flutter-quill/issues/1562)\n* Fix the second bug of [#1480](https://github.com/singerdmx/flutter-quill/issues/1480)","9.0.2-dev.1":"* Add configurations for the new dropdown `QuillToolbarSelectHeaderStyleButton`, you can use the orignal one or this\n* Fix the [issue](https://github.com/singerdmx/flutter-quill/issues/1119) when enter is pressed, all font settings is lost","9.0.2-dev":"* **Breaking change** Remove the spacer widget, removed the controller option for each button\n* Add `toolbarRunSpacing` property to the simple toolbar","9.0.1":"* Fix default icon size","9.0.0":"* This version is quite stable but it's not how we wanted to be, because the lack of time and there are not too many maintainers active, we decided to publish it, we might make a new breaking changes verion","9.0.1-dev.1":"* Flutter Quill Extensions:\n * Update `QuillImageUtilities` and fixining some bugs","9.0.1-dev":"* Test new GitHub workflows","9.0.0-dev-10":"* Fix a bug of the improved pasting HTML contents contents into the editor","9.0.0-dev-9":"* Improves the new logic of pasting HTML contents into the Editor\n* Update `README.md` and the doc\n* Dispose the `QuillToolbarSelectHeaderStyleButton` state listener in `dispose`\n* Upgrade the font family button to material 3\n* Rework the font family and font size functionalities to change the font once and type all over the editor","9.0.0-dev-8":"* Better support for pasting HTML contents from external websites to the editor\n* The experimental support of converting the HTML from `quill_html_converter` is now built-in in the `flutter_quill` and removed from there (Breaking change for `quill_html_converter`)","9.0.0-dev-7":"* Fix a bug in chaning the background/font color of ol/ul list\n* Flutter Quill Extensions:\n * Fix link bug in the video url\n * Fix patterns","9.0.0-dev-6":"* Move the `child` from `QuillToolbarConfigurations` into `QuillToolbar` directly\n* Bug fixes\n* Add the ability to change the background and font color of the ol/ul elements dots and numbers\n* Flutter Quill Extensions:\n * **Breaking Change**: The `imageProviderBuilder`is now providing the context and image url","9.0.0-dev-5":"* The `QuillToolbar` is now accepting only `child` with no configurations so you can customize everything you wants, the `QuillToolbar.simple()` or `QuillSimpleToolbar` implements a simple toolbar that is based on `QuillToolbar`, you are free to use it but it just an example and not standard\n* Flutter Quill Extensions:\n * Improve the camera button","9.0.0-dev-4":"* The options parameter in all of the buttons is no longer required which can be useful to create custom toolbar with minimal efforts\n* Toolbar buttons fixes in both `flutter_quill` and `flutter_quill_extensions`\n* The `QuillProvider` has been dropped and no longer used, the providers will be used only internally from now on and we will not using them as much as possible","9.0.0-dev-3":"* Breaking Changes:\n * Rename `QuillToolbar` to `QuillSimpleToolbar`\n * Rename `QuillBaseToolbar` to `QuillToolbar`\n * Replace `pasteboard` with `rich_cliboard`\n* Fix a bug in the example when inserting an image from url\n* Flutter Quill Extensions:\n * Add support for copying the image to the system cliboard","9.0.0-dev-2":"* An attemp to fix CI automated publishing","9.0.0-dev-1":"* An attemp to fix CI automated publishing","9.0.0-dev":"* **Major Breaking change**: The `QuillProvider` is now optional, the `controller` parameter has been moved to the `QuillEditor` and `QuillToolbar` once again.\n* Flutter Quill Extensions;\n * **Breaking Change**: Completly change the way how the source code structured to more basic and simple way, organize folders and file names, if you use the library\nfrom `flutter_quill_extensions.dart` then there is nothing you need to do, but if you are using any other import then you need to re-imports\nembed, this won't affect how quill js work\n * Improvemenets to the image embed\n * Add support for `margin` for web\n * Add untranslated strings to the `quill_en.arb`","8.6.4":"* The default value of `keyboardAppearance` for the iOS will be the one from the App/System theme mode instead of always using the `Brightness.light`\n* Fix typos in `README.md`","8.6.3":"* Update the minimum flutter version to `3.16.0`","8.6.2":"* Restore use of alternative QuillToolbarLinkStyleButton2 widget","8.6.1":"* Temporary revert style bug fix","8.6.0":"* **Breaking Change** Support [Flutter 3.16](https://medium.com/flutter/whats-new-in-flutter-3-16-dba6cb1015d1), please upgrade to the latest stable version of flutter to use this update\n* **Breaking Change**: Remove Deprecated Fields\n* **Breaking Change**: Extract the shared things between `QuillToolbarConfigurations` and `QuillBaseToolbarConfigurations`\n* **Breaking Change**: You no longer need to use `QuillToolbarProvider` when using custom toolbar buttons, the example has been updated\n* Bug fixes","8.5.5":"* Now when opening dialogs by `QuillToolbar` you will not get an exception when you don't use `FlutterQuillLocalizations.delegate` in your `WidgetsApp`, `MaterialApp`, or `CupertinoApp`. The fix is for the `QuillToolbarSearchButton`, `QuillToolbarLinkStyleButton`, and `QuillToolbarColorButton` buttons","8.5.4":"* The `mobileWidth`, `mobileHeight`, `mobileMargin`, and `mobileAlignment` is now deprecated in `flutter_quill`, they are now defined in `flutter_quill_extensions`\n* Deprecate `replaceStyleStringWithSize` function which is in `string.dart`\n* Deprecate `alignment`, and `margin` as they don't conform to official Quill JS","8.5.3":"* Update doc\n* Update `README.md` and `CHANGELOG.md`\n* Fix typos\n* Use `immutable` when possible\n* Update `.pubignore`","8.5.2":"* Updated `README.md`.\n* Feature: Added the ability to include a custom callback when the `QuillToolbarColorButton` is pressed.\n* The `QuillToolbar` now implements `PreferredSizeWidget`, enabling usage in the AppBar, similar to `QuillBaseToolbar`.","8.5.1":"* Updated `README.md`.","8.5.0":"* Migrated to `flutter_localizations` for translations.\n* Fixed: Translated all previously untranslated localizations.\n* Fixed: Added translations for missing items.\n* Fixed: Introduced default Chinese fallback translation.\n* Removed: Unused parameters `items` in `QuillToolbarFontFamilyButtonOptions` and `QuillToolbarFontSizeButtonOptions`.\n* Updated: Documentation.","8.4.4":"* Updated `.pubignore` to ignore unnecessary files and folders.","8.4.3":"* Updated `CHANGELOG.md`.","8.4.2":"* **Breaking change**: Configuration for `QuillRawEditor` has been moved to a separate class. Additionally, `readOnly` has been renamed to `isReadOnly`. If using `QuillEditor`, no action is required.\n* Introduced the ability for developers to override `TextInputAction` in both `QuillRawEditor` and `QuillEditor`.\n* Enabled using `QuillRawEditor` without `QuillEditorProvider`.\n* Bug fixes.\n* Added image cropping implementation in the example.","8.4.1":"* Added `copyWith` in `OptionalSize` class.","8.4.0":"* **Breaking change**: Updated `QuillCustomButton` to use `QuillCustomButtonOptions`. Moved all properties from `QuillCustomButton` to `QuillCustomButtonOptions`, replacing `iconData` with `icon` widget for increased customization.\n* **Breaking change**: `customButtons` in `QuillToolbarConfigurations` is now of type `List`.\n* Bug fixes following the `8.0.0` update.\n* Updated `README.md`.\n* Improved platform checking.","8.3.0":"* Added `iconButtonFactor` property to `QuillToolbarBaseButtonOptions` for customizing button size relative to its icon size (defaults to `kIconButtonFactor`, consistent with previous releases).","8.2.6":"* Organized `QuillRawEditor` code.","8.2.5":"* Added `builder` property in `QuillEditorConfigurations`.","8.2.4":"* Adhered to Flutter best practices.\n* Fixed auto-focus bug.","8.2.3":"* Updated `README.md`.","8.2.2":"* Moved `flutter_quill_test` to a separate package: [flutter_quill_test](https://pub.dev/packages/flutter_quill_test).","8.2.1":"* Updated `README.md`.","8.2.0":"* Added the option to add configurations for `flutter_quill_extensions` using `extraConfigurations`.","8.1.11":"* Followed Dart best practices by using `lints` and removed `pedantic` and `platform` since they are not used.\n* Fixed text direction bug.\n* Updated `README.md`.","8.1.10":"* Secret for automated publishing to pub.dev.","8.1.9":"* Fixed automated publishing to pub.dev.","8.1.8":"* Fixed automated publishing to pub.dev.","8.1.7":"* Automated publishing to pub.dev.","8.1.6":"* Fixed compatibility with `integration_test` by downgrading the minimum version of the platform package to 3.1.0.","8.1.5":"* Reversed background/font color toolbar button icons.","8.1.4":"* Reversed background/font color toolbar button tooltips.","8.1.3":"* Moved images to screenshots instead of `README.md`.","8.1.2":"* Fixed a bug related to the regexp of the insert link dialog.\n* Required Dart 3 as the minimum version.\n* Code cleanup.\n* Added a spacer widget between each button in the `QuillToolbar`.","8.1.1":"* Fixed null error in line.dart #1487(https://github.com/singerdmx/flutter*quill/issues/1487).","8.1.0":"* Fixed a word typo of `mirgration` to `migration` in the readme & migration document.\n* Updated migration guide.\n* Removed property `enableUnfocusOnTapOutside` in `QuillEditor` configurations and added `isOnTapOutsideEnabled` instead.\n* Added a new callback called `onTapOutside` in the `QuillEditorConfigurations` to perform actions when tapping outside the editor.\n* Fixed a bug that caused the web platform to not unfocus the editor when tapping outside of it. To override this, please pass a value to the `onTapOutside` callback.\n* Removed the old property of `iconTheme`. Instead, pass `iconTheme` in the button options; you will find the `base` property inside it with `iconTheme`.","8.0.0":"* If you have migrated recently, don't be alarmed by this update; it adds documentation, a migration guide, and marks the version as a more stable release. Although there are breaking changes (as reported by some developers), the major version was not changed due to time constraints during development. A single property was also renamed from `code` to `codeBlock` in the `elements` of the new `QuillEditorConfigurations` class.\n* Updated the README for better readability.","7.10.2":"* Removed line numbers from code blocks by default. You can still enable this feature thanks to the new configurations in the `QuillEditor`. Find the `elementOptions` property and enable `enableLineNumbers`.","7.10.1":"* Fixed issues and utilized the new parameters.\n* No longer need to use `MaterialApp` for most toolbar button child builders.\n* Compatibility with [fresh_quill_extensions](https://pub.dev/packages/fresh_quill_extensions), a temporary alternative to [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions).\n* Updated most of the documentation in `README.md`.","7.10.0":"* **Breaking change**: `QuillToolbar.basic()` can be accessed directly from `QuillToolbar()`, and the old `QuillToolbar` can be accessed from `QuillBaseToolbar`.\n* Refactored Quill editor and toolbar configurations into a single class each.\n* After changing checkbox list values, the controller will not request keyboard focus by default.\n* Moved toolbar and editor configurations directly into the widget but still use inherited widgets internally.\n* Fixes to some code after the refactoring.","7.9.0":"* Buttons Improvemenets\n* Refactor all the button configurations that used in `QuillToolbar.basic()` but there are still few lefts\n* **Breaking change**: Remove some configurations from the QuillToolbar and move them to the new `QuillProvider`, please notice this is a development version and this might be changed in the next few days, the stable release will be ready in less than 3 weeks\n* Update `flutter_quill_extensions` and it will be published into pub.dev soon.\n* Allow you to customize the search dialog by custom callback with child builder","7.8.0":"* **Important note**: this is not test release yet, it works but need more test and changes and breaking changes, we don't have development version and it will help us if you try the latest version and report the issues in Github but if you want a stable version please use `7.4.16`. this refactoring process will not take long and should be done less than three weeks with the testing.\n* We managed to refactor most of the buttons configurations and customizations in the `QuillProvider`, only three lefts then will start on refactoring the toolbar configurations\n* Code improvemenets","7.7.0":"* **Breaking change**: We have mirgrated more buttons in the toolbar configurations, you can do change them in the `QuillProvider`\n* Important bug fixes","7.6.1":"* Bug fixes","7.6.0":"* **Breaking change**: To customize the buttons in the toolbar, you can do that in the `QuillProvider`","7.5.0":"* **Breaking change**: The widgets `QuillEditor` and `QuillToolbar` are no longer have controller parameter, instead you need to make sure in the widget tree you have wrapped them with `QuillProvider` widget and provide the controller and the require configurations","7.4.16":"* Update documentation and README.md","7.4.15":"* Custom style attrbuites for platforms other than mobile (alignment, margin, width, height)\n* Bug fixes and other improvemenets","7.4.14":"* Improve performance by reducing the number of widgets rebuilt by listening to media query for only the needed things, for example instead of using `MediaQuery.of(context).size`, now we are using `MediaQuery.sizeOf(context)`\n* Add MediaButton for picking the images only since the video one is not ready\n* A new feature which allows customizing the text selection in quill editor which is useful for custom theme design system for custom app widget","7.4.13":"* Fixed tab editing when in readOnly mode.","7.4.12":"* Update the minimum version of device_info_plus to 9.1.0.","7.4.11":"* Add sw locale.","7.4.10":"* Update translations.","7.4.9":"* Style recognition fixes.","7.4.8":"* Upgrade dependencies.","7.4.7":"* Add Vietnamese and German translations.","7.4.6":"* Fix more null errors in Leaf.retain [##1394](https://github.com/singerdmx/flutter-quill/issues/1394) and Line.delete [##1395](https://github.com/singerdmx/flutter-quill/issues/1395).","7.4.5":"* Fix null error in Container.insert [##1392](https://github.com/singerdmx/flutter-quill/issues/1392).","7.4.4":"* Fix extra padding on checklists [##1131](https://github.com/singerdmx/flutter-quill/issues/1131).","7.4.3":"* Fixed a space input error on iPad.","7.4.2":"* Fix bug with keepStyleOnNewLine for link.","7.4.1":"* Fix toolbar dividers condition.","7.4.0":"* Support Flutter version 3.13.0.","7.3.3":"* Updated Dependencies conflicting.","7.3.2":"* Added builder for custom button in _LinkDialog.","7.3.1":"* Added case sensitive and whole word search parameters.\n* Added wrap around.\n* Moved search dialog to the bottom in order not to override the editor and the text found.\n* Other minor search dialog enhancements.","7.3.0":"* Add default attributes to basic factory.","7.2.19":"* Feat/link regexp.","7.2.18":"* Fix paste block text in words apply same style.","7.2.17":"* Fix paste text mess up style.\n* Add support copy/cut block text.","7.2.16":"* Allow for custom context menu.","7.2.15":"* Add flutter_quill.delta library which only exposes Delta datatype.","7.2.14":"* Fix errors when the editor is used in the `screenshot` package.","7.2.13":"* Fix around image can't delete line break.","7.2.12":"* Add support for copy/cut select image and text together.","7.2.11":"* Add affinity for localPosition.","7.2.10":"* LINE._getPlainText queryChild inclusive=false.","7.2.9":"* Add toPlainText method to `EmbedBuilder`.","7.2.8":"* Add custom button widget in toolbar.","7.2.7":"* Fix language code of Japan.","7.2.6":"* Style custom toolbar buttons like builtins.","7.2.5":"* Always use text cursor for editor on desktop.","7.2.4":"* Fixed keepStyleOnNewLine.","7.2.3":"* Get pixel ratio from view.","7.2.2":"* Prevent operations on stale editor state.","7.2.1":"* Add support for android keyboard content insertion.\n* 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.","7.1.20":"* Pass linestyle to embedded block.","7.1.19":"* Fix Rtl leading alignment problem.","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":"* 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.","7.1.14":"* Add indents change for multiline selection.","7.1.13":"* Add custom recognizer.","7.1.12":"* Add superscript and subscript styles.","7.1.11":"* Add inserting indents for lines of list if text is selected.","7.1.10":"* Image embedding tweaks\n * Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working.\n * Implement image insert for web (image as base64)","7.1.9":"* Editor tweaks PR from bambinoua(https://github.com/bambinoua).\n * Shortcuts now working in Mac OS\n * QuillDialogTheme is extended with new properties buttonStyle, linkDialogConstraints, imageDialogConstraints, isWrappable, runSpacing,\n * Added LinkStyleButton2 with new LinkStyleDialog (similar to Quill implementation\n * Conditinally use Row or Wrap for dialog's children.\n * Update minimum Dart SDK version to 2.17.0 to use enum extensions.\n * Use merging shortcuts and actions correclty (if the key combination is the same)","7.1.8":"* Dropdown tweaks\n * Add itemHeight, itemPadding, defaultItemColor for customization of dropdown items.\n * Remove alignment property as useless.\n * Fix bugs with max width when width property is null.","7.1.7":"* Toolbar tweaks.\n * Implement tooltips for embed CameraButton, VideoButton, FormulaButton, ImageButton.\n * Extends customization for SelectAlignmentButton, QuillFontFamilyButton, QuillFontSizeButton adding padding, text style, alignment, width.\n * Add renderFontFamilies to QuillFontFamilyButton to show font faces in dropdown.\n * Add AxisDivider and its named constructors for for use in parent project.\n * Export ToolbarButtons enum to allow specify tooltips for SelectAlignmentButton.\n * Export QuillFontFamilyButton, SearchButton as they were not exported before.\n * Deprecate items property in QuillFontFamilyButton, QuillFontSizeButton as the it can be built usinr rawItemsMap.\n * Make onSelection QuillFontFamilyButton, QuillFontSizeButton omittable as no need to execute callback outside if controller is passed to widget.\n\nNow the package is more friendly for web projects.","7.1.6":"* Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets.","7.1.5":"* Add tooltips for toolbar buttons.","7.1.4":"* Fix inserting tab character in lists.","7.1.3":"* Fix ios cursor bug when word.length==1.","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.","7.1.0":"* Fix ordered list numeration with several lists in document.","7.0.9":"* Use const constructor for EmbedBuilder.","7.0.8":"* Fix IME position bug with scroller.","7.0.7":"* Add TextFieldTapRegion for contextMenu.","7.0.6":"* Fix line style loss on new line from non string.","7.0.5":"* Fix IME position bug for Mac and Windows.\n* 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.","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.","7.0.1":"* 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.","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)","6.4.2":"* Replace `buildToolbar` with `contextMenuBuilder`.","6.4.1":"* Control the detect word boundary behaviour.","6.4.0":"* Use `axis` to make the toolbar vertical.\n* Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis.\n* Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`.","6.3.5":"* Ability to add custom shortcuts.","6.3.4":"* Update clipboard status prior to showing selected text overlay.","6.3.3":"* Fixed handling of mac intents.","6.3.2":"* Added `unknownEmbedBuilder` to QuillEditor.\n* Fix error style when input chinese japanese or korean.","6.3.1":"* Add color property to the basic factory function.","6.3.0":"* Support Flutter 3.7.","6.2.2":"* Fix: nextLine getter null where no assertion.","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.","6.1.12":"* Apply i18n for default font dropdown option labels corresponding to 'Clear'.","6.1.11":"* Remove iOS hack for delaying focus calculation.","6.1.10":"* Delay focus calculation for iOS.","6.1.9":"* Bump keyboard show up wait to 1 sec.","6.1.8":"* Recalculate focus when showing keyboard.","6.1.7":"* Add czech localizations.","6.1.6":"* Upgrade i18n_extension to 6.0.0.","6.1.5":"* Fix formatting exception.","6.1.4":"* Add double quotes validation.","6.1.3":"* Revert \"fix order list numbering (##988)\".","6.1.2":"* Add typing shortcuts.","6.1.1":"* Fix order list numbering.","6.1.0":"* Add keyboard shortcuts for editor actions.","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.","6.0.8+1":"* Fixes null pointer when setting documents.","6.0.8":"* Make QuillController.document mutable.","6.0.7":"* Allow disabling of selection toolbar.","6.0.6+1":"* Revert 6.0.6.","6.0.6":"* Fix wrong custom embed key.","6.0.5":"* Fixes toolbar buttons stealing focus from editor.","6.0.4":"* Bug fix for Type 'Uint8List' not found.","6.0.3":"* Add ability to paste images.","6.0.2":"* 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.\n* Add localization in example's main to demonstrate translation.\n* 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.\n* 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.\n* 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).\n* 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.\n\nThese blocks have been moved to the package `flutter_quill_extensions`, migrate by filling the `embedBuilders` and `embedButtons` parameters as follows:\n\n```\nimport 'package:flutter_quill_extensions/flutter_quill_extensions.dart';\n\nQuillEditor.basic(\n controller: controller,\n embedBuilders: FlutterQuillEmbeds.builders(),\n);\n\nQuillToolbar.basic(\n controller: controller,\n embedButtons: FlutterQuillEmbeds.buttons(),\n);\n```","5.4.2":"* Upgrade i18n_extension.","5.4.1":"* Update German Translation.","5.4.0":"* Added Formula Button (for maths support).","5.3.2":"* Add more font family.","5.3.1":"* Enable search when text is not empty.","5.3.0":"* Added search function.","5.2.11":"* Remove default small color.","5.2.10":"* Don't wrap the QuillEditor's child in the EditorTextSelectionGestureDetector if selection is disabled.","5.2.9":"* Added option to modify SelectHeaderStyleButton options.\n* Added option to click again on h1, h2, h3 button to go back to normal.","5.2.8":"* Remove tooltip for LinkStyleButton.\n* Make link match regex case insensitive.","5.2.7":"* Add locale to QuillEditor.basic.","5.2.6":"* Fix keyboard pops up when resizing the image.","5.2.5":"* Upgrade youtube_player_flutter_quill to 8.2.2.","5.2.4":"* 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').","5.2.2":"* Fix Web Unsupported operation: Platform.\\_operatingSystem error.","5.2.1":"* Rename QuillCustomIcon to QuillCustomButton.","5.2.0":"* Support font family selection.","5.1.1":"* Update README.","5.1.0":"* Added CustomBlockEmbed and customElementsEmbedBuilder.","5.0.5":"* Upgrade device_info_plus to 4.0.0.","5.0.4":"* Added onVideoInit callback for video documents.","5.0.3":"* Update dependencies.","5.0.2":"* Keep cursor position on checkbox tap.","5.0.1":"* Fix static analysis errors.","5.0.0":"* Flutter 3.0.0 support.","4.2.3":"* Ignore color:inherit and convert double to int for level.","4.2.2":"* Add clear option to font size dropdown.","4.2.1":"* Refactor font size dropdown.","4.2.0":"* Ensure selectionOverlay is available for showToolbar.","4.1.9":"* Using properly iconTheme colors.","4.1.8":"* Update font size dropdown.","4.1.7":"* Convert FontSize to a Map to allow for named Font Size.","4.1.6":"* Update quill_dropdown_button.dart.","4.1.5":"* Add Font Size dropdown to the toolbar.","4.1.4":"* New borderRadius for iconTheme.","4.1.3":"* 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.","4.1.1":"* Added textSelectionControls field in QuillEditor.","4.1.0":"* Added Node to linkActionPickerDelegate.","4.0.12":"* Add Persian(fa) language.","4.0.11":"* Fix cut selection error in multi-node line.","4.0.10":"* Fix vertical caret position bug.","4.0.9":"* Request keyboard focus when no child is found.","4.0.8":"* 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.","4.0.6":"* 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).","4.0.4":"* Bug fix for text direction rtl.","4.0.3":"* Support text direction rtl.","4.0.2":"* Clear toggled style on selection change.","4.0.1":"* Fix copy/cut/paste/selectAll not working.","4.0.0":"* Upgrade for Flutter 2.10.","3.9.11":"* Added Indonesian translation.","3.9.10":"* 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.","3.9.8":"* Added Urdu translation.","3.9.7":"* Fix for clicking on the Link button without any text on a new line crashes.","3.9.6":"* Apply locale to QuillEditor(contents).","3.9.5":"* Fix image pasting.","3.9.4":"* Hiding dialog after selecting action for image.","3.9.3":"* Update ImageResizer for Android.","3.9.2":"* Copy image with its style.","3.9.1":"* Support resizing image.","3.9.0":"* Image menu options for copy/remove.","3.8.8":"* Update set textEditingValue.","3.8.7":"* Fix checkbox not toggled correctly in toolbar button.","3.8.6":"* Fix cursor position changes when checking/unchecking the checkbox.","3.8.5":"* Fix \\_handleDragUpdate in \\_TextSelectionHandleOverlayState.","3.8.4":"* Fix link dialog layout.","3.8.3":"* 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.","3.8.1":"* Refactor \\_QuillEditorState to QuillEditorState.","3.8.0":"* Support pasting with format.","3.7.3":"* Fix selection overlay for collapsed selection.","3.7.2":"* Reverted Embed toPlainText change.","3.7.1":"* Change Embed toPlainText to be empty string.","3.7.0":"* Replace Toolbar showHistory group with individual showRedo and showUndo.","3.6.5":"* Update Link dialogue for image/video.","3.6.4":"* Link dialogue TextInputType.multiline.","3.6.3":"* Bug fix for link button text selection.","3.6.2":"* Improve link button.","3.6.1":"* Remove SnackBar 'What is entered is not a link'.","3.6.0":"* Allow link button to enter text.","3.5.3":"* Change link button behavior.","3.5.2":"* Bug fix for embed.","3.5.1":"* Bug fix for platform util.","3.5.0":"* Removed redundant classes.","3.4.4":"* Add more translations.","3.4.3":"* Preset link from attributes.","3.4.2":"* Fix launch link edit mode.","3.4.1":"* Placeholder effective in scrollable.","3.4.0":"* Option to save image in read-only mode.","3.3.1":"* Pass any specified key in QuillEditor constructor to super.","3.3.0":"* Fixed Style toggle issue.","3.2.1":"* Added new translations.","3.2.0":"* Support multiple links insertion on the go.","3.1.1":"* Add selection completed callback.","3.1.0":"* Fixed image ontap functionality.","3.0.4":"* Add maxContentWidth constraint to editor.","3.0.3":"* Do not show caret on screen when the editor is not focused.","3.0.2":"* Fix launch link for read-only mode.","3.0.1":"* Handle null value of Attribute.link.","3.0.0":"* Launch link improvements.\n* Removed QuillSimpleViewer.","2.5.2":"* Skip image when pasting.","2.5.1":"* Bug fix for Desktop `Shift` + `Click` support.","2.5.0":"* Update checkbox list.","2.4.1":"* Desktop selection improvements.","2.4.0":"* Improve inline code style.","2.3.3":"* Improves selection rects to have consistent height regardless of individual segment text styles.","2.3.2":"* Allow disabling floating cursor.","2.3.1":"* Preserve last newline character on delete.","2.3.0":"* Massive changes to support flutter 2.8.","2.2.2":"* iOS - floating cursor.","2.2.1":"* Bug fix for imports supporting flutter 2.8.","2.2.0":"* Support flutter 2.8.","2.1.1":"* Add methods of clearing editor and moving cursor.","2.1.0":"* Add delete handler.","2.0.23":"* Support custom replaceText handler.","2.0.22":"* Fix attribute compare and fix font size parsing.","2.0.21":"* Handle click on embed object.","2.0.20":"* Improved UX/UI of Image widget.","2.0.19":"* When uploading a video, applying indicator.","2.0.18":"* Make toolbar dividers optional.","2.0.17":"* Allow alignment of the toolbar icons to match WrapAlignment.","2.0.16":"* Add hide / show alignment buttons.","2.0.15":"* Implement change cursor to SystemMouseCursors.click when hovering a link styled text.","2.0.14":"* Enable customize the checkbox widget using DefaultListBlockStyle style.","2.0.13":"* 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.","2.0.11":"* Fix visibility of text selection handlers on scroll.","2.0.10":"* cursorConnt.color notify the text_line to repaint if it was disposed.","2.0.9":"* Improve UX when trying to add a link.","2.0.8":"* Adding translations to the toolbar.","2.0.7":"* Added theming options for toolbar icons and LinkDialog.","2.0.6":"* Avoid runtime error when placed inside TabBarView.","2.0.5":"* Support inline code formatting.","2.0.4":"* Enable history shortcuts for desktop.","2.0.3":"* Fix cursor when line contains image.","2.0.2":"* Address KeyboardListener class name conflict.","2.0.1":"* Upgrade flutter_colorpicker to 0.5.0.","2.0.0":"* Text Alignment functions + Block Format standards.","1.9.6":"* Support putting QuillEditor inside a Scrollable view.","1.9.5":"* Skip image when pasting.","1.9.4":"* 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.","1.9.2":"* Support for building custom inline styles.","1.9.1":"* Cursor jumps to the most appropriate offset to display selection.","1.9.0":"* Support inline image.","1.8.3":"* Updated quill_delta.","1.8.2":"* Support mobile image alignment.","1.8.1":"* Support mobile custom size image.","1.8.0":"* Support entering link for image/video.","1.7.3":"* Bumps photo_view version.","1.7.2":"* Fix static analysis error.","1.7.1":"* Support Youtube video.","1.7.0":"* Support video.","1.6.4":"* Bug fix for clear format button.","1.6.3":"* Fixed dragging right handle scrolling issue.","1.6.2":"* Fixed the position of the selection status drag handle.","1.6.1":"* Upgrade image_picker and flutter_colorpicker.","1.6.0":"* Support Multi Row Toolbar.","1.5.0":"* Remove file_picker dependency.","1.4.1":"* Remove filesystem_picker dependency.","1.4.0":"* Remove path_provider dependency.","1.3.4":"* Add option to paintCursorAboveText.","1.3.3":"* Upgrade file_picker version.","1.3.2":"* Fix copy/paste bug.","1.3.1":"* New logo.","1.3.0":"* Support flutter 2.2.0.","1.2.2":"* Checkbox supports tapping.","1.2.1":"* Indented position not holding while editing.","1.2.0":"* Fix image button cancel causes crash.","1.1.8":"* Fix height of empty line bug.","1.1.7":"* Fix text selection in read-only mode.","1.1.6":"* Remove universal_html dependency.","1.1.5":"* Enable \"Select\", \"Select All\" and \"Copy\" in read-only mode.","1.1.4":"* Fix text selection issue.","1.1.3":"* Update example folder.","1.1.2":"* Add pedantic.","1.1.1":"* Base64 image support.","1.1.0":"* Support null safety.","1.0.9":"* Web support for raw editor and keyboard listener.","1.0.8":"* Support token attribute.","1.0.7":"* Fix crash on web (dart:io).","1.0.6":"* Add desktop support WINDOWS, MACOS and LINUX.","1.0.5":"* Bug fix: Can not insert newline when Bold is toggled ON.","1.0.4":"* Upgrade photo_view to ^0.11.0.","1.0.3":"* Fix issue that text is not displayed while typing WEB.","1.0.2":"* Update toolbar in sample home page.","1.0.1":"* Fix static analysis errors.","1.0.0":"* Support flutter 2.0.","1.0.0-dev.2":"* Improve link handling for tel, mailto and etc.","1.0.0-dev.1":"* Upgrade prerelease SDK & Bump for master.","0.3.5":"* Fix for cursor focus issues when keyboard is on.","0.3.4":"* Improve link handling for tel, mailto and etc.","0.3.3":"* More fix on cursor focus issue when keyboard is on.","0.3.2":"* Fix cursor focus issue when keyboard is on.","0.3.1":"* cursor focus when keyboard is on.","0.3.0":"* Line Height calculated based on font size.","0.2.12":"* Support placeholder.","0.2.11":"* Fix static analysis error.","0.2.10":"* Update TextInputConfiguration autocorrect to true in stable branch.","0.2.9":"* Update TextInputConfiguration autocorrect to true.","0.2.8":"* Support display local image besides network image in stable branch.","0.2.7":"* Support display local image besides network image.","0.2.6":"* Fix cursor after pasting.","0.2.5":"* Toggle text/background color button in toolbar.","0.2.4":"* Support the use of custom icon size in toolbar.","0.2.3":"* Support custom styles and image on local device storage without uploading.","0.2.2":"* Update git repo.","0.2.1":"* Fix static analysis error.","0.2.0":"* Add checked/unchecked list button in toolbar.","0.1.8":"* Support font and size attributes.","0.1.7":"* Support checked/unchecked list.","0.1.6":"* Fix getExtentEndpointForSelection.","0.1.5":"* Support text alignment.","0.1.4":"* Handle url with trailing spaces.","0.1.3":"* Handle cursor position change when undo/redo.","0.1.2":"* Handle more text colors.","0.1.1":"* Fix cursor issue when undo.","0.1.0":"* Fix insert image.","0.0.9":"* Handle rgba color.","0.0.8":"* Fix launching url.","0.0.7":"* Handle multiple image inserts.","0.0.6":"* More toolbar functionality.","0.0.5":"* Update example.","0.0.4":"* Update example.","0.0.3":"* Update home page meta data.","0.0.2":"* Support image upload and launch url in read-only mode.","0.0.1":"* Rich text editor based on Quill Delta."} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3f884bd6..bdf2dbbd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,19 +2,26 @@ First of all, we would like to thank you for your time and efforts on this project, we appreciate it -You can see tutorials online on how to contribute to any open source project, it's a simple process, and you can do it even if you are not Git expert, simply start by forking the repository, clone it, creating a new branch, make your changes and commit them, then push the branch to your fork, and you will get link to send a PR to the upstream repository +You can see tutorials online on how to contribute to any open source project, it's a simple process, and you can do it +even if you are not Git expert, simply start by forking the repository, clone it, create a new branch, make your +changes and commit them, then push the branch to your fork, and you will get link to send a PR to the upstream +repository -If you don't have anything specific in mind to improve or fix, you can take a look at the issues tab or take a look at the todos of the project, they all start with `TODO:` so you can search in your IDE or use the todos tab in the IDE +If you don't have anything specific in mind to improve or fix, you can take a look at the issues tab or take a look at +the todos of the project, they all start with `TODO:` so you can search in your IDE or use the todos tab in the IDE -You can also check the [Todo](./doc/todo.md) list or the issues if you want to +You can also check the [Todo](./doc/todo.md) list or the issues if you want to -> Make sure to not edit the `CHANGELOG.md` or the version in `pubspec.yaml` for any of the packages, this process will be automated by CI. +> Make sure to not edit the `CHANGELOG.md` or the version in `pubspec.yaml` for any of the packages, CI will automate +> this process. ## Requirements -- [Flutter SDK](https://docs.flutter.dev/get-started/install) which can be installed by following the instructions the provided link, also make sure to add it to your path so `flutter --version` and `dart --version` work -- [Intellij IDEA Community Edition](https://www.jetbrains.com/idea/download/) or [Android Studio](https://developer.android.com/studio) (with Dart and Flutter plugins) or use [VS Code](https://code.visualstudio.com/) (with Dart and flutter extensions) - +- [Flutter SDK](https://docs.flutter.dev/get-started/install), which can be installed by following the instructions the + provided link, also make sure to add it to your path so `flutter --version` and `dart --version` work +- [IntelliJ IDEA Community Edition](https://www.jetbrains.com/idea/download/) + or [Android Studio](https://developer.android.com/studio) (with Dart and Flutter plugins) or + use [VS Code](https://code.visualstudio.com/) (with Dart and flutter extensions) ## Test your changes 🧪 @@ -22,8 +29,10 @@ Make sure you have the [Requirement](#requirements) installed and configured cor To test your changes: -1. Go to the [Example project](./example/) in [main.dart](./example/lib/main.dart) and run the project either by using your IDE or `flutter run` -2. Make sure to read the [Development Notes](./doc/development_notes.md) if you made certain changes or [Translations Page](./doc/translation.md) if you made changes to the translations of the package +1. Go to the [Example project](./example/) in [main.dart](./example/lib/main.dart) and run the project either by using + your IDE or `flutter run` +2. Make sure to read the [Development Notes](./doc/development_notes.md) if you made certain changes + or [Translations Page](./doc/translation.md) if you made changes to the translations of the package ## Steps to contributing @@ -35,24 +44,29 @@ You will need a GitHub account as well as Git installed and configured with your ``` git remote add upstream git@github.com:singerdmx/flutter-quill.git ``` -4. Open the project with your favorite IDE, usually, we prefer to use Jetbrains IDEs, but since [VS Code](https://code.visualstudio.com) is more used and has more support for Dart, then we suggest using it if you want to. +4. Open the project with your favorite IDE, usually, we prefer to use Jetbrains IDEs, but + since [VS Code](https://code.visualstudio.com) is more used and has more support for Dart, then we suggest using it + if you want to. 5. Create a new git branch and switch to it using `git checkout -b` 6. Make your changes -7. If you are working on changes that depend on different libraries in the same repo, then in that directory copy `pubspec_overrides.yaml.disabled` which exists in all the packages (`flutter_quill_test` and `flutter_quill_extensions` etc...) -to `pubspec_overrides.yaml` which will be ignored by `.gitignore` and will be used by dart pub to override the libraries +7. If you are working on changes that depend on different libraries in the same repo, then in that directory + copy `pubspec_overrides.yaml.disabled` which exists in all the packages (`flutter_quill_test` + and `flutter_quill_extensions` etc...) + to `pubspec_overrides.yaml` which will be ignored by `.gitignore` and will be used by dart pub to override the + libraries ``` cp pubspec_overrides.yaml.disabled pubspec_overrides.yaml ``` - or save some time with the following script: + or save some time with the following script: ``` ./scripts/enable_local_dev.sh ``` 8. Test them in the [example](./example) and add changes in there if necessary -9. Run the following script if possible - ``` - ./scripts/before_push.sh - ``` +9. Run the following script if possible + ``` + ./scripts/before_push.sh + ``` 10. When you are done sending your pull request, run: ``` git add . @@ -62,10 +76,11 @@ to `pubspec_overrides.yaml` which will be ignored by `.gitignore` and will be us this will push the new branch to your forked repository 11. Now you can send your pull request either by following the link that you will get in the command line or open your -forked repository. You will find an option to send the pull request, you can also -open the [Pull Requests](https://github.com/singerdmx/flutter-quill) tab and send new pull request + forked repository. You will find an option to send the pull request, you can also + open the [Pull Requests](https://github.com/singerdmx/flutter-quill) tab and send new pull request 12. Now, wait for the review, and we might ask you to make more changes, then run: + ``` git add . git commit -m "Your new commit message" diff --git a/README.md b/README.md index e37e1dea..8a55bb0d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Flutter Quill

- + Flutter Quill

A rich text editor for Flutter

@@ -12,14 +12,23 @@ [![Watch on GitHub][github-forks-badge]][github-forks-link] [license-badge]: https://img.shields.io/github/license/singerdmx/flutter-quill.svg?style=for-the-badge + [license-link]: ./LICENSE + [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge + [prs-link]: https://github.com/singerdmx/flutter-quill/issues + [github-watch-badge]: https://img.shields.io/github/watchers/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff + [github-watch-link]: https://github.com/singerdmx/flutter-quill/watchers + [github-star-badge]: https://img.shields.io/github/stars/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff + [github-star-link]: https://github.com/singerdmx/flutter-quill/stargazers + [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 --- @@ -27,37 +36,38 @@ FlutterQuill is a rich text editor and a [Quill] component for [Flutter]. This library is a WYSIWYG (What You See Is What You Get) editor built -for the modern Android, iOS, +for the modern Android, iOS, web and desktop platforms. Check out our [Youtube Playlist] or [Code Introduction](./doc/code_introduction.md) to take a detailed walkthrough of the code base. You can join our [Slack Group] for discussion. > If you are viewing this page from [pub.dev](https://pub.dev/) page, then you -might have some issues with opening some links, open it in the GitHub repo instead. +> might have some issues with opening some links, open it in the GitHub repo instead. + +## 📚 Table of contents -## Table of contents - [Flutter Quill](#flutter-quill) - - [Table of contents](#table-of-contents) - - [Screenshots](#screenshots) - - [Installation](#installation) - - [Platform Specific Configurations](#platform-specific-configurations) - - [Usage](#usage) - - [Migration](#migration) - - [Input / Output](#input--output) - - [Links](#links) - - [Configurations](#configurations) - - [Links](#links-1) - - [Font Family](#font-family) - - [Embed Blocks](#embed-blocks) - - [Using the embed blocks from `flutter_quill_extensions`](#using-the-embed-blocks-from-flutter_quill_extensions) - - [Links](#links-2) - - [Conversion to HTML](#conversion-to-html) - - [Translation](#translation) - - [Testing](#testing) - - [Contributors](#contributors) - -## Screenshots + - [📚 Table of contents](#-table-of-contents) + - [📸 Screenshots](#-screenshots) + - [📦 Installation](#-installation) + - [🛠 Platform Specific Configurations](#-platform-specific-configurations) + - [🚀 Usage](#-usage) + - [🔄 Migration](#-migration) + - [🔤 Input / Output](#-input--output) + - [🔗 Links](#-links) + - [⚙️ Configurations](#️-configurations) + - [🔗 Links](#-links-1) + - [🖋 Font Family](#-font-family) + - [📦 Embed Blocks](#-embed-blocks) + - [🛠️ Using the embed blocks from `flutter_quill_extensions`](#️-using-the-embed-blocks-from-flutter_quill_extensions) + - [🔗 Links](#-links-2) + - [🔄 Conversion to HTML](#-conversion-to-html) + - [🌐 Translation](#-translation) + - [🧪 Testing](#-testing) + - [👥 Contributors](#-contributors) + +## 📸 Screenshots
Tap to show/hide screenshots @@ -71,7 +81,7 @@ might have some issues with opening some links, open it in the GitHub repo inste
-## Installation +## 📦 Installation ```yaml dependencies: @@ -84,27 +94,31 @@ dependencies: dependencies: flutter_quill: git: https://github.com/singerdmx/flutter-quill.git + ref: v ``` -> Using the latest version and reporting any issues you encounter on GitHub will greatly contribute to the improvement of the library. -> Your input and insights are valuable in shaping a stable and reliable version for all the developers. Thank you for being part of the open-source community! +> Using the latest version and reporting any issues you encounter on GitHub will greatly contribute to the improvement +> of the library. +> Your input and insights are valuable in shaping a stable and reliable version for all the developers. Thank you for +> being part of the open-source community! > -## Platform Specific Configurations +## 🛠 Platform Specific Configurations + +Before using the package, we must inform you the package uses the following plugins: -Before using the package, we must inform you the package use the following plugins: - ``` - url_launcher - flutter_keyboard_visibility - device_info_plus - super_clipboard - ``` +1. [`url_launcher`](https://pub.dev/packages/url_launcher) to open links. +2. [`device_info_plus`](https://pub.dev/packages/device_info_plus) to view info about the current device. +3. [`flutter_keyboard_visibility`](https://pub.dev/packages/flutter_keyboard_visibility) to listen for keyboard visibility + changes. -All of them doesn't require any platform specific setup, except [super_clipboard](https://pub.dev/packages/super_clipboard) which needs some setup on Android only, it's used to support copying images and pasting them into editor then you must setup it, open the page in pub.dev and read the `README.md` to get the instructions. +All of them don't require any platform-specific setup. -The minSdkVersion is `23` as `super_clipboard` requires it +> Starting from Flutter Quill `9.4.x`, [super_clipboard](https://pub.dev/packages/super_clipboard) has been moved +> to [FlutterQuill Extensions], to use rich text pasting, support pasting images, and gif files, take a look +> at `flutter_quill_extensions` Readme. -## Usage +## 🚀 Usage First, you need to instantiate a controller @@ -143,11 +157,12 @@ in most cases, it's better to. Check out [Sample Page] for more advanced usage. -## Migration +## 🔄 Migration + Starting from version `8.0.0` We have added [Migration Guide](/doc/migration.md) for migration from different versions -## Input / Output +## 🔤 Input / Output This library uses [Quill Delta](https://quilljs.com/docs/delta/) to represent the document content. @@ -171,7 +186,7 @@ final json = jsonEncode(_controller.document.toDelta().toJson()); You can then write this to storage. -To open a FlutterQuill editor with an existing JSON representation that you've previously stored, +To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this: ```dart @@ -180,7 +195,7 @@ final json = jsonDecode(r'{"insert":"hello\n"}'); _controller.document = Document.fromJson(json); ``` -### Links +### 🔗 Links - [Quill Delta](https://quilljs.com/docs/delta/) - [Quill Delta Formats](https://quilljs.com/docs/formats) @@ -189,76 +204,95 @@ _controller.document = Document.fromJson(json); - [Quill JS Interactive Playground](https://quilljs.com/playground/) - [Quill JS GitHub repo](https://github.com/quilljs/quill) -## Configurations +## ⚙️ Configurations The `QuillToolbar` and `QuillEditor` widgets let you customize a lot of things [Sample Page] provides sample code for advanced usage and configuration. -### Links +### 🔗 Links + - [Using Custom App Widget](./doc/configurations/using_custom_app_widget.md) - [Localizations Setup](./doc/configurations/localizations_setup.md) - [Font Size](./doc/configurations/font_size.md) - [Font Family](#font-family) - [Custom Toolbar buttons](./doc/configurations/custom_buttons.md) -### Font Family +### 🖋 Font Family + +To use your own fonts, update your [Assets](./example/assets/fonts) folder and pass in `fontFamilyValues`. +More details +on [this commit](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/). -To use your own fonts, update your [assets folder](./example/assets/fonts) and pass in `fontFamilyValues`. -More details on [this commit](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/). +## 📦 Embed Blocks -## Embed Blocks +As of version 6.0, embed blocks are not provided by default as part of this package. -As of version 6.0, embed blocks are not provided by default as part of this package. Instead, this package provides an interface for all the users to provide their own implementations for embed blocks. Implementations for image, video, and formula embed blocks are proved in a separate package [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions). +Instead, this package provides an interface for all the users to provide their own implementations for embed blocks. +Implementations for image, video, and +formula embed blocks are proved in a separate +package [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions). Provide a list of embed -### Using the embed blocks from `flutter_quill_extensions` +### 🛠️ Using the embed blocks from `flutter_quill_extensions` -To see how to use the extension package, please take a look at the [README](./flutter_quill_extensions/README.md) of [FlutterQuill Extensions] +To see how to use the extension package, please take a look at the [README](./flutter_quill_extensions/README.md) +of [FlutterQuill Extensions] -### Links +### 🔗 Links - [Custom Embed Blocks](./doc/custom_embed_blocks.md) - [Custom Toolbar](./doc/custom_toolbar.md) -## Conversion to HTML +## 🔄 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 to publish it, or send an email. -**Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor +**Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when +pasting HTML content from the clipboard to the Quill Editor You have two options: -1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well -(it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly -1. Another option is to use -[vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document -to HTML. +1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to + HTML well + (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension + to do it more quickly +2. Another 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. + 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. + 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. this package doesn't convert the HTML back to Quill Delta as far as we know -## Translation -The package offers translations for the quill toolbar and editor, it will follow the system locale unless you set your own locale. +> **Converting to Delta from Markdown and HTML is highly experimental and shouldn't be used for production applications**, while the current implementation is far from perfect, it could improved a lot however **it will likely not work as expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info.
+> We use it **internally** as it is more suitable for our specific use case, copying content from external websites and pasting it into the editor +previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. + +## 🌐 Translation + +The package offers translations for the quill toolbar and editor, it will follow the system locale unless you set your +own locale. Open this [page](./doc/translation.md) for more info -## Testing +## 🧪 Testing Please use [flutter_quill_test](https://pub.dev/packages/flutter_quill_test) for testing -## Contributors +## 👥 Contributors - Special thanks to everyone who has contributed to this project... - + Contributors
@@ -270,20 +304,31 @@ Made with [contrib.rocks](https://contrib.rocks). and contributors who put time and effort into everything including making all the libraries, tools, and the information we rely on - We are incredibly grateful to many individuals and organizations who have played a -role in the project. This includes the welcoming community, dedicated volunteers, talented developers and -contributors, and the creators of the open-source tools we rely on. + role in the project. + This includes the welcoming community, dedicated volunteers, talented developers and + contributors, and the creators of the open-source tools we rely on. We welcome all contributions! -Please follow these guidelines when contributing to the project. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
+Please follow these guidelines when contributing to the project. +See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. +
-The [CONTRIBUTING.md](./CONTRIBUTING.md) has development notes, if you're planning on contributing to the package, please consider reading it. +The [CONTRIBUTING.md](./CONTRIBUTING.md) has development notes, if you're planning on contributing to the package, +please consider reading it. [Quill]: https://quilljs.com/docs/formats + [Flutter]: https://github.com/flutter/flutter + [FlutterQuill]: https://pub.dev/packages/flutter_quill + [FlutterQuill Extensions]: https://pub.dev/packages/flutter_quill_extensions + [ReactQuill]: https://github.com/zenoamaro/react-quill + [Youtube Playlist]: https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2 + [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/screens/quill/quill_screen.dart diff --git a/dart_quill_delta/CHANGELOG.md b/dart_quill_delta/CHANGELOG.md index 0bdf2ede..0064e681 100644 --- a/dart_quill_delta/CHANGELOG.md +++ b/dart_quill_delta/CHANGELOG.md @@ -4,6 +4,244 @@ All notable changes to this project will be documented in this file. +## 9.5.2 + +* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2 + +## 9.5.1 + +* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1 + +## 9.5.0 + +* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0 + +## 9.4.9 + +* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9 + +## 9.4.8 + +* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8 + +## 9.4.7 + +* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954 + +## New Contributors +* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7 + +## 9.4.6 + +* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938 +* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6 + +## 9.4.5 + +* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934 + +## New Contributors +* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5 + +## 9.4.4 + +* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4 + +## 9.4.3 + +* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926 + +## New Contributors +* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3 + +## 9.4.2 + +* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2 + +## 9.4.1 + +* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919 + +## New Contributors +* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1 + +## 9.4.0 + +This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x` + +- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`: + ```dart + QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( + searchButtonType: SearchButtonType.legacy, + ), + ) + ``` + While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future. + + If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget + + Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated) + +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b) + + +
+ +
+ Improved + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04) + +
+ + For the detailed changes, see #1904 + +- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911 + +- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call: + + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x` + + We initially wanted to publish `flutter_quill_super_clipboard` to allow: + - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins + - Using `flutter_quill_extensions` with optional `super_clipboard` + + To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above. + + Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated. + + The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by: + ```dart + // ignore: implementation_imports + import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; + ``` + + Then you can call: + ```dart + // ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; + ClipboardServiceProvider.setInstance(YourClipboardService()); +``` + + The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future. + + If you use the above imports, you might get **breaking changes** in **non-breaking change releases**. + +- Subscript and Superscript should now work for all languages and characters + + The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices. +We have removed the Apple font from the example + +- Allow pasting Markdown and HTML file content from the system to the editor + + Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor + Copying an HTML file, or HTML content from apps and websites is different than copying plain text. + + This is why this change requires `super_clipboard` implementation as this is platform-dependent: + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + as mentioned above. + + The following example for copying a Markdown file: + +
+ Markdown File Content + + ```md + + **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor + + You have two options: + + 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well + (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly + 1. Another 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 part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) + Typescript/Javascript package. + this package doesn't convert the HTML back to Quill Delta as far as we know + + ``` + +
+ +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e) + +
+ +
+ After + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788) + +
+ + Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot + however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info. + + ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9) + + For more details see #1915 + + Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. + + We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor + previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. + + Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions) + +## New Contributors +* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0 + ## 9.3.21 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 diff --git a/dart_quill_delta/pubspec.yaml b/dart_quill_delta/pubspec.yaml index d3b4fa78..8ab52492 100644 --- a/dart_quill_delta/pubspec.yaml +++ b/dart_quill_delta/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_quill_delta description: A port of quill-js-delta from typescript to dart -version: 9.3.21 +version: 9.5.2 homepage: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ repository: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/doc/configurations/custom_buttons.md b/doc/configurations/custom_buttons.md index 24f9e1ca..699b7b02 100644 --- a/doc/configurations/custom_buttons.md +++ b/doc/configurations/custom_buttons.md @@ -1,6 +1,9 @@ -# Custom `QuillToolbar` Buttons +# Custom `QuillToolbar` Buttons ✨ -You may add custom buttons to the _end_ of the toolbar, via the `customButtons` option, which is a `List` of `QuillToolbarCustomButtonOptions`. +You may add custom buttons to the _end_ of the toolbar, via the `customButtons` option, which is a `List` +of `QuillToolbarCustomButtonOptions`. + +## Adding an Icon 🖌️ To add an Icon, we should use a new `QuillToolbarCustomButtonOptions` class @@ -13,11 +16,13 @@ To add an Icon, we should use a new `QuillToolbarCustomButtonOptions` class ), ``` +## Example Usage 📚 + Each `QuillCustomButton` is used as part of the `customButtons` option as follows: ```dart -QuillToolbar( - configurations: QuillToolbarConfigurations( +QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( customButtons: [ QuillToolbarCustomButtonOptions( icon: const Icon(Icons.ac_unit), diff --git a/doc/configurations/font_size.md b/doc/configurations/font_size.md index c73ddcb8..2eb3d8e9 100644 --- a/doc/configurations/font_size.md +++ b/doc/configurations/font_size.md @@ -1,8 +1,12 @@ -# Font Size +# 🔠 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: +When enabled, the default font-size values can be modified via _optional_ `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'} diff --git a/doc/configurations/localizations_setup.md b/doc/configurations/localizations_setup.md index cf446868..fa7efa16 100644 --- a/doc/configurations/localizations_setup.md +++ b/doc/configurations/localizations_setup.md @@ -1,7 +1,7 @@ -# Localizations Setup -in addition to the required delegates mentioned above in [Using custom app widget](./using_custom_app_widget.md) +# 🌍 Localizations Setup + +In addition to the required delegates mentioned above in [Using custom app widget](./using_custom_app_widget.md), which are: -which are: ```dart localizationsDelegates: const [ DefaultCupertinoLocalizations.delegate, @@ -9,17 +9,16 @@ localizationsDelegates: const [ DefaultWidgetsLocalizations.delegate, ], ``` -which are used by official Flutter widgets -The library also needs the +Which are used by Flutter widgets. + +📌 Note: The library also needs the `FlutterQuillLocalizations.delegate`: + ```dart // Required localizations delegates ... FlutterQuillLocalizations.delegate ``` -To offer the default localizations. - -But **you don't have to** since we have wrapped the `QuillEditor` and `QuillToolbar` with `FlutterQuillLocalizationsWidget` which will check if it sets then it will go, if not, then it will be provided only for them, so it's not really required, but if you are overriding the `localizationsDelegates` you could also add the `FlutterQuillLocalizations.delegate` -which won't change anything +**You don't have to add this explicitly** because we have wrapped the `QuillEditor` and `QuillToolbar` with `FlutterQuillLocalizationsWidget`. This widget will check if the necessary localizations are set; if not, it will provide them only for these widgets. Therefore, it's not strictly required. However, if you are overriding the `localizationsDelegates`, you can also add the `FlutterQuillLocalizations.delegate`. -There are additional notes in the [Translation](../translation.md) section +📄 For additional notes, refer to the [Translation](../translation.md) section. diff --git a/doc/configurations/using_custom_app_widget.md b/doc/configurations/using_custom_app_widget.md index d9457211..f498db58 100644 --- a/doc/configurations/using_custom_app_widget.md +++ b/doc/configurations/using_custom_app_widget.md @@ -1,14 +1,12 @@ -# Using Custom App Widget +# 🛠️ Using Custom App Widget -This project uses some adaptive widgets like `AdaptiveTextSelectionToolbar` which require the following delegates: +The project uses some adaptive widgets like `AdaptiveTextSelectionToolbar` which require the following delegates: 1. Default Material Localizations delegate 2. Default Cupertino Localizations delegate 3. Default Widgets Localizations delegate -You don't need to include those since they are defined by default - but if you are using a Custom app or you are overriding the `localizationsDelegates` in the App widget -then please make sure it includes those: +You don't need to include these since they are defined by default. However, if you are using a custom app or overriding the `localizationsDelegates` in the App widget, ensure it includes the following: ```dart localizationsDelegates: const [ @@ -18,8 +16,8 @@ localizationsDelegates: const [ ], ``` -You might need more depending on your use case, for example, if you are using custom localizations for your app, using a custom app widget like `FluentApp` from [FluentUI] -which will also need + +You might need more depending on your use case. For example, if you are using custom localizations for your app with a custom app widget like `FluentApp` from [FluentUI], you will also need: ```dart localizationsDelegates: const [ @@ -29,8 +27,8 @@ localizationsDelegates: const [ ], ``` -Note: In the latest versions of `FluentApp` you no longer need to add the `localizationsDelegates` but this is just an example, for more [info](https://github.com/bdlukaa/fluent_ui/pull/946) +📌 Note: In recent versions of `FluentApp`, you no longer need to add the `localizationsDelegates`. This is just an example. For more information, refer to the [#946](https://github.com/bdlukaa/fluent_ui/pull/946). -There are additional notes on the [Localizations](./localizations_setup.md) page +📄 For additional notes, see the [Localizations](./localizations_setup.md) page. [FluentUI]: https://pub.dev/packages/fluent_ui diff --git a/doc/custom_embed_blocks.md b/doc/custom_embed_blocks.md index a4f3b700..19f23ab5 100644 --- a/doc/custom_embed_blocks.md +++ b/doc/custom_embed_blocks.md @@ -1,12 +1,16 @@ # Custom Embed Blocks -Sometimes you want to add some custom content inside your text, custom widgets inside of them. An example is adding notes to the text, or anything custom that you want to add in your text editor. +Sometimes you want to add some custom content inside your text, custom widgets inside them. +An example is adding +notes to the text, or anything custom that you want to add in your text editor. -The only thing that you need is to add a `CustomBlockEmbed` and provide a builder for it to the `embedBuilders` parameter, to transform the data inside of the Custom Block into a widget! +The only thing that you need is to add a `CustomBlockEmbed` and provide a builder for it to the `embedBuilders` +parameter, to transform the data inside the Custom Block into a widget! Here is an example: -Starting with the `CustomBlockEmbed`, here we extend it and add the methods that are useful for the 'Note' widget, which will be the `Document`, used by the `flutter_quill` to render the rich text. +Starting with the `CustomBlockEmbed`, here we extend it and add the methods that are useful for the 'Note' widget, which +will be the `Document`, used by the `flutter_quill` to render the rich text. ```dart class NotesBlockEmbed extends CustomBlockEmbed { @@ -21,7 +25,8 @@ class NotesBlockEmbed extends CustomBlockEmbed { } ``` -After that, we need to map this "notes" type into a widget. In that case, I used a `ListTile` with a text to show the plain text resume of the note, and the `onTap` function to edit the note. +After that, we need to map this "notes" type into a widget. In that case, I used a `ListTile` with a text to show the +plain text resume of the note, and the `onTap` function to edit the note. Don't forget to add this method to the `QuillEditor` after that! ```dart @@ -64,7 +69,11 @@ class NotesEmbedBuilder extends EmbedBuilder { } ``` -And finally, we write the function to add/edit this note. The `showDialog` function shows the QuillEditor to edit the note after the user ends the edition, we check if the document has something, and if it has, we add or edit the `NotesBlockEmbed` inside of a `BlockEmbed.custom` (this is a little detail that will not work if you don't pass the `CustomBlockEmbed` inside of a `BlockEmbed.custom`). +And finally, we write the function to add/edit this note. +The `showDialog` function shows the QuillEditor to edit the +note after the user ends the edition, we check if the document has something, and if it has, we add or edit +the `NotesBlockEmbed` inside of a `BlockEmbed.custom` (this is a little detail that will not work if you don't pass +the `CustomBlockEmbed` inside of a `BlockEmbed.custom`). ```dart Future _addEditNote(BuildContext context, {Document? document}) async { @@ -116,11 +125,12 @@ Future _addEditNote(BuildContext context, {Document? document}) async { } ``` -And voila, we have a custom widget inside of the rich text editor! +And voilà, we have a custom widget inside the rich text editor!

1

-> 1. For more info and a video example, see the [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877) +> 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) diff --git a/doc/custom_toolbar.md b/doc/custom_toolbar.md index fc661b6d..f2ba0ff7 100644 --- a/doc/custom_toolbar.md +++ b/doc/custom_toolbar.md @@ -1,13 +1,13 @@ # Custom Toolbar -If you want to use a custom toolbar but still want the support of this library +If you want to use a custom toolbar but still want the support of this library, You can use the `QuillBaseToolbar` which is the base for the `QuillToolbar` Example: ```dart -QuillToolbar( - configurations: const QuillToolbarConfigurations( +QuillToolbar.simple( + configurations: const QuillSimpleToolbarConfigurations( buttonOptions: QuillToolbarButtonOptions( base: QuillToolbarBaseButtonOptions( globalIconSize: 20, @@ -112,4 +112,5 @@ QuillToolbar( ) ``` -if you want a more customized toolbar feel free to create your own and use the `controller` to interact with the editor. checkout the `QuillToolbar` and the buttons inside it to see an example of how that will work +if you want a more customized toolbar feel free to create your own and use the `controller` to interact with the editor. +checkout the `QuillToolbar` and the buttons inside it to see an example of how that will work diff --git a/doc/development_notes.md b/doc/development_notes.md index aaaba2be..dfdc6960 100644 --- a/doc/development_notes.md +++ b/doc/development_notes.md @@ -1,10 +1,22 @@ # Development notes -- When updating the translations or localizations in the app, please take a look at the [Translation](./translation.md) page as it has important notes in order to work, if you also add a feature that adds new localizations then you need to the instructions of it in order for the translations to take effect -- We use the same package version and `CHANGELOG.md` for all the packages, for more [details](https://github.com/singerdmx/flutter-quill/pull/1878), the process is automated. We have a script that will do the followings: - 1. Generate the `CHANGELOG.md` files by `CHANGELOG_JSON.json` (source of data) and then paste them into all the packages we have (overwrite), you don't need to - manually change/update any of the mentioned files above, once a new GitHub release published, the CI will take the release notes from the release, pass the info to the - script, the release notes can be auto-generated by Github using a button, a descirptive PRs title would help but you don't have to since we can change it at anytime. - 2. The script require the new version as an argument, you don't need to run the script manually, when a maintainer create a new tag and publish a new GitHub release, the publish workflow will extract the new version from the tag name, run the script (pass the extracted version as an argument), commit the changes and push them into the repository, the script will update the `version` property for all the packages so the `flutter pub publish` will use the new version for each package correctly. +- When updating the translations or localizations in the app, please take a look at the [Translation](./translation.md) + page as it has important notes to work. + If you also add a feature that adds new localizations, then you need it + to the instructions of it in order for the translations to take effect +- We use the same package version and `CHANGELOG.md` for all the packages, for + more [details](https://github.com/singerdmx/flutter-quill/pull/1878), the process is automated. We have a script that + will do the following: + 1. Generate the `CHANGELOG.md` files by `CHANGELOG_JSON.json` (source of data) and then paste them into all the + packages we have (overwrite), you don't need to + manually change/update any of the mentioned files above, once a new GitHub release published, the CI will take + the release notes from the release, pass the info to the + script, the release notes can be auto-generated by GitHub using a button, a descriptive PRs title would help but + you don't have to since we can change it at anytime. + 2. The script require the new version as an argument, you don't need to run the script manually, when a maintainer + create a new tag and publish a new GitHub release, the publish workflow will extract the new version from the tag + name, run the script (pass the extracted version as an argument), commit the changes and push them into the + repository, the script will update the `version` property for all the packages so the `flutter pub publish` will + use the new version for each package correctly. - the script will be used the CI and no need to run it manually \ No newline at end of file + the script will be used the CI and no need to run it manually \ No newline at end of file diff --git a/doc/todo.md b/doc/todo.md deleted file mode 100644 index 7617b022..00000000 --- a/doc/todo.md +++ /dev/null @@ -1,55 +0,0 @@ -# Todo - -This is a todo list page that added recently and will be updated soon. - -## Table of contents -- [Todo](#todo) - - [Table of contents](#table-of-contents) - - [Flutter Quill](#flutter-quill) - - [Features](#features) - - [Improvemenets](#improvemenets) - - [Bugs](#bugs) - - [Flutter Quill Extensions](#flutter-quill-extensions) - - [Features](#features-1) - - [Improvemenets](#improvemenets-1) - - [Bugs](#bugs-1) - -## Flutter Quill - -### Features - - - Add support for Text magnification feature, for more [info](https://github.com/singerdmx/flutter-quill/issues/1504) - - Provide a way to expose quills undo redo stacks, for more [info](https://github.com/singerdmx/flutter-quill/issues/1381) - - Add callback to the `QuillToolbarColorButton` for custom color picking logic - -### Improvemenets - - - Improve the Raw Quill Editor, for more [info](https://github.com/singerdmx/flutter-quill/issues/1509) - - Provide more support to all the platforms - - Extract the shared properties between `QuillRawEditorConfigurations` and `QuillEditorConfigurations` - - The todo in the this [commit](https://github.com/singerdmx/flutter-quill/commit/79597ea6425357795c0663588ac079665241f23a) needs to be checked - - use `maybeOf` and of instead `ofNotNull` in the providers to follow flutter offical convenstion, completly rework the providers and update the build context extensions - - Add line through to the text when the check point checked is true - - Change the color of the numbers and dots in ol/ul to match the ones in the item list - - Fix the bugs of the font family and font size - - Try to update Quill Html Converter - - When pasting a HTML text from cliboard by not using the context menu builder, the new logic won't work - - When selecting all text and paste HTML text, it will not replace the current text, instead it will add a text - - Add strike-through in checkbox text when the checkpoint is checked - - No more using of dynamic - - There is a bug here, the first character is not being formatted when choosing font family or font size and type in the editor - - Fix the toolbar and the toolbar buttons, rework some of them, for example missing tooltips - -### Bugs - -Empty for now. -Please go to the [issues](https://github.com/singerdmx/flutter-quill/issues) - - -## Flutter Quill Extensions - -### Features - -### Improvemenets - -### Bugs \ No newline at end of file diff --git a/doc/translation.md b/doc/translation.md index e255a461..130eb653 100644 --- a/doc/translation.md +++ b/doc/translation.md @@ -1,6 +1,8 @@ -# Translation +# 🌍 Translation -The package offers translations for the quill toolbar and editor, it will follow the locale that is defined in your `WidgetsApp` for example `MaterialApp` which usually follows the system locally unless you set your own locale with: +The package offers translations for the quill toolbar and editor, it will follow the locale that is defined in +your `WidgetsApp` for example `MaterialApp` which usually follows the system locally unless you set your own locale +with: ```dart QuillToolbar.simple( @@ -23,46 +25,76 @@ Expanded( ) ``` -Currently, translations are available for these 37 locales: +## 🌐 Supported Locales -* `Locale('en')`, `Locale('ar')`, `Locale('bn')`, `Locale('bs')` -* `Locale('cs')`, `Locale('de')`, `Locale('da')`, `Locale('fr')` -* `Locale('he')`, `Locale('zh', 'CN')`, `Locale('zh', 'HK')` -* `Locale('ko')`, `Locale('ku')`, `Locale('ku', 'CKB')` -* `Locale('ro', 'RO')`, `Locale('ru')`, `Locale('es')`, `Locale('tk')`, `Locale('tr')` -* `Locale('uk')`, `Locale('ur')`, `Locale('pt')`, `Locale('pl')` -* `Locale('vi')`, `Locale('id')`, `Locale('it')`, `Locale('ms')` -* `Locale('nl')`, `Locale('no')`, `Locale('ne', 'NP')`, `Locale('fa')`, `Locale('hi')` -* `Locale('sk')`, `Locale('sr')`, `Locale('sv')`, `Locale('sw')`, `Locale('ja')` +Currently, translations are available for these 41 locales: -#### Contributing to translations +* `Locale('en')`, `Locale('hi')`, `Locale('ku', 'CKB')`, `Locale('pt')`, `Locale('sr')`, `Locale('ur')` +* `Locale('bg')`, `Locale('en', 'US')`, `Locale('id')`, `Locale('ms')`, `Locale('pt', 'br')`, `Locale('sv')`, `Locale('vi')` +* `Locale('bn')`, `Locale('es')`, `Locale('it')`, `Locale('ne')`, `Locale('ro')`, `Locale('sw')`, `Locale('zh')` +* `Locale('cs')`, `Locale('fa')`, `Locale('ja')`, `Locale('nl')`, `Locale('ro', 'RO')`, `Locale('tk')`, `Locale('zh', 'CN')` +* `Locale('da')`, `Locale('fr')`, `Locale('ko')`, `Locale('no')`, `Locale('ru')`, `Locale('tr')`, `Locale('zh', 'HK')` +* `Locale('de')`, `Locale('he')`, `Locale('ku')`, `Locale('pl')`, `Locale('ar')`, `Locale('sk')`, `Locale('uk')` -The translation files are located in the [l10n folder](../lib/src/l10n/). Feel free to contribute your own translations, just copy the [English translations](../lib/src/l10n/quill_en.arb) map and replace the values with your translations. +## 📌 Contributing to translations -Add a new file in the l10n folder with the following name -`quill_${localName}.arb` for example `quill_de.arb` +The translation files are located in the [l10n](../lib/src/l10n/) folder. Feel free to contribute your own translations. -paste the English version and replace the values +You can take a look at the [untranslated.json](../lib/src/l10n/untranslated.json) file, which is a generated file that +tells you which keys with which locales haven't translated so you can find the missing easily. -Also, you can take a look at the [untranslated.json](../lib/src/l10n/untranslated.json) JSON file, which is a generated file that tells you which keys with which locales haven't translated so you can find the missings easily +
+Add new local -After you are done and want to test the changes, run the following in the root folder (preferred): +1. Create a new file in [l10n](../lib/src/l10n/) folder, with the following name`quill_${localName}.arb` for + example `quill_de.arb` -``` -./scripts/regenerate_translations.sh -``` +2. Copy the [Arb Template](../lib/src/l10n/quill_en.arb) file and paste it into your new file, replace the values with + your translations + +3. Update [Supported Locales](#supported-locales) section in this page to update the supported translations for both the + number and the list + +
+ +
+Update existing local + +1. Navigate to [l10n](../lib/src/l10n/) folder -or (if you can't run the script for some reasons): +2. Find the existing local, let's say you want to update the Korean translations, it will be `quill_ko.arb` +3. Use [untranslated.json](../lib/src/l10n/untranslated.json) as a reference to find missing, update or add what you + want + to translate. + +
+
+ +> We usually avoid **updating the existing value of a key in the template file without updating the key or creating a new +one**. +> This will not update the [untranslated.json](../lib/src/l10n/untranslated.json) correctly and will make it harder +for contributors to find missing or incomplete. + +Once you finish, run the following script: + +```bash +dart ./scripts/regenerate_translations.dart ``` + +Or (if you can't run the script for some reason): + +```bash flutter gen-l10n dart fix --apply ./lib/src/l10n/generated dart format ./lib/src/l10n/generated ``` -This will generate the new dart files from the arb files in order to take effect, otherwise, you won't notice a difference +The script above will generate Dart files from the Arb files to test the changes and take effect, otherwise you +won't notice a difference. -> If you added or removed translations, make sure to update `_expectedTranslationKeysLength` variable in `./scripts/ensure_translations_correct.dart`
+> 🔧 If you added or removed translations in the template file, make sure to update `_expectedTranslationKeysLength` +> variable in [scripts/ensure_translations_correct.dart](../scripts/ensure_translations_correct.dart)
> Otherwise you don't need to update it. Then open a pull request so everyone can benefit from your translations! diff --git a/example/.metadata b/example/.metadata index e724e168..68b331fb 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" + revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - - platform: linux - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: web + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 # User provided section diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 735e0400..f20ebe56 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -11,7 +11,7 @@ linter: annotate_overrides: true avoid_empty_else: true avoid_escaping_inner_quotes: true - avoid_print: false + avoid_print: true avoid_redundant_argument_values: false avoid_types_on_closure_parameters: true avoid_void_async: true diff --git a/example/assets/fonts/SF-Pro-Display-Regular.otf b/example/assets/fonts/SF-Pro-Display-Regular.otf deleted file mode 100755 index 1279121f..00000000 Binary files a/example/assets/fonts/SF-Pro-Display-Regular.otf and /dev/null differ diff --git a/example/lib/gen/fonts.gen.dart b/example/lib/gen/fonts.gen.dart index f61cfa18..5a0580f1 100644 --- a/example/lib/gen/fonts.gen.dart +++ b/example/lib/gen/fonts.gen.dart @@ -10,9 +10,6 @@ class FontFamily { FontFamily._(); - /// Font family: SF-UI-Display - static const String sFUIDisplay = 'SF-UI-Display'; - /// Font family: ibarra-real-nova static const String ibarraRealNova = 'ibarra-real-nova'; diff --git a/example/lib/main.dart b/example/lib/main.dart index fbca48eb..2a8ed52c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter_localizations/flutter_localizations.dart' GlobalWidgetsLocalizations; import 'package:flutter_quill/flutter_quill.dart' show Document; import 'package:flutter_quill/translations.dart' show FlutterQuillLocalizations; +import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart' show HydratedBloc, HydratedStorage; import 'package:path_provider/path_provider.dart' @@ -29,6 +30,7 @@ void main() async { ? HydratedStorage.webStorageDirectory : await getApplicationDocumentsDirectory(), ); + FlutterQuillExtensions.useSuperClipboardPlugin(); runApp(const MyApp()); } @@ -69,6 +71,9 @@ class MyApp extends StatelessWidget { GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + // Uncomment this line to use provide flutter quill localizations + // in your widgets app, otherwise the quill widgets will provide it + // internally: // FlutterQuillLocalizations.delegate, ], supportedLocales: FlutterQuillLocalizations.supportedLocales, diff --git a/example/lib/screens/home/widgets/home_screen.dart b/example/lib/screens/home/widgets/home_screen.dart index 5d7a4369..6bfd6472 100644 --- a/example/lib/screens/home/widgets/home_screen.dart +++ b/example/lib/screens/home/widgets/home_screen.dart @@ -155,7 +155,7 @@ class HomeScreen extends StatelessWidget { ), ); } catch (e) { - print( + debugPrint( 'Error while loading json delta file: ${e.toString()}', ); scaffoldMessenger.showText( diff --git a/example/lib/screens/quill/my_quill_editor.dart b/example/lib/screens/quill/my_quill_editor.dart index 21791c7c..d50f75d8 100644 --- a/example/lib/screens/quill/my_quill_editor.dart +++ b/example/lib/screens/quill/my_quill_editor.dart @@ -4,11 +4,13 @@ import 'package:cached_network_image/cached_network_image.dart' show CachedNetworkImageProvider; import 'package:desktop_drop/desktop_drop.dart' show DropTarget; import 'package:flutter/material.dart'; -import 'package:flutter_quill/extensions.dart' show isAndroid, isIOS, isWeb; +import 'package:flutter_quill/extensions.dart' + show isAndroid, isDesktop, isIOS, isWeb; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill_extensions/embeds/widgets/image.dart' show getImageProviderByImageSource, imageFileExtensions; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; +import 'package:flutter_quill_extensions/models/config/video/editor/youtube_video_support_mode.dart'; import 'package:path/path.dart' as path; import '../../extensions/scaffold_messenger.dart'; @@ -28,6 +30,7 @@ class MyQuillEditor extends StatelessWidget { @override Widget build(BuildContext context) { + final defaultTextStyle = DefaultTextStyle.of(context); return QuillEditor( scrollController: scrollController, focusNode: focusNode, @@ -41,36 +44,29 @@ class MyQuillEditor extends StatelessWidget { useTextColorForDot: true, ), ), - customStyles: const DefaultStyles( + customStyles: DefaultStyles( h1: DefaultTextBlockStyle( - TextStyle( + defaultTextStyle.style.copyWith( fontSize: 32, height: 1.15, fontWeight: FontWeight.w300, ), - VerticalSpacing(16, 0), - VerticalSpacing(0, 0), + const VerticalSpacing(16, 0), + const VerticalSpacing(0, 0), null, ), - sizeSmall: TextStyle(fontSize: 9), - subscript: TextStyle( - fontFamily: 'SF-UI-Display', - fontFeatures: [FontFeature.subscripts()], - ), - superscript: TextStyle( - fontFamily: 'SF-UI-Display', - fontFeatures: [FontFeature.superscripts()], - ), + sizeSmall: defaultTextStyle.style.copyWith(fontSize: 9), ), scrollable: true, - placeholder: 'Start writting your notes...', + placeholder: 'Start writing your notes...', padding: const EdgeInsets.all(16), onImagePaste: (imageBytes) async { if (isWeb()) { return null; } // We will save it to system temporary files - final newFileName = '${DateTime.now().toIso8601String()}.png'; + final newFileName = + 'imageFile-${DateTime.now().toIso8601String()}.png'; final newPath = path.join( io.Directory.systemTemp.path, newFileName, @@ -85,7 +81,7 @@ class MyQuillEditor extends StatelessWidget { return null; } // We will save it to system temporary files - final newFileName = '${DateTime.now().toIso8601String()}.gif'; + final newFileName = 'gifFile-${DateTime.now().toIso8601String()}.gif'; final newPath = path.join( io.Directory.systemTemp.path, newFileName, @@ -129,6 +125,13 @@ class MyQuillEditor extends StatelessWidget { ); }, ), + videoEmbedConfigurations: QuillEditorVideoEmbedConfigurations( + // Loading YouTube videos on Desktop is not supported yet + // when using iframe platform view + youtubeVideoSupportMode: isDesktop(supportWeb: false) + ? YoutubeVideoSupportMode.customPlayerWithDownloadUrl + : YoutubeVideoSupportMode.iframeView, + ), )), TimeStampEmbedBuilderWidget(), ], diff --git a/example/lib/screens/quill/my_quill_toolbar.dart b/example/lib/screens/quill/my_quill_toolbar.dart index 7e177956..3f0281fb 100644 --- a/example/lib/screens/quill/my_quill_toolbar.dart +++ b/example/lib/screens/quill/my_quill_toolbar.dart @@ -222,25 +222,7 @@ class MyQuillToolbar extends StatelessWidget { '35': '35.0', '40': '40.0' }, - // headerStyleType: HeaderStyleType.buttons, - // buttonOptions: QuillSimpleToolbarButtonOptions( - // base: QuillToolbarBaseButtonOptions( - // afterButtonPressed: focusNode.requestFocus, - // // iconSize: 20, - // iconTheme: QuillIconTheme( - // iconButtonSelectedData: IconButtonData( - // style: IconButton.styleFrom( - // foregroundColor: Colors.blue, - // ), - // ), - // iconButtonUnselectedData: IconButtonData( - // style: IconButton.styleFrom( - // foregroundColor: Colors.red, - // ), - // ), - // ), - // ), - //), + searchButtonType: SearchButtonType.modern, customButtons: [ QuillToolbarCustomButtonOptions( icon: const Icon(Icons.add_alarm_rounded), @@ -306,6 +288,7 @@ class MyQuillToolbar extends StatelessWidget { : onImageInsert, ), ), + tableButtonOptions: const QuillToolbarTableButtonOptions(), ), ), ); diff --git a/example/lib/screens/quill/quill_screen.dart b/example/lib/screens/quill/quill_screen.dart index ec97b5d8..9cae766b 100644 --- a/example/lib/screens/quill/quill_screen.dart +++ b/example/lib/screens/quill/quill_screen.dart @@ -127,7 +127,7 @@ class _QuillScreenState extends State { IconButton( tooltip: 'Print to log', onPressed: () { - print( + debugPrint( jsonEncode(_controller.document.toDelta().toJson()), ); ScaffoldMessenger.of(context).showText( diff --git a/example/lib/screens/quill/samples/quill_videos_sample.dart b/example/lib/screens/quill/samples/quill_videos_sample.dart index 90f0243e..9e47456d 100644 --- a/example/lib/screens/quill/samples/quill_videos_sample.dart +++ b/example/lib/screens/quill/samples/quill_videos_sample.dart @@ -1,7 +1,7 @@ const quillVideosSample = [ {'insert': '\n'}, { - 'insert': {'video': 'https://youtu.be/xz6_AlJkDPA'}, + 'insert': {'video': 'https://youtu.be/fq4N0hgOWzU?si=SqoY_bAZYnjCkUvn'}, 'attributes': { 'width': '300', 'height': '300', @@ -10,10 +10,8 @@ const quillVideosSample = [ }, {'insert': '\n'}, {'insert': '\n'}, - {'insert': 'And this is just a youtube video'}, {'insert': '\n'}, - { - 'insert': 'This sample is not complete.', - }, + {'insert': 'The video above is a Youtube video.'}, + {'insert': '\n'}, {'insert': '\n'}, ]; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2c979d07..c89c5374 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: flutter_quill_test: ^9.3.4 quill_html_converter: ^9.3.4 quill_pdf_converter: ^9.3.4 - # Normal packages + # Dart Packages path: ^1.8.3 equatable: ^2.0.5 cross_file: ^0.3.4 @@ -108,8 +108,5 @@ flutter: - family: roboto-mono fonts: - asset: assets/fonts/RobotoMono-Regular.ttf - - family: SF-UI-Display - fonts: - - asset: assets/fonts/SF-Pro-Display-Regular.otf flutter_gen: \ No newline at end of file diff --git a/example/web/index.html b/example/web/index.html index dbf15e99..e627ce09 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -23,45 +23,24 @@ - + - example + Flutter Quill Example - - - - + + + + - - - - - - + + - + diff --git a/example/web/manifest.json b/example/web/manifest.json index 096edf8f..1088bbfd 100644 --- a/example/web/manifest.json +++ b/example/web/manifest.json @@ -1,5 +1,5 @@ { - "name": "example", + "name": "Flutter Quill Example", "short_name": "example", "start_url": ".", "display": "standalone", diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index 0bdf2ede..0064e681 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -4,6 +4,244 @@ All notable changes to this project will be documented in this file. +## 9.5.2 + +* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2 + +## 9.5.1 + +* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1 + +## 9.5.0 + +* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0 + +## 9.4.9 + +* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9 + +## 9.4.8 + +* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8 + +## 9.4.7 + +* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954 + +## New Contributors +* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7 + +## 9.4.6 + +* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938 +* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6 + +## 9.4.5 + +* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934 + +## New Contributors +* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5 + +## 9.4.4 + +* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4 + +## 9.4.3 + +* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926 + +## New Contributors +* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3 + +## 9.4.2 + +* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2 + +## 9.4.1 + +* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919 + +## New Contributors +* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1 + +## 9.4.0 + +This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x` + +- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`: + ```dart + QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( + searchButtonType: SearchButtonType.legacy, + ), + ) + ``` + While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future. + + If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget + + Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated) + +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b) + + +
+ +
+ Improved + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04) + +
+ + For the detailed changes, see #1904 + +- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911 + +- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call: + + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x` + + We initially wanted to publish `flutter_quill_super_clipboard` to allow: + - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins + - Using `flutter_quill_extensions` with optional `super_clipboard` + + To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above. + + Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated. + + The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by: + ```dart + // ignore: implementation_imports + import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; + ``` + + Then you can call: + ```dart + // ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; + ClipboardServiceProvider.setInstance(YourClipboardService()); +``` + + The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future. + + If you use the above imports, you might get **breaking changes** in **non-breaking change releases**. + +- Subscript and Superscript should now work for all languages and characters + + The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices. +We have removed the Apple font from the example + +- Allow pasting Markdown and HTML file content from the system to the editor + + Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor + Copying an HTML file, or HTML content from apps and websites is different than copying plain text. + + This is why this change requires `super_clipboard` implementation as this is platform-dependent: + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + as mentioned above. + + The following example for copying a Markdown file: + +
+ Markdown File Content + + ```md + + **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor + + You have two options: + + 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well + (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly + 1. Another 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 part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) + Typescript/Javascript package. + this package doesn't convert the HTML back to Quill Delta as far as we know + + ``` + +
+ +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e) + +
+ +
+ After + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788) + +
+ + Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot + however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info. + + ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9) + + For more details see #1915 + + Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. + + We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor + previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. + + Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions) + +## New Contributors +* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0 + ## 9.3.21 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 diff --git a/flutter_quill_extensions/README.md b/flutter_quill_extensions/README.md index 6bcbb4cc..834008bd 100644 --- a/flutter_quill_extensions/README.md +++ b/flutter_quill_extensions/README.md @@ -1,38 +1,42 @@ # Flutter Quill Extensions An extensions for [flutter_quill](https://pub.dev/packages/flutter_quill) -to support embedding widgets like images, formulas, videos, and more. +to support embedding widgets like images, formulas, videos, tables and more. - Check [Flutter Quill](https://github.com/singerdmx/flutter-quill) for details of use. +Check [Flutter Quill](https://github.com/singerdmx/flutter-quill) for details of use. - ## Table of Contents +> The support for tables is currently limited and under development, more are changes expected to arrive. We are actively working on enhancing its functionality and usability. We appreciate your feedback as it is invaluable in helping us refine and expand this feature. + +## 📚 Table of Contents - [Flutter Quill Extensions](#flutter-quill-extensions) - - [Table of Contents](#table-of-contents) - - [About](#about) - - [Installation](#installation) - - [Platform Specific Configurations](#platform-specific-configurations) - - [Usage](#usage) - - [Embed Blocks](#embed-blocks) - - [Element properties](#element-properties) - - [Custom Element properties](#custom-element-properties) - - [Image Assets](#image-assets) - - [Drag and drop feature](#drag-and-drop-feature) - - [Features](#features) - - [Contributing](#contributing) - - [Acknowledgments](#acknowledgments) - - -## About - -Flutter Quill is a rich editor text. It'd allow you to customize a lot of things, + - [📚 Table of Contents](#-table-of-contents) + - [📝 About](#-about) + - [📦 Installation](#-installation) + - [🛠 Platform Specific Configurations](#-platform-specific-configurations) + - [🚀 Usage](#-usage) + - [⚙️ Configurations](#️-configurations) + - [📦 Embed Blocks](#-embed-blocks) + - [🔍 Element properties](#-element-properties) + - [🔧 Custom Element properties](#-custom-element-properties) + - [🖼️ Image Assets](#️-image-assets) + - [🎯 Drag and drop feature](#-drag-and-drop-feature) + - [💡 Features](#-features) + - [🤝 Contributing](#-contributing) + - [🌟 Acknowledgments](#-acknowledgments) + +## 📝 About + +Flutter Quill is a rich editor text. +It'd allow you to customize a lot of things, it has custom embed builders that allow you to render custom widgets in the editor
-this is an extension to extend its functionalities by adding more features like images, videos, and more -## Installation +This is an extension to extend its functionalities by adding more features like images, videos, and more + +## 📦 Installation Before starting using this package, please make sure to install -[flutter_quill](https://github.com/singerdmx/flutter-quill) package first and follow +[flutter_quill](https://github.com/singerdmx/flutter-quill) package first and follow its usage instructions. ```yaml @@ -47,76 +51,119 @@ dependencies: flutter_quill_extensions: git: https://github.com/singerdmx/flutter-quill.git path: flutter_quill_extensions + ref: v ``` -## Platform Specific Configurations - -> -> 1. We are using the [`gal`](https://github.com/natsuk4ze/) plugin to save images. -> For this to work, you need to add the appropriate configurations -> See to add the needed lines. -> -> 2. We also use [`image_picker`](https://pub.dev/packages/image_picker) plugin for picking images so please make sure to follow the instructions +## 🛠 Platform Specific Configurations + +The package uses the following plugins: + +1. [`gal`](https://github.com/natsuk4ze/) plugin to save images. + For this to work, you need to add the appropriate configurations + See to add the needed lines. +2. [`image_picker`](https://pub.dev/packages/image_picker) plugin for picking images so please make sure to follow the + instructions +3. [`youtube_player_flutter`](https://pub.dev/packages/youtube_player_flutter) plugin which + uses [`flutter_inappwebview`](https://pub.dev/packages/flutter_inappwebview) which has a requirement on web, please + follow this [link](https://pub.dev/packages/flutter_inappwebview#installation) to set up the support for web +4. [`image_picker`](https://pub.dev/packages/image_picker) which also + requires some configurations, follow this [link](https://pub.dev/packages/image_picker#installation). + It's needed for + Android, iOS, and macOS, we must inform you that you can't pick photos using the camera on a desktop so make sure to + handle that if you plan on adding support for the desktop, this may change in the future, and for more info follow + this [link](https://pub.dev/packages/image_picker#windows-macos-and-linux) +5. [`super_clipboard`](https://pub.dev/packages/super_clipboard) which needs some setup on Android only, it's used to + support copying images and pasting them into editor then you must set up it, open the page in pub.dev and read + the `README.md` or click on this [link](https://pub.dev/packages/super_clipboard#android-support) to get the + instructions. + +The minSdkVersion is `23` as `super_clipboard` requires it + + +> For loading the image from the internet

+> **Android**: you need to add permissions in `AndroidManifest.xml`, Follow +> this [Android Guide](https://developer.android.com/training/basics/network-ops/connecting) +> or [Flutter Networking](https://docs.flutter.dev/data-and-backend/networking#android) for more info, the internet +> permission is included by default only for debugging, you need to follow this link to add it in the release version +> too. you should allow loading images and videos only for the `https` protocol but if you want http too then you need +> to +> configure your Android application to accept `http` in the release mode, follow +> this [Android Cleartext / Plaintext HTTP](https://developer.android.com/privacy-and-security/risks/cleartext) page for +> more info.

+> **macOS**: you need to include a key in your `Info.plist`, follow +> this [link](https://docs.flutter.dev/data-and-backend/networking#macos) to add the required configurations > -> 3. We are using [youtube_player_flutter](https://pub.dev/packages/youtube_player_flutter) plugin which uses [flutter_inappwebview](https://pub.dev/packages/flutter_inappwebview) which has requirement on web, please follow this [link](https://pub.dev/packages/flutter_inappwebview#installation) in order to setup the support for web -> 4. For loading the image from the internet, we need the internet permission -> 1. For Android, you need to add some permissions in `AndroidManifest.xml`, Please follow this [link](https://developer.android.com/training/basics/network-ops/connecting) for more info, the internet permission is included by default only for debugging so you need to follow this link to add it in the release version too. you should allow loading images and videos only for the `https` protocol but if you want http too then you need to configure your Android application to accept `http` in the release mode, follow this [link](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted) for more info. -> 2. For macOS, you also need to include a key in your `Info.plist`, please follow this [link](https://stackoverflow.com/a/61201081/18519412) to add the required configurations -> -> The extension package also uses [image_picker](https://pub.dev/packages/image_picker) which also -> requires some configurations, follow this [link](https://pub.dev/packages/image_picker#installation). It's needed for Android, iOS, and macOS, we must inform you that you can't pick photos using the camera on a desktop so make sure to handle that if you plan on adding support for the desktop, this may change in the future, and for more info follow this [link](https://pub.dev/packages/image_picker#windows-macos-and-linux)
-> -## Usage +## 🚀 Usage -Before starting to use this package you must follow the [setup](#installation) +Start using the package in 3 steps: -Set the `embedBuilders` and `embedToolbar` params in configurations of `QuillEditor` and `QuillToolbar` with the -values provided by this repository. +1. Be sure to follow the [Installation](#installation) section. +2. This package already include `super_clipboard` and will be used internally in this package, to use it + in `flutter_quill`, call this function before using any of the widgets or functionalities -**Quill Toolbar**: -```dart -QuillToolbar( - configurations: QuillToolbarConfigurations( - embedButtons: FlutterQuillEmbeds.toolbarButtons(), - ), -), -``` + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` -**Quill Editor** -```dart -Expanded( - child: QuillEditor.basic( - configurations: QuillEditorConfigurations( - embedBuilders: kIsWeb ? FlutterQuillEmbeds.editorWebBuilders() : FlutterQuillEmbeds.editorBuilders(), + `super_clipboard` is comprehensive plugin that provides many clipboard features for reading and writing of rich text, + images and other formats. + + Executing this function will allow `flutter_quill` to use modern rich text features to paste HTML and Markdown, + support for Gif files, and other formats. + +3. Set the `embedBuilders` and `embedToolbar` params in configurations of `QuillEditor` and `QuillToolbar` with the + values provided by this repository. + + **Quill Toolbar**: + ```dart + QuillToolbar( + configurations: QuillToolbarConfigurations( + embedButtons: FlutterQuillEmbeds.toolbarButtons(), + ), ), - ), -) -``` + ``` -## Embed Blocks + **Quill Editor** + ```dart + Expanded( + child: QuillEditor.basic( + configurations: QuillEditorConfigurations( + embedBuilders: kIsWeb ? FlutterQuillEmbeds.editorWebBuilders() : FlutterQuillEmbeds.editorBuilders(), + ), + ), + ) + ``` -As of version [flutter_quill](https://pub.dev/packages/flutter_quill) 6.0, embed blocks are not provided by default as part of Flutter quill. Instead, it provides an interface for all the users to provide their implementations for embed blocks. Implementations for image, video, and formula embed blocks are proved in this package +## ⚙️ Configurations + +### 📦 Embed Blocks + +As of version [flutter_quill](https://pub.dev/packages/flutter_quill) `6.0.x`, embed blocks are not provided by default +as part of Flutter quill. +Instead, it provides an interface for all the users to provide their implementations for embed +blocks. +Implementations for image, video, and formula embed blocks are proved in this package The instructions for using the embed blocks are in the [Usage](#usage) section -### Element properties +### 🔍 Element properties -Currently the library has limitied support for the image and video properties +Currently, the library has limited support for the image and video properties, and it supports only `width`, `height`, `margin` ```json { - "insert": { - "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" - }, - "attributes": { - "style":"width: 50px; height: 50px; margin: 10px;" - } + "insert": { + "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" + }, + "attributes": { + "style": "width: 50px; height: 50px; margin: 10px;" + } } ``` -### Custom Element properties +### 🔧 Custom Element properties Doesn't apply to official Quill JS @@ -124,18 +171,18 @@ Define flutterAlignment` as follows: ```json { - "insert": { - "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" - }, - "attributes":{ - "style":"flutterAlignment: topLeft" - } + "insert": { + "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" + }, + "attributes": { + "style": "flutterAlignment: topLeft" + } } ``` This works for all platforms except Web -### Image Assets +### 🖼️ Image Assets If you want to use image assets in the Quill Editor, you need to make sure your assets folder is `assets` otherwise: @@ -155,22 +202,27 @@ QuillEditor.basic( ); ``` -This info is needed by the package to check if it asset image to use the `AssetImage` provider +This info is needed by the package to check if its asset image to use the `AssetImage` provider -### Drag and drop feature -Currently, the drag-and-drop feature is not officially supported, but you can achieve this very easily in the following steps: +### 🎯 Drag and drop feature -1. Drag and drop require native code, you can use any Flutter plugin you like, if you want a suggestion we recommend [desktop_drop](https://pub.dev/packages/desktop_drop), it was originally developed for desktop but it has support for the web as well as Android (that is not the case for iOS) +Currently, the drag-and-drop feature is not officially supported, but you can achieve this very easily in the following +steps: + +1. Drag and drop require native code, you can use any Flutter plugin you like, if you want a suggestion we + recommend [desktop_drop](https://pub.dev/packages/desktop_drop), it was originally developed for desktop. + It has support for the web as well as Android (that is not the case for iOS) 2. Add the dependency in your `pubspec.yaml` using the following command: ```yaml flutter pub add desktop_drop ``` - and import it with + and import it with ```dart import 'package:desktop_drop/desktop_drop.dart'; ``` -3. in the configurations of `QuillEditor`, use the `builder` to wrap the editor with `DropTarget` which comes from `desktop_drop` +3. in the configurations of `QuillEditor`, use the `builder` to wrap the editor with `DropTarget` which comes + from `desktop_drop` ```dart import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; @@ -191,6 +243,7 @@ Currently, the drag-and-drop feature is not officially supported, but you can ac ) ``` 4. Implement the `_onDragDone`, it depends on your use case but this is just a simple example + ```dart const List imageFileExtensions = [ '.jpeg', @@ -230,24 +283,25 @@ OnDragDoneCallback get _onDragDone { } ``` -## Features +## 💡 Features ```markdown ## Features — Easy to use and customizable -- Has the option to use a custom image provider for the images + +- Rich text, images and other formats - Useful utilities and widgets -- Handle different errors ``` -## Contributing +## 🤝 Contributing We welcome contributions! -Please follow these guidelines when contributing to our project. See [CONTRIBUTING.md](../CONTRIBUTING.md) for more details. +Please follow these guidelines when contributing to our project. See [CONTRIBUTING.md](../CONTRIBUTING.md) for more +details. -## Acknowledgments +## 🌟 Acknowledgments - Thanks to the [Flutter Team](https://flutter.dev/) - Thanks to the welcoming community, the volunteers who helped along the journey, developers, contributors diff --git a/flutter_quill_extensions/lib/embeds/table/editor/table_cell_embed.dart b/flutter_quill_extensions/lib/embeds/table/editor/table_cell_embed.dart new file mode 100644 index 00000000..27a4f5c6 --- /dev/null +++ b/flutter_quill_extensions/lib/embeds/table/editor/table_cell_embed.dart @@ -0,0 +1,75 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; + +class TableCellWidget extends StatefulWidget { + const TableCellWidget({ + required this.cellId, + required this.cellData, + required this.onUpdate, + required this.onTap, + super.key, + }); + final String cellId; + final String cellData; + final Function(FocusNode node) onTap; + final Function(String data) onUpdate; + + @override + State createState() => _TableCellWidgetState(); +} + +class _TableCellWidgetState extends State { + late final TextEditingController controller; + late final FocusNode node; + Timer? _debounce; + @override + void initState() { + controller = TextEditingController(text: widget.cellData); + node = FocusNode(); + super.initState(); + } + + void _onTextChanged() { + if (!_debounce!.isActive) { + widget.onUpdate(controller.text); + return; + } + } + + @override + void dispose() { + controller + ..removeListener(_onTextChanged) + ..dispose(); + node.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 40, + constraints: const BoxConstraints( + minHeight: 50, + ), + padding: const EdgeInsets.only(left: 5, right: 5, top: 5), + child: TextFormField( + controller: controller, + focusNode: node, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: const InputDecoration.collapsed(hintText: ''), + onTap: () { + widget.onTap.call(node); + }, + onTapAlwaysCalled: true, + onChanged: (value) { + _debounce = Timer( + const Duration(milliseconds: 900), + _onTextChanged, + ); + }, + ), + ); + } +} diff --git a/flutter_quill_extensions/lib/embeds/table/editor/table_embed.dart b/flutter_quill_extensions/lib/embeds/table/editor/table_embed.dart new file mode 100644 index 00000000..d313f92d --- /dev/null +++ b/flutter_quill_extensions/lib/embeds/table/editor/table_embed.dart @@ -0,0 +1,234 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/quill_delta.dart'; +import '../../../utils/quill_table_utils.dart'; +import 'table_cell_embed.dart'; +import 'table_models.dart'; + +class CustomTableEmbed extends CustomBlockEmbed { + const CustomTableEmbed(String value) : super(tableType, value); + + static const String tableType = 'table'; + + static CustomTableEmbed fromDocument(Document document) => + CustomTableEmbed(jsonEncode(document.toDelta().toJson())); + + Document get document => Document.fromJson(jsonDecode(data)); +} + +//Embed builder + +class QuillEditorTableEmbedBuilder extends EmbedBuilder { + @override + String get key => 'table'; + + @override + Widget build( + BuildContext context, + QuillController controller, + Embed node, + bool readOnly, + bool inline, + TextStyle textStyle, + ) { + final tableData = node.value.data; + return TableWidget( + tableData: tableData, + controller: controller, + ); + } +} + +class TableWidget extends StatefulWidget { + const TableWidget({ + required this.tableData, + required this.controller, + super.key, + }); + final QuillController controller; + final Map tableData; + + @override + State createState() => _TableWidgetState(); +} + +class _TableWidgetState extends State { + TableModel _tableModel = TableModel(columns: {}, rows: {}); + String _selectedColumnId = ''; + String _selectedRowId = ''; + + @override + void initState() { + _tableModel = TableModel.fromMap(widget.tableData); + super.initState(); + } + + void _addColumn() { + setState(() { + final id = '${_tableModel.columns.length + 1}'; + final position = _tableModel.columns.length; + _tableModel.columns[id] = ColumnModel(id: id, position: position); + _tableModel.rows.forEach((key, row) { + row.cells[id] = ''; + }); + }); + _updateTable(); + } + + void _addRow() { + setState(() { + final id = '${_tableModel.rows.length + 1}'; + final cells = {}; + _tableModel.columns.forEach((key, column) { + cells[key] = ''; + }); + _tableModel.rows[id] = RowModel(id: id, cells: cells); + }); + _updateTable(); + } + + void _removeColumn(String columnId) { + setState(() { + _tableModel.columns.remove(columnId); + _tableModel.rows.forEach((key, row) { + row.cells.remove(columnId); + }); + if (_selectedRowId == _selectedColumnId) { + _selectedRowId = ''; + } + _selectedColumnId = ''; + }); + _updateTable(); + } + + void _removeRow(String rowId) { + setState(() { + _tableModel.rows.remove(rowId); + _selectedRowId = ''; + }); + _updateTable(); + } + + void _updateCell(String columnId, String rowId, String data) { + setState(() { + _tableModel.rows[rowId]!.cells[columnId] = data; + }); + _updateTable(); + } + + void _updateTable() { + WidgetsBinding.instance.addPostFrameCallback((_) { + final offset = getEmbedNode( + widget.controller, + widget.controller.selection.start, + ).offset; + final delta = Delta()..insert({'table': _tableModel.toMap()}); + widget.controller.replaceText( + offset, + 1, + delta, + TextSelection.collapsed( + offset: offset, + ), + ); + }); + } + + @override + Widget build(BuildContext context) { + return Material( + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).textTheme.bodyMedium?.color ?? + Colors.black)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () async { + final position = renderPosition(context); + await showMenu( + context: context, + position: position, + items: [ + const PopupMenuItem( + value: TableOperation.addColumn, + child: Text('Add column'), + ), + const PopupMenuItem( + value: TableOperation.addRow, + child: Text('Add row'), + ), + const PopupMenuItem( + value: TableOperation.removeColumn, + child: Text('Delete column'), + ), + const PopupMenuItem( + value: TableOperation.removeRow, + child: Text('Delete row'), + ), + ]).then((value) { + if (value != null) { + if (value == TableOperation.addRow) { + _addRow(); + } + if (value == TableOperation.addColumn) { + _addColumn(); + } + if (value == TableOperation.removeColumn) { + _removeColumn(_selectedColumnId); + } + if (value == TableOperation.removeRow) { + _removeRow(_selectedRowId); + } + } + }); + }, + ), + const Divider( + color: Colors.white, + height: 1, + ), + Table( + border: const TableBorder.symmetric( + inside: BorderSide(color: Colors.white)), + children: _buildTableRows(), + ), + ], + ), + ), + ); + } + + List _buildTableRows() { + final rows = []; + + _tableModel.rows.forEach((rowId, rowModel) { + final rowCells = []; + final rowKey = rowId; + rowModel.cells.forEach((key, value) { + if (key != 'id') { + final columnId = key; + final data = value; + rowCells.add(TableCellWidget( + cellId: rowKey, + onTap: (node) { + setState(() { + _selectedColumnId = columnId; + _selectedRowId = rowModel.id; + }); + }, + cellData: data, + onUpdate: (data) => _updateCell(columnId, rowKey, data), + )); + } + }); + rows.add(TableRow(children: rowCells)); + }); + return rows; + } +} diff --git a/flutter_quill_extensions/lib/embeds/table/editor/table_models.dart b/flutter_quill_extensions/lib/embeds/table/editor/table_models.dart new file mode 100644 index 00000000..2c7ff4e9 --- /dev/null +++ b/flutter_quill_extensions/lib/embeds/table/editor/table_models.dart @@ -0,0 +1,85 @@ +class TableModel { + TableModel({required this.columns, required this.rows}); + + factory TableModel.fromMap(Map json) { + return TableModel( + columns: (json['columns'] as Map).map( + (key, value) => MapEntry( + key, + ColumnModel.fromMap( + value, + ), + ), + ), + rows: (json['rows'] as Map).map( + (key, value) => MapEntry( + key, + RowModel.fromMap( + value, + ), + ), + ), + ); + } + Map columns; + Map rows; + + Map toMap() { + return { + 'columns': columns.map( + (key, value) => MapEntry( + key, + value.toMap(), + ), + ), + 'rows': rows.map( + (key, value) => MapEntry( + key, + value.toMap(), + ), + ), + }; + } +} + +class ColumnModel { + ColumnModel({required this.id, required this.position}); + + factory ColumnModel.fromMap(Map json) { + return ColumnModel( + id: json['id'], + position: json['position'], + ); + } + String id; + int position; + + Map toMap() { + return { + 'id': id, + 'position': position, + }; + } +} + +class RowModel { + // Key is column ID, value is cell content + + RowModel({required this.id, required this.cells}); + + factory RowModel.fromMap(Map json) { + return RowModel( + id: json['id'], + cells: Map.from(json['cells']), + ); + } + String id; + Map cells; + + Map toMap() { + return { + 'id': id, + 'cells': cells, + }; + } +} diff --git a/flutter_quill_extensions/lib/embeds/table/toolbar/table_button.dart b/flutter_quill_extensions/lib/embeds/table/toolbar/table_button.dart new file mode 100644 index 00000000..071965ad --- /dev/null +++ b/flutter_quill_extensions/lib/embeds/table/toolbar/table_button.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/translations.dart'; +import '../../../models/config/table/table_configurations.dart'; +import '../../../utils/quill_table_utils.dart'; + +class QuillToolbarTableButton extends StatelessWidget { + const QuillToolbarTableButton({ + required this.controller, + this.options = const QuillToolbarTableButtonOptions(), + super.key, + }); + + final QuillController controller; + + final QuillToolbarTableButtonOptions options; + + double _iconSize(BuildContext context) { + final baseFontSize = baseButtonExtraOptions(context)?.iconSize; + final iconSize = options.iconSize; + return iconSize ?? baseFontSize ?? kDefaultIconSize; + } + + double _iconButtonFactor(BuildContext context) { + final baseIconFactor = baseButtonExtraOptions(context)?.iconButtonFactor; + final iconButtonFactor = options.iconButtonFactor; + return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; + } + + VoidCallback? _afterButtonPressed(BuildContext context) { + return options.afterButtonPressed ?? + baseButtonExtraOptions(context)?.afterButtonPressed; + } + + QuillIconTheme? _iconTheme(BuildContext context) { + return options.iconTheme ?? baseButtonExtraOptions(context)?.iconTheme; + } + + QuillToolbarBaseButtonOptions? baseButtonExtraOptions(BuildContext context) { + return context.quillToolbarBaseButtonOptions; + } + + IconData _iconData(BuildContext context) { + return options.iconData ?? + baseButtonExtraOptions(context)?.iconData ?? + Icons.table_chart; + } + + String _tooltip(BuildContext context) { + return options.tooltip ?? + baseButtonExtraOptions(context)?.tooltip ?? + context.loc.insertTable; + } + + void _sharedOnPressed(BuildContext context) { + _onPressedHandler(context); + _afterButtonPressed(context); + } + + @override + Widget build(BuildContext context) { + final tooltip = _tooltip(context); + final iconSize = _iconSize(context); + final iconButtonFactor = _iconButtonFactor(context); + final iconData = _iconData(context); + final childBuilder = + options.childBuilder ?? baseButtonExtraOptions(context)?.childBuilder; + + if (childBuilder != null) { + return childBuilder( + QuillToolbarTableButtonOptions( + afterButtonPressed: _afterButtonPressed(context), + iconData: iconData, + iconSize: iconSize, + iconButtonFactor: iconButtonFactor, + iconTheme: options.iconTheme, + tooltip: options.tooltip, + ), + QuillToolbarTableButtonExtraOptions( + context: context, + controller: controller, + onPressed: () => _sharedOnPressed(context), + ), + ); + } + + return QuillToolbarIconButton( + icon: Icon( + iconData, + size: iconButtonFactor * iconSize, + ), + tooltip: tooltip, + isSelected: false, + onPressed: () => _sharedOnPressed(context), + iconTheme: _iconTheme(context), + ); + } + + Future _onPressedHandler(BuildContext context) async { + final position = renderPosition(context); + await showMenu(context: context, position: position, items: [ + const PopupMenuItem(value: 2, child: Text('2x2')), + const PopupMenuItem(value: 4, child: Text('4x4')), + const PopupMenuItem(value: 6, child: Text('6x6')), + ]).then( + (value) { + if (value != null) { + insertTable(value, value, controller, ChangeSource.local); + } + }, + ); + } +} diff --git a/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart b/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart index 45c37f2a..b201e162 100644 --- a/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart +++ b/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart @@ -37,6 +37,7 @@ class QuillEditorVideoEmbedBuilder extends EmbedBuilder { return YoutubeVideoApp( videoUrl: videoUrl, readOnly: readOnly, + youtubeVideoSupportMode: configurations.youtubeVideoSupportMode, ); } final ((elementSize), margin, alignment) = getElementAttributes( @@ -53,7 +54,6 @@ class QuillEditorVideoEmbedBuilder extends EmbedBuilder { alignment: alignment, child: VideoApp( videoUrl: videoUrl, - context: context, readOnly: readOnly, onVideoInit: configurations.onVideoInit, ), diff --git a/flutter_quill_extensions/lib/embeds/video/toolbar/video_button.dart b/flutter_quill_extensions/lib/embeds/video/toolbar/video_button.dart index 51bf2b95..409cb718 100644 --- a/flutter_quill_extensions/lib/embeds/video/toolbar/video_button.dart +++ b/flutter_quill_extensions/lib/embeds/video/toolbar/video_button.dart @@ -9,6 +9,8 @@ import '../../others/image_video_utils.dart'; import '../video.dart'; import 'select_video_source.dart'; +// TODO: Add custom callback to validate the video link input + class QuillToolbarVideoButton extends StatelessWidget { const QuillToolbarVideoButton({ required this.controller, @@ -72,11 +74,6 @@ class QuillToolbarVideoButton extends StatelessWidget { final childBuilder = options.childBuilder ?? baseButtonExtraOptions(context)?.childBuilder; - // final iconColor = - // iconTheme?.iconUnselectedFillColor ?? theme.iconTheme.color; - // final iconFillColor = iconTheme?.iconUnselectedFillColor ?? - // (options.fillColor ?? theme.canvasColor); - if (childBuilder != null) { return childBuilder( QuillToolbarVideoButtonOptions( @@ -150,18 +147,6 @@ class QuillToolbarVideoButton extends StatelessWidget { .onVideoInsertCallback(videoUrl, controller); await options.videoConfigurations.onVideoInsertedCallback?.call(videoUrl); } - - // if (options.onVideoPickCallback != null) { - // final selector = options.mediaPickSettingSelector ?? - // ImageVideoUtils.selectMediaPickSetting; - // final source = await selector(context); - // if (source != null) { - // if (source == MediaPickSetting.gallery) { - // } else { - // await _typeLink(context); - // } - // } - // } else {} } Future _typeLink(BuildContext context) async { @@ -176,13 +161,4 @@ class QuillToolbarVideoButton extends StatelessWidget { ); return value; } - - // void _linkSubmitted(String? value) { - // if (value != null && value.isNotEmpty) { - // final index = controller.selection.baseOffset; - // final length = controller.selection.extentOffset - index; - - // controller.replaceText(index, length, BlockEmbed.video(value), null); - // } - // } } diff --git a/flutter_quill_extensions/lib/embeds/widgets/video_app.dart b/flutter_quill_extensions/lib/embeds/widgets/video_app.dart index 334f64ca..f6dc64fa 100644 --- a/flutter_quill_extensions/lib/embeds/widgets/video_app.dart +++ b/flutter_quill_extensions/lib/embeds/widgets/video_app.dart @@ -13,14 +13,16 @@ import '../../flutter_quill_extensions.dart'; class VideoApp extends StatefulWidget { const VideoApp({ required this.videoUrl, - required this.context, required this.readOnly, + @Deprecated( + 'The context is no longer required and will be removed on future releases', + ) + BuildContext? context, super.key, this.onVideoInit, }); final String videoUrl; - final BuildContext context; final bool readOnly; final void Function(GlobalKey videoContainerKey)? onVideoInit; @@ -92,29 +94,33 @@ class VideoAppState extends State { : _controller.play(); }); }, - child: Stack(alignment: Alignment.center, children: [ - Center( - child: AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: VideoPlayer(_controller), - )), - _controller.value.isPlaying - ? const SizedBox.shrink() - : Container( - color: const Color(0xfff5f5f5), - child: const Icon( - Icons.play_arrow, - size: 60, - color: Colors.blueGrey, - )) - ]), + child: Stack( + alignment: Alignment.center, + children: [ + Center( + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + )), + _controller.value.isPlaying + ? const SizedBox.shrink() + : Container( + color: const Color(0xfff5f5f5), + child: const Icon( + Icons.play_arrow, + size: 60, + color: Colors.blueGrey, + ), + ) + ], + ), ), ); } @override void dispose() { - super.dispose(); _controller.dispose(); + super.dispose(); } } 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 57b0c3d6..a6ce72cc 100644 --- a/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart +++ b/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart @@ -1,78 +1,143 @@ import 'package:flutter/gestures.dart' show TapGestureRecognizer; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart' show DefaultStyles; -import 'package:url_launcher/url_launcher.dart' show launchUrl; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import '../../models/config/video/editor/youtube_video_support_mode.dart'; +import 'video_app.dart'; + class YoutubeVideoApp extends StatefulWidget { const YoutubeVideoApp({ required this.videoUrl, required this.readOnly, + required this.youtubeVideoSupportMode, super.key, }); final String videoUrl; final bool readOnly; + final YoutubeVideoSupportMode youtubeVideoSupportMode; @override YoutubeVideoAppState createState() => YoutubeVideoAppState(); } class YoutubeVideoAppState extends State { - YoutubePlayerController? _youtubeController; + YoutubePlayerController? _youtubeIframeController; + + /// On some platforms such as desktop, Webview is not supported yet + /// as a result the youtube video player package is not supported too + /// this future will be not null and fetch the video url to load it using + /// [VideoApp] + Future? _loadYoutubeVideoByDownloadUrlFuture; + + /// Null if the video URL is not a YouTube video + String? get _videoId { + return YoutubePlayer.convertUrlToId(widget.videoUrl); + } @override void initState() { super.initState(); - final videoId = YoutubePlayer.convertUrlToId(widget.videoUrl); - if (videoId != null) { - _youtubeController = YoutubePlayerController( - initialVideoId: videoId, - flags: const YoutubePlayerFlags( - autoPlay: false, - ), - ); + final videoId = _videoId; + if (videoId == null) { + return; } + switch (widget.youtubeVideoSupportMode) { + case YoutubeVideoSupportMode.disabled: + break; + case YoutubeVideoSupportMode.iframeView: + _youtubeIframeController = YoutubePlayerController( + initialVideoId: videoId, + flags: const YoutubePlayerFlags( + autoPlay: false, + ), + ); + break; + case YoutubeVideoSupportMode.customPlayerWithDownloadUrl: + _loadYoutubeVideoByDownloadUrlFuture = + _loadYoutubeVideoWithVideoPlayerByVideoUrl(); + break; + } + } + + Future _loadYoutubeVideoWithVideoPlayerByVideoUrl() async { + final youtubeExplode = YoutubeExplode(); + final manifest = + await youtubeExplode.videos.streamsClient.getManifest(_videoId); + final streamInfo = manifest.muxed.withHighestBitrate(); + final videoDownloadUri = streamInfo.url; + return videoDownloadUri.toString(); + } + + Widget _clickableVideoLinkText({required DefaultStyles defaultStyles}) { + return RichText( + text: TextSpan( + text: widget.videoUrl, + style: defaultStyles.link, + recognizer: TapGestureRecognizer() + ..onTap = () => launchUrlString(widget.videoUrl), + ), + ); } @override Widget build(BuildContext context) { final defaultStyles = DefaultStyles.getInstance(context); - final youtubeController = _youtubeController; - - if (youtubeController == null) { - if (widget.readOnly) { - return RichText( - text: TextSpan( - text: widget.videoUrl, - style: defaultStyles.link, - recognizer: TapGestureRecognizer() - ..onTap = () => launchUrl( - Uri.parse(widget.videoUrl), - ), + + switch (widget.youtubeVideoSupportMode) { + case YoutubeVideoSupportMode.disabled: + throw UnsupportedError('YouTube video links are not supported'); + case YoutubeVideoSupportMode.iframeView: + final youtubeController = _youtubeIframeController; + + if (youtubeController == null) { + if (widget.readOnly) { + return _clickableVideoLinkText(defaultStyles: defaultStyles); + } + + return RichText( + text: TextSpan(text: widget.videoUrl, style: defaultStyles.link), + ); + } + return YoutubePlayerBuilder( + player: YoutubePlayer( + controller: youtubeController, + showVideoProgressIndicator: true, ), + builder: (context, player) { + return player; + }, + ); + case YoutubeVideoSupportMode.customPlayerWithDownloadUrl: + assert( + _loadYoutubeVideoByDownloadUrlFuture != null, + 'The load youtube video future should not null for "${widget.youtubeVideoSupportMode}" mode', ); - } - return RichText( - text: TextSpan(text: widget.videoUrl, style: defaultStyles.link), - ); + return FutureBuilder( + future: _loadYoutubeVideoByDownloadUrlFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator.adaptive()); + } + if (snapshot.hasError) { + return _clickableVideoLinkText(defaultStyles: defaultStyles); + } + return VideoApp( + videoUrl: snapshot.requireData, + readOnly: widget.readOnly, + ); + }, + ); } - - return YoutubePlayerBuilder( - player: YoutubePlayer( - controller: youtubeController, - showVideoProgressIndicator: true, - ), - builder: (context, player) { - return player; - }, - ); } @override void dispose() { - _youtubeController?.dispose(); + _youtubeIframeController?.dispose(); super.dispose(); } } diff --git a/flutter_quill_extensions/lib/flutter_quill_embeds.dart b/flutter_quill_extensions/lib/flutter_quill_embeds.dart index 2b0efb02..57b849e0 100644 --- a/flutter_quill_extensions/lib/flutter_quill_embeds.dart +++ b/flutter_quill_extensions/lib/flutter_quill_embeds.dart @@ -6,6 +6,8 @@ import 'embeds/image/editor/image_embed.dart'; import 'embeds/image/editor/image_web_embed.dart'; import 'embeds/image/toolbar/image_button.dart'; import 'embeds/others/camera_button/camera_button.dart'; +import 'embeds/table/editor/table_embed.dart'; +import 'embeds/table/toolbar/table_button.dart'; import 'embeds/video/editor/video_embed.dart'; import 'embeds/video/editor/video_web_embed.dart'; import 'embeds/video/toolbar/video_button.dart'; @@ -13,6 +15,7 @@ import 'models/config/camera/camera_configurations.dart'; import 'models/config/image/editor/image_configurations.dart'; import 'models/config/image/toolbar/image_configurations.dart'; import 'models/config/media/media_button_configurations.dart'; +import 'models/config/table/table_configurations.dart'; import 'models/config/video/editor/video_configurations.dart'; import 'models/config/video/editor/video_web_configurations.dart'; import 'models/config/video/toolbar/video_configurations.dart'; @@ -61,6 +64,7 @@ class FlutterQuillEmbeds { QuillEditorVideoEmbedBuilder( configurations: videoEmbedConfigurations, ), + QuillEditorTableEmbedBuilder(), ]; } @@ -117,6 +121,10 @@ class FlutterQuillEmbeds { QuillToolbarVideoButtonOptions? videoButtonOptions = const QuillToolbarVideoButtonOptions(), QuillToolbarCameraButtonOptions? cameraButtonOptions, + QuillToolbarTableButtonOptions? tableButtonOptions, + @Deprecated( + 'Media button has been removed, the value of this parameter will be ignored', + ) QuillToolbarMediaButtonOptions? mediaButtonOptions, }) => [ @@ -138,18 +146,11 @@ class FlutterQuillEmbeds { controller: controller, options: cameraButtonOptions, ), - // if (mediaButtonOptions != null) - // (controller, toolbarIconSize, iconTheme, dialogTheme) => - // QuillToolbarMediaButton( - // controller: mediaButtonOptions.controller ?? controller, - // options: mediaButtonOptions, - // ), - // Drop the support for formula button for now - // if (formulaButtonOptions != null) - // (controller, toolbarIconSize, iconTheme, dialogTheme) => - // QuillToolbarFormulaButton( - // controller: formulaButtonOptions.controller ?? controller, - // options: formulaButtonOptions, - // ), + if (tableButtonOptions != null) + (controller, toolbarIconSize, iconTheme, dialogTheme) => + QuillToolbarTableButton( + controller: controller, + options: tableButtonOptions, + ), ]; } diff --git a/flutter_quill_extensions/lib/flutter_quill_extensions.dart b/flutter_quill_extensions/lib/flutter_quill_extensions.dart index 772e5b15..f5ad43d4 100644 --- a/flutter_quill_extensions/lib/flutter_quill_extensions.dart +++ b/flutter_quill_extensions/lib/flutter_quill_extensions.dart @@ -1,5 +1,11 @@ library flutter_quill_extensions; +// ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; +import 'package:meta/meta.dart' show immutable; + +import 'services/clipboard/super_clipboard_service.dart'; + export 'embeds/embed_types.dart'; export 'embeds/formula/toolbar/formula_button.dart'; export 'embeds/image/editor/image_embed.dart'; @@ -8,6 +14,10 @@ export 'embeds/image/editor/image_web_embed.dart'; export 'embeds/image/toolbar/image_button.dart'; export 'embeds/others/camera_button/camera_button.dart'; export 'embeds/others/media_button/media_button.dart'; +export 'embeds/table/editor/table_cell_embed.dart'; +export 'embeds/table/editor/table_embed.dart'; +export 'embeds/table/editor/table_models.dart'; +export 'embeds/table/toolbar/table_button.dart'; export 'embeds/unknown/editor/unknown_embed.dart'; export 'embeds/video/editor/video_embed.dart'; export 'embeds/video/editor/video_web_embed.dart'; @@ -22,7 +32,20 @@ export 'models/config/image/editor/image_web_configurations.dart'; export 'models/config/image/toolbar/image_configurations.dart'; export 'models/config/media/media_button_configurations.dart'; export 'models/config/shared_configurations.dart'; +export 'models/config/table/table_configurations.dart'; export 'models/config/video/editor/video_configurations.dart'; export 'models/config/video/editor/video_web_configurations.dart'; export 'models/config/video/toolbar/video_configurations.dart'; export 'utils/utils.dart'; + +@immutable +class FlutterQuillExtensions { + const FlutterQuillExtensions._(); + + /// Override default implementation of [ClipboardServiceProvider.instacne] + /// to allow `flutter_quill` package to use `super_clipboard` plugin + /// to support rich text features, gif and images. + static void useSuperClipboardPlugin() { + ClipboardServiceProvider.setInstance(SuperClipboardService()); + } +} diff --git a/flutter_quill_extensions/lib/models/config/table/table_configurations.dart b/flutter_quill_extensions/lib/models/config/table/table_configurations.dart new file mode 100644 index 00000000..27d9a44e --- /dev/null +++ b/flutter_quill_extensions/lib/models/config/table/table_configurations.dart @@ -0,0 +1,27 @@ +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:meta/meta.dart' show immutable; + +class QuillToolbarTableButtonExtraOptions + extends QuillToolbarBaseButtonExtraOptions { + const QuillToolbarTableButtonExtraOptions({ + required super.controller, + required super.context, + required super.onPressed, + }); +} + +@immutable +class QuillToolbarTableButtonOptions extends QuillToolbarBaseButtonOptions< + QuillToolbarTableButtonOptions, QuillToolbarTableButtonExtraOptions> { + const QuillToolbarTableButtonOptions({ + super.iconData, + super.iconSize, + super.iconButtonFactor, + + /// specifies the tooltip text for the image button. + super.tooltip, + super.afterButtonPressed, + super.childBuilder, + super.iconTheme, + }); +} diff --git a/flutter_quill_extensions/lib/models/config/video/editor/video_configurations.dart b/flutter_quill_extensions/lib/models/config/video/editor/video_configurations.dart index 53039dee..cecc1c2e 100644 --- a/flutter_quill_extensions/lib/models/config/video/editor/video_configurations.dart +++ b/flutter_quill_extensions/lib/models/config/video/editor/video_configurations.dart @@ -1,10 +1,13 @@ import 'package:flutter/widgets.dart' show GlobalKey; import 'package:meta/meta.dart' show immutable; +import 'youtube_video_support_mode.dart'; + @immutable class QuillEditorVideoEmbedConfigurations { const QuillEditorVideoEmbedConfigurations({ this.onVideoInit, + this.youtubeVideoSupportMode = YoutubeVideoSupportMode.iframeView, }); /// [onVideoInit] is a callback function that gets triggered when @@ -21,4 +24,8 @@ class QuillEditorVideoEmbedConfigurations { /// // Customize other callback functions as needed /// ``` final void Function(GlobalKey videoContainerKey)? onVideoInit; + + /// Specifies how YouTube videos should be loaded if the video URL + /// is YouTube video. + final YoutubeVideoSupportMode youtubeVideoSupportMode; } diff --git a/flutter_quill_extensions/lib/models/config/video/editor/youtube_video_support_mode.dart b/flutter_quill_extensions/lib/models/config/video/editor/youtube_video_support_mode.dart new file mode 100644 index 00000000..d66285ce --- /dev/null +++ b/flutter_quill_extensions/lib/models/config/video/editor/youtube_video_support_mode.dart @@ -0,0 +1,19 @@ +/// Enum represents the different modes for handling YouTube video support. +enum YoutubeVideoSupportMode { + /// Disable loading of YouTube videos. + disabled, + + /// Load the video using the official YouTube IFrame API. + /// See [YouTube IFrame API](https://developers.google.com/youtube/iframe_api_reference) for more details. + /// + /// This will use Platform View on native platforms to use WebView + /// The WebView might not be supported on Desktop and will throw an exception + /// + /// See [Flutter InAppWebview Support for Flutter Desktop](https://github.com/pichillilorenzo/flutter_inappwebview/issues/460) + iframeView, + + /// Load the video using a custom video player by fetching the YouTube video URL. + /// Note: This might violate YouTube's terms of service. + /// See [YouTube Terms of Service](https://www.youtube.com/static?template=terms) for more details. + customPlayerWithDownloadUrl, +} diff --git a/flutter_quill_extensions/lib/services/clipboard/super_clipboard_service.dart b/flutter_quill_extensions/lib/services/clipboard/super_clipboard_service.dart new file mode 100644 index 00000000..2514f7a8 --- /dev/null +++ b/flutter_quill_extensions/lib/services/clipboard/super_clipboard_service.dart @@ -0,0 +1,177 @@ +import 'dart:async' show Completer; +import 'dart:convert' show utf8; + +import 'package:flutter/foundation.dart'; +// ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; +import 'package:super_clipboard/super_clipboard.dart'; + +/// Implementation based on https://pub.dev/packages/super_clipboard +class SuperClipboardService implements ClipboardService { + /// Null if the Clipboard API is not supported on this platform + /// https://pub.dev/packages/super_clipboard#usage + SystemClipboard? _getSuperClipboard() { + return SystemClipboard.instance; + } + + SystemClipboard _getSuperClipboardOrThrow() { + final clipboard = _getSuperClipboard(); + if (clipboard == null) { + // To avoid getting this exception, use _canProvide() + throw UnsupportedError( + 'Clipboard API is not supported on this platform.', + ); + } + return clipboard; + } + + Future _canProvide({required DataFormat format}) async { + final clipboard = _getSuperClipboard(); + if (clipboard == null) { + return false; + } + final reader = await clipboard.read(); + return reader.canProvide(format); + } + + Future _provideFileAsBytes({ + required SimpleFileFormat format, + }) async { + final clipboard = _getSuperClipboardOrThrow(); + final reader = await clipboard.read(); + final completer = Completer(); + + reader.getFile( + format, + (file) async { + final bytes = await file.readAll(); + completer.complete(bytes); + }, + onError: completer.completeError, + ); + final bytes = await completer.future; + return bytes; + } + + Future _provideFileAsString({ + required SimpleFileFormat format, + }) async { + final fileBytes = await _provideFileAsBytes(format: format); + final fileText = utf8.decode(fileBytes); + return fileText; + } + + /// According to super_clipboard docs, will return `null` if the value + /// is not available or the data is virtual (macOS and Windows) + Future _provideSimpleValueFormatAsString({ + required SimpleValueFormat format, + }) async { + final clipboard = _getSuperClipboardOrThrow(); + final reader = await clipboard.read(); + final value = await reader.readValue(format); + return value; + } + + @override + Future canProvideHtmlText() { + return _canProvide(format: Formats.htmlText); + } + + @override + Future getHtmlText() { + return _provideSimpleValueFormatAsString(format: Formats.htmlText); + } + + @override + Future canProvideHtmlTextFromFile() { + return _canProvide(format: Formats.htmlFile); + } + + @override + Future getHtmlTextFromFile() { + return _provideFileAsString(format: Formats.htmlFile); + } + + @override + Future canProvideMarkdownText() async { + // Formats.markdownText or Formats.mdText does not exist yet in super_clipboard + return false; + } + + @override + Future getMarkdownText() async { + // Formats.markdownText or Formats.mdText does not exist yet in super_clipboard + throw UnsupportedError( + 'SuperClipboardService does not support retrieving image files.', + ); + } + + @override + Future canProvideMarkdownTextFromFile() async { + // Formats.md is for markdown files + return _canProvide(format: Formats.md); + } + + @override + Future getMarkdownTextFromFile() async { + // Formats.md is for markdown files + return _provideFileAsString(format: Formats.md); + } + + @override + Future canProvidePlainText() { + return _canProvide(format: Formats.plainText); + } + + @override + Future getPlainText() { + return _provideSimpleValueFormatAsString(format: Formats.plainText); + } + + /// This will need to be updated if [getImageFileAsBytes] updated. + /// Notice that even if the copied image is JPEG, it still can be provided + /// as PNG, will handle JPEG check in case this info is incorrect. + @override + Future canProvideImageFile() async { + final canProvidePngFile = await _canProvide(format: Formats.png); + if (canProvidePngFile) { + return true; + } + final canProvideJpegFile = await _canProvide(format: Formats.jpeg); + if (canProvideJpegFile) { + return true; + } + return false; + } + + /// This will need to be updated if [canProvideImageFile] updated. + @override + Future getImageFileAsBytes() async { + final canProvidePngFile = await _canProvide(format: Formats.png); + if (canProvidePngFile) { + return _provideFileAsBytes(format: Formats.png); + } + return _provideFileAsBytes(format: Formats.jpeg); + } + + @override + Future canProvideGifFile() { + return _canProvide(format: Formats.gif); + } + + @override + Future getGifFileAsBytes() { + return _provideFileAsBytes(format: Formats.gif); + } + + @override + Future canPaste() async { + final clipboard = _getSuperClipboard(); + if (clipboard == null) { + return false; + } + final reader = await clipboard.read(); + final availablePlatformFormats = reader.platformFormats; + return availablePlatformFormats.isNotEmpty; + } +} diff --git a/flutter_quill_extensions/lib/utils/patterns.dart b/flutter_quill_extensions/lib/utils/patterns.dart index 53c1650d..abb75171 100644 --- a/flutter_quill_extensions/lib/utils/patterns.dart +++ b/flutter_quill_extensions/lib/utils/patterns.dart @@ -1,5 +1,5 @@ RegExp base64RegExp = RegExp( - r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$', + r'^(?:[A-Za-z0-9+\/][A-Za-z0-9+\/][A-Za-z0-9+\/][A-Za-z0-9+\/])*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$', ); final imageRegExp = RegExp( diff --git a/flutter_quill_extensions/lib/utils/quill_table_utils.dart b/flutter_quill_extensions/lib/utils/quill_table_utils.dart new file mode 100644 index 00000000..df2f8d00 --- /dev/null +++ b/flutter_quill_extensions/lib/utils/quill_table_utils.dart @@ -0,0 +1,85 @@ +import 'package:flutter/widgets.dart' + show + BuildContext, + MediaQuery, + Offset, + Overlay, + Rect, + RelativeRect, + RenderBox, + Size, + TextSelection; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/quill_delta.dart'; + +enum TableOperation { + addColumn, + addRow, + removeColumn, + removeRow, +} + +RelativeRect renderPosition(BuildContext context, [Size? size]) { + size ??= MediaQuery.sizeOf(context); + final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final button = context.findRenderObject() as RenderBox; + final position = RelativeRect.fromRect( + Rect.fromPoints( + button.localToGlobal(const Offset(0, -65), ancestor: overlay), + button.localToGlobal( + button.size.bottomRight(Offset.zero) + const Offset(-50, 0), + ancestor: overlay), + ), + Offset.zero & size * 0.40, + ); + return position; +} + +void insertTable(int rows, int columns, QuillController quillController, + ChangeSource? changeFrom) { + final tableData = _createTableData(rows, columns); + final delta = Delta()..insert({'table': tableData}); + final selection = quillController.selection; + final replacedLength = selection.extentOffset - selection.baseOffset; + final newBaseOffset = selection.baseOffset; + final newExtentOffsetCandidate = + (selection.baseOffset + 1 - replacedLength).toInt(); + final newExtentOffsetAdjusted = + newExtentOffsetCandidate < 0 ? 0 : newExtentOffsetCandidate; + quillController.replaceText( + newBaseOffset, + replacedLength, + delta, + TextSelection( + baseOffset: newBaseOffset, extentOffset: newExtentOffsetAdjusted), + ); +} + +Map _createTableData(int rows, int columns) { + // Crear el mapa para las columnas + final columnsData = {}; + for (var col = 0; col < columns; col++) { + final columnId = '${col + 1}'; + columnsData[columnId] = {'id': columnId, 'position': col}; + } + + // Crear el mapa para las filas + final rowsData = {}; + for (var row = 0; row < rows; row++) { + final rowId = '${row + 1}'; + rowsData[rowId] = {'id': rowId, 'cells': {}}; + + for (var col = 0; col < columns; col++) { + final columnId = '${col + 1}'; + rowsData[rowId]['cells'][columnId] = ''; + } + } + + // Combinar las columnas y filas en una estructura de tabla + final tableData = { + 'columns': columnsData, + 'rows': rowsData, + }; + + return tableData; +} diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index ae49584e..6033c879 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_extensions description: Embed extensions for flutter_quill including image, video, formula and etc. -version: 9.3.21 +version: 9.5.2 homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -28,19 +28,20 @@ dependencies: flutter: sdk: flutter - # Normal packages + # Dart Packages http: ^1.2.0 path: ^1.8.3 meta: ^1.10.0 universal_html: ^2.2.4 cross_file: ^0.3.3+6 - flutter_quill: ^9.3.12 - photo_view: ^0.14.0 + flutter_quill: ^9.5.1 + photo_view: ^0.15.0 + youtube_explode_dart: ^2.2.1 # Plugins video_player: ^2.8.1 - youtube_player_flutter: ^9.0.0 + youtube_player_flutter: ^9.0.1 url_launcher: ^6.2.1 super_clipboard: ^0.8.15 gal: ^2.3.0 @@ -53,4 +54,4 @@ dev_dependencies: flutter_lints: ^4.0.0 flutter: - uses-material-design: true \ No newline at end of file + uses-material-design: true diff --git a/flutter_quill_test/CHANGELOG.md b/flutter_quill_test/CHANGELOG.md index 0bdf2ede..0064e681 100644 --- a/flutter_quill_test/CHANGELOG.md +++ b/flutter_quill_test/CHANGELOG.md @@ -4,6 +4,244 @@ All notable changes to this project will be documented in this file. +## 9.5.2 + +* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2 + +## 9.5.1 + +* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1 + +## 9.5.0 + +* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0 + +## 9.4.9 + +* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9 + +## 9.4.8 + +* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8 + +## 9.4.7 + +* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954 + +## New Contributors +* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7 + +## 9.4.6 + +* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938 +* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6 + +## 9.4.5 + +* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934 + +## New Contributors +* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5 + +## 9.4.4 + +* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4 + +## 9.4.3 + +* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926 + +## New Contributors +* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3 + +## 9.4.2 + +* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2 + +## 9.4.1 + +* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919 + +## New Contributors +* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1 + +## 9.4.0 + +This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x` + +- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`: + ```dart + QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( + searchButtonType: SearchButtonType.legacy, + ), + ) + ``` + While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future. + + If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget + + Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated) + +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b) + + +
+ +
+ Improved + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04) + +
+ + For the detailed changes, see #1904 + +- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911 + +- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call: + + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x` + + We initially wanted to publish `flutter_quill_super_clipboard` to allow: + - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins + - Using `flutter_quill_extensions` with optional `super_clipboard` + + To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above. + + Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated. + + The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by: + ```dart + // ignore: implementation_imports + import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; + ``` + + Then you can call: + ```dart + // ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; + ClipboardServiceProvider.setInstance(YourClipboardService()); +``` + + The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future. + + If you use the above imports, you might get **breaking changes** in **non-breaking change releases**. + +- Subscript and Superscript should now work for all languages and characters + + The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices. +We have removed the Apple font from the example + +- Allow pasting Markdown and HTML file content from the system to the editor + + Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor + Copying an HTML file, or HTML content from apps and websites is different than copying plain text. + + This is why this change requires `super_clipboard` implementation as this is platform-dependent: + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + as mentioned above. + + The following example for copying a Markdown file: + +
+ Markdown File Content + + ```md + + **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor + + You have two options: + + 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well + (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly + 1. Another 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 part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) + Typescript/Javascript package. + this package doesn't convert the HTML back to Quill Delta as far as we know + + ``` + +
+ +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e) + +
+ +
+ After + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788) + +
+ + Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot + however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info. + + ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9) + + For more details see #1915 + + Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. + + We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor + previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. + + Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions) + +## New Contributors +* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0 + ## 9.3.21 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 diff --git a/flutter_quill_test/pubspec.yaml b/flutter_quill_test/pubspec.yaml index e7e9cb4a..d0b9139e 100644 --- a/flutter_quill_test/pubspec.yaml +++ b/flutter_quill_test/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_test description: Test utilities for flutter_quill which includes methods to simplify interacting with the editor in test cases. -version: 9.3.21 +version: 9.5.2 homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/lib/src/l10n/extensions/localizations.dart b/lib/src/l10n/extensions/localizations.dart index dea6be89..d8726dee 100644 --- a/lib/src/l10n/extensions/localizations.dart +++ b/lib/src/l10n/extensions/localizations.dart @@ -4,18 +4,23 @@ import '../generated/quill_localizations.dart' as generated; typedef FlutterQuillLocalizations = generated.FlutterQuillLocalizations; +class MissingFlutterQuillLocalizationException extends UnimplementedError { + MissingFlutterQuillLocalizationException(); + @override + String? get message => + 'FlutterQuillLocalizations instance is required and could not found. ' + 'Ensure that you are wrapping the current widget with ' + 'FlutterQuillLocalizationsWidget or add ' + 'FlutterQuillLocalizations.delegate to the localizationsDelegates ' + 'in your App widget (e.,g WidgetsApp, MaterialApp). If you believe this is a bug, consider reporting it.'; +} + extension LocalizationsExt on BuildContext { /// Require the [FlutterQuillLocalizations] instance /// /// `loc` is short for `localizations` FlutterQuillLocalizations get loc { return FlutterQuillLocalizations.of(this) ?? - (throw UnimplementedError( - "The instance of FlutterQuillLocalizations.of(context) is null and it's" - ' required, please make sure you wrapping the current widget with ' - 'FlutterQuillLocalizationsWidget or add ' - 'FlutterQuillLocalizations.delegate to the localizationsDelegates ' - 'in your App widget, please consider report this in GitHub as a bug', - )); + (throw MissingFlutterQuillLocalizationException()); } } diff --git a/lib/src/l10n/generated/quill_localizations.dart b/lib/src/l10n/generated/quill_localizations.dart index b9df03c0..76b25532 100644 --- a/lib/src/l10n/generated/quill_localizations.dart +++ b/lib/src/l10n/generated/quill_localizations.dart @@ -415,6 +415,12 @@ abstract class FlutterQuillLocalizations { /// **'Align right'** String get alignRight; + /// Justify the text over the full window width + /// + /// In en, this message translates to: + /// **'Align justify'** + String get alignJustify; + /// No description provided for @justifyWinWidth. /// /// In en, this message translates to: @@ -732,6 +738,12 @@ abstract class FlutterQuillLocalizations { /// In en, this message translates to: /// **'Paste'** String get paste; + + /// No description provided for @insertTable. + /// + /// In en, this message translates to: + /// **'Insert table'** + String get insertTable; } class _FlutterQuillLocalizationsDelegate diff --git a/lib/src/l10n/generated/quill_localizations_ar.dart b/lib/src/l10n/generated/quill_localizations_ar.dart index a3933936..c0b999bc 100644 --- a/lib/src/l10n/generated/quill_localizations_ar.dart +++ b/lib/src/l10n/generated/quill_localizations_ar.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsAr extends FlutterQuillLocalizations { @override String get alignRight => 'محاذاة اليمين'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'تبرير مع العرض'; @@ -288,4 +291,7 @@ class FlutterQuillLocalizationsAr extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_bg.dart b/lib/src/l10n/generated/quill_localizations_bg.dart index 0d09070b..e0ed5f85 100644 --- a/lib/src/l10n/generated/quill_localizations_bg.dart +++ b/lib/src/l10n/generated/quill_localizations_bg.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsBg extends FlutterQuillLocalizations { @override String get alignRight => 'Подравни вдясно'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Подравни във всяка колонка'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsBg extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_bn.dart b/lib/src/l10n/generated/quill_localizations_bn.dart index d0fce65a..bd3bff79 100644 --- a/lib/src/l10n/generated/quill_localizations_bn.dart +++ b/lib/src/l10n/generated/quill_localizations_bn.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsBn extends FlutterQuillLocalizations { @override String get alignRight => 'ডান সারিবদ্ধ'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'প্রস্থের সাথে সংযত'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsBn extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_cs.dart b/lib/src/l10n/generated/quill_localizations_cs.dart index 4bee5da7..f5b05858 100644 --- a/lib/src/l10n/generated/quill_localizations_cs.dart +++ b/lib/src/l10n/generated/quill_localizations_cs.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsCs extends FlutterQuillLocalizations { @override String get alignRight => 'Zarovnat vpravo'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Zarovnat do bloku'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsCs extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_da.dart b/lib/src/l10n/generated/quill_localizations_da.dart index aed1dbbf..0440ab08 100644 --- a/lib/src/l10n/generated/quill_localizations_da.dart +++ b/lib/src/l10n/generated/quill_localizations_da.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsDa extends FlutterQuillLocalizations { @override String get alignRight => 'Align right'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justify win width'; @@ -288,4 +291,7 @@ class FlutterQuillLocalizationsDa extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_de.dart b/lib/src/l10n/generated/quill_localizations_de.dart index e987cd86..f40346c6 100644 --- a/lib/src/l10n/generated/quill_localizations_de.dart +++ b/lib/src/l10n/generated/quill_localizations_de.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsDe extends FlutterQuillLocalizations { @override String get alignRight => 'Rechtsbündig ausrichten'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Blocksatz'; @@ -294,4 +297,7 @@ class FlutterQuillLocalizationsDe extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_en.dart b/lib/src/l10n/generated/quill_localizations_en.dart index a73d71ca..cd53070d 100644 --- a/lib/src/l10n/generated/quill_localizations_en.dart +++ b/lib/src/l10n/generated/quill_localizations_en.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsEn extends FlutterQuillLocalizations { @override String get alignRight => 'Align right'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justify win width'; @@ -290,6 +293,9 @@ class FlutterQuillLocalizationsEn extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } /// The translations for English, as used in the United States (`en_US`). diff --git a/lib/src/l10n/generated/quill_localizations_es.dart b/lib/src/l10n/generated/quill_localizations_es.dart index d418394c..e7e7b56e 100644 --- a/lib/src/l10n/generated/quill_localizations_es.dart +++ b/lib/src/l10n/generated/quill_localizations_es.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsEs extends FlutterQuillLocalizations { @override String get alignRight => 'Alinear a la derecha'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justificar'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsEs extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_fa.dart b/lib/src/l10n/generated/quill_localizations_fa.dart index 7566152d..aee8d865 100644 --- a/lib/src/l10n/generated/quill_localizations_fa.dart +++ b/lib/src/l10n/generated/quill_localizations_fa.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsFa extends FlutterQuillLocalizations { @override String get alignRight => 'چیدمان راست'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'تضمین عرض پنجره'; @@ -291,4 +294,7 @@ class FlutterQuillLocalizationsFa extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_fr.dart b/lib/src/l10n/generated/quill_localizations_fr.dart index d039b6f1..f382e131 100644 --- a/lib/src/l10n/generated/quill_localizations_fr.dart +++ b/lib/src/l10n/generated/quill_localizations_fr.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsFr extends FlutterQuillLocalizations { @override String get alignRight => 'Aligner à droite'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justifier'; @@ -296,4 +299,7 @@ class FlutterQuillLocalizationsFr extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_he.dart b/lib/src/l10n/generated/quill_localizations_he.dart index 7497db7b..92a24bf0 100644 --- a/lib/src/l10n/generated/quill_localizations_he.dart +++ b/lib/src/l10n/generated/quill_localizations_he.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsHe extends FlutterQuillLocalizations { @override String get alignRight => 'יישור לימין'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'יישור לרוחב החלון'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsHe extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_hi.dart b/lib/src/l10n/generated/quill_localizations_hi.dart index dda88d59..3c689f07 100644 --- a/lib/src/l10n/generated/quill_localizations_hi.dart +++ b/lib/src/l10n/generated/quill_localizations_hi.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsHi extends FlutterQuillLocalizations { @override String get alignRight => 'दाएं संरेखित करें'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'जस्टीफ़ी विन चौड़ाई'; @@ -291,4 +294,7 @@ class FlutterQuillLocalizationsHi extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_id.dart b/lib/src/l10n/generated/quill_localizations_id.dart index 27ee79dd..cfa0b8af 100644 --- a/lib/src/l10n/generated/quill_localizations_id.dart +++ b/lib/src/l10n/generated/quill_localizations_id.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsId extends FlutterQuillLocalizations { @override String get alignRight => 'Rata Kanan'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Rata Kanan dan Kiri'; @@ -292,4 +295,7 @@ class FlutterQuillLocalizationsId extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_it.dart b/lib/src/l10n/generated/quill_localizations_it.dart index 33b68757..584bab12 100644 --- a/lib/src/l10n/generated/quill_localizations_it.dart +++ b/lib/src/l10n/generated/quill_localizations_it.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsIt extends FlutterQuillLocalizations { @override String get alignRight => 'Allinea a destra'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Giustifica per larghezza finestra'; @@ -292,4 +295,7 @@ class FlutterQuillLocalizationsIt extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_ja.dart b/lib/src/l10n/generated/quill_localizations_ja.dart index 18ce5f55..f9da78d8 100644 --- a/lib/src/l10n/generated/quill_localizations_ja.dart +++ b/lib/src/l10n/generated/quill_localizations_ja.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsJa extends FlutterQuillLocalizations { @override String get alignRight => '右揃え'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => '両端揃え'; @@ -287,4 +290,7 @@ class FlutterQuillLocalizationsJa extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_ko.dart b/lib/src/l10n/generated/quill_localizations_ko.dart index d26d3900..d9b89188 100644 --- a/lib/src/l10n/generated/quill_localizations_ko.dart +++ b/lib/src/l10n/generated/quill_localizations_ko.dart @@ -5,7 +5,7 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations { FlutterQuillLocalizationsKo([super.locale = 'ko']); @override - String get pasteLink => '링크를 붙여넣어 주세요.'; + String get pasteLink => '링크를 붙여 넣어 주세요'; @override String get ok => '확인'; @@ -35,10 +35,10 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations { String get zoom => '확대하기'; @override - String get saved => '저장되었습니다.'; + String get saved => '저장되었습니다'; @override - String get text => '텍스트'; + String get text => '제목'; @override String get resize => '크기조정'; @@ -77,165 +77,168 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations { String get video => '비디오'; @override - String get undo => 'Undo'; + String get undo => '되돌리기'; @override - String get redo => 'Redo'; + String get redo => '다시실행'; @override - String get fontFamily => 'Font family'; + String get fontFamily => '글꼴'; @override - String get fontSize => 'Font size'; + String get fontSize => '글자 크기'; @override - String get bold => 'Bold'; + String get bold => '굵게'; @override - String get subscript => 'Subscript'; + String get subscript => '아래 첨자'; @override - String get superscript => 'Superscript'; + String get superscript => '위 첨자'; @override - String get italic => 'Italic'; + String get italic => '기울이기'; @override - String get underline => 'Underline'; + String get underline => '밑줄'; @override - String get strikeThrough => 'Strike through'; + String get strikeThrough => '취소선'; @override - String get inlineCode => 'Inline code'; + String get inlineCode => '인라인 코드'; @override - String get fontColor => 'Font color'; + String get fontColor => '글자 색상'; @override - String get backgroundColor => 'Background color'; + String get backgroundColor => '배경 색상'; @override - String get clearFormat => 'Clear format'; + String get clearFormat => '서식 지우기'; @override - String get alignLeft => 'Align left'; + String get alignLeft => '왼쪽 정렬'; @override - String get alignCenter => 'Align center'; + String get alignCenter => '가운데 정렬'; @override - String get alignRight => 'Align right'; + String get alignRight => '오른쪽 정렬'; @override - String get justifyWinWidth => 'Justify win width'; + String get alignJustify => 'Align justify'; @override - String get textDirection => 'Text direction'; + String get justifyWinWidth => '좌우로 정렬'; @override - String get headerStyle => 'Header style'; + String get textDirection => '텍스트 방향'; @override - String get normal => 'Normal'; + String get headerStyle => '헤더 스타일'; @override - String get heading1 => 'Heading 1'; + String get normal => '일반 텍스트'; @override - String get heading2 => 'Heading 2'; + String get heading1 => '제목 1'; @override - String get heading3 => 'Heading 3'; + String get heading2 => '제목 2'; @override - String get heading4 => 'Heading 4'; + String get heading3 => '제목 3'; @override - String get heading5 => 'Heading 5'; + String get heading4 => '제목 4'; @override - String get heading6 => 'Heading 6'; + String get heading5 => '제목 5'; @override - String get numberedList => 'Numbered list'; + String get heading6 => '제목 6'; @override - String get bulletList => 'Bullet list'; + String get numberedList => '번호 매기기 목록'; @override - String get checkedList => 'Checked list'; + String get bulletList => '글머리 기호 목록'; @override - String get codeBlock => 'Code block'; + String get checkedList => '체크리스트'; @override - String get quote => 'Quote'; + String get codeBlock => '코드 블록'; @override - String get increaseIndent => 'Increase indent'; + String get quote => '인용'; @override - String get decreaseIndent => 'Decrease indent'; + String get increaseIndent => '들여쓰기 증가'; @override - String get insertURL => 'Insert URL'; + String get decreaseIndent => '들여쓰기 감소'; @override - String get visitLink => 'Visit link'; + String get insertURL => 'URL 삽입'; @override - String get enterLink => 'Enter link'; + String get visitLink => '링크 방문'; @override - String get enterMedia => 'Enter media'; + String get enterLink => '링크 입력'; @override - String get edit => 'Edit'; + String get enterMedia => '미디어 입력'; @override - String get apply => 'Apply'; + String get edit => '편집'; @override - String get hex => 'Hex'; + String get apply => '적용'; @override - String get material => 'Material'; + String get hex => 'Hex 값'; @override - String get color => 'Color'; + String get material => 'Material 색상'; @override - String get findText => 'Find text'; + String get color => '색상'; @override - String get moveToPreviousOccurrence => 'Move to previous occurrence'; + String get findText => '찾기'; @override - String get moveToNextOccurrence => 'Move to next occurrence'; + String get moveToPreviousOccurrence => '이전 위치로 이동'; @override - String get savedUsingTheNetwork => 'Saved using the network'; + String get moveToNextOccurrence => '다음 위치로 이동'; @override - String get savedUsingLocalStorage => 'Saved using the local storage'; + String get savedUsingTheNetwork => '네트워크를 통해 저장'; + + @override + String get savedUsingLocalStorage => '로컬 스토리지를 통해 저장'; @override String theImageHasBeenSavedAt(String imagePath) { - return 'The image has been saved at: $imagePath'; + return '이미지가 저장되었습니다 $imagePath'; } @override - String get errorWhileSavingImage => 'Error while saving image'; + String get errorWhileSavingImage => '이미지를 저장하는데 실패했습니다'; @override - String get pleaseEnterTextForYourLink => "e.g., 'Learn more'"; + String get pleaseEnterTextForYourLink => '링크 제목 입력'; @override - String get pleaseEnterTheLinkURL => "e.g., 'https://example.com'"; + String get pleaseEnterTheLinkURL => "예시) 'https://example.com'"; @override - String get pleaseEnterAValidImageURL => 'Please enter a valid image URL'; + String get pleaseEnterAValidImageURL => '유효한 이미지 URL을 입력하세요'; @override String get pleaseEnterAValidVideoURL => '유효한 비디오 URL을 입력하세요'; @@ -250,41 +253,44 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations { String get caseSensitivityAndWholeWordSearch => '대소문자 구분 및 전체 단어 검색'; @override - String get caseSensitive => 'Case sensitive'; + String get caseSensitive => '대소문자 구분'; @override - String get wholeWord => 'Whole word'; + String get wholeWord => '전체 단어'; @override String get insertImage => '이미지 삽입'; @override - String get pickAPhotoFromYourGallery => 'Pick a photo from your gallery'; + String get pickAPhotoFromYourGallery => '갤러리에서 이미지 선택'; + + @override + String get takeAPhotoUsingYourCamera => '카메라로 사진 촬영'; @override - String get takeAPhotoUsingYourCamera => 'Take a photo using your camera'; + String get pasteAPhotoUsingALink => '이미지 링크 입력'; @override - String get pasteAPhotoUsingALink => 'Paste a photo using a link'; + String get pickAVideoFromYourGallery => '갤러리에서 동영상 선택'; @override - String get pickAVideoFromYourGallery => 'Pick a video from your gallery'; + String get recordAVideoUsingYourCamera => '카메라로 동영상 촬영'; @override - String get recordAVideoUsingYourCamera => 'Record a video using your camera'; + String get pasteAVideoUsingALink => '동영상 링크 입력'; @override - String get pasteAVideoUsingALink => 'Paste a video using a link'; + String get close => '닫기'; @override - String get close => 'Close'; + String get searchSettings => '검색 설정'; @override - String get searchSettings => 'Search settings'; + String get cut => '잘라내기'; @override - String get cut => 'Cut'; + String get paste => '붙여넣기'; @override - String get paste => 'Paste'; + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_ku.dart b/lib/src/l10n/generated/quill_localizations_ku.dart index 772d8110..c18d039a 100644 --- a/lib/src/l10n/generated/quill_localizations_ku.dart +++ b/lib/src/l10n/generated/quill_localizations_ku.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsKu extends FlutterQuillLocalizations { @override String get alignRight => 'بۆ ڕاست'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'پانی ڕێکبخە'; @@ -291,6 +294,9 @@ class FlutterQuillLocalizationsKu extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } /// The translations for Kurdish (`ku_CKB`). diff --git a/lib/src/l10n/generated/quill_localizations_ms.dart b/lib/src/l10n/generated/quill_localizations_ms.dart index c0218f2c..0be220c3 100644 --- a/lib/src/l10n/generated/quill_localizations_ms.dart +++ b/lib/src/l10n/generated/quill_localizations_ms.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsMs extends FlutterQuillLocalizations { @override String get alignRight => 'Align right'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justify win width'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsMs extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_ne.dart b/lib/src/l10n/generated/quill_localizations_ne.dart index ede805d7..b3e65f4d 100644 --- a/lib/src/l10n/generated/quill_localizations_ne.dart +++ b/lib/src/l10n/generated/quill_localizations_ne.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsNe extends FlutterQuillLocalizations { @override String get alignRight => 'दायाँ पङ्क्तिबद्ध'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'जस्टीफ़ी विन चौड़ाई'; @@ -294,4 +297,7 @@ class FlutterQuillLocalizationsNe extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_nl.dart b/lib/src/l10n/generated/quill_localizations_nl.dart index bb68eea8..cb5933d1 100644 --- a/lib/src/l10n/generated/quill_localizations_nl.dart +++ b/lib/src/l10n/generated/quill_localizations_nl.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsNl extends FlutterQuillLocalizations { @override String get alignRight => 'Align right'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justify win width'; @@ -292,4 +295,7 @@ class FlutterQuillLocalizationsNl extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_no.dart b/lib/src/l10n/generated/quill_localizations_no.dart index d7e97e4e..061aa27f 100644 --- a/lib/src/l10n/generated/quill_localizations_no.dart +++ b/lib/src/l10n/generated/quill_localizations_no.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsNo extends FlutterQuillLocalizations { @override String get alignRight => 'Høyrejuster'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Rettferdiggjør bredden'; @@ -292,4 +295,7 @@ class FlutterQuillLocalizationsNo extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_pl.dart b/lib/src/l10n/generated/quill_localizations_pl.dart index 4b5d400d..f1b8b289 100644 --- a/lib/src/l10n/generated/quill_localizations_pl.dart +++ b/lib/src/l10n/generated/quill_localizations_pl.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsPl extends FlutterQuillLocalizations { @override String get alignRight => 'Align right'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justify win width'; @@ -289,4 +292,7 @@ class FlutterQuillLocalizationsPl extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_pt.dart b/lib/src/l10n/generated/quill_localizations_pt.dart index aa28b8ff..33b3c5ab 100644 --- a/lib/src/l10n/generated/quill_localizations_pt.dart +++ b/lib/src/l10n/generated/quill_localizations_pt.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsPt extends FlutterQuillLocalizations { @override String get alignRight => 'Align right'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justify win width'; @@ -290,6 +293,9 @@ class FlutterQuillLocalizationsPt extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } /// The translations for Portuguese, as used in Brazil (`pt_BR`). diff --git a/lib/src/l10n/generated/quill_localizations_ro.dart b/lib/src/l10n/generated/quill_localizations_ro.dart index 92a9b40f..2e4d6dda 100644 --- a/lib/src/l10n/generated/quill_localizations_ro.dart +++ b/lib/src/l10n/generated/quill_localizations_ro.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsRo extends FlutterQuillLocalizations { @override String get alignRight => 'Aliniază la dreapta'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justifică lățimea ferestrei'; @@ -293,6 +296,9 @@ class FlutterQuillLocalizationsRo extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } /// The translations for Romanian Moldavian Moldovan, as used in Romania (`ro_RO`). diff --git a/lib/src/l10n/generated/quill_localizations_ru.dart b/lib/src/l10n/generated/quill_localizations_ru.dart index 07a2b4ba..d79d928d 100644 --- a/lib/src/l10n/generated/quill_localizations_ru.dart +++ b/lib/src/l10n/generated/quill_localizations_ru.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsRu extends FlutterQuillLocalizations { @override String get alignRight => 'Выровнять по правому краю'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Выровнять по ширине окна'; @@ -293,4 +296,7 @@ class FlutterQuillLocalizationsRu extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_sk.dart b/lib/src/l10n/generated/quill_localizations_sk.dart index a4609530..b881445e 100644 --- a/lib/src/l10n/generated/quill_localizations_sk.dart +++ b/lib/src/l10n/generated/quill_localizations_sk.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSk extends FlutterQuillLocalizations { @override String get alignRight => 'Zarovnať vpravo'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Zarovnať na šírku okna'; @@ -294,4 +297,7 @@ class FlutterQuillLocalizationsSk extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_sr.dart b/lib/src/l10n/generated/quill_localizations_sr.dart index 10b73608..98382366 100644 --- a/lib/src/l10n/generated/quill_localizations_sr.dart +++ b/lib/src/l10n/generated/quill_localizations_sr.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSr extends FlutterQuillLocalizations { @override String get alignRight => 'Poravnanje desno'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Centriraj širinu prozora'; @@ -291,4 +294,7 @@ class FlutterQuillLocalizationsSr extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_sv.dart b/lib/src/l10n/generated/quill_localizations_sv.dart index 013fac0a..0b42d28b 100644 --- a/lib/src/l10n/generated/quill_localizations_sv.dart +++ b/lib/src/l10n/generated/quill_localizations_sv.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSv extends FlutterQuillLocalizations { @override String get alignRight => 'Högerjustera'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justera till fönsterbredd'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsSv extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_sw.dart b/lib/src/l10n/generated/quill_localizations_sw.dart index 0757e4a4..dec19ee6 100644 --- a/lib/src/l10n/generated/quill_localizations_sw.dart +++ b/lib/src/l10n/generated/quill_localizations_sw.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSw extends FlutterQuillLocalizations { @override String get alignRight => 'Pangilia Kulia'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Kuhalalisha Upana wa Ushindi'; @@ -289,4 +292,7 @@ class FlutterQuillLocalizationsSw extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_tk.dart b/lib/src/l10n/generated/quill_localizations_tk.dart index 19b2f73e..bade8b07 100644 --- a/lib/src/l10n/generated/quill_localizations_tk.dart +++ b/lib/src/l10n/generated/quill_localizations_tk.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsTk extends FlutterQuillLocalizations { @override String get alignRight => 'Saga deňleşdir'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Justify win width'; @@ -288,4 +291,7 @@ class FlutterQuillLocalizationsTk extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_tr.dart b/lib/src/l10n/generated/quill_localizations_tr.dart index 499bbf50..bdae4d64 100644 --- a/lib/src/l10n/generated/quill_localizations_tr.dart +++ b/lib/src/l10n/generated/quill_localizations_tr.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsTr extends FlutterQuillLocalizations { @override String get alignRight => 'Sağa Hizala'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Kenarlara Hizala'; @@ -289,4 +292,7 @@ class FlutterQuillLocalizationsTr extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_uk.dart b/lib/src/l10n/generated/quill_localizations_uk.dart index 0e5b1314..8674d964 100644 --- a/lib/src/l10n/generated/quill_localizations_uk.dart +++ b/lib/src/l10n/generated/quill_localizations_uk.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsUk extends FlutterQuillLocalizations { @override String get alignRight => 'Вирівняти праворуч'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Вирівняти за шириною вікна'; @@ -294,4 +297,7 @@ class FlutterQuillLocalizationsUk extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_ur.dart b/lib/src/l10n/generated/quill_localizations_ur.dart index 82d0469e..0d277f94 100644 --- a/lib/src/l10n/generated/quill_localizations_ur.dart +++ b/lib/src/l10n/generated/quill_localizations_ur.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsUr extends FlutterQuillLocalizations { @override String get alignRight => 'دائیں ہم آہنگ ہوں'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'جسٹیفائی ون چوڑائی'; @@ -293,4 +296,7 @@ class FlutterQuillLocalizationsUr extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_vi.dart b/lib/src/l10n/generated/quill_localizations_vi.dart index 6eb72dcb..3b5edcbc 100644 --- a/lib/src/l10n/generated/quill_localizations_vi.dart +++ b/lib/src/l10n/generated/quill_localizations_vi.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsVi extends FlutterQuillLocalizations { @override String get alignRight => 'Căn phải'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => 'Căn đều chiều rộng'; @@ -290,4 +293,7 @@ class FlutterQuillLocalizationsVi extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } diff --git a/lib/src/l10n/generated/quill_localizations_zh.dart b/lib/src/l10n/generated/quill_localizations_zh.dart index e38b1d81..3c01429d 100644 --- a/lib/src/l10n/generated/quill_localizations_zh.dart +++ b/lib/src/l10n/generated/quill_localizations_zh.dart @@ -127,6 +127,9 @@ class FlutterQuillLocalizationsZh extends FlutterQuillLocalizations { @override String get alignRight => '右对齐'; + @override + String get alignJustify => 'Align justify'; + @override String get justifyWinWidth => '两端对齐'; @@ -287,6 +290,9 @@ class FlutterQuillLocalizationsZh extends FlutterQuillLocalizations { @override String get paste => 'Paste'; + + @override + String get insertTable => 'Insert table'; } /// The translations for Chinese, as used in China (`zh_CN`). diff --git a/lib/src/l10n/quill_en.arb b/lib/src/l10n/quill_en.arb index 1a6f4b7e..c36de903 100644 --- a/lib/src/l10n/quill_en.arb +++ b/lib/src/l10n/quill_en.arb @@ -41,6 +41,10 @@ "alignLeft": "Align left", "alignCenter": "Align center", "alignRight": "Align right", + "alignJustify": "Align justify", + "@alignJustify": { + "description": "Justify the text over the full window width" + }, "justifyWinWidth": "Justify win width", "textDirection": "Text direction", "headerStyle": "Header style", @@ -102,5 +106,6 @@ "close": "Close", "searchSettings": "Search settings", "cut": "Cut", - "paste": "Paste" + "paste": "Paste", + "insertTable": "Insert table" } \ No newline at end of file diff --git a/lib/src/l10n/quill_ko.arb b/lib/src/l10n/quill_ko.arb index 87608dae..a5bfb0c3 100644 --- a/lib/src/l10n/quill_ko.arb +++ b/lib/src/l10n/quill_ko.arb @@ -1,6 +1,6 @@ { "@@locale": "ko", - "pasteLink": "링크를 붙여넣어 주세요.", + "pasteLink": "링크를 붙여 넣어 주세요", "ok": "확인", "selectColor": "색상 선택", "gallery": "갤러리", @@ -10,8 +10,8 @@ "remove": "제거하기", "save": "저장하기", "zoom": "확대하기", - "saved": "저장되었습니다.", - "text": "텍스트", + "saved": "저장되었습니다", + "text": "제목", "resize": "크기조정", "width": "넓이", "height": "높이", @@ -24,55 +24,85 @@ "search": "검색", "camera": "카메라", "video": "비디오", - "undo": "Undo", - "redo": "Redo", - "fontFamily": "Font family", - "fontSize": "Font size", - "bold": "Bold", - "subscript": "Subscript", - "superscript": "Superscript", - "italic": "Italic", - "underline": "Underline", - "strikeThrough": "Strike through", - "inlineCode": "Inline code", - "fontColor": "Font color", - "backgroundColor": "Background color", - "clearFormat": "Clear format", - "alignLeft": "Align left", - "alignCenter": "Align center", - "alignRight": "Align right", - "justifyWinWidth": "Justify win width", - "textDirection": "Text direction", - "headerStyle": "Header style", - "numberedList": "Numbered list", - "bulletList": "Bullet list", - "checkedList": "Checked list", - "codeBlock": "Code block", - "quote": "Quote", - "increaseIndent": "Increase indent", - "decreaseIndent": "Decrease indent", - "insertURL": "Insert URL", - "visitLink": "Visit link", - "enterLink": "Enter link", - "enterMedia": "Enter media", - "edit": "Edit", - "apply": "Apply", - "findText": "Find text", - "moveToPreviousOccurrence": "Move to previous occurrence", - "moveToNextOccurrence": "Move to next occurrence", - "savedUsingTheNetwork": "Saved using the network", - "savedUsingLocalStorage": "Saved using the local storage", - "errorWhileSavingImage": "Error while saving image", - "pleaseEnterTextForYourLink": "e.g., 'Learn more'", - "pleaseEnterTheLinkURL": "e.g., 'https://example.com'", - "pleaseEnterAValidImageURL": "Please enter a valid image URL", - "hex": "Hex", - "material": "Material", - "color": "Color", + "undo": "되돌리기", + "redo": "다시실행", + "fontFamily": "글꼴", + "fontSize": "글자 크기", + "bold": "굵게", + "subscript": "아래 첨자", + "superscript": "위 첨자", + "italic": "기울이기", + "underline": "밑줄", + "strikeThrough": "취소선", + "inlineCode": "인라인 코드", + "fontColor": "글자 색상", + "backgroundColor": "배경 색상", + "clearFormat": "서식 지우기", + "alignLeft": "왼쪽 정렬", + "alignCenter": "가운데 정렬", + "alignRight": "오른쪽 정렬", + "justifyWinWidth": "좌우로 정렬", + "textDirection": "텍스트 방향", + "headerStyle": "헤더 스타일", + "normal": "일반 텍스트", + "heading1": "제목 1", + "heading2": "제목 2", + "heading3": "제목 3", + "heading4": "제목 4", + "heading5": "제목 5", + "heading6": "제목 6", + "numberedList": "번호 매기기 목록", + "bulletList": "글머리 기호 목록", + "checkedList": "체크리스트", + "codeBlock": "코드 블록", + "quote": "인용", + "increaseIndent": "들여쓰기 증가", + "decreaseIndent": "들여쓰기 감소", + "insertURL": "URL 삽입", + "visitLink": "링크 방문", + "enterLink": "링크 입력", + "enterMedia": "미디어 입력", + "edit": "편집", + "apply": "적용", + "hex": "Hex 값", + "material": "Material 색상", + "color": "색상", + "findText": "찾기", + "moveToPreviousOccurrence": "이전 위치로 이동", + "moveToNextOccurrence": "다음 위치로 이동", + "savedUsingTheNetwork": "네트워크를 통해 저장", + "savedUsingLocalStorage": "로컬 스토리지를 통해 저장", + "theImageHasBeenSavedAt": "이미지가 저장되었습니다 {imagePath}", + "@theImageHasBeenSavedAt": { + "description": "A message with a single parameter", + "placeholders": { + "imagePath": { + "type": "String", + "example": "path/to/location" + } + } + }, + "errorWhileSavingImage": "이미지를 저장하는데 실패했습니다", + "pleaseEnterTextForYourLink": "링크 제목 입력", + "pleaseEnterTheLinkURL": "예시) 'https://example.com'", + "pleaseEnterAValidImageURL": "유효한 이미지 URL을 입력하세요", "pleaseEnterAValidVideoURL": "유효한 비디오 URL을 입력하세요", "photo": "사진", "image": "이미지", "caseSensitivityAndWholeWordSearch": "대소문자 구분 및 전체 단어 검색", - "insertImage": "이미지 삽입" + "caseSensitive": "대소문자 구분", + "wholeWord": "전체 단어", + "insertImage": "이미지 삽입", + "pickAPhotoFromYourGallery": "갤러리에서 이미지 선택", + "takeAPhotoUsingYourCamera": "카메라로 사진 촬영", + "pasteAPhotoUsingALink": "이미지 링크 입력", + "pickAVideoFromYourGallery": "갤러리에서 동영상 선택", + "recordAVideoUsingYourCamera": "카메라로 동영상 촬영", + "pasteAVideoUsingALink": "동영상 링크 입력", + "close": "닫기", + "searchSettings": "검색 설정", + "cut": "잘라내기", + "paste": "붙여넣기" } - \ No newline at end of file + + diff --git a/lib/src/l10n/untranslated.json b/lib/src/l10n/untranslated.json index ae0adf9c..e0037fb8 100644 --- a/lib/src/l10n/untranslated.json +++ b/lib/src/l10n/untranslated.json @@ -1,15 +1,18 @@ { "ar": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "bg": [ + "alignJustify", "normal", "heading1", "heading2", @@ -29,10 +32,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "bn": [ + "alignJustify", "normal", "heading1", "heading2", @@ -52,19 +57,23 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "cs": [ + "alignJustify", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "da": [ + "alignJustify", "normal", "heading1", "heading2", @@ -84,10 +93,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "de": [ + "alignJustify", "normal", "heading1", "heading2", @@ -102,10 +113,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "en_US": [ + "alignJustify", "normal", "heading1", "heading2", @@ -125,10 +138,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "es": [ + "alignJustify", "normal", "heading1", "heading2", @@ -143,10 +158,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "fa": [ + "alignJustify", "normal", "heading1", "heading2", @@ -166,20 +183,24 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "fr": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "he": [ + "alignJustify", "normal", "heading1", "heading2", @@ -199,10 +220,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "hi": [ + "alignJustify", "normal", "heading1", "heading2", @@ -222,10 +245,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "id": [ + "alignJustify", "normal", "heading1", "heading2", @@ -245,10 +270,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "it": [ + "alignJustify", "normal", "heading1", "heading2", @@ -268,10 +295,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ja": [ + "alignJustify", "normal", "heading1", "heading2", @@ -291,53 +320,41 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ko": [ - "normal", - "heading1", - "heading2", - "heading3", - "heading4", - "heading5", - "heading6", - "theImageHasBeenSavedAt", - "caseSensitive", - "wholeWord", - "pickAPhotoFromYourGallery", - "takeAPhotoUsingYourCamera", - "pasteAPhotoUsingALink", - "pickAVideoFromYourGallery", - "recordAVideoUsingYourCamera", - "pasteAVideoUsingALink", - "close", - "searchSettings", - "cut", - "paste" + "alignJustify", + "insertTable" ], "ku": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ku_CKB": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ms": [ + "alignJustify", "normal", "heading1", "heading2", @@ -357,19 +374,23 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ne": [ + "alignJustify", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "nl": [ + "alignJustify", "normal", "heading1", "heading2", @@ -389,10 +410,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "no": [ + "alignJustify", "normal", "heading1", "heading2", @@ -412,10 +435,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "pl": [ + "alignJustify", "normal", "heading1", "heading2", @@ -435,10 +460,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "pt": [ + "alignJustify", "normal", "heading1", "heading2", @@ -458,10 +485,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "pt_BR": [ + "alignJustify", "normal", "heading1", "heading2", @@ -481,30 +510,36 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ro": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ro_RO": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ru": [ + "alignJustify", "normal", "heading1", "heading2", @@ -519,19 +554,23 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "sk": [ + "alignJustify", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "sr": [ + "alignJustify", "normal", "heading1", "heading2", @@ -551,20 +590,24 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "sv": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "sw": [ + "alignJustify", "normal", "heading1", "heading2", @@ -584,10 +627,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "tk": [ + "alignJustify", "normal", "heading1", "heading2", @@ -607,10 +652,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "tr": [ + "alignJustify", "normal", "heading1", "heading2", @@ -630,20 +677,24 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "uk": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "ur": [ + "alignJustify", "normal", "heading1", "heading2", @@ -663,10 +714,12 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "vi": [ + "alignJustify", "normal", "heading1", "heading2", @@ -686,30 +739,36 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "zh": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "zh_CN": [ + "alignJustify", "theImageHasBeenSavedAt", "caseSensitive", "wholeWord", "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ], "zh_HK": [ + "alignJustify", "normal", "heading1", "heading2", @@ -729,6 +788,7 @@ "close", "searchSettings", "cut", - "paste" + "paste", + "insertTable" ] } diff --git a/lib/src/l10n/widgets/localizations.dart b/lib/src/l10n/widgets/localizations.dart index 57277b6b..874ffd47 100644 --- a/lib/src/l10n/widgets/localizations.dart +++ b/lib/src/l10n/widgets/localizations.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import '../../extensions/quill_configurations_ext.dart'; import '../extensions/localizations.dart'; +/// A widget that check if [FlutterQuillLocalizations.delegate] is provided +/// in the widgets app (e.g, [MaterialApp] or [WidgetsApp]). +/// +/// If not, will provide in the [child] to access it in the widget tree. class FlutterQuillLocalizationsWidget extends StatelessWidget { const FlutterQuillLocalizationsWidget({ required this.child, diff --git a/lib/src/models/config/quill_controller_configurations.dart b/lib/src/models/config/quill_controller_configurations.dart index ae38841a..95126178 100644 --- a/lib/src/models/config/quill_controller_configurations.dart +++ b/lib/src/models/config/quill_controller_configurations.dart @@ -1,8 +1,14 @@ class QuillControllerConfigurations { - const QuillControllerConfigurations({this.onClipboardPaste}); + const QuillControllerConfigurations( + {this.onClipboardPaste, this.requireScriptFontFeatures = false}); /// Callback when the user pastes and data has not already been processed /// /// Return true if the paste operation was handled final Future Function()? onClipboardPaste; + + /// Render subscript and superscript text using Open Type FontFeatures + /// + /// Default is false to use built-in script rendering that is independent of font capabilities + final bool requireScriptFontFeatures; } diff --git a/lib/src/models/config/toolbar/buttons/search_configurations.dart b/lib/src/models/config/toolbar/buttons/search_configurations.dart index e110c3ea..8d791fcc 100644 --- a/lib/src/models/config/toolbar/buttons/search_configurations.dart +++ b/lib/src/models/config/toolbar/buttons/search_configurations.dart @@ -1,4 +1,4 @@ -import 'package:flutter/widgets.dart' show Color; +import 'package:flutter/material.dart'; import '../../../../../flutter_quill.dart'; @@ -24,6 +24,7 @@ class QuillToolbarSearchButtonOptions extends QuillToolbarBaseButtonOptions< super.iconButtonFactor, this.dialogBarrierColor, this.customOnPressedCallback, + this.searchBarAlignment, }); final QuillDialogTheme? dialogTheme; @@ -34,6 +35,8 @@ class QuillToolbarSearchButtonOptions extends QuillToolbarBaseButtonOptions< /// By default we will show simple search dialog ui /// you can pass value to this callback to change this final QuillToolbarSearchButtonOnPressedCallback? customOnPressedCallback; + + final AlignmentGeometry? searchBarAlignment; } typedef QuillToolbarSearchButtonOnPressedCallback = Future Function( diff --git a/lib/src/models/config/toolbar/simple_toolbar_configurations.dart b/lib/src/models/config/toolbar/simple_toolbar_configurations.dart index 89175980..6df75977 100644 --- a/lib/src/models/config/toolbar/simple_toolbar_configurations.dart +++ b/lib/src/models/config/toolbar/simple_toolbar_configurations.dart @@ -4,6 +4,12 @@ import 'package:flutter/widgets.dart' import '../../../widgets/quill/embeds.dart'; import '../../../widgets/quill/quill_controller.dart'; +import '../../../widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart'; +import '../../../widgets/toolbar/buttons/hearder_style/select_header_style_dropdown_button.dart'; +import '../../../widgets/toolbar/buttons/link_style2_button.dart'; +import '../../../widgets/toolbar/buttons/link_style_button.dart'; +import '../../../widgets/toolbar/buttons/search/legacy/legacy_search_button.dart'; +import '../../../widgets/toolbar/buttons/search/search_button.dart'; import '../../themes/quill_dialog_theme.dart'; import '../../themes/quill_icon_theme.dart'; import 'simple_toolbar_button_options.dart'; @@ -62,6 +68,14 @@ enum HeaderStyleType { bool get isButtons => this == HeaderStyleType.buttons; } +enum SearchButtonType { + /// Will use [QuillToolbarSearchButton] + legacy, + + /// Will use [QuillToolbarLegacySearchButton] + modern, +} + /// The configurations for the toolbar widget of flutter quill @immutable class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties { @@ -112,6 +126,7 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties { this.showClipboardPaste = true, this.linkStyleType = LinkStyleType.original, this.headerStyleType = HeaderStyleType.original, + this.searchButtonType = SearchButtonType.modern, /// The decoration to use for the toolbar. super.decoration, @@ -218,6 +233,9 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties { /// Defines which dialog is used for applying header attribute. final HeaderStyleType headerStyleType; + /// Define which button type should be used for the [showSearchButton] + final SearchButtonType searchButtonType; + @override List get props => [ buttonOptions, diff --git a/lib/src/models/documents/delta_x.dart b/lib/src/models/documents/delta_x.dart index 76c6489d..30d03594 100644 --- a/lib/src/models/documents/delta_x.dart +++ b/lib/src/models/documents/delta_x.dart @@ -1,48 +1,49 @@ import 'package:html2md/html2md.dart' as html2md; import 'package:markdown/markdown.dart' as md; import 'package:meta/meta.dart'; - import '../../../markdown_quill.dart'; import '../../../quill_delta.dart'; +import '../../utils/delta_x_utils.dart'; @immutable +@experimental class DeltaX { + const DeltaX._(); + + /// Convert Markdown text to [Delta] + /// + /// This api is **experimental** and designed to be used **internally** and shouldn't + /// used for **production applications**. + @experimental + static Delta fromMarkdown(String markdownText) { + final mdDocument = md.Document( + encodeHtml: false, + inlineSyntaxes: [UnderlineSyntax(), VideoSyntax()], + ); + final mdToDelta = MarkdownToDelta(markdownDocument: mdDocument); + return mdToDelta.convert(markdownText); + } + /// Convert the HTML Raw string to [Delta] /// /// It will run using the following steps: /// /// 1. Convert the html to markdown string using `html2md` package - /// 2. Convert the markdown string to quill delta json string - /// 3. Decode the delta json string to [Delta] + /// 2. Convert the markdown string to [Delta] using [fromMarkdown] /// - /// for more [info](https://github.com/singerdmx/flutter-quill/issues/1100) - /// - /// Please notice that this api is designed to be used internally and shouldn't - /// used for real world applications + /// This api is **experimental** and designed to be used **internally** and shouldn't + /// used for **production applications**. /// @experimental - static Delta fromHtml(String html) { - final markdown = html2md - .convert( - html, - ) - .replaceAll('unsafe:', ''); - - final mdDocument = md.Document(encodeHtml: false); - - final mdToDelta = MarkdownToDelta(markdownDocument: mdDocument); - - return mdToDelta.convert(markdown); - - // final deltaJsonString = markdownToDelta(markdown); - // final deltaJson = jsonDecode(deltaJsonString); - // if (deltaJson is! List) { - // throw ArgumentError( - // 'The delta json string should be of type list when jsonDecode() it', - // ); - // } - // return Delta.fromJson( - // deltaJson, - // ); + static Delta fromHtml(String htmlText) { + final markdownText = html2md.convert( + htmlText, + rules: [underlineRule, videoRule], + styleOptions: {'emDelimiter': '*'}, + ).replaceAll( + 'unsafe:', + '', + ); + return fromMarkdown(markdownText); } } diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart index 6043b6f9..95036ca1 100644 --- a/lib/src/models/documents/document.dart +++ b/lib/src/models/documents/document.dart @@ -177,26 +177,46 @@ class Document { /// Only attributes applied to all characters within this range are /// included in the result. + /// Special case of no-selection at start of empty line: gets inline style(s) from preceding non-empty line. Style collectStyle(int index, int len) { - final res = queryChild(index); - Style rangeStyle; + var res = queryChild(index); if (len > 0) { return (res.node as Line).collectStyle(res.offset, len); } + // if (res.offset == 0) { - return rangeStyle = (res.node as Line).collectStyle(res.offset, len); + final current = (res.node as Line).collectStyle(0, 0); + // + while ((res.node as Line).length == 1 && index > 0) { + res = queryChild(--index); + } + // + final style = (res.node as Line).collectStyle(res.offset, 0); + final remove = {}; + for (final attr in style.attributes.values) { + if (!Attribute.inlineKeys.contains(attr.key)) { + if (!current.containsKey(attr.key)) { + remove.add(attr); + } + } + } + if (remove.isNotEmpty) { + return style.removeAll(remove); + } + return style; } - rangeStyle = (res.node as Line).collectStyle(res.offset - 1, len); - final linkAttribute = rangeStyle.attributes[Attribute.link.key]; + // + final style = (res.node as Line).collectStyle(res.offset - 1, 0); + final linkAttribute = style.attributes[Attribute.link.key]; if ((linkAttribute != null) && (linkAttribute.value != (res.node as Line) .collectStyle(res.offset, len) .attributes[Attribute.link.key] ?.value)) { - return rangeStyle.removeAll({linkAttribute}); + return style.removeAll({linkAttribute}); } - return rangeStyle; + return style; } /// Returns all styles and Embed for each node within selection diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index 9f6bf723..6b2a05b6 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -369,18 +369,18 @@ class AutoFormatMultipleLinksRule extends InsertRule { r'https?:\/\/[\w\-]+(\.[\w\-]+)*(:\d+)?(\/[^\s]*)?'; /// It requires a valid link in one link - static final oneLineLinkRegExp = RegExp( - _oneLineLinkPattern, - caseSensitive: false, - ); + RegExp get oneLineLinkRegExp => RegExp( + _oneLineLinkPattern, + caseSensitive: false, + ); /// It detect if there is a link in the text whatever if it in the middle etc // Used to solve bug https://github.com/singerdmx/flutter-quill/issues/1432 - static final detectLinkRegExp = RegExp( - _detectLinkPattern, - caseSensitive: false, - ); - static final linkRegExp = oneLineLinkRegExp; + RegExp get detectLinkRegExp => RegExp( + _detectLinkPattern, + caseSensitive: false, + ); + RegExp get linkRegExp => oneLineLinkRegExp; @override Delta? applyRule( diff --git a/lib/src/packages/quill_markdown/markdown_to_delta.dart b/lib/src/packages/quill_markdown/markdown_to_delta.dart index 53194f77..f9103447 100644 --- a/lib/src/packages/quill_markdown/markdown_to_delta.dart +++ b/lib/src/packages/quill_markdown/markdown_to_delta.dart @@ -82,6 +82,7 @@ class MarkdownToDelta extends Converter final _elementToInlineAttr = { 'em': (_) => [Attribute.italic], + 'u': (_) => [Attribute.underline], 'strong': (_) => [Attribute.bold], 'del': (_) => [Attribute.strikeThrough], 'a': (element) => [LinkAttribute(element.attributes['href'])], @@ -91,6 +92,7 @@ class MarkdownToDelta extends Converter final _elementToEmbed = { 'hr': (_) => horizontalRule, 'img': (elAttrs) => BlockEmbed.image(elAttrs['src'] ?? ''), + 'video': (elAttrs) => BlockEmbed.video(elAttrs['src'] ?? '') }; var _delta = Delta(); diff --git a/lib/src/services/clipboard/clipboard_service.dart b/lib/src/services/clipboard/clipboard_service.dart new file mode 100644 index 00000000..848821f5 --- /dev/null +++ b/lib/src/services/clipboard/clipboard_service.dart @@ -0,0 +1,44 @@ +import 'package:flutter/foundation.dart'; + +/// An abstraction to make it easy to provide different implementations +@immutable +abstract class ClipboardService { + Future canProvideHtmlText(); + + /// Get Clipboard content as Html Text, this is platform specific and not the + /// same as [getPlainText] for two reasons: + /// 1. The user might want to paste Html text + /// 2. Copying Html text from other apps and use [getPlainText] will ignore + /// the Html content and provide it as text + Future getHtmlText(); + + Future canProvideHtmlTextFromFile(); + + /// Get the Html file in the Clipboard from the system + Future getHtmlTextFromFile(); + + Future canProvideMarkdownText(); + + /// Get Clipboard content as Markdown Text, this is platform specific and not the + /// same as [getPlainText] for two reasons: + /// 1. The user might want to paste Markdown text + /// 2. Copying Markdown text from other apps and use [getPlainText] will ignore + /// the Markdown content and provide it as text + Future getMarkdownText(); + + Future canProvideMarkdownTextFromFile(); + + /// Get the Markdown file in the Clipboard from the system + Future getMarkdownTextFromFile(); + + Future canProvidePlainText(); + Future getPlainText(); + + Future canProvideImageFile(); + Future getImageFileAsBytes(); + + Future canProvideGifFile(); + Future getGifFileAsBytes(); + + Future canPaste(); +} diff --git a/lib/src/services/clipboard/clipboard_service_provider.dart b/lib/src/services/clipboard/clipboard_service_provider.dart new file mode 100644 index 00000000..2766ada1 --- /dev/null +++ b/lib/src/services/clipboard/clipboard_service_provider.dart @@ -0,0 +1,19 @@ +import 'package:meta/meta.dart'; + +import 'clipboard_service.dart'; +import 'default_clipboard_service.dart'; + +@immutable +class ClipboardServiceProvider { + const ClipboardServiceProvider._(); + static ClipboardService _instance = DefaultClipboardService(); + static ClipboardService get instacne => _instance; + + static void setInstance(ClipboardService service) { + _instance = service; + } + + static void setInstanceToDefault() { + _instance = DefaultClipboardService(); + } +} diff --git a/lib/src/services/clipboard/default_clipboard_service.dart b/lib/src/services/clipboard/default_clipboard_service.dart new file mode 100644 index 00000000..4509c110 --- /dev/null +++ b/lib/src/services/clipboard/default_clipboard_service.dart @@ -0,0 +1,96 @@ +import 'package:flutter/services.dart' show Clipboard, Uint8List; + +import 'clipboard_service.dart'; + +/// Default implementation using only internal flutter plugins +class DefaultClipboardService implements ClipboardService { + @override + Future canProvideHtmlText() async { + return false; + } + + @override + Future getHtmlText() { + throw UnsupportedError( + 'DefaultClipboardService does not support retrieving HTML text.', + ); + } + + @override + Future canProvideHtmlTextFromFile() async { + return false; + } + + @override + Future getHtmlTextFromFile() { + throw UnsupportedError( + 'DefaultClipboardService does not support retrieving HTML files.', + ); + } + + @override + Future canProvideMarkdownText() async { + return false; + } + + @override + Future getMarkdownText() { + throw UnsupportedError( + 'DefaultClipboardService does not support retrieving HTML files.', + ); + } + + @override + Future canProvideMarkdownTextFromFile() async { + return false; + } + + @override + Future getMarkdownTextFromFile() { + throw UnsupportedError( + 'DefaultClipboardService does not support retrieving Markdown text.', + ); + } + + @override + Future canProvidePlainText() async { + final plainText = (await Clipboard.getData(Clipboard.kTextPlain))?.text; + return plainText == null; + } + + @override + Future getPlainText() async { + final plainText = (await Clipboard.getData(Clipboard.kTextPlain))?.text; + return plainText; + } + + @override + Future canProvideImageFile() async { + return false; + } + + @override + Future getImageFileAsBytes() { + throw UnsupportedError( + 'DefaultClipboardService does not support retrieving image files.', + ); + } + + @override + Future canProvideGifFile() async { + return false; + } + + @override + Future getGifFileAsBytes() { + throw UnsupportedError( + 'DefaultClipboardService does not support retrieving GIF files.', + ); + } + + @override + Future canPaste() async { + final plainText = await getPlainText(); + return plainText != null; + } +} diff --git a/lib/src/utils/delta_x_utils.dart b/lib/src/utils/delta_x_utils.dart new file mode 100644 index 00000000..b7767b1e --- /dev/null +++ b/lib/src/utils/delta_x_utils.dart @@ -0,0 +1,80 @@ +import 'package:html2md/html2md.dart' as hmd; +import 'package:markdown/markdown.dart' as md; + +// [ character +const int _$lbracket = 0x5B; +final RegExp _youtubeVideoUrlValidator = RegExp( + r'^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$'); + +///Local syntax implementation for underline +class UnderlineSyntax extends md.DelimiterSyntax { + UnderlineSyntax() + : super( + '', + requiresDelimiterRun: true, + allowIntraWord: true, + tags: [md.DelimiterTag('u', 5)], + ); +} + +class VideoSyntax extends md.LinkSyntax { + VideoSyntax({super.linkResolver}) + : super( + pattern: r'\[', + startCharacter: _$lbracket, + ); + + @override + md.Element createNode( + String destination, + String? title, { + required List Function() getChildren, + }) { + final element = md.Element.empty('video'); + element.attributes['src'] = destination; + if (title != null && title.isNotEmpty) { + element.attributes['title'] = title; + } + return element; + } +} + +///This rule avoid the default converter from html2md ignore underline tag for or +final underlineRule = + hmd.Rule('underline', filters: ['u', 'ins'], replacement: (content, node) { + //Is used a local underline implemenation since markdown just use underline with html tags + return '$content'; +}); +final videoRule = hmd.Rule('video', filters: ['iframe', 'video'], + replacement: (content, node) { + //This need to be verified by a different way of iframes, since video tag can have children + if (node.nodeName == 'video') { + //if has children then just will be taked as different part of code + if (node.childNum > 0) { + var child = node.firstChild!; + var src = child.getAttribute('src'); + if (src == null) { + child = node.childNodes().last; + src = child.getAttribute('src'); + } + if (!_youtubeVideoUrlValidator.hasMatch(src ?? '')) { + return ''; + } + return '[$content]($src)'; + } + final src = node.getAttribute('src'); + if (src == null || !_youtubeVideoUrlValidator.hasMatch(src)) { + return node.outerHTML; + } + return '[$content]($src)'; + } + //by now, we can only access to src + final src = node.getAttribute('src'); + //if the source is null or is not valid youtube url, then just return the html instead remove it + //by now is only available validation for youtube videos + if (src == null || !_youtubeVideoUrlValidator.hasMatch(src)) { + return node.outerHTML; + } + final title = node.getAttribute('title'); + return '[$title]($src)'; +}); diff --git a/lib/src/widgets/editor/editor.dart b/lib/src/widgets/editor/editor.dart index a5635967..ad81ea20 100644 --- a/lib/src/widgets/editor/editor.dart +++ b/lib/src/widgets/editor/editor.dart @@ -173,6 +173,9 @@ class QuillEditorState extends State void initState() { super.initState(); widget.configurations.controller.editorFocusNode ??= widget.focusNode; + if (configurations.autoFocus) { + widget.configurations.controller.editorFocusNode?.requestFocus(); + } _editorKey = configurations.editorKey ?? GlobalKey(); _selectionGestureDetectorBuilder = _QuillEditorSelectionGestureDetectorBuilder( diff --git a/lib/src/widgets/quill/quill_controller.dart b/lib/src/widgets/quill/quill_controller.dart index b6369e2e..012f1a25 100644 --- a/lib/src/widgets/quill/quill_controller.dart +++ b/lib/src/widgets/quill/quill_controller.dart @@ -4,11 +4,11 @@ import 'package:flutter/services.dart' show ClipboardData, Clipboard; import 'package:flutter/widgets.dart'; import 'package:html/parser.dart' as html_parser; import 'package:meta/meta.dart'; -import 'package:super_clipboard/super_clipboard.dart'; import '../../../flutter_quill.dart'; import '../../../quill_delta.dart'; import '../../models/documents/delta_x.dart'; +import '../../services/clipboard/clipboard_service_provider.dart'; import '../../utils/delta.dart'; typedef ReplaceTextCallback = bool Function(int index, int len, Object? data); @@ -507,16 +507,24 @@ class QuillController extends ChangeNotifier { Future clipboardPaste({void Function()? updateEditor}) async { if (readOnly || !selection.isValid) return true; - if (await _pasteHTML()) { + final pasteUsingHtmlSuccess = await _pasteHTML(); + if (pasteUsingHtmlSuccess) { + updateEditor?.call(); + return true; + } + + final pasteUsingMarkdownSuccess = await _pasteMarkdown(); + if (pasteUsingMarkdownSuccess) { updateEditor?.call(); return true; } // Snapshot the input before using `await`. // See https://github.com/flutter/flutter/issues/11427 - final plainText = await Clipboard.getData(Clipboard.kTextPlain); - if (plainText != null) { - final lines = plainText.text!.split('\n'); + final plainTextClipboardData = + await Clipboard.getData(Clipboard.kTextPlain); + if (plainTextClipboardData != null) { + final lines = plainTextClipboardData.text!.split('\n'); for (var i = 0; i < lines.length; ++i) { final line = lines[i]; if (line.isNotEmpty) { @@ -549,27 +557,62 @@ class QuillController extends ChangeNotifier { return false; } + void _pasteUsingDelta(Delta deltaFromClipboard) { + replaceText( + selection.start, + selection.end - selection.start, + deltaFromClipboard, + TextSelection.collapsed(offset: selection.end), + ); + } + + /// Return true if can paste using HTML Future _pasteHTML() async { - final clipboard = SystemClipboard.instance; - if (clipboard != null) { - final reader = await clipboard.read(); - if (reader.canProvide(Formats.htmlText)) { - final html = await reader.readValue(Formats.htmlText); - if (html == null) { - return false; - } - final htmlBody = html_parser.parse(html).body?.outerHtml; - final deltaFromClipboard = DeltaX.fromHtml(htmlBody ?? html); - - replaceText( - selection.start, - selection.end - selection.start, - deltaFromClipboard, - TextSelection.collapsed(offset: selection.end), - ); + final clipboardService = ClipboardServiceProvider.instacne; - return true; + Future getHTML() async { + if (await clipboardService.canProvideHtmlTextFromFile()) { + return await clipboardService.getHtmlTextFromFile(); + } + if (await clipboardService.canProvideHtmlText()) { + return await clipboardService.getHtmlText(); } + return null; + } + + final htmlText = await getHTML(); + if (htmlText != null) { + final htmlBody = html_parser.parse(htmlText).body?.outerHtml; + final deltaFromClipboard = DeltaX.fromHtml(htmlBody ?? htmlText); + + _pasteUsingDelta(deltaFromClipboard); + + return true; + } + return false; + } + + /// Return true if can paste using Markdown + Future _pasteMarkdown() async { + final clipboardService = ClipboardServiceProvider.instacne; + + Future getMarkdown() async { + if (await clipboardService.canProvideMarkdownTextFromFile()) { + return await clipboardService.getMarkdownTextFromFile(); + } + if (await clipboardService.canProvideMarkdownText()) { + return await clipboardService.getMarkdownText(); + } + return null; + } + + final markdownText = await getMarkdown(); + if (markdownText != null) { + final deltaFromClipboard = DeltaX.fromMarkdown(markdownText); + + _pasteUsingDelta(deltaFromClipboard); + + return true; } return false; } diff --git a/lib/src/widgets/quill/text_line.dart b/lib/src/widgets/quill/text_line.dart index e03e5ae8..1604ba9b 100644 --- a/lib/src/widgets/quill/text_line.dart +++ b/lib/src/widgets/quill/text_line.dart @@ -322,19 +322,71 @@ class _TextLineState extends State { return textStyle; } - TextSpan _getTextSpanFromNode( + /// Processes subscript and superscript attributed text. + /// + /// Reduces text fontSize and shifts down or up. Increases fontWeight to maintain balance with normal text. + /// Outputs characters individually to allow correct caret positioning and text selection. + InlineSpan _scriptSpan(String text, bool superScript, TextStyle style, + DefaultStyles defaultStyles) { + assert(text.isNotEmpty); + // + final lineStyle = style.fontSize == null || style.fontWeight == null + ? _getLineStyle(defaultStyles) + : null; + final fontWeight = FontWeight.lerp( + style.fontWeight ?? lineStyle?.fontWeight ?? FontWeight.normal, + FontWeight.w900, + 0.25); + final fontSize = style.fontSize ?? lineStyle?.fontSize ?? 16; + final y = (superScript ? -0.4 : 0.14) * fontSize; + final charStyle = style.copyWith( + fontFeatures: [], + fontWeight: fontWeight, + fontSize: fontSize * 0.7); + // + final offset = Offset(0, y); + final children = []; + for (final c in text.characters) { + children.add(WidgetSpan( + child: Transform.translate( + offset: offset, + child: Text( + c, + style: charStyle, + )))); + } + // + if (children.length > 1) { + return TextSpan(children: children); + } + return children[0]; + } + + InlineSpan _getTextSpanFromNode( DefaultStyles defaultStyles, Node node, Style lineStyle) { final textNode = node as leaf.QuillText; final nodeStyle = textNode.style; final isLink = nodeStyle.containsKey(Attribute.link.key) && nodeStyle.attributes[Attribute.link.key]!.value != null; - final recognizer = _getRecognizer(node, isLink); + final style = _getInlineTextStyle( + textNode, defaultStyles, nodeStyle, lineStyle, isLink); + + if (widget.controller.configurations.requireScriptFontFeatures == false && + textNode.value.isNotEmpty) { + if (nodeStyle.containsKey(Attribute.script.key)) { + final attr = nodeStyle.attributes[Attribute.script.key]; + if (attr == Attribute.superscript || attr == Attribute.subscript) { + return _scriptSpan(textNode.value, attr == Attribute.superscript, + style, defaultStyles); + } + } + } + final recognizer = _getRecognizer(node, isLink); return TextSpan( text: textNode.value, - style: _getInlineTextStyle( - textNode, defaultStyles, nodeStyle, lineStyle, isLink), + style: style, recognizer: recognizer, mouseCursor: (recognizer != null) ? SystemMouseCursors.click : null, ); @@ -500,7 +552,6 @@ class _TextLineState extends State { _tapLink(link); break; case LinkMenuAction.copy: - // ignore: unawaited_futures Clipboard.setData(ClipboardData(text: link)); break; case LinkMenuAction.remove: diff --git a/lib/src/widgets/raw_editor/raw_editor_actions.dart b/lib/src/widgets/raw_editor/raw_editor_actions.dart index 40580d70..a343f03f 100644 --- a/lib/src/widgets/raw_editor/raw_editor_actions.dart +++ b/lib/src/widgets/raw_editor/raw_editor_actions.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../../translations.dart'; import '../../models/documents/attribute.dart'; import '../editor/editor.dart'; import '../toolbar/buttons/link_style2_button.dart'; @@ -460,9 +461,10 @@ class QuillEditorOpenSearchAction extends ContextAction { } await showDialog( context: context, - builder: (_) => QuillToolbarSearchDialog( - controller: state.controller, - text: '', + builder: (_) => FlutterQuillLocalizationsWidget( + child: QuillToolbarSearchDialog( + controller: state.controller, + ), ), ); } @@ -578,3 +580,29 @@ class QuillEditorInsertEmbedIntent extends Intent { final Attribute type; } + +class NavigateToDocumentBoundaryAction + extends ContextAction { + NavigateToDocumentBoundaryAction(this.state); + + final QuillRawEditorState state; + + @override + Object? invoke( + ScrollToDocumentBoundaryIntent intent, [ + BuildContext? context, + ]) { + return Actions.invoke( + context!, + UpdateSelectionIntent( + state.textEditingValue, + intent.forward + ? TextSelection.collapsed( + offset: state.controller.plainTextEditingValue.text.length, + ) + : const TextSelection.collapsed(offset: 0), + SelectionChangedCause.keyboard, + ), + ); + } +} diff --git a/lib/src/widgets/raw_editor/raw_editor_state.dart b/lib/src/widgets/raw_editor/raw_editor_state.dart index 07e5f013..ccd91fb5 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state.dart @@ -19,7 +19,6 @@ import 'package:flutter/services.dart' TextInputControl; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart' show KeyboardVisibilityController; -import 'package:super_clipboard/super_clipboard.dart'; import '../../models/documents/attribute.dart'; import '../../models/documents/document.dart'; @@ -30,6 +29,7 @@ import '../../models/documents/nodes/line.dart'; import '../../models/documents/nodes/node.dart'; import '../../models/structs/offset_value.dart'; import '../../models/structs/vertical_spacing.dart'; +import '../../services/clipboard/clipboard_service_provider.dart'; import '../../utils/cast.dart'; import '../../utils/delta.dart'; import '../../utils/embeds.dart'; @@ -184,53 +184,41 @@ class QuillRawEditorState extends EditorState return; } - final clipboard = SystemClipboard.instance; + final clipboardService = ClipboardServiceProvider.instacne; final onImagePaste = widget.configurations.onImagePaste; if (onImagePaste != null) { - if (clipboard != null) { - final reader = await clipboard.read(); - if (reader.canProvide(Formats.png)) { - reader.getFile(Formats.png, (value) async { - final image = value; - - final imageUrl = await onImagePaste(await image.readAll()); - if (imageUrl == null) { - return; - } - - controller.replaceText( - textEditingValue.selection.end, - 0, - BlockEmbed.image(imageUrl), - null, - ); - }); + if (await clipboardService.canProvideImageFile()) { + final imageBytes = await clipboardService.getImageFileAsBytes(); + final imageUrl = await onImagePaste(imageBytes); + if (imageUrl == null) { + return; } + + controller.replaceText( + textEditingValue.selection.end, + 0, + BlockEmbed.image(imageUrl), + null, + ); } } final onGifPaste = widget.configurations.onGifPaste; if (onGifPaste != null) { - if (clipboard != null) { - final reader = await clipboard.read(); - if (reader.canProvide(Formats.gif)) { - reader.getFile(Formats.gif, (value) async { - final gif = value; - - final gifUrl = await onGifPaste(await gif.readAll()); - if (gifUrl == null) { - return; - } - - controller.replaceText( - textEditingValue.selection.end, - 0, - BlockEmbed.image(gifUrl), - null, - ); - }); + if (await clipboardService.canProvideGifFile()) { + final gifBytes = await clipboardService.getGifFileAsBytes(); + final gifUrl = await onGifPaste(gifBytes); + if (gifUrl == null) { + return; } + + controller.replaceText( + textEditingValue.selection.end, + 0, + BlockEmbed.image(gifUrl), + null, + ); } } } @@ -713,6 +701,18 @@ class QuillRawEditorState extends EditorState control: !isDesktopMacOS, meta: isDesktopMacOS, ): const OpenSearchIntent(), + + // Navigate to the start or end of the document + SingleActivator( + LogicalKeyboardKey.home, + control: !isDesktopMacOS, + meta: isDesktopMacOS, + ): const ScrollToDocumentBoundaryIntent(forward: false), + SingleActivator( + LogicalKeyboardKey.end, + control: !isDesktopMacOS, + meta: isDesktopMacOS, + ): const ScrollToDocumentBoundaryIntent(forward: true), }, { ...?widget.configurations.customShortcuts }), @@ -875,6 +875,14 @@ class QuillRawEditorState extends EditorState ..formatSelection(attribute) // Remove the added newline. ..replaceText(controller.selection.baseOffset + 1, 1, '', null); + // + final style = + controller.document.collectStyle(controller.selection.baseOffset, 0); + if (style.isNotEmpty) { + for (final attr in style.values) { + controller.formatSelection(attr); + } + } } void _handleSelectionChanged( @@ -1686,7 +1694,8 @@ class QuillRawEditorState extends EditorState IndentSelectionIntent: _indentSelectionAction, QuillEditorApplyHeaderIntent: _applyHeaderAction, QuillEditorApplyCheckListIntent: _applyCheckListAction, - QuillEditorApplyLinkIntent: QuillEditorApplyLinkAction(this) + QuillEditorApplyLinkIntent: QuillEditorApplyLinkAction(this), + ScrollToDocumentBoundaryIntent: NavigateToDocumentBoundaryAction(this) }; @override diff --git a/lib/src/widgets/toolbar/base_button/base_value_button.dart b/lib/src/widgets/toolbar/base_button/base_value_button.dart index 670e2548..24cf993d 100644 --- a/lib/src/widgets/toolbar/base_button/base_value_button.dart +++ b/lib/src/widgets/toolbar/base_button/base_value_button.dart @@ -84,6 +84,7 @@ abstract class QuillToolbarBaseButtonState< } void didChangeEditingValue() { + if (!mounted) return; setState(() => currentValue = currentStateValue); } diff --git a/lib/src/widgets/toolbar/base_toolbar.dart b/lib/src/widgets/toolbar/base_toolbar.dart index e9ee529e..14e1b3eb 100644 --- a/lib/src/widgets/toolbar/base_toolbar.dart +++ b/lib/src/widgets/toolbar/base_toolbar.dart @@ -22,6 +22,7 @@ export 'buttons/indent_button.dart'; export 'buttons/link_style2_button.dart'; export 'buttons/link_style_button.dart'; export 'buttons/quill_icon_button.dart'; +export 'buttons/search/legacy/legacy_search_button.dart'; export 'buttons/search/search_button.dart'; export 'buttons/toggle_check_list_button.dart'; export 'buttons/toggle_style_button.dart'; diff --git a/lib/src/widgets/toolbar/buttons/clipboard_button.dart b/lib/src/widgets/toolbar/buttons/clipboard_button.dart index ab873b7d..29b5162a 100644 --- a/lib/src/widgets/toolbar/buttons/clipboard_button.dart +++ b/lib/src/widgets/toolbar/buttons/clipboard_button.dart @@ -2,11 +2,11 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:super_clipboard/super_clipboard.dart'; import '../../../../extensions.dart'; import '../../../../flutter_quill.dart'; import '../../../l10n/extensions/localizations.dart'; +import '../../../services/clipboard/clipboard_service_provider.dart'; import '../base_button/base_value_button.dart'; enum ClipboardAction { cut, copy, paste } @@ -27,13 +27,10 @@ class ClipboardMonitor { } Future _update(void Function() listener) async { - final reader = await SystemClipboard.instance?.read(); - if (reader != null) { - final available = reader.platformFormats; - if (_canPaste != available.isNotEmpty) { - _canPaste = available.isNotEmpty; - listener(); - } + final clipboardService = ClipboardServiceProvider.instacne; + if (await clipboardService.canPaste()) { + _canPaste = true; + listener(); } } } diff --git a/lib/src/widgets/toolbar/buttons/color/color_dialog.dart b/lib/src/widgets/toolbar/buttons/color/color_dialog.dart index e7d65800..5f0d7985 100644 --- a/lib/src/widgets/toolbar/buttons/color/color_dialog.dart +++ b/lib/src/widgets/toolbar/buttons/color/color_dialog.dart @@ -54,6 +54,7 @@ class ColorPickerDialogState extends State { actions: [ TextButton( onPressed: () { + widget.onRequestChangeColor(context, selectedColor); Navigator.of(context).pop(); }, child: Text(context.loc.ok)), @@ -123,8 +124,6 @@ class ColorPickerDialogState extends State { controller: hexController, onChanged: (value) { selectedColor = hexToColor(value); - widget.onRequestChangeColor(context, selectedColor); - colorBoxSetState(() {}); }, decoration: InputDecoration( diff --git a/lib/src/widgets/toolbar/buttons/link_style2_button.dart b/lib/src/widgets/toolbar/buttons/link_style2_button.dart index b6178cdd..bdc4388d 100644 --- a/lib/src/widgets/toolbar/buttons/link_style2_button.dart +++ b/lib/src/widgets/toolbar/buttons/link_style2_button.dart @@ -413,7 +413,9 @@ class _LinkStyleDialogState extends State { String? _validateLink(String? value) { if ((value?.isEmpty ?? false) || - !AutoFormatMultipleLinksRule.oneLineLinkRegExp.hasMatch(value!)) { + !const AutoFormatMultipleLinksRule() + .oneLineLinkRegExp + .hasMatch(value!)) { return widget.validationMessage ?? 'That is not a valid URL'; } diff --git a/lib/src/widgets/toolbar/buttons/link_style_button.dart b/lib/src/widgets/toolbar/buttons/link_style_button.dart index 92724012..3831f8b3 100644 --- a/lib/src/widgets/toolbar/buttons/link_style_button.dart +++ b/lib/src/widgets/toolbar/buttons/link_style_button.dart @@ -196,7 +196,8 @@ class _LinkDialogState extends State<_LinkDialog> { late String _text; RegExp get linkRegExp { - return widget.linkRegExp ?? AutoFormatMultipleLinksRule.oneLineLinkRegExp; + return widget.linkRegExp ?? + const AutoFormatMultipleLinksRule().oneLineLinkRegExp; } late TextEditingController _linkController; diff --git a/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_button.dart b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_button.dart new file mode 100644 index 00000000..fd86303d --- /dev/null +++ b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_button.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; + +import '../../../../../extensions/quill_configurations_ext.dart'; +import '../../../../../l10n/extensions/localizations.dart'; +import '../../../../../l10n/widgets/localizations.dart'; +import '../../../../../models/themes/quill_dialog_theme.dart'; +import '../../../../../models/themes/quill_icon_theme.dart'; +import '../../../../quill/quill_controller.dart'; +import '../../../base_toolbar.dart'; +import 'legacy_search_dialog.dart'; + +/// We suggest to see [QuillToolbarSearchButton] before using this widget. +class QuillToolbarLegacySearchButton extends StatelessWidget { + const QuillToolbarLegacySearchButton({ + required QuillController controller, + this.options = const QuillToolbarSearchButtonOptions(), + super.key, + }) : _controller = controller; + + final QuillController _controller; + final QuillToolbarSearchButtonOptions options; + + QuillController get controller { + return _controller; + } + + // TODO: The logic is common and can be extracted + + double _iconSize(BuildContext context) { + final baseFontSize = baseButtonExtraOptions(context)?.iconSize; + final iconSize = options.iconSize; + return iconSize ?? baseFontSize ?? kDefaultIconSize; + } + + double _iconButtonFactor(BuildContext context) { + final baseIconFactor = baseButtonExtraOptions(context)?.iconButtonFactor; + final iconButtonFactor = options.iconButtonFactor; + return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; + } + + VoidCallback? _afterButtonPressed(BuildContext context) { + return options.afterButtonPressed ?? + baseButtonExtraOptions(context)?.afterButtonPressed; + } + + QuillIconTheme? _iconTheme(BuildContext context) { + return options.iconTheme ?? baseButtonExtraOptions(context)?.iconTheme; + } + + QuillToolbarBaseButtonOptions? baseButtonExtraOptions(BuildContext context) { + return context.quillToolbarBaseButtonOptions; + } + + IconData _iconData(BuildContext context) { + return options.iconData ?? + baseButtonExtraOptions(context)?.iconData ?? + Icons.search; + } + + String _tooltip(BuildContext context) { + return options.tooltip ?? + baseButtonExtraOptions(context)?.tooltip ?? + (context.loc.search); + } + + Color _dialogBarrierColor(BuildContext context) { + return options.dialogBarrierColor ?? + context.quillSharedConfigurations?.dialogBarrierColor ?? + Colors.black54; + } + + QuillDialogTheme? _dialogTheme(BuildContext context) { + return options.dialogTheme ?? + context.quillSharedConfigurations?.dialogTheme; + } + + @override + Widget build(BuildContext context) { + final iconTheme = _iconTheme(context); + final tooltip = _tooltip(context); + final iconData = _iconData(context); + final iconSize = _iconSize(context); + final iconButtonFactor = _iconButtonFactor(context); + final afterButtonPressed = _afterButtonPressed(context); + + final childBuilder = + options.childBuilder ?? baseButtonExtraOptions(context)?.childBuilder; + + if (childBuilder != null) { + return childBuilder( + options, + QuillToolbarSearchButtonExtraOptions( + controller: controller, + context: context, + onPressed: () { + _sharedOnPressed(context); + afterButtonPressed?.call(); + }, + ), + ); + } + + return QuillToolbarIconButton( + tooltip: tooltip, + icon: Icon( + iconData, + size: iconSize * iconButtonFactor, + ), + isSelected: false, + onPressed: () => _sharedOnPressed(context), + afterPressed: afterButtonPressed, + iconTheme: iconTheme, + ); + } + + Future _sharedOnPressed(BuildContext context) async { + final customCallback = options.customOnPressedCallback; + if (customCallback != null) { + await customCallback( + controller, + ); + return; + } + await showDialog( + barrierColor: _dialogBarrierColor(context), + context: context, + builder: (_) => FlutterQuillLocalizationsWidget( + child: QuillToolbarLegacySearchDialog( + controller: controller, + dialogTheme: _dialogTheme(context), + text: '', + ), + ), + ); + } +} diff --git a/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_dialog.dart b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_dialog.dart new file mode 100644 index 00000000..de92e27f --- /dev/null +++ b/lib/src/widgets/toolbar/buttons/search/legacy/legacy_search_dialog.dart @@ -0,0 +1,230 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../translations.dart'; +import '../../../../../models/documents/document.dart'; +import '../../../../../models/themes/quill_dialog_theme.dart'; +import '../../../../quill/quill_controller.dart'; + +class QuillToolbarLegacySearchDialog extends StatefulWidget { + const QuillToolbarLegacySearchDialog({ + required this.controller, + this.dialogTheme, + this.text, + super.key, + }); + + final QuillController controller; + final QuillDialogTheme? dialogTheme; + final String? text; + + @override + QuillToolbarLegacySearchDialogState createState() => + QuillToolbarLegacySearchDialogState(); +} + +class QuillToolbarLegacySearchDialogState + extends State { + late String _text; + late TextEditingController _controller; + late List? _offsets; + late int _index; + bool _caseSensitive = false; + bool _wholeWord = false; + + @override + void initState() { + super.initState(); + _text = widget.text ?? ''; + _offsets = null; + _index = 0; + _controller = TextEditingController(text: _text); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var matchShown = ''; + if (_offsets != null) { + if (_offsets!.isEmpty) { + matchShown = '0/0'; + } else { + matchShown = '${_index + 1}/${_offsets!.length}'; + } + } + + return Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + backgroundColor: widget.dialogTheme?.dialogBackgroundColor, + alignment: Alignment.bottomCenter, + insetPadding: EdgeInsets.zero, + child: FlutterQuillLocalizationsWidget( + child: Builder( + builder: (context) { + return SizedBox( + height: 45, + child: Row( + children: [ + Tooltip( + message: context.loc.caseSensitivityAndWholeWordSearch, + child: ToggleButtons( + onPressed: (index) { + if (index == 0) { + _changeCaseSensitivity(); + } else if (index == 1) { + _changeWholeWord(); + } + }, + borderRadius: const BorderRadius.all(Radius.circular(2)), + isSelected: [_caseSensitive, _wholeWord], + children: const [ + Text( + '\u0391\u03b1', + style: TextStyle( + fontFamily: 'MaterialIcons', + fontSize: 24, + ), + ), + Text( + '\u201c\u2026\u201d', + style: TextStyle( + fontFamily: 'MaterialIcons', + fontSize: 24, + ), + ), + ], + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 12, left: 5), + child: TextField( + style: widget.dialogTheme?.inputTextStyle, + decoration: InputDecoration( + isDense: true, + suffixText: (_offsets != null) ? matchShown : '', + suffixStyle: widget.dialogTheme?.labelTextStyle, + ), + autofocus: true, + onChanged: _textChanged, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + onEditingComplete: _findText, + controller: _controller, + ), + ), + ), + if (_offsets == null) + IconButton( + icon: const Icon(Icons.search), + tooltip: context.loc.findText, + onPressed: _findText, + ), + if (_offsets != null) + IconButton( + icon: const Icon(Icons.keyboard_arrow_up), + tooltip: context.loc.moveToPreviousOccurrence, + onPressed: + (_offsets!.isNotEmpty) ? _moveToPrevious : null, + ), + if (_offsets != null) + IconButton( + icon: const Icon(Icons.keyboard_arrow_down), + tooltip: context.loc.moveToNextOccurrence, + onPressed: (_offsets!.isNotEmpty) ? _moveToNext : null, + ), + ], + ), + ); + }, + ), + ), + ); + } + + void _findText() { + _text = _controller.text; + if (_text.isEmpty) { + return; + } + setState(() { + _offsets = widget.controller.document.search( + _text, + caseSensitive: _caseSensitive, + wholeWord: _wholeWord, + ); + _index = 0; + }); + if (_offsets!.isNotEmpty) { + _moveToPosition(); + } + } + + void _moveToPosition() { + widget.controller.updateSelection( + TextSelection( + baseOffset: _offsets![_index], + extentOffset: _offsets![_index] + _text.length, + ), + ChangeSource.local, + ); + } + + void _moveToPrevious() { + if (_offsets!.isEmpty) { + return; + } + setState(() { + if (_index > 0) { + _index -= 1; + } else { + _index = _offsets!.length - 1; + } + }); + _moveToPosition(); + } + + void _moveToNext() { + if (_offsets!.isEmpty) { + return; + } + setState(() { + if (_index < _offsets!.length - 1) { + _index += 1; + } else { + _index = 0; + } + }); + _moveToPosition(); + } + + void _textChanged(String value) { + setState(() { + _text = value; + _offsets = null; + _index = 0; + }); + } + + void _changeCaseSensitivity() { + setState(() { + _caseSensitive = !_caseSensitive; + _offsets = null; + _index = 0; + }); + } + + void _changeWholeWord() { + setState(() { + _wholeWord = !_wholeWord; + _offsets = null; + _index = 0; + }); + } +} diff --git a/lib/src/widgets/toolbar/buttons/search/search_button.dart b/lib/src/widgets/toolbar/buttons/search/search_button.dart index c063c6d6..05f7779b 100644 --- a/lib/src/widgets/toolbar/buttons/search/search_button.dart +++ b/lib/src/widgets/toolbar/buttons/search/search_button.dart @@ -22,6 +22,8 @@ class QuillToolbarSearchButton extends StatelessWidget { return _controller; } + // TODO: The logic is common and can be extracted + double _iconSize(BuildContext context) { final baseFontSize = baseButtonExtraOptions(context)?.iconSize; final iconSize = options.iconSize; @@ -60,9 +62,7 @@ class QuillToolbarSearchButton extends StatelessWidget { } Color _dialogBarrierColor(BuildContext context) { - return options.dialogBarrierColor ?? - context.quillSharedConfigurations?.dialogBarrierColor ?? - Colors.black54; + return options.dialogBarrierColor ?? Colors.transparent; } QuillDialogTheme? _dialogTheme(BuildContext context) { @@ -96,17 +96,11 @@ class QuillToolbarSearchButton extends StatelessWidget { ); } - // final theme = Theme.of(context); - - // final iconColor = - // iconTheme?.iconUnselectedFillColor ?? theme.iconTheme.color; - return QuillToolbarIconButton( tooltip: tooltip, icon: Icon( iconData, size: iconSize * iconButtonFactor, - // color: iconColor, ), isSelected: false, onPressed: () => _sharedOnPressed(context), @@ -130,7 +124,7 @@ class QuillToolbarSearchButton extends StatelessWidget { child: QuillToolbarSearchDialog( controller: controller, dialogTheme: _dialogTheme(context), - text: '', + searchBarAlignment: options.searchBarAlignment, ), ), ); diff --git a/lib/src/widgets/toolbar/buttons/search/search_dialog.dart b/lib/src/widgets/toolbar/buttons/search/search_dialog.dart index 7d28d413..19e041ef 100644 --- a/lib/src/widgets/toolbar/buttons/search/search_dialog.dart +++ b/lib/src/widgets/toolbar/buttons/search/search_dialog.dart @@ -1,5 +1,8 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import '../../../../../extensions.dart'; import '../../../../l10n/extensions/localizations.dart'; import '../../../../l10n/widgets/localizations.dart'; import '../../../../models/documents/document.dart'; @@ -44,6 +47,7 @@ class QuillToolbarSearchDialog extends StatefulWidget { this.dialogTheme, this.text, this.childBuilder, + this.searchBarAlignment, super.key, }); @@ -51,6 +55,7 @@ class QuillToolbarSearchDialog extends StatefulWidget { final QuillDialogTheme? dialogTheme; final String? text; final QuillToolbarSearchDialogChildBuilder? childBuilder; + final AlignmentGeometry? searchBarAlignment; @override QuillToolbarSearchDialogState createState() => @@ -58,39 +63,30 @@ class QuillToolbarSearchDialog extends StatefulWidget { } class QuillToolbarSearchDialogState extends State { + final TextEditingController _textController = TextEditingController(); late String _text; - late TextEditingController _controller; - late List? _offsets; - late int _index; + List _offsets = []; + int _index = 0; bool _caseSensitive = false; bool _wholeWord = false; + bool _searchSettingsUnfolded = false; + Timer? _searchTimer; @override void initState() { super.initState(); _text = widget.text ?? ''; - _offsets = null; - _index = 0; - _controller = TextEditingController(text: _text); } @override void dispose() { - _controller.dispose(); + _textController.dispose(); + _searchTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { - var matchShown = ''; - if (_offsets != null) { - if (_offsets!.isEmpty) { - matchShown = '0/0'; - } else { - matchShown = '${_index + 1}/${_offsets!.length}'; - } - } - final childBuilder = widget.childBuilder; if (childBuilder != null) { return childBuilder( @@ -99,7 +95,7 @@ class QuillToolbarSearchDialogState extends State { onEditingComplete: _findText, onTextChanged: _textChanged, caseSensitive: _caseSensitive, - textEditingController: _controller, + textEditingController: _textController, index: _index, offsets: _offsets, text: _text, @@ -110,90 +106,139 @@ class QuillToolbarSearchDialogState extends State { ); } + final searchBarAlignment = + widget.searchBarAlignment ?? Alignment.bottomCenter; + final searchBarAtBottom = (searchBarAlignment == Alignment.bottomCenter) || + (searchBarAlignment == Alignment.bottomLeft) || + (searchBarAlignment == Alignment.bottomRight); + final addBottomPadding = searchBarAtBottom && isMobile(supportWeb: true); + var matchShown = ''; + if (_text.isNotEmpty) { + if (_offsets.isEmpty) { + matchShown = '0/0'; + } else { + matchShown = '${_index + 1}/${_offsets.length}'; + } + } + + final searchBar = Container( + height: addBottomPadding ? 50 : 45, + padding: addBottomPadding ? const EdgeInsets.only(bottom: 12) : null, + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.close), + tooltip: context.loc.close, + visualDensity: VisualDensity.compact, + onPressed: () { + Navigator.of(context).pop(); + }, + ), + IconButton( + icon: const Icon(Icons.more_vert), + isSelected: _caseSensitive || _wholeWord, + tooltip: context.loc.searchSettings, + visualDensity: VisualDensity.compact, + onPressed: () { + setState(() { + _searchSettingsUnfolded = !_searchSettingsUnfolded; + }); + }, + ), + Expanded( + child: TextField( + style: widget.dialogTheme?.inputTextStyle, + decoration: InputDecoration( + isDense: true, + suffixText: matchShown, + suffixStyle: widget.dialogTheme?.labelTextStyle, + ), + autofocus: true, + onChanged: _textChanged, + textInputAction: TextInputAction.done, + keyboardType: TextInputType.text, + controller: _textController, + ), + ), + IconButton( + icon: const Icon(Icons.keyboard_arrow_up), + tooltip: context.loc.moveToPreviousOccurrence, + onPressed: (_offsets.isNotEmpty) ? _moveToPrevious : null, + ), + IconButton( + icon: const Icon(Icons.keyboard_arrow_down), + tooltip: context.loc.moveToNextOccurrence, + onPressed: (_offsets.isNotEmpty) ? _moveToNext : null, + ), + ], + ), + ); + + final searchSettings = SizedBox( + height: 45, + child: Row( + children: [ + Expanded( + child: CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + visualDensity: VisualDensity.compact, + contentPadding: EdgeInsets.zero, + title: Text( + context.loc.caseSensitive, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + value: _caseSensitive, + onChanged: (value) { + setState(() { + _caseSensitive = value!; + _findText(); + }); + }, + ), + ), + Expanded( + child: CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + visualDensity: VisualDensity.compact, + contentPadding: EdgeInsets.zero, + title: Text( + context.loc.wholeWord, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + value: _wholeWord, + onChanged: (value) { + setState(() { + _wholeWord = value!; + _findText(); + }); + }, + ), + ), + ], + ), + ); + return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), ), backgroundColor: widget.dialogTheme?.dialogBackgroundColor, - alignment: Alignment.bottomCenter, + alignment: searchBarAlignment, insetPadding: EdgeInsets.zero, child: FlutterQuillLocalizationsWidget( child: Builder( builder: (context) { - return SizedBox( - height: 45, - child: Row( - children: [ - Tooltip( - message: context.loc.caseSensitivityAndWholeWordSearch, - child: ToggleButtons( - onPressed: (index) { - if (index == 0) { - _changeCaseSensitivity(); - } else if (index == 1) { - _changeWholeWord(); - } - }, - borderRadius: const BorderRadius.all(Radius.circular(2)), - isSelected: [_caseSensitive, _wholeWord], - children: const [ - Text( - '\u0391\u03b1', - style: TextStyle( - fontFamily: 'MaterialIcons', - fontSize: 24, - ), - ), - Text( - '\u201c\u2026\u201d', - style: TextStyle( - fontFamily: 'MaterialIcons', - fontSize: 24, - ), - ), - ], - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: 12, left: 5), - child: TextField( - style: widget.dialogTheme?.inputTextStyle, - decoration: InputDecoration( - isDense: true, - suffixText: (_offsets != null) ? matchShown : '', - suffixStyle: widget.dialogTheme?.labelTextStyle, - ), - autofocus: true, - onChanged: _textChanged, - textInputAction: TextInputAction.done, - keyboardType: TextInputType.text, - onEditingComplete: _findText, - controller: _controller, - ), - ), - ), - if (_offsets == null) - IconButton( - icon: const Icon(Icons.search), - tooltip: context.loc.findText, - onPressed: _findText, - ), - if (_offsets != null) - IconButton( - icon: const Icon(Icons.keyboard_arrow_up), - tooltip: context.loc.moveToPreviousOccurrence, - onPressed: - (_offsets!.isNotEmpty) ? _moveToPrevious : null, - ), - if (_offsets != null) - IconButton( - icon: const Icon(Icons.keyboard_arrow_down), - tooltip: context.loc.moveToNextOccurrence, - onPressed: (_offsets!.isNotEmpty) ? _moveToNext : null, - ), - ], - ), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (_searchSettingsUnfolded && searchBarAtBottom) + searchSettings, + searchBar, + if (_searchSettingsUnfolded && !searchBarAtBottom) + searchSettings, + ], ); }, ), @@ -201,9 +246,30 @@ class QuillToolbarSearchDialogState extends State { ); } + void _textChanged(String text) { + _text = text; + if (_searchTimer?.isActive ?? false) { + _searchTimer?.cancel(); + } + _searchTimer = Timer( + const Duration(milliseconds: 300), + _findText, + ); + } + void _findText() { - _text = _controller.text; if (_text.isEmpty) { + setState(() { + _offsets = []; + _index = 0; + widget.controller.updateSelection( + TextSelection( + baseOffset: widget.controller.selection.baseOffset, + extentOffset: widget.controller.selection.baseOffset, + ), + ChangeSource.local, + ); + }); return; } setState(() { @@ -214,7 +280,7 @@ class QuillToolbarSearchDialogState extends State { ); _index = 0; }); - if (_offsets!.isNotEmpty) { + if (_offsets.isNotEmpty) { _moveToPosition(); } } @@ -222,33 +288,33 @@ class QuillToolbarSearchDialogState extends State { void _moveToPosition() { widget.controller.updateSelection( TextSelection( - baseOffset: _offsets![_index], - extentOffset: _offsets![_index] + _text.length, + baseOffset: _offsets[_index], + extentOffset: _offsets[_index] + _text.length, ), ChangeSource.local, ); } void _moveToPrevious() { - if (_offsets!.isEmpty) { + if (_offsets.isEmpty) { return; } setState(() { if (_index > 0) { _index -= 1; } else { - _index = _offsets!.length - 1; + _index = _offsets.length - 1; } }); _moveToPosition(); } void _moveToNext() { - if (_offsets!.isEmpty) { + if (_offsets.isEmpty) { return; } setState(() { - if (_index < _offsets!.length - 1) { + if (_index < _offsets.length - 1) { _index += 1; } else { _index = 0; @@ -256,28 +322,4 @@ class QuillToolbarSearchDialogState extends State { }); _moveToPosition(); } - - void _textChanged(String value) { - setState(() { - _text = value; - _offsets = null; - _index = 0; - }); - } - - void _changeCaseSensitivity() { - setState(() { - _caseSensitive = !_caseSensitive; - _offsets = null; - _index = 0; - }); - } - - void _changeWholeWord() { - setState(() { - _wholeWord = !_wholeWord; - _offsets = null; - _index = 0; - }); - } } diff --git a/lib/src/widgets/toolbar/buttons/toggle_style_button.dart b/lib/src/widgets/toolbar/buttons/toggle_style_button.dart index 941a893e..aad3b3fb 100644 --- a/lib/src/widgets/toolbar/buttons/toggle_style_button.dart +++ b/lib/src/widgets/toolbar/buttons/toggle_style_button.dart @@ -78,10 +78,7 @@ class QuillToolbarToggleStyleButtonState 'left' => (context.loc.alignLeft, Icons.format_align_left), 'right' => (context.loc.alignRight, Icons.format_align_right), 'center' => (context.loc.alignCenter, Icons.format_align_center), - 'justify' => ( - context.loc.justifyWinWidth, - Icons.format_align_justify - ), + 'justify' => (context.loc.alignJustify, Icons.format_align_justify), Object() => throw ArgumentError(widget.attribute.value), null => (context.loc.alignCenter, Icons.format_align_center), }; diff --git a/lib/src/widgets/toolbar/simple_toolbar.dart b/lib/src/widgets/toolbar/simple_toolbar.dart index 789717be..b4d986a0 100644 --- a/lib/src/widgets/toolbar/simple_toolbar.dart +++ b/lib/src/widgets/toolbar/simple_toolbar.dart @@ -277,10 +277,16 @@ class QuillSimpleToolbar extends StatelessWidget options: toolbarConfigurations.buttonOptions.linkStyle2, ), if (configurations.showSearchButton) - QuillToolbarSearchButton( - controller: globalController, - options: toolbarConfigurations.buttonOptions.search, - ), + switch (configurations.searchButtonType) { + SearchButtonType.legacy => QuillToolbarLegacySearchButton( + controller: globalController, + options: toolbarConfigurations.buttonOptions.search, + ), + SearchButtonType.modern => QuillToolbarSearchButton( + controller: globalController, + options: toolbarConfigurations.buttonOptions.search, + ), + }, if (configurations.showClipboardCut) QuillToolbarClipboardButton( options: toolbarConfigurations.buttonOptions.clipboardCut, @@ -306,20 +312,6 @@ class QuillSimpleToolbar extends StatelessWidget options: customButton, controller: globalController, ), - // if (customButton.child != null) ...[ - // InkWell( - // onTap: customButton.onTap, - // child: customButton.child, - // ), - // ] else ...[ - // QuillToolbarCustomButton( - // options: - // toolbarConfigurations.buttonOptions.customButtons, - // controller: toolbarConfigurations - // .buttonOptions.customButtons.controller ?? - // globalController, - // ), - // ], ], ]; } diff --git a/pubspec.yaml b/pubspec.yaml index f25d46b1..85bcdae3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter. -version: 9.3.21 +version: 9.5.2 homepage: https://1o24bbs.com/c/bulletjournal/108/ repository: https://github.com/singerdmx/flutter-quill/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -42,7 +42,7 @@ dependencies: flutter_localizations: sdk: flutter - # Normal packages + # Dart Packages intl: ^0.19.0 dart_quill_delta: ^9.3.3 collection: ^1.17.0 @@ -62,7 +62,6 @@ dependencies: url_launcher: ^6.2.4 flutter_keyboard_visibility: ^6.0.0 device_info_plus: ^10.0.1 - super_clipboard: ^0.8.15 dev_dependencies: flutter_lints: ^4.0.0 @@ -71,10 +70,12 @@ dev_dependencies: flutter_quill_test: ^9.3.4 test: ^1.24.3 - # Both yaml_edit and yaml are only used for the scripts + # For scripts only yaml_edit: ^2.2.1 yaml: ^3.1.2 + http: ^1.2.1 + path: any flutter: uses-material-design: true generate: true diff --git a/quill_html_converter/CHANGELOG.md b/quill_html_converter/CHANGELOG.md index 0bdf2ede..0064e681 100644 --- a/quill_html_converter/CHANGELOG.md +++ b/quill_html_converter/CHANGELOG.md @@ -4,6 +4,244 @@ All notable changes to this project will be documented in this file. +## 9.5.2 + +* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2 + +## 9.5.1 + +* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1 + +## 9.5.0 + +* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0 + +## 9.4.9 + +* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9 + +## 9.4.8 + +* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8 + +## 9.4.7 + +* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954 + +## New Contributors +* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7 + +## 9.4.6 + +* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938 +* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6 + +## 9.4.5 + +* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934 + +## New Contributors +* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5 + +## 9.4.4 + +* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4 + +## 9.4.3 + +* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926 + +## New Contributors +* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3 + +## 9.4.2 + +* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2 + +## 9.4.1 + +* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919 + +## New Contributors +* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1 + +## 9.4.0 + +This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x` + +- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`: + ```dart + QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( + searchButtonType: SearchButtonType.legacy, + ), + ) + ``` + While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future. + + If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget + + Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated) + +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b) + + +
+ +
+ Improved + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04) + +
+ + For the detailed changes, see #1904 + +- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911 + +- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call: + + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x` + + We initially wanted to publish `flutter_quill_super_clipboard` to allow: + - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins + - Using `flutter_quill_extensions` with optional `super_clipboard` + + To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above. + + Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated. + + The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by: + ```dart + // ignore: implementation_imports + import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; + ``` + + Then you can call: + ```dart + // ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; + ClipboardServiceProvider.setInstance(YourClipboardService()); +``` + + The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future. + + If you use the above imports, you might get **breaking changes** in **non-breaking change releases**. + +- Subscript and Superscript should now work for all languages and characters + + The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices. +We have removed the Apple font from the example + +- Allow pasting Markdown and HTML file content from the system to the editor + + Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor + Copying an HTML file, or HTML content from apps and websites is different than copying plain text. + + This is why this change requires `super_clipboard` implementation as this is platform-dependent: + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + as mentioned above. + + The following example for copying a Markdown file: + +
+ Markdown File Content + + ```md + + **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor + + You have two options: + + 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well + (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly + 1. Another 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 part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) + Typescript/Javascript package. + this package doesn't convert the HTML back to Quill Delta as far as we know + + ``` + +
+ +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e) + +
+ +
+ After + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788) + +
+ + Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot + however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info. + + ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9) + + For more details see #1915 + + Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. + + We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor + previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. + + Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions) + +## New Contributors +* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0 + ## 9.3.21 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 diff --git a/quill_html_converter/pubspec.yaml b/quill_html_converter/pubspec.yaml index 42a80920..08f68074 100644 --- a/quill_html_converter/pubspec.yaml +++ b/quill_html_converter/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_html_converter description: A extension for flutter_quill package to add support for dealing with conversion to/from html -version: 9.3.21 +version: 9.5.2 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/quill_pdf_converter/CHANGELOG.md b/quill_pdf_converter/CHANGELOG.md index 0bdf2ede..0064e681 100644 --- a/quill_pdf_converter/CHANGELOG.md +++ b/quill_pdf_converter/CHANGELOG.md @@ -4,6 +4,244 @@ All notable changes to this project will be documented in this file. +## 9.5.2 + +* Fix style settings by @AtlasAutocode in https://github.com/singerdmx/flutter-quill/pull/1962 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.1...v9.5.2 + +## 9.5.1 + +* feat(extensions): Youtube Video Player Support Mode by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1916 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.5.0...v9.5.1 + +## 9.5.0 + +* Partial support for table embed by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1960 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.9...v9.5.0 + +## 9.4.9 + +* Upgrade photo_view to 0.15.0 for flutter_quill_extensions by @singerdmx in https://github.com/singerdmx/flutter-quill/pull/1958 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.8...v9.4.9 + +## 9.4.8 + +* Add support for html underline and videos by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1955 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.7...v9.4.8 + +## 9.4.7 + +* fixed #1953 italic detection error by @CatHood0 in https://github.com/singerdmx/flutter-quill/pull/1954 + +## New Contributors +* @CatHood0 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1954 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.6...v9.4.7 + +## 9.4.6 + +* fix: search dialog throw an exception due to missing FlutterQuillLocalizations.delegate in the editor by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1938 +* fix(editor): implement editor shortcut action for home and end keys to fix exception about unimplemented ScrollToDocumentBoundaryIntent by @ellet0 in https://github.com/singerdmx/flutter-quill/pull/1937 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.5...v9.4.6 + +## 9.4.5 + +* fix: color picker hex unfocus on web by @geronimol in https://github.com/singerdmx/flutter-quill/pull/1934 + +## New Contributors +* @geronimol made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1934 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.4...v9.4.5 + +## 9.4.4 + +* fix: Enabled link regex to be overridden by @JoepHeijnen in https://github.com/singerdmx/flutter-quill/pull/1931 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.3...v9.4.4 + +## 9.4.3 + +* Fix: setState() called after dispose(): QuillToolbarClipboardButtonState #1895 by @windows7lake in https://github.com/singerdmx/flutter-quill/pull/1926 + +## New Contributors +* @windows7lake made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1926 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.2...v9.4.3 + +## 9.4.2 + +* Respect autofocus, closes #1923 by @Guillergood in https://github.com/singerdmx/flutter-quill/pull/1924 + + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.1...v9.4.2 + +## 9.4.1 + +* replace base64 regex string by @salba360496 in https://github.com/singerdmx/flutter-quill/pull/1919 + +## New Contributors +* @salba360496 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1919 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.4.0...v9.4.1 + +## 9.4.0 + +This release can be used without changing anything, although it can break the behavior a little, we provided a way to use the old behavior in `9.3.x` + +- Thanks to @Alspb, the search bar/dialog has been reworked for improved UI that fits **Material 3** look and feel, the search happens on the fly, and other minor changes, if you want the old search bar, you can restore it with one line if you're using `QuillSimpleToolbar`: + ```dart + QuillToolbar.simple( + configurations: QuillSimpleToolbarConfigurations( + searchButtonType: SearchButtonType.legacy, + ), + ) + ``` + While the changes are mostly to the `QuillToolbarSearchDialog` and it seems this should be `searchDialogType`, we provided the old button with the old dialog in case we update the button in the future. + + If you're using `QuillToolbarSearchButton` in a custom Toolbar, you don't need anything to get the new button. if you want the old button, use the `QuillToolbarLegacySearchButton` widget + + Consider using the improved button with the improved dialog as the legacy button might removed in future releases (for now, it's not deprecated) + +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b) + + +
+ +
+ Improved + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04) + +
+ + For the detailed changes, see #1904 + +- Korean translations by @leegh519 in https://github.com/singerdmx/flutter-quill/pull/1911 + +- The usage of `super_clipboard` plugin in `flutter_quill` has been moved to the `flutter_quill_extensions` package, this will restore the old behavior in `8.x.x` though it will break the `onImagePaste`, `onGifPaste` and rich text pasting from HTML or Markdown, most of those features are available in `super_clipboard` plugin except `onImagePaste` which was available as we were using [pasteboard](https://pub.dev/packages/pasteboard), Unfortunately, it's no longer supported on recent versions of Flutter, and some functionalities such as an image from Clipboard and Html paste are not supported on some platforms such as Android, your project will continue to work, calls of `onImagePaste` and `onGifPaste` will be ignored unless you include [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package in your project and call: + + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + Before using any `flutter_quill` widgets, this will restore the old behavior in `9.x.x` + + We initially wanted to publish `flutter_quill_super_clipboard` to allow: + - Using `super_clipboard` without `flutter_quill_extensions` packages and plugins + - Using `flutter_quill_extensions` with optional `super_clipboard` + + To simplify the usage, we moved it to `flutter_quill_extensions`, let us know if you want any of the use cases above. + + Overall `super_clipboard` is a Comprehensive clipboard plugin with a lot of features, the only thing that developers didn't want is Rust installation even though it's automated. + + The main goal of `ClipboardService` is to make `super_clipboard` optional, you can use your own implementation, and create a class that implements `ClipboardService`, which you can get by: + ```dart + // ignore: implementation_imports + import 'package:flutter_quill/src/services/clipboard/clipboard_service.dart'; + ``` + + Then you can call: + ```dart + // ignore: implementation_imports +import 'package:flutter_quill/src/services/clipboard/clipboard_service_provider.dart'; + ClipboardServiceProvider.setInstance(YourClipboardService()); +``` + + The interface could change at any time and will be updated internally for `flutter_quill` and `flutter_quill_extensions`, we didn't export those two classes by default to avoid breaking changes in case you use them as we might change them in the future. + + If you use the above imports, you might get **breaking changes** in **non-breaking change releases**. + +- Subscript and Superscript should now work for all languages and characters + + The previous implementation required the Apple 'SF-Pro-Display-Regular.otf' font which is only licensed/permitted for use on Apple devices. +We have removed the Apple font from the example + +- Allow pasting Markdown and HTML file content from the system to the editor + + Before `9.4.x` if you try to copy an HTML or Markdown file, and paste it into the editor, you will get the file name in the editor + Copying an HTML file, or HTML content from apps and websites is different than copying plain text. + + This is why this change requires `super_clipboard` implementation as this is platform-dependent: + ```dart + FlutterQuillExtensions.useSuperClipboardPlugin(); + ``` + as mentioned above. + + The following example for copying a Markdown file: + +
+ Markdown File Content + + ```md + + **Note**: This package supports converting from HTML back to Quill delta but it's experimental and used internally when pasting HTML content from the clipboard to the Quill Editor + + You have two options: + + 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to HTML well + (it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)), it is just a handy extension to do it more quickly + 1. Another 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 part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) + Typescript/Javascript package. + this package doesn't convert the HTML back to Quill Delta as far as we know + + ``` + +
+ +
+ Before + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e) + +
+ +
+ After + + ![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788) + +
+ + Markdown, and HTML converting from and to Delta are **currently far from perfect**, the current implementation could improved a lot + however **it will likely not work like expected**, due to differences between HTML and Delta, see this [comment](https://github.com/slab/quill/issues/1551#issuecomment-311458570) for more info. + + ![Copying Markdown file into Flutter Quill Editor](https://github.com/singerdmx/flutter-quill/assets/73608287/63bd6ba6-cc49-4335-84dc-91a0fa5c95a9) + + For more details see #1915 + + Using or converting to HTML or Markdown is highly experimental and shouldn't be used for production applications. + + We use it internally as it is more suitable for our specific use case., copying content from external websites and pasting it into the editor + previously breaks the styles, while the current implementation is not ready, it provides a better user experience and doesn't have many downsides. + + Feel free to report any bugs or feature requests at [Issues](https://github.com/singerdmx/flutter-quill/issues) or drop any suggestions and questions at [Discussions](https://github.com/singerdmx/flutter-quill/discussions) + +## New Contributors +* @leegh519 made their first contribution in https://github.com/singerdmx/flutter-quill/pull/1911 + +**Full Changelog**: https://github.com/singerdmx/flutter-quill/compare/v9.3.21...v9.4.0 + ## 9.3.21 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 diff --git a/quill_pdf_converter/pubspec.yaml b/quill_pdf_converter/pubspec.yaml index 31e8aa52..7136d1db 100644 --- a/quill_pdf_converter/pubspec.yaml +++ b/quill_pdf_converter/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_pdf_converter description: A extension for flutter_quill package to add support for dealing with conversion to pdf -version: 9.3.21 +version: 9.5.2 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/scripts/create_version_content_from_github_release.dart b/scripts/create_version_content_from_github_release.dart new file mode 100644 index 00000000..63b51919 --- /dev/null +++ b/scripts/create_version_content_from_github_release.dart @@ -0,0 +1,68 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; +import 'dart:io' show exit; + +import 'package:http/http.dart' as http; + +import 'update_package_version.dart'; + +// A script is used in CI that will fetch release notes using GitHub API +// and create a file from it that will be used by another script +// +// This script should run from root project folder instead of scripts folder +// or others. + +const _usage = + 'Usage: ./script (optional)'; + +Future main(List args) async { + print('📑 Fetch release notes from Github API'); + + if (args.isEmpty) { + print('Missing required arguments ($args). $_usage'); + exit(1); + } + if (args.length > 2) { + print('Too many arguments ($args). $_usage'); + exit(1); + } + if (args.length < 2) { + print('Missing arguments ($args). $_usage'); + exit(1); + } + + final githubRepository = args[0]; + final releaseTag = args[1]; + final githubAuthorization = args.elementAtOrNull(2); + final response = await http.get( + Uri.parse( + 'https://api.github.com/repos/$githubRepository/releases/tags/$releaseTag', + ), + headers: { + if (githubAuthorization != null) 'Authorization': githubAuthorization, + }, + ); + if (response.statusCode != 200) { + print('Response status code is ${response.statusCode} which is not 200'); + print('Response body: ${response.body}'); + exit(1); + } + final responseBody = response.body; + print('⚠️ Validate release notes response'); + if (responseBody.trim().isEmpty) { + print('Release notes response is empty.'); + exit(1); + } + print('Response body: $responseBody'); + final githubReleaseNotes = + (jsonDecode(responseBody) as Map)['body'] as String?; + if (githubReleaseNotes == null) { + print('Release notes is null.'); + exit(1); + } + if (!await versionContentFile.parent.exists()) { + await versionContentFile.parent.create(recursive: true); + } + await versionContentFile.writeAsString(githubReleaseNotes); +} diff --git a/scripts/ensure_translations_correct.dart b/scripts/ensure_translations_correct.dart index 092178d7..ef03b5e9 100644 --- a/scripts/ensure_translations_correct.dart +++ b/scripts/ensure_translations_correct.dart @@ -18,7 +18,7 @@ import 'package:yaml/yaml.dart'; // This must be updated once add or remove some translation keys // if you update existing keys, no need to update it -const _expectedTranslationKeysLength = 96; +const _expectedTranslationKeysLength = 99; Future main(List args) async { final l10nYamlText = await File('l10n.yaml').readAsString(); diff --git a/scripts/pub_get.sh b/scripts/pub_get.sh old mode 100644 new mode 100755 diff --git a/scripts/regenerate_translations.dart b/scripts/regenerate_translations.dart new file mode 100644 index 00000000..3b17cca4 --- /dev/null +++ b/scripts/regenerate_translations.dart @@ -0,0 +1,24 @@ +// ignore_for_file: avoid_print + +import 'dart:io' show Directory, Process; + +Future main(List args) async { + final generatedDartLocalizationsFolder = Directory('lib/src/l10n/generated'); + if (await generatedDartLocalizationsFolder.exists()) { + print( + 'Generated directory (${generatedDartLocalizationsFolder.path}) exists, deleting it... 📁', + ); + await generatedDartLocalizationsFolder.delete(recursive: true); + } + print('Running flutter pub get... 📦'); + await Process.run('flutter', ['pub', 'get']); + + print('Running flutter gen-l10n... 🌍'); + await Process.run('flutter', ['gen-l10n']); + + print('Applying Dart fixes to the newly generated files... 🔧'); + await Process.run('dart', ['fix', '--apply', './lib/src/l10n/generated']); + + print('Formatting the newly generated Dart files... ✨'); + await Process.run('dart', ['format', './lib/src/l10n/generated']); +} diff --git a/scripts/regenerate_translations.sh b/scripts/regenerate_translations.sh deleted file mode 100755 index 921863c2..00000000 --- a/scripts/regenerate_translations.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# Important: make sure to run the script in the root folder of the repo: -# ./scripts/regenerate_translations.sh -# otherwise the script could delete the wrong folder in rare cases - -# TODO: Refactor this to a dart script to allow developers who use Windows to use it - -echo "" - -echo "Delete the current generated localizations..." -rm -rf lib/src/l10n/generated -echo "" - -echo "Run flutter pub get.." -flutter pub get -echo "" - -echo "Run flutter gen-l10n" -flutter gen-l10n -echo "" - -echo "" -echo "Apply dart fixes to the newly generated files" -dart fix --apply ./lib/src/l10n/generated - -echo "" -echo "Formate the newly generated dart files" -dart format ./lib/src/l10n/generated \ No newline at end of file diff --git a/scripts/update_package_version.dart b/scripts/update_package_version.dart index 0e831b53..8ee1c9e8 100644 --- a/scripts/update_package_version.dart +++ b/scripts/update_package_version.dart @@ -3,6 +3,8 @@ import 'dart:convert' show jsonDecode, jsonEncode; import 'dart:io' show File, exit; +// ignore: depend_on_referenced_packages +import 'package:path/path.dart' as path; import 'package:yaml_edit/yaml_edit.dart' show YamlEditor; /// The list of the packages that which will be used to update the `CHANGELOG.md` @@ -16,7 +18,9 @@ final _packages = [ './quill_pdf_converter', ]; -const _usage = 'Usage: ./script '; +const _usage = 'Usage: ./script '; +const _versionContentFileName = 'versionContent.md'; +final versionContentFile = File(path.join('build', _versionContentFileName)); /// A script that should run in the root folder and not inside any other folder /// it has one task, which update the version for `pubspec.yaml` and @@ -34,11 +38,11 @@ const _usage = 'Usage: ./script '; /// this script designed to run in CI to automate the process of updating /// the package Future main(List args) async { - if (args.isEmpty || args.length < 2) { + if (args.isEmpty) { print('Missing required arguments ($args). $_usage'); exit(1); } - if (args.length > 2) { + if (args.length > 1) { print('Too many arguments ($args). $_usage'); exit(1); } @@ -47,14 +51,16 @@ Future main(List args) async { print('The version is empty ($args). $_usage'); exit(1); } - final passedVersionContent = args[1]; - if (passedVersionContent.isEmpty) { - print('The version content is empty ($args). $_usage'); + if (!(await versionContentFile.exists())) { + print( + 'The file "$_versionContentFileName" in ${versionContentFile.path} does not exist.', + ); exit(1); } + final versionContent = await versionContentFile.readAsString(); print( - 'The version is $passedVersion and the content is:\n$passedVersionContent', + 'The version is "$passedVersion" and the content is:\n$versionContent', ); // A file that will be used to build the `CHANGELOG.md` files @@ -63,7 +69,7 @@ Future main(List args) async { await _replaceVersion( sourceChangeLogFile: sourceChangeLogFile, version: passedVersion, - versionContent: passedVersionContent, + versionContent: versionContent, ); final sourceChangeLog = jsonDecode(await sourceChangeLogFile.readAsString()) as Map; diff --git a/test/utils/delta_x_test.dart b/test/utils/delta_x_test.dart new file mode 100644 index 00000000..489a7493 --- /dev/null +++ b/test/utils/delta_x_test.dart @@ -0,0 +1,56 @@ +import 'package:flutter_quill/quill_delta.dart'; +import 'package:flutter_quill/src/models/documents/delta_x.dart'; +import 'package:test/test.dart'; + +void main() { + const htmlWithEmp = + '

This is a normal sentence, and this section has greater emphasis.

'; + + const htmlWithUnderline = + '

This is a normal sentence, and this section has greater underline'; + + const htmlWithIframeVideo = + ''; + + const htmlWithVideoTag = + ''' +'''; + final expectedDeltaEmp = Delta.fromOperations([ + Operation.insert( + 'This is a normal sentence, and this section has greater emp'), + Operation.insert('hasis.', {'italic': true}), + Operation.insert('\n'), + ]); + + final expectedDeltaUnderline = Delta.fromOperations([ + Operation.insert( + 'This is a normal sentence, and this section has greater '), + Operation.insert('underline', {'underline': true}), + Operation.insert('\n'), + ]); + + final expectedDeltaVideo = Delta.fromOperations([ + Operation.insert({'video': 'https://www.youtube.com/embed/dQw4w9WgXcQ'}), + Operation.insert('\n'), + ]); + + test('should detect emphasis and parse correctly', () { + final delta = DeltaX.fromHtml(htmlWithEmp); + expect(delta, expectedDeltaEmp); + }); + + test('should detect underline and parse correctly', () { + final delta = DeltaX.fromHtml(htmlWithUnderline); + expect(delta, expectedDeltaUnderline); + }); + + test('should detect iframe and parse correctly', () { + final delta = DeltaX.fromHtml(htmlWithIframeVideo); + expect(delta, expectedDeltaVideo); + }); + + test('should detect video and parse correctly', () { + final delta = DeltaX.fromHtml(htmlWithVideoTag); + expect(delta, expectedDeltaVideo); + }); +} diff --git a/test/utils/document_test.dart b/test/utils/document_test.dart new file mode 100644 index 00000000..c672e74d --- /dev/null +++ b/test/utils/document_test.dart @@ -0,0 +1,85 @@ +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_quill/quill_delta.dart'; +import 'package:test/test.dart'; + +void main() { + group('collectStyle', () { + /// Enter key inserts newline as plain text without inline styles. + /// collectStyle needs to retrieve style of preceding line + test('Simulate double enter key at end', () { + final delta = Delta() + ..insert('data\n') + ..insert('second\n', {'bold': true}) + ..insert('\n\nplain\n'); + final document = Document.fromDelta(delta); + // + expect(document.getPlainText(0, document.length), + 'data\nsecond\n\n\nplain\n'); + expect(document.length, 20); + // + expect('data\n', document.getPlainText(0, 5)); + for (var index = 0; index < 5; index++) { + expect(const Style(), document.collectStyle(index, 0)); + } + // + expect('second\n', document.getPlainText(5, 7)); + for (var index = 5; index < 12; index++) { + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(index, 0)); + } + // + expect('\n\n', document.getPlainText(12, 2)); + for (var index = 12; index < 14; index++) { + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(index, 0)); + } + // + for (var index = 14; index < document.length; index++) { + expect(const Style(), document.collectStyle(index, 0)); + } + }); + + test('No selection', () { + final delta = Delta() + ..insert('plain\n') + ..insert('bold\n', {'bold': true}) + ..insert('italic\n', {'italic': true}); + final document = Document.fromDelta(delta); + // + expect( + document.getPlainText(0, document.length), 'plain\nbold\nitalic\n'); + expect(document.length, 18); + // + for (var index = 0; index < 6; index++) { + expect(const Style(), document.collectStyle(index, 0)); + } + // + for (var index = 6; index < 11; index++) { + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(index, 0)); + } + // + for (var index = 11; index < document.length; index++) { + expect(const Style.attr({'italic': Attribute.italic}), + document.collectStyle(index, 0)); + } + }); + + test('Selection', () { + final delta = Delta() + ..insert('data\n') + ..insert('second\n', {'bold': true}); + final document = Document.fromDelta(delta); + // + expect(const Style(), document.collectStyle(0, 4)); + expect(const Style(), document.collectStyle(1, 3)); + // + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(5, 3)); + expect(const Style.attr({'bold': Attribute.bold}), + document.collectStyle(8, 3)); + // + expect(const Style(), document.collectStyle(3, 3)); + }); + }); +} diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart index 2a4bd8ab..4e3a79f6 100644 --- a/test/widgets/editor_test.dart +++ b/test/widgets/editor_test.dart @@ -3,6 +3,7 @@ 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/translations.dart'; import 'package:flutter_quill_test/flutter_quill_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -137,5 +138,82 @@ void main() { await tester.tap(find.byIcon(Icons.copy)); expect(didCopy, isTrue); }); + + testWidgets( + 'make sure QuillEditorOpenSearchAction does not throw an exception', + (tester) async { + final editorFocusNode = FocusNode(); + await tester.pumpWidget( + MaterialApp( + home: QuillEditor.basic( + configurations: QuillEditorConfigurations(controller: controller), + focusNode: editorFocusNode, + ), + ), + ); + // Required, otherwise the action shortcuts won't be invoked. + editorFocusNode.requestFocus(); + + await tester.sendKeyDownEvent(LogicalKeyboardKey.control); + await tester.sendKeyEvent(LogicalKeyboardKey.keyF); + await tester.sendKeyUpEvent(LogicalKeyboardKey.control); + + await tester.pump(); + + final exception = tester.takeException(); + expect( + exception, + isNot( + isInstanceOf(), + ), + ); + + expect(exception, isNull); + }, + ); + + testWidgets( + 'should throw MissingFlutterQuillLocalizationException if the delegate not provided', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) => Text(context.loc.font), + ), + ), + ); + + final exception = tester.takeException(); + + expect(exception, isNotNull); + expect( + exception, + isA(), + ); + }, + ); + + testWidgets( + 'should not throw MissingFlutterQuillLocalizationException if the delegate is provided', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: FlutterQuillLocalizationsWidget( + child: Builder( + builder: (context) => Text(context.loc.font), + ), + ), + ), + ); + + final exception = tester.takeException(); + + expect(exception, isNull); + expect( + exception, + isNot(isA()), + ); + }, + ); }); }