Merge branch 'master' of https://github.com/Alspb/flutter-quill into performance_optimization

pull/1964/head
Alspb 10 months ago
commit 7af204a17d
  1. 2
      .github/ISSUE_TEMPLATE/3_question.yml
  2. 1
      .github/ISSUE_TEMPLATE/config.yml
  3. 20
      .github/workflows/publish.yml
  4. 238
      CHANGELOG.md
  5. 2
      CHANGELOG_DATA.json
  6. 37
      CONTRIBUTING.md
  7. 175
      README.md
  8. 238
      dart_quill_delta/CHANGELOG.md
  9. 2
      dart_quill_delta/pubspec.yaml
  10. 13
      doc/configurations/custom_buttons.md
  11. 10
      doc/configurations/font_size.md
  12. 19
      doc/configurations/localizations_setup.md
  13. 16
      doc/configurations/using_custom_app_widget.md
  14. 24
      doc/custom_embed_blocks.md
  15. 9
      doc/custom_toolbar.md
  16. 24
      doc/development_notes.md
  17. 55
      doc/todo.md
  18. 82
      doc/translation.md
  19. 12
      example/.metadata
  20. 2
      example/analysis_options.yaml
  21. BIN
      example/assets/fonts/SF-Pro-Display-Regular.otf
  22. 3
      example/lib/gen/fonts.gen.dart
  23. 5
      example/lib/main.dart
  24. 2
      example/lib/screens/home/widgets/home_screen.dart
  25. 37
      example/lib/screens/quill/my_quill_editor.dart
  26. 21
      example/lib/screens/quill/my_quill_toolbar.dart
  27. 2
      example/lib/screens/quill/quill_screen.dart
  28. 8
      example/lib/screens/quill/samples/quill_videos_sample.dart
  29. 5
      example/pubspec.yaml
  30. 27
      example/web/index.html
  31. 2
      example/web/manifest.json
  32. 238
      flutter_quill_extensions/CHANGELOG.md
  33. 170
      flutter_quill_extensions/README.md
  34. 75
      flutter_quill_extensions/lib/embeds/table/editor/table_cell_embed.dart
  35. 234
      flutter_quill_extensions/lib/embeds/table/editor/table_embed.dart
  36. 85
      flutter_quill_extensions/lib/embeds/table/editor/table_models.dart
  37. 113
      flutter_quill_extensions/lib/embeds/table/toolbar/table_button.dart
  38. 2
      flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart
  39. 28
      flutter_quill_extensions/lib/embeds/video/toolbar/video_button.dart
  40. 18
      flutter_quill_extensions/lib/embeds/widgets/video_app.dart
  41. 99
      flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart
  42. 27
      flutter_quill_extensions/lib/flutter_quill_embeds.dart
  43. 23
      flutter_quill_extensions/lib/flutter_quill_extensions.dart
  44. 27
      flutter_quill_extensions/lib/models/config/table/table_configurations.dart
  45. 7
      flutter_quill_extensions/lib/models/config/video/editor/video_configurations.dart
  46. 19
      flutter_quill_extensions/lib/models/config/video/editor/youtube_video_support_mode.dart
  47. 177
      flutter_quill_extensions/lib/services/clipboard/super_clipboard_service.dart
  48. 2
      flutter_quill_extensions/lib/utils/patterns.dart
  49. 85
      flutter_quill_extensions/lib/utils/quill_table_utils.dart
  50. 11
      flutter_quill_extensions/pubspec.yaml
  51. 238
      flutter_quill_test/CHANGELOG.md
  52. 2
      flutter_quill_test/pubspec.yaml
  53. 19
      lib/src/l10n/extensions/localizations.dart
  54. 12
      lib/src/l10n/generated/quill_localizations.dart
  55. 6
      lib/src/l10n/generated/quill_localizations_ar.dart
  56. 6
      lib/src/l10n/generated/quill_localizations_bg.dart
  57. 6
      lib/src/l10n/generated/quill_localizations_bn.dart
  58. 6
      lib/src/l10n/generated/quill_localizations_cs.dart
  59. 6
      lib/src/l10n/generated/quill_localizations_da.dart
  60. 6
      lib/src/l10n/generated/quill_localizations_de.dart
  61. 6
      lib/src/l10n/generated/quill_localizations_en.dart
  62. 6
      lib/src/l10n/generated/quill_localizations_es.dart
  63. 6
      lib/src/l10n/generated/quill_localizations_fa.dart
  64. 6
      lib/src/l10n/generated/quill_localizations_fr.dart
  65. 6
      lib/src/l10n/generated/quill_localizations_he.dart
  66. 6
      lib/src/l10n/generated/quill_localizations_hi.dart
  67. 6
      lib/src/l10n/generated/quill_localizations_id.dart
  68. 6
      lib/src/l10n/generated/quill_localizations_it.dart
  69. 6
      lib/src/l10n/generated/quill_localizations_ja.dart
  70. 142
      lib/src/l10n/generated/quill_localizations_ko.dart
  71. 6
      lib/src/l10n/generated/quill_localizations_ku.dart
  72. 6
      lib/src/l10n/generated/quill_localizations_ms.dart
  73. 6
      lib/src/l10n/generated/quill_localizations_ne.dart
  74. 6
      lib/src/l10n/generated/quill_localizations_nl.dart
  75. 6
      lib/src/l10n/generated/quill_localizations_no.dart
  76. 6
      lib/src/l10n/generated/quill_localizations_pl.dart
  77. 6
      lib/src/l10n/generated/quill_localizations_pt.dart
  78. 6
      lib/src/l10n/generated/quill_localizations_ro.dart
  79. 6
      lib/src/l10n/generated/quill_localizations_ru.dart
  80. 6
      lib/src/l10n/generated/quill_localizations_sk.dart
  81. 6
      lib/src/l10n/generated/quill_localizations_sr.dart
  82. 6
      lib/src/l10n/generated/quill_localizations_sv.dart
  83. 6
      lib/src/l10n/generated/quill_localizations_sw.dart
  84. 6
      lib/src/l10n/generated/quill_localizations_tk.dart
  85. 6
      lib/src/l10n/generated/quill_localizations_tr.dart
  86. 6
      lib/src/l10n/generated/quill_localizations_uk.dart
  87. 6
      lib/src/l10n/generated/quill_localizations_ur.dart
  88. 6
      lib/src/l10n/generated/quill_localizations_vi.dart
  89. 6
      lib/src/l10n/generated/quill_localizations_zh.dart
  90. 7
      lib/src/l10n/quill_en.arb
  91. 128
      lib/src/l10n/quill_ko.arb
  92. 178
      lib/src/l10n/untranslated.json
  93. 5
      lib/src/l10n/widgets/localizations.dart
  94. 8
      lib/src/models/config/quill_controller_configurations.dart
  95. 5
      lib/src/models/config/toolbar/buttons/search_configurations.dart
  96. 18
      lib/src/models/config/toolbar/simple_toolbar_configurations.dart
  97. 61
      lib/src/models/documents/delta_x.dart
  98. 34
      lib/src/models/documents/document.dart
  99. 6
      lib/src/models/rules/insert.dart
  100. 2
      lib/src/packages/quill_markdown/markdown_to_delta.dart
  101. Some files were not shown because too many files have changed in this diff Show More

@ -6,7 +6,7 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | 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 - type: checkboxes
attributes: attributes:

@ -0,0 +1 @@
blank_issues_enabled: false

@ -74,25 +74,11 @@ jobs:
id: release_tag id: release_tag
run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT
- name: 📑 Fetch release notes from Github API - name: 📑 Fetch release notes from Github API and create a required file by the next step
id: fetch-release-notes-request run: dart ./scripts/create_version_content_from_github_release.dart "${{ github.repository }}" "${{ steps.release_tag.outputs.tag }}"
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: 📝 Update version and CHANGELOG for all the packages - 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 - name: 💾 Commit updated version and CHANGELOG
id: auto-commit-action id: auto-commit-action

@ -4,6 +4,244 @@
All notable changes to this project will be documented in this file. 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)
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b)
</details>
<details>
<summary>Improved</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04)
</details>
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:
<details>
<summary>Markdown File Content</summary>
```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
```
</details>
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e)
</details>
<details>
<summary>After</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788)
</details>
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 ## 9.3.21
* fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898

File diff suppressed because one or more lines are too long

@ -2,19 +2,26 @@
First of all, we would like to thank you for your time and efforts on this project, we appreciate it 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 ## 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 - [Flutter SDK](https://docs.flutter.dev/get-started/install), which can be installed by following the instructions the
- [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) 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 🧪 ## Test your changes 🧪
@ -22,8 +29,10 @@ Make sure you have the [Requirement](#requirements) installed and configured cor
To test your changes: 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` 1. Go to the [Example project](./example/) in [main.dart](./example/lib/main.dart) and run the project either by using
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 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 ## Steps to contributing
@ -35,11 +44,16 @@ 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 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` 5. Create a new git branch and switch to it using `git checkout -b`
6. Make your changes 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...) 7. If you are working on changes that depend on different libraries in the same repo, then in that directory
to `pubspec_overrides.yaml` which will be ignored by `.gitignore` and will be used by dart pub to override the libraries 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 cp pubspec_overrides.yaml.disabled pubspec_overrides.yaml
``` ```
@ -66,6 +80,7 @@ forked repository. You will find an option to send the pull request, you can als
open the [Pull Requests](https://github.com/singerdmx/flutter-quill) tab and send new pull request 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: 12. Now, wait for the review, and we might ask you to make more changes, then run:
``` ```
git add . git add .
git commit -m "Your new commit message" git commit -m "Your new commit message"

@ -1,7 +1,7 @@
# Flutter Quill # Flutter Quill
<p align="center" style="background-color:#282C34"> <p align="center" style="background-color:#282C34">
<img src="https://user-images.githubusercontent.com/10923085/119221946-2de89000-baf2-11eb-8285-68168a78c658.png" width="600px"> <img src="https://user-images.githubusercontent.com/10923085/119221946-2de89000-baf2-11eb-8285-68168a78c658.png" width="600px" alt="Flutter Quill">
</p> </p>
<h1 align="center">A rich text editor for Flutter</h1> <h1 align="center">A rich text editor for Flutter</h1>
@ -12,14 +12,23 @@
[![Watch on GitHub][github-forks-badge]][github-forks-link] [![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-badge]: https://img.shields.io/github/license/singerdmx/flutter-quill.svg?style=for-the-badge
[license-link]: ./LICENSE [license-link]: ./LICENSE
[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
[prs-link]: https://github.com/singerdmx/flutter-quill/issues [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-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-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-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-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-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 [github-forks-link]: https://github.com/singerdmx/flutter-quill/network/members
--- ---
@ -34,30 +43,31 @@ to take a detailed walkthrough of the code base.
You can join our [Slack Group] for discussion. You can join our [Slack Group] for discussion.
> If you are viewing this page from [pub.dev](https://pub.dev/) page, then you > 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 - [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
<details> <details>
<summary>Tap to show/hide screenshots</summary> <summary>Tap to show/hide screenshots</summary>
@ -71,7 +81,7 @@ might have some issues with opening some links, open it in the GitHub repo inste
</details> </details>
## Installation ## 📦 Installation
```yaml ```yaml
dependencies: dependencies:
@ -84,27 +94,31 @@ dependencies:
dependencies: dependencies:
flutter_quill: flutter_quill:
git: https://github.com/singerdmx/flutter-quill.git git: https://github.com/singerdmx/flutter-quill.git
ref: v<latest-version-here>
``` ```
> Using the latest version and reporting any issues you encounter on GitHub will greatly contribute to the improvement of the library. > Using the latest version and reporting any issues you encounter on GitHub will greatly contribute to the improvement
> 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! > 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 use the following plugins: Before using the package, we must inform you the package uses the following plugins:
```
url_launcher
flutter_keyboard_visibility
device_info_plus
super_clipboard
```
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. 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.
The minSdkVersion is `23` as `super_clipboard` requires it All of them don't require any platform-specific setup.
## Usage > 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
First, you need to instantiate a controller 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. Check out [Sample Page] for more advanced usage.
## Migration ## 🔄 Migration
Starting from version `8.0.0` Starting from version `8.0.0`
We have added [Migration Guide](/doc/migration.md) for migration from different versions 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/) This library uses [Quill Delta](https://quilljs.com/docs/delta/)
to represent the document content. to represent the document content.
@ -180,7 +195,7 @@ final json = jsonDecode(r'{"insert":"hello\n"}');
_controller.document = Document.fromJson(json); _controller.document = Document.fromJson(json);
``` ```
### Links ### 🔗 Links
- [Quill Delta](https://quilljs.com/docs/delta/) - [Quill Delta](https://quilljs.com/docs/delta/)
- [Quill Delta Formats](https://quilljs.com/docs/formats) - [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 Interactive Playground](https://quilljs.com/playground/)
- [Quill JS GitHub repo](https://github.com/quilljs/quill) - [Quill JS GitHub repo](https://github.com/quilljs/quill)
## Configurations ## Configurations
The `QuillToolbar` and `QuillEditor` widgets let you customize a lot of things The `QuillToolbar` and `QuillEditor` widgets let you customize a lot of things
[Sample Page] provides sample code for advanced usage and configuration. [Sample Page] provides sample code for advanced usage and configuration.
### Links ### 🔗 Links
- [Using Custom App Widget](./doc/configurations/using_custom_app_widget.md) - [Using Custom App Widget](./doc/configurations/using_custom_app_widget.md)
- [Localizations Setup](./doc/configurations/localizations_setup.md) - [Localizations Setup](./doc/configurations/localizations_setup.md)
- [Font Size](./doc/configurations/font_size.md) - [Font Size](./doc/configurations/font_size.md)
- [Font Family](#font-family) - [Font Family](#font-family)
- [Custom Toolbar buttons](./doc/configurations/custom_buttons.md) - [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`. ## 📦 Embed Blocks
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 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 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 Embed Blocks](./doc/custom_embed_blocks.md)
- [Custom Toolbar](./doc/custom_toolbar.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 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. 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: 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 1. Using [quill_html_converter](./quill_html_converter/) to convert to HTML, the package can convert the Quill delta to
(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 HTML well
1. Another option is to use (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 [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document
to HTML. to HTML.
This package has full support for all Quill operations—including images, videos, formulas, 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. 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) It is a complete Dart part of the popular and
mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html)
Typescript/Javascript package. Typescript/Javascript package.
this package doesn't convert the HTML back to Quill Delta as far as we know 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.<br>
> 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 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 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... - Special thanks to everyone who has contributed to this project...
<a href="https://github.com/singerdmx/flutter-quill/graphs/contributors"> <a href="https://github.com/singerdmx/flutter-quill/graphs/contributors">
<img src="https://contrib.rocks/image?repo=singerdmx/flutter-quill" /> <img src="https://contrib.rocks/image?repo=singerdmx/flutter-quill" alt="Contributors"/>
</a> </a>
<br> <br>
@ -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 and contributors who put time and effort into everything including making all the libraries, tools, and the
information we rely on information we rely on
- We are incredibly grateful to many individuals and organizations who have played a - 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 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. contributors, and the creators of the open-source tools we rely on.
We welcome all contributions! We welcome all contributions!
Please follow these guidelines when contributing to the project. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. <br> Please follow these guidelines when contributing to the project.
See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
<br>
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 [Quill]: https://quilljs.com/docs/formats
[Flutter]: https://github.com/flutter/flutter [Flutter]: https://github.com/flutter/flutter
[FlutterQuill]: https://pub.dev/packages/flutter_quill [FlutterQuill]: https://pub.dev/packages/flutter_quill
[FlutterQuill Extensions]: https://pub.dev/packages/flutter_quill_extensions [FlutterQuill Extensions]: https://pub.dev/packages/flutter_quill_extensions
[ReactQuill]: https://github.com/zenoamaro/react-quill [ReactQuill]: https://github.com/zenoamaro/react-quill
[Youtube Playlist]: https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2 [Youtube Playlist]: https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2
[Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g [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 [Sample Page]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/screens/quill/quill_screen.dart

@ -4,6 +4,244 @@
All notable changes to this project will be documented in this file. 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)
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b)
</details>
<details>
<summary>Improved</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04)
</details>
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:
<details>
<summary>Markdown File Content</summary>
```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
```
</details>
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e)
</details>
<details>
<summary>After</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788)
</details>
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 ## 9.3.21
* fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898

@ -1,6 +1,6 @@
name: dart_quill_delta name: dart_quill_delta
description: A port of quill-js-delta from typescript to dart 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/ homepage: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/
repository: 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/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -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 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: Each `QuillCustomButton` is used as part of the `customButtons` option as follows:
```dart ```dart
QuillToolbar( QuillToolbar.simple(
configurations: QuillToolbarConfigurations( configurations: QuillSimpleToolbarConfigurations(
customButtons: [ customButtons: [
QuillToolbarCustomButtonOptions( QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.ac_unit), icon: const Icon(Icons.ac_unit),

@ -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<String, String>` 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<String, String>` consisting of a `String` title for the font size and a `String` value for the font size.
Example:
```dart ```dart
fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'} fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'}

@ -1,7 +1,7 @@
# Localizations Setup # 🌍 Localizations Setup
in addition to the required delegates mentioned above in [Using custom app widget](./using_custom_app_widget.md)
In addition to the required delegates mentioned above in [Using custom app widget](./using_custom_app_widget.md), which are:
which are:
```dart ```dart
localizationsDelegates: const [ localizationsDelegates: const [
DefaultCupertinoLocalizations.delegate, DefaultCupertinoLocalizations.delegate,
@ -9,17 +9,16 @@ localizationsDelegates: const [
DefaultWidgetsLocalizations.delegate, 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 ```dart
// Required localizations delegates ... // Required localizations delegates ...
FlutterQuillLocalizations.delegate FlutterQuillLocalizations.delegate
``` ```
To offer the default localizations. **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`.
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
There are additional notes in the [Translation](../translation.md) section 📄 For additional notes, refer to the [Translation](../translation.md) section.

@ -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 1. Default Material Localizations delegate
2. Default Cupertino Localizations delegate 2. Default Cupertino Localizations delegate
3. Default Widgets Localizations delegate 3. Default Widgets Localizations delegate
You don't need to include those since they are defined by default 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:
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:
```dart ```dart
localizationsDelegates: const [ 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 ```dart
localizationsDelegates: const [ 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 [FluentUI]: https://pub.dev/packages/fluent_ui

@ -1,12 +1,16 @@
# Custom Embed Blocks # 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: 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 ```dart
class NotesBlockEmbed extends CustomBlockEmbed { 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! Don't forget to add this method to the `QuillEditor` after that!
```dart ```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 ```dart
Future<void> _addEditNote(BuildContext context, {Document? document}) async { Future<void> _addEditNote(BuildContext context, {Document? document}) async {
@ -116,11 +125,12 @@ Future<void> _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!
<p float="left"> <p float="left">
<img width="400" alt="1" src="https://i.imgur.com/yBTPYeS.png"> <img width="400" alt="1" src="https://i.imgur.com/yBTPYeS.png">
</p> </p>
> 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) > 2. For more details, check out [this YouTube video](https://youtu.be/pI5p5j7cfHc)

@ -1,13 +1,13 @@
# Custom Toolbar # 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` You can use the `QuillBaseToolbar` which is the base for the `QuillToolbar`
Example: Example:
```dart ```dart
QuillToolbar( QuillToolbar.simple(
configurations: const QuillToolbarConfigurations( configurations: const QuillSimpleToolbarConfigurations(
buttonOptions: QuillToolbarButtonOptions( buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions( base: QuillToolbarBaseButtonOptions(
globalIconSize: 20, 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

@ -1,10 +1,22 @@
# Development notes # 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 - When updating the translations or localizations in the app, please take a look at the [Translation](./translation.md)
- 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: page as it has important notes to work.
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 If you also add a feature that adds new localizations, then you need it
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 to the instructions of it in order for the translations to take effect
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. - We use the same package version and `CHANGELOG.md` for all the packages, for
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. 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 the script will be used the CI and no need to run it manually

@ -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

@ -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 ```dart
QuillToolbar.simple( 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')` Currently, translations are available for these 41 locales:
* `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')`
#### 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 The translation files are located in the [l10n](../lib/src/l10n/) folder. Feel free to contribute your own translations.
`quill_${localName}.arb` for example `quill_de.arb`
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 <details>
<summary>Add new local</summary>
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`
``` 2. Copy the [Arb Template](../lib/src/l10n/quill_en.arb) file and paste it into your new file, replace the values with
./scripts/regenerate_translations.sh your translations
```
3. Update [Supported Locales](#supported-locales) section in this page to update the supported translations for both the
number and the list
</details>
<details>
<summary>Update existing local</summary>
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.
</details>
<br>
> 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 flutter gen-l10n
dart fix --apply ./lib/src/l10n/generated dart fix --apply ./lib/src/l10n/generated
dart format ./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` <br> > 🔧 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) <br>
> Otherwise you don't need to update it. > Otherwise you don't need to update it.
Then open a pull request so everyone can benefit from your translations! Then open a pull request so everyone can benefit from your translations!

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited. # This file should be version controlled and should not be manually edited.
version: version:
revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
channel: "stable" channel: "stable"
project_type: app project_type: app
@ -13,11 +13,11 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: linux - platform: web
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
# User provided section # User provided section

@ -11,7 +11,7 @@ linter:
annotate_overrides: true annotate_overrides: true
avoid_empty_else: true avoid_empty_else: true
avoid_escaping_inner_quotes: true avoid_escaping_inner_quotes: true
avoid_print: false avoid_print: true
avoid_redundant_argument_values: false avoid_redundant_argument_values: false
avoid_types_on_closure_parameters: true avoid_types_on_closure_parameters: true
avoid_void_async: true avoid_void_async: true

@ -10,9 +10,6 @@
class FontFamily { class FontFamily {
FontFamily._(); FontFamily._();
/// Font family: SF-UI-Display
static const String sFUIDisplay = 'SF-UI-Display';
/// Font family: ibarra-real-nova /// Font family: ibarra-real-nova
static const String ibarraRealNova = 'ibarra-real-nova'; static const String ibarraRealNova = 'ibarra-real-nova';

@ -8,6 +8,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'
GlobalWidgetsLocalizations; GlobalWidgetsLocalizations;
import 'package:flutter_quill/flutter_quill.dart' show Document; import 'package:flutter_quill/flutter_quill.dart' show Document;
import 'package:flutter_quill/translations.dart' show FlutterQuillLocalizations; import 'package:flutter_quill/translations.dart' show FlutterQuillLocalizations;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart' import 'package:hydrated_bloc/hydrated_bloc.dart'
show HydratedBloc, HydratedStorage; show HydratedBloc, HydratedStorage;
import 'package:path_provider/path_provider.dart' import 'package:path_provider/path_provider.dart'
@ -29,6 +30,7 @@ void main() async {
? HydratedStorage.webStorageDirectory ? HydratedStorage.webStorageDirectory
: await getApplicationDocumentsDirectory(), : await getApplicationDocumentsDirectory(),
); );
FlutterQuillExtensions.useSuperClipboardPlugin();
runApp(const MyApp()); runApp(const MyApp());
} }
@ -69,6 +71,9 @@ class MyApp extends StatelessWidget {
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.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, // FlutterQuillLocalizations.delegate,
], ],
supportedLocales: FlutterQuillLocalizations.supportedLocales, supportedLocales: FlutterQuillLocalizations.supportedLocales,

@ -155,7 +155,7 @@ class HomeScreen extends StatelessWidget {
), ),
); );
} catch (e) { } catch (e) {
print( debugPrint(
'Error while loading json delta file: ${e.toString()}', 'Error while loading json delta file: ${e.toString()}',
); );
scaffoldMessenger.showText( scaffoldMessenger.showText(

@ -4,11 +4,13 @@ import 'package:cached_network_image/cached_network_image.dart'
show CachedNetworkImageProvider; show CachedNetworkImageProvider;
import 'package:desktop_drop/desktop_drop.dart' show DropTarget; import 'package:desktop_drop/desktop_drop.dart' show DropTarget;
import 'package:flutter/material.dart'; 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/flutter_quill.dart';
import 'package:flutter_quill_extensions/embeds/widgets/image.dart' import 'package:flutter_quill_extensions/embeds/widgets/image.dart'
show getImageProviderByImageSource, imageFileExtensions; show getImageProviderByImageSource, imageFileExtensions;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; 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 'package:path/path.dart' as path;
import '../../extensions/scaffold_messenger.dart'; import '../../extensions/scaffold_messenger.dart';
@ -28,6 +30,7 @@ class MyQuillEditor extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final defaultTextStyle = DefaultTextStyle.of(context);
return QuillEditor( return QuillEditor(
scrollController: scrollController, scrollController: scrollController,
focusNode: focusNode, focusNode: focusNode,
@ -41,36 +44,29 @@ class MyQuillEditor extends StatelessWidget {
useTextColorForDot: true, useTextColorForDot: true,
), ),
), ),
customStyles: const DefaultStyles( customStyles: DefaultStyles(
h1: DefaultTextBlockStyle( h1: DefaultTextBlockStyle(
TextStyle( defaultTextStyle.style.copyWith(
fontSize: 32, fontSize: 32,
height: 1.15, height: 1.15,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
), ),
VerticalSpacing(16, 0), const VerticalSpacing(16, 0),
VerticalSpacing(0, 0), const VerticalSpacing(0, 0),
null, null,
), ),
sizeSmall: TextStyle(fontSize: 9), sizeSmall: defaultTextStyle.style.copyWith(fontSize: 9),
subscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.subscripts()],
),
superscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.superscripts()],
),
), ),
scrollable: true, scrollable: true,
placeholder: 'Start writting your notes...', placeholder: 'Start writing your notes...',
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
onImagePaste: (imageBytes) async { onImagePaste: (imageBytes) async {
if (isWeb()) { if (isWeb()) {
return null; return null;
} }
// We will save it to system temporary files // 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( final newPath = path.join(
io.Directory.systemTemp.path, io.Directory.systemTemp.path,
newFileName, newFileName,
@ -85,7 +81,7 @@ class MyQuillEditor extends StatelessWidget {
return null; return null;
} }
// We will save it to system temporary files // 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( final newPath = path.join(
io.Directory.systemTemp.path, io.Directory.systemTemp.path,
newFileName, 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(), TimeStampEmbedBuilderWidget(),
], ],

@ -222,25 +222,7 @@ class MyQuillToolbar extends StatelessWidget {
'35': '35.0', '35': '35.0',
'40': '40.0' '40': '40.0'
}, },
// headerStyleType: HeaderStyleType.buttons, searchButtonType: SearchButtonType.modern,
// 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,
// ),
// ),
// ),
// ),
//),
customButtons: [ customButtons: [
QuillToolbarCustomButtonOptions( QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.add_alarm_rounded), icon: const Icon(Icons.add_alarm_rounded),
@ -306,6 +288,7 @@ class MyQuillToolbar extends StatelessWidget {
: onImageInsert, : onImageInsert,
), ),
), ),
tableButtonOptions: const QuillToolbarTableButtonOptions(),
), ),
), ),
); );

@ -127,7 +127,7 @@ class _QuillScreenState extends State<QuillScreen> {
IconButton( IconButton(
tooltip: 'Print to log', tooltip: 'Print to log',
onPressed: () { onPressed: () {
print( debugPrint(
jsonEncode(_controller.document.toDelta().toJson()), jsonEncode(_controller.document.toDelta().toJson()),
); );
ScaffoldMessenger.of(context).showText( ScaffoldMessenger.of(context).showText(

@ -1,7 +1,7 @@
const quillVideosSample = [ const quillVideosSample = [
{'insert': '\n'}, {'insert': '\n'},
{ {
'insert': {'video': 'https://youtu.be/xz6_AlJkDPA'}, 'insert': {'video': 'https://youtu.be/fq4N0hgOWzU?si=SqoY_bAZYnjCkUvn'},
'attributes': { 'attributes': {
'width': '300', 'width': '300',
'height': '300', 'height': '300',
@ -10,10 +10,8 @@ const quillVideosSample = [
}, },
{'insert': '\n'}, {'insert': '\n'},
{'insert': '\n'}, {'insert': '\n'},
{'insert': 'And this is just a youtube video'},
{'insert': '\n'}, {'insert': '\n'},
{ {'insert': 'The video above is a Youtube video.'},
'insert': 'This sample is not complete.', {'insert': '\n'},
},
{'insert': '\n'}, {'insert': '\n'},
]; ];

@ -19,7 +19,7 @@ dependencies:
flutter_quill_test: ^9.3.4 flutter_quill_test: ^9.3.4
quill_html_converter: ^9.3.4 quill_html_converter: ^9.3.4
quill_pdf_converter: ^9.3.4 quill_pdf_converter: ^9.3.4
# Normal packages # Dart Packages
path: ^1.8.3 path: ^1.8.3
equatable: ^2.0.5 equatable: ^2.0.5
cross_file: ^0.3.4 cross_file: ^0.3.4
@ -108,8 +108,5 @@ flutter:
- family: roboto-mono - family: roboto-mono
fonts: fonts:
- asset: assets/fonts/RobotoMono-Regular.ttf - asset: assets/fonts/RobotoMono-Regular.ttf
- family: SF-UI-Display
fonts:
- asset: assets/fonts/SF-Pro-Display-Regular.otf
flutter_gen: flutter_gen:

@ -23,13 +23,13 @@
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example"> <meta name="apple-mobile-web-app-title" content="Flutter Quill Example">
<link rel="apple-touch-icon" href="icons/Icon-192.png"> <link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png"/>
<title>example</title> <title>Flutter Quill Example</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<!-- Croppie for `image_cropper` plugin --> <!-- Croppie for `image_cropper` plugin -->
@ -39,29 +39,8 @@
<!-- Requirement by flutter_inappwebview, for more info: https://pub.dev/packages/flutter_inappwebview#installation --> <!-- Requirement by flutter_inappwebview, for more info: https://pub.dev/packages/flutter_inappwebview#installation -->
<script type="application/javascript" src="/assets/packages/flutter_inappwebview_web/assets/web/web_support.js" defer></script> <script type="application/javascript" src="/assets/packages/flutter_inappwebview_web/assets/web/web_support.js" defer></script>
<script>
// The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head> </head>
<body> <body>
<script> <script src="flutter_bootstrap.js" async></script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body> </body>
</html> </html>

@ -1,5 +1,5 @@
{ {
"name": "example", "name": "Flutter Quill Example",
"short_name": "example", "short_name": "example",
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",

@ -4,6 +4,244 @@
All notable changes to this project will be documented in this file. 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)
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b)
</details>
<details>
<summary>Improved</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04)
</details>
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:
<details>
<summary>Markdown File Content</summary>
```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
```
</details>
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e)
</details>
<details>
<summary>After</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788)
</details>
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 ## 9.3.21
* fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898

@ -1,35 +1,39 @@
# Flutter Quill Extensions # Flutter Quill Extensions
An extensions for [flutter_quill](https://pub.dev/packages/flutter_quill) 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) - [Flutter Quill Extensions](#flutter-quill-extensions)
- [Table of Contents](#table-of-contents) - [📚 Table of Contents](#-table-of-contents)
- [About](#about) - [📝 About](#-about)
- [Installation](#installation) - [📦 Installation](#-installation)
- [Platform Specific Configurations](#platform-specific-configurations) - [🛠 Platform Specific Configurations](#-platform-specific-configurations)
- [Usage](#usage) - [🚀 Usage](#-usage)
- [Embed Blocks](#embed-blocks) - [ Configurations](#-configurations)
- [Element properties](#element-properties) - [📦 Embed Blocks](#-embed-blocks)
- [Custom Element properties](#custom-element-properties) - [🔍 Element properties](#-element-properties)
- [Image Assets](#image-assets) - [🔧 Custom Element properties](#-custom-element-properties)
- [Drag and drop feature](#drag-and-drop-feature) - [🖼 Image Assets](#-image-assets)
- [Features](#features) - [🎯 Drag and drop feature](#-drag-and-drop-feature)
- [Contributing](#contributing) - [💡 Features](#-features)
- [Acknowledgments](#acknowledgments) - [🤝 Contributing](#-contributing)
- [🌟 Acknowledgments](#-acknowledgments)
## About ## 📝 About
Flutter Quill is a rich editor text. It'd allow you to customize a lot of things, 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 <br> it has custom embed builders that allow you to render custom widgets in the editor <br>
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 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
@ -47,31 +51,68 @@ dependencies:
flutter_quill_extensions: flutter_quill_extensions:
git: https://github.com/singerdmx/flutter-quill.git git: https://github.com/singerdmx/flutter-quill.git
path: flutter_quill_extensions path: flutter_quill_extensions
ref: v<latest-version-here>
``` ```
## Platform Specific Configurations ## 🛠 Platform Specific Configurations
> The package uses the following plugins:
> 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 1. [`gal`](https://github.com/natsuk4ze/) plugin to save images.
> See <https://github.com/natsuk4ze/gal#-get-started> to add the needed lines. For this to work, you need to add the appropriate configurations
> See <https://github.com/natsuk4ze/gal#-get-started> 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 2. [`image_picker`](https://pub.dev/packages/image_picker) plugin for picking images so please make sure to follow the
> instructions
> 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 3. [`youtube_player_flutter`](https://pub.dev/packages/youtube_player_flutter) plugin which
> 4. For loading the image from the internet, we need the internet permission uses [`flutter_inappwebview`](https://pub.dev/packages/flutter_inappwebview) which has a requirement on web, please
> 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. follow this [link](https://pub.dev/packages/flutter_inappwebview#installation) to set up the support for web
> 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 4. [`image_picker`](https://pub.dev/packages/image_picker) which also
> requires some configurations, follow this [link](https://pub.dev/packages/image_picker#installation).
> The extension package also uses [image_picker](https://pub.dev/packages/image_picker) which also It's needed for
> 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) <br> 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 <br> <br>
> **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. <br> <br>
> **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
> >
## Usage ## 🚀 Usage
Start using the package in 3 steps:
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
Before starting to use this package you must follow the [setup](#installation) ```dart
FlutterQuillExtensions.useSuperClipboardPlugin();
```
`super_clipboard` is comprehensive plugin that provides many clipboard features for reading and writing of rich text,
images and other formats.
Set the `embedBuilders` and `embedToolbar` params in configurations of `QuillEditor` and `QuillToolbar` with the 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. values provided by this repository.
**Quill Toolbar**: **Quill Toolbar**:
@ -94,15 +135,21 @@ Expanded(
) )
``` ```
## Embed Blocks ## ⚙ Configurations
### 📦 Embed Blocks
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 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 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` and it supports only `width`, `height`, `margin`
```json ```json
@ -116,7 +163,7 @@ and it supports only `width`, `height`, `margin`
} }
``` ```
### Custom Element properties ### 🔧 Custom Element properties
Doesn't apply to official Quill JS Doesn't apply to official Quill JS
@ -135,7 +182,7 @@ Define flutterAlignment` as follows:
This works for all platforms except Web 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: If you want to use image assets in the Quill Editor, you need to make sure your assets folder is `assets` otherwise:
@ -155,12 +202,16 @@ 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 ### 🎯 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:
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: 2. Add the dependency in your `pubspec.yaml` using the following command:
```yaml ```yaml
@ -170,7 +221,8 @@ Currently, the drag-and-drop feature is not officially supported, but you can ac
```dart ```dart
import 'package:desktop_drop/desktop_drop.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 ```dart
import 'package:flutter_quill_extensions/flutter_quill_extensions.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 4. Implement the `_onDragDone`, it depends on your use case but this is just a simple example
```dart ```dart
const List<String> imageFileExtensions = [ const List<String> imageFileExtensions = [
'.jpeg', '.jpeg',
@ -230,24 +283,25 @@ OnDragDoneCallback get _onDragDone {
} }
``` ```
## Features ## 💡 Features
```markdown ```markdown
## Features ## Features
— Easy to use and customizable — 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 - Useful utilities and widgets
- Handle different errors
``` ```
## Contributing ## 🤝 Contributing
We welcome contributions! 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 [Flutter Team](https://flutter.dev/)
- Thanks to the welcoming community, the volunteers who helped along the journey, developers, contributors - Thanks to the welcoming community, the volunteers who helped along the journey, developers, contributors

@ -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<TableCellWidget> createState() => _TableCellWidgetState();
}
class _TableCellWidgetState extends State<TableCellWidget> {
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,
);
},
),
);
}
}

@ -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<String, dynamic> tableData;
@override
State<TableWidget> createState() => _TableWidgetState();
}
class _TableWidgetState extends State<TableWidget> {
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 = <String, String>{};
_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<TableOperation>(
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<TableRow> _buildTableRows() {
final rows = <TableRow>[];
_tableModel.rows.forEach((rowId, rowModel) {
final rowCells = <Widget>[];
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;
}
}

@ -0,0 +1,85 @@
class TableModel {
TableModel({required this.columns, required this.rows});
factory TableModel.fromMap(Map<String, dynamic> json) {
return TableModel(
columns: (json['columns'] as Map<String, dynamic>).map(
(key, value) => MapEntry(
key,
ColumnModel.fromMap(
value,
),
),
),
rows: (json['rows'] as Map<String, dynamic>).map(
(key, value) => MapEntry(
key,
RowModel.fromMap(
value,
),
),
),
);
}
Map<String, ColumnModel> columns;
Map<String, RowModel> rows;
Map<String, dynamic> 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<String, dynamic> json) {
return ColumnModel(
id: json['id'],
position: json['position'],
);
}
String id;
int position;
Map<String, dynamic> 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<String, dynamic> json) {
return RowModel(
id: json['id'],
cells: Map<String, String>.from(json['cells']),
);
}
String id;
Map<String, String> cells;
Map<String, dynamic> toMap() {
return {
'id': id,
'cells': cells,
};
}
}

@ -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<void> _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);
}
},
);
}
}

@ -37,6 +37,7 @@ class QuillEditorVideoEmbedBuilder extends EmbedBuilder {
return YoutubeVideoApp( return YoutubeVideoApp(
videoUrl: videoUrl, videoUrl: videoUrl,
readOnly: readOnly, readOnly: readOnly,
youtubeVideoSupportMode: configurations.youtubeVideoSupportMode,
); );
} }
final ((elementSize), margin, alignment) = getElementAttributes( final ((elementSize), margin, alignment) = getElementAttributes(
@ -53,7 +54,6 @@ class QuillEditorVideoEmbedBuilder extends EmbedBuilder {
alignment: alignment, alignment: alignment,
child: VideoApp( child: VideoApp(
videoUrl: videoUrl, videoUrl: videoUrl,
context: context,
readOnly: readOnly, readOnly: readOnly,
onVideoInit: configurations.onVideoInit, onVideoInit: configurations.onVideoInit,
), ),

@ -9,6 +9,8 @@ import '../../others/image_video_utils.dart';
import '../video.dart'; import '../video.dart';
import 'select_video_source.dart'; import 'select_video_source.dart';
// TODO: Add custom callback to validate the video link input
class QuillToolbarVideoButton extends StatelessWidget { class QuillToolbarVideoButton extends StatelessWidget {
const QuillToolbarVideoButton({ const QuillToolbarVideoButton({
required this.controller, required this.controller,
@ -72,11 +74,6 @@ class QuillToolbarVideoButton extends StatelessWidget {
final childBuilder = final childBuilder =
options.childBuilder ?? baseButtonExtraOptions(context)?.childBuilder; options.childBuilder ?? baseButtonExtraOptions(context)?.childBuilder;
// final iconColor =
// iconTheme?.iconUnselectedFillColor ?? theme.iconTheme.color;
// final iconFillColor = iconTheme?.iconUnselectedFillColor ??
// (options.fillColor ?? theme.canvasColor);
if (childBuilder != null) { if (childBuilder != null) {
return childBuilder( return childBuilder(
QuillToolbarVideoButtonOptions( QuillToolbarVideoButtonOptions(
@ -150,18 +147,6 @@ class QuillToolbarVideoButton extends StatelessWidget {
.onVideoInsertCallback(videoUrl, controller); .onVideoInsertCallback(videoUrl, controller);
await options.videoConfigurations.onVideoInsertedCallback?.call(videoUrl); 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<String?> _typeLink(BuildContext context) async { Future<String?> _typeLink(BuildContext context) async {
@ -176,13 +161,4 @@ class QuillToolbarVideoButton extends StatelessWidget {
); );
return value; 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);
// }
// }
} }

@ -13,14 +13,16 @@ import '../../flutter_quill_extensions.dart';
class VideoApp extends StatefulWidget { class VideoApp extends StatefulWidget {
const VideoApp({ const VideoApp({
required this.videoUrl, required this.videoUrl,
required this.context,
required this.readOnly, required this.readOnly,
@Deprecated(
'The context is no longer required and will be removed on future releases',
)
BuildContext? context,
super.key, super.key,
this.onVideoInit, this.onVideoInit,
}); });
final String videoUrl; final String videoUrl;
final BuildContext context;
final bool readOnly; final bool readOnly;
final void Function(GlobalKey videoContainerKey)? onVideoInit; final void Function(GlobalKey videoContainerKey)? onVideoInit;
@ -92,7 +94,9 @@ class VideoAppState extends State<VideoApp> {
: _controller.play(); : _controller.play();
}); });
}, },
child: Stack(alignment: Alignment.center, children: [ child: Stack(
alignment: Alignment.center,
children: [
Center( Center(
child: AspectRatio( child: AspectRatio(
aspectRatio: _controller.value.aspectRatio, aspectRatio: _controller.value.aspectRatio,
@ -106,15 +110,17 @@ class VideoAppState extends State<VideoApp> {
Icons.play_arrow, Icons.play_arrow,
size: 60, size: 60,
color: Colors.blueGrey, color: Colors.blueGrey,
)) ),
]), )
],
),
), ),
); );
} }
@override @override
void dispose() { void dispose() {
super.dispose();
_controller.dispose(); _controller.dispose();
super.dispose();
} }
} }

@ -1,64 +1,107 @@
import 'package:flutter/gestures.dart' show TapGestureRecognizer; 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: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 '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 { class YoutubeVideoApp extends StatefulWidget {
const YoutubeVideoApp({ const YoutubeVideoApp({
required this.videoUrl, required this.videoUrl,
required this.readOnly, required this.readOnly,
required this.youtubeVideoSupportMode,
super.key, super.key,
}); });
final String videoUrl; final String videoUrl;
final bool readOnly; final bool readOnly;
final YoutubeVideoSupportMode youtubeVideoSupportMode;
@override @override
YoutubeVideoAppState createState() => YoutubeVideoAppState(); YoutubeVideoAppState createState() => YoutubeVideoAppState();
} }
class YoutubeVideoAppState extends State<YoutubeVideoApp> { class YoutubeVideoAppState extends State<YoutubeVideoApp> {
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<String>? _loadYoutubeVideoByDownloadUrlFuture;
/// Null if the video URL is not a YouTube video
String? get _videoId {
return YoutubePlayer.convertUrlToId(widget.videoUrl);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final videoId = YoutubePlayer.convertUrlToId(widget.videoUrl); final videoId = _videoId;
if (videoId != null) { if (videoId == null) {
_youtubeController = YoutubePlayerController( return;
}
switch (widget.youtubeVideoSupportMode) {
case YoutubeVideoSupportMode.disabled:
break;
case YoutubeVideoSupportMode.iframeView:
_youtubeIframeController = YoutubePlayerController(
initialVideoId: videoId, initialVideoId: videoId,
flags: const YoutubePlayerFlags( flags: const YoutubePlayerFlags(
autoPlay: false, autoPlay: false,
), ),
); );
break;
case YoutubeVideoSupportMode.customPlayerWithDownloadUrl:
_loadYoutubeVideoByDownloadUrlFuture =
_loadYoutubeVideoWithVideoPlayerByVideoUrl();
break;
} }
} }
@override Future<String> _loadYoutubeVideoWithVideoPlayerByVideoUrl() async {
Widget build(BuildContext context) { final youtubeExplode = YoutubeExplode();
final defaultStyles = DefaultStyles.getInstance(context); final manifest =
final youtubeController = _youtubeController; await youtubeExplode.videos.streamsClient.getManifest(_videoId);
final streamInfo = manifest.muxed.withHighestBitrate();
final videoDownloadUri = streamInfo.url;
return videoDownloadUri.toString();
}
if (youtubeController == null) { Widget _clickableVideoLinkText({required DefaultStyles defaultStyles}) {
if (widget.readOnly) {
return RichText( return RichText(
text: TextSpan( text: TextSpan(
text: widget.videoUrl, text: widget.videoUrl,
style: defaultStyles.link, style: defaultStyles.link,
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => launchUrl( ..onTap = () => launchUrlString(widget.videoUrl),
Uri.parse(widget.videoUrl),
),
), ),
); );
} }
@override
Widget build(BuildContext context) {
final defaultStyles = DefaultStyles.getInstance(context);
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( return RichText(
text: TextSpan(text: widget.videoUrl, style: defaultStyles.link), text: TextSpan(text: widget.videoUrl, style: defaultStyles.link),
); );
} }
return YoutubePlayerBuilder( return YoutubePlayerBuilder(
player: YoutubePlayer( player: YoutubePlayer(
controller: youtubeController, controller: youtubeController,
@ -68,11 +111,33 @@ class YoutubeVideoAppState extends State<YoutubeVideoApp> {
return player; return player;
}, },
); );
case YoutubeVideoSupportMode.customPlayerWithDownloadUrl:
assert(
_loadYoutubeVideoByDownloadUrlFuture != null,
'The load youtube video future should not null for "${widget.youtubeVideoSupportMode}" mode',
);
return FutureBuilder<String>(
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,
);
},
);
}
} }
@override @override
void dispose() { void dispose() {
_youtubeController?.dispose(); _youtubeIframeController?.dispose();
super.dispose(); super.dispose();
} }
} }

@ -6,6 +6,8 @@ import 'embeds/image/editor/image_embed.dart';
import 'embeds/image/editor/image_web_embed.dart'; import 'embeds/image/editor/image_web_embed.dart';
import 'embeds/image/toolbar/image_button.dart'; import 'embeds/image/toolbar/image_button.dart';
import 'embeds/others/camera_button/camera_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_embed.dart';
import 'embeds/video/editor/video_web_embed.dart'; import 'embeds/video/editor/video_web_embed.dart';
import 'embeds/video/toolbar/video_button.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/editor/image_configurations.dart';
import 'models/config/image/toolbar/image_configurations.dart'; import 'models/config/image/toolbar/image_configurations.dart';
import 'models/config/media/media_button_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_configurations.dart';
import 'models/config/video/editor/video_web_configurations.dart'; import 'models/config/video/editor/video_web_configurations.dart';
import 'models/config/video/toolbar/video_configurations.dart'; import 'models/config/video/toolbar/video_configurations.dart';
@ -61,6 +64,7 @@ class FlutterQuillEmbeds {
QuillEditorVideoEmbedBuilder( QuillEditorVideoEmbedBuilder(
configurations: videoEmbedConfigurations, configurations: videoEmbedConfigurations,
), ),
QuillEditorTableEmbedBuilder(),
]; ];
} }
@ -117,6 +121,10 @@ class FlutterQuillEmbeds {
QuillToolbarVideoButtonOptions? videoButtonOptions = QuillToolbarVideoButtonOptions? videoButtonOptions =
const QuillToolbarVideoButtonOptions(), const QuillToolbarVideoButtonOptions(),
QuillToolbarCameraButtonOptions? cameraButtonOptions, QuillToolbarCameraButtonOptions? cameraButtonOptions,
QuillToolbarTableButtonOptions? tableButtonOptions,
@Deprecated(
'Media button has been removed, the value of this parameter will be ignored',
)
QuillToolbarMediaButtonOptions? mediaButtonOptions, QuillToolbarMediaButtonOptions? mediaButtonOptions,
}) => }) =>
[ [
@ -138,18 +146,11 @@ class FlutterQuillEmbeds {
controller: controller, controller: controller,
options: cameraButtonOptions, options: cameraButtonOptions,
), ),
// if (mediaButtonOptions != null) if (tableButtonOptions != null)
// (controller, toolbarIconSize, iconTheme, dialogTheme) => (controller, toolbarIconSize, iconTheme, dialogTheme) =>
// QuillToolbarMediaButton( QuillToolbarTableButton(
// controller: mediaButtonOptions.controller ?? controller, controller: controller,
// options: mediaButtonOptions, options: tableButtonOptions,
// ), ),
// Drop the support for formula button for now
// if (formulaButtonOptions != null)
// (controller, toolbarIconSize, iconTheme, dialogTheme) =>
// QuillToolbarFormulaButton(
// controller: formulaButtonOptions.controller ?? controller,
// options: formulaButtonOptions,
// ),
]; ];
} }

@ -1,5 +1,11 @@
library flutter_quill_extensions; 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/embed_types.dart';
export 'embeds/formula/toolbar/formula_button.dart'; export 'embeds/formula/toolbar/formula_button.dart';
export 'embeds/image/editor/image_embed.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/image/toolbar/image_button.dart';
export 'embeds/others/camera_button/camera_button.dart'; export 'embeds/others/camera_button/camera_button.dart';
export 'embeds/others/media_button/media_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/unknown/editor/unknown_embed.dart';
export 'embeds/video/editor/video_embed.dart'; export 'embeds/video/editor/video_embed.dart';
export 'embeds/video/editor/video_web_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/image/toolbar/image_configurations.dart';
export 'models/config/media/media_button_configurations.dart'; export 'models/config/media/media_button_configurations.dart';
export 'models/config/shared_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_configurations.dart';
export 'models/config/video/editor/video_web_configurations.dart'; export 'models/config/video/editor/video_web_configurations.dart';
export 'models/config/video/toolbar/video_configurations.dart'; export 'models/config/video/toolbar/video_configurations.dart';
export 'utils/utils.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());
}
}

@ -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,
});
}

@ -1,10 +1,13 @@
import 'package:flutter/widgets.dart' show GlobalKey; import 'package:flutter/widgets.dart' show GlobalKey;
import 'package:meta/meta.dart' show immutable; import 'package:meta/meta.dart' show immutable;
import 'youtube_video_support_mode.dart';
@immutable @immutable
class QuillEditorVideoEmbedConfigurations { class QuillEditorVideoEmbedConfigurations {
const QuillEditorVideoEmbedConfigurations({ const QuillEditorVideoEmbedConfigurations({
this.onVideoInit, this.onVideoInit,
this.youtubeVideoSupportMode = YoutubeVideoSupportMode.iframeView,
}); });
/// [onVideoInit] is a callback function that gets triggered when /// [onVideoInit] is a callback function that gets triggered when
@ -21,4 +24,8 @@ class QuillEditorVideoEmbedConfigurations {
/// // Customize other callback functions as needed /// // Customize other callback functions as needed
/// ``` /// ```
final void Function(GlobalKey videoContainerKey)? onVideoInit; final void Function(GlobalKey videoContainerKey)? onVideoInit;
/// Specifies how YouTube videos should be loaded if the video URL
/// is YouTube video.
final YoutubeVideoSupportMode youtubeVideoSupportMode;
} }

@ -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,
}

@ -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<bool> _canProvide({required DataFormat format}) async {
final clipboard = _getSuperClipboard();
if (clipboard == null) {
return false;
}
final reader = await clipboard.read();
return reader.canProvide(format);
}
Future<Uint8List> _provideFileAsBytes({
required SimpleFileFormat format,
}) async {
final clipboard = _getSuperClipboardOrThrow();
final reader = await clipboard.read();
final completer = Completer<Uint8List>();
reader.getFile(
format,
(file) async {
final bytes = await file.readAll();
completer.complete(bytes);
},
onError: completer.completeError,
);
final bytes = await completer.future;
return bytes;
}
Future<String> _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<String?> _provideSimpleValueFormatAsString({
required SimpleValueFormat<String> format,
}) async {
final clipboard = _getSuperClipboardOrThrow();
final reader = await clipboard.read();
final value = await reader.readValue<String>(format);
return value;
}
@override
Future<bool> canProvideHtmlText() {
return _canProvide(format: Formats.htmlText);
}
@override
Future<String?> getHtmlText() {
return _provideSimpleValueFormatAsString(format: Formats.htmlText);
}
@override
Future<bool> canProvideHtmlTextFromFile() {
return _canProvide(format: Formats.htmlFile);
}
@override
Future<String?> getHtmlTextFromFile() {
return _provideFileAsString(format: Formats.htmlFile);
}
@override
Future<bool> canProvideMarkdownText() async {
// Formats.markdownText or Formats.mdText does not exist yet in super_clipboard
return false;
}
@override
Future<String?> 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<bool> canProvideMarkdownTextFromFile() async {
// Formats.md is for markdown files
return _canProvide(format: Formats.md);
}
@override
Future<String?> getMarkdownTextFromFile() async {
// Formats.md is for markdown files
return _provideFileAsString(format: Formats.md);
}
@override
Future<bool> canProvidePlainText() {
return _canProvide(format: Formats.plainText);
}
@override
Future<String?> 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<bool> 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<Uint8List> getImageFileAsBytes() async {
final canProvidePngFile = await _canProvide(format: Formats.png);
if (canProvidePngFile) {
return _provideFileAsBytes(format: Formats.png);
}
return _provideFileAsBytes(format: Formats.jpeg);
}
@override
Future<bool> canProvideGifFile() {
return _canProvide(format: Formats.gif);
}
@override
Future<Uint8List> getGifFileAsBytes() {
return _provideFileAsBytes(format: Formats.gif);
}
@override
Future<bool> canPaste() async {
final clipboard = _getSuperClipboard();
if (clipboard == null) {
return false;
}
final reader = await clipboard.read();
final availablePlatformFormats = reader.platformFormats;
return availablePlatformFormats.isNotEmpty;
}
}

@ -1,5 +1,5 @@
RegExp base64RegExp = RegExp( 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( final imageRegExp = RegExp(

@ -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<String, dynamic> _createTableData(int rows, int columns) {
// Crear el mapa para las columnas
final columnsData = <String, dynamic>{};
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 = <String, dynamic>{};
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 = <String, dynamic>{
'columns': columnsData,
'rows': rowsData,
};
return tableData;
}

@ -1,6 +1,6 @@
name: flutter_quill_extensions name: flutter_quill_extensions
description: Embed extensions for flutter_quill including image, video, formula and etc. 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/ homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/
repository: 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/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/
@ -28,19 +28,20 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# Normal packages # Dart Packages
http: ^1.2.0 http: ^1.2.0
path: ^1.8.3 path: ^1.8.3
meta: ^1.10.0 meta: ^1.10.0
universal_html: ^2.2.4 universal_html: ^2.2.4
cross_file: ^0.3.3+6 cross_file: ^0.3.3+6
flutter_quill: ^9.3.12 flutter_quill: ^9.5.1
photo_view: ^0.14.0 photo_view: ^0.15.0
youtube_explode_dart: ^2.2.1
# Plugins # Plugins
video_player: ^2.8.1 video_player: ^2.8.1
youtube_player_flutter: ^9.0.0 youtube_player_flutter: ^9.0.1
url_launcher: ^6.2.1 url_launcher: ^6.2.1
super_clipboard: ^0.8.15 super_clipboard: ^0.8.15
gal: ^2.3.0 gal: ^2.3.0

@ -4,6 +4,244 @@
All notable changes to this project will be documented in this file. 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)
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/9b40ad03-717f-4518-95f1-8d9cad773b2b)
</details>
<details>
<summary>Improved</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/e581733d-63fa-4984-9c41-4a325a0a0c04)
</details>
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:
<details>
<summary>Markdown File Content</summary>
```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
```
</details>
<details>
<summary>Before</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/03f5ae20-796c-4e8b-8668-09a994211c1e)
</details>
<details>
<summary>After</summary>
![image](https://github.com/singerdmx/flutter-quill/assets/73608287/7e3a1987-36e7-4665-944a-add87d24e788)
</details>
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 ## 9.3.21
* fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898 * fix: assertion failure for swipe typing and undo on Android by @crasowas in https://github.com/singerdmx/flutter-quill/pull/1898

@ -1,6 +1,6 @@
name: flutter_quill_test name: flutter_quill_test
description: Test utilities for flutter_quill which includes methods to simplify interacting with the editor in test cases. 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/ homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/
repository: 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/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/

@ -4,18 +4,23 @@ import '../generated/quill_localizations.dart' as generated;
typedef FlutterQuillLocalizations = generated.FlutterQuillLocalizations; 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 { extension LocalizationsExt on BuildContext {
/// Require the [FlutterQuillLocalizations] instance /// Require the [FlutterQuillLocalizations] instance
/// ///
/// `loc` is short for `localizations` /// `loc` is short for `localizations`
FlutterQuillLocalizations get loc { FlutterQuillLocalizations get loc {
return FlutterQuillLocalizations.of(this) ?? return FlutterQuillLocalizations.of(this) ??
(throw UnimplementedError( (throw MissingFlutterQuillLocalizationException());
"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',
));
} }
} }

@ -415,6 +415,12 @@ abstract class FlutterQuillLocalizations {
/// **'Align right'** /// **'Align right'**
String get alignRight; 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. /// No description provided for @justifyWinWidth.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@ -732,6 +738,12 @@ abstract class FlutterQuillLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Paste'** /// **'Paste'**
String get paste; String get paste;
/// No description provided for @insertTable.
///
/// In en, this message translates to:
/// **'Insert table'**
String get insertTable;
} }
class _FlutterQuillLocalizationsDelegate class _FlutterQuillLocalizationsDelegate

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsAr extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'محاذاة اليمين'; String get alignRight => 'محاذاة اليمين';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'تبرير مع العرض'; String get justifyWinWidth => 'تبرير مع العرض';
@ -288,4 +291,7 @@ class FlutterQuillLocalizationsAr extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsBg extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Подравни вдясно'; String get alignRight => 'Подравни вдясно';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Подравни във всяка колонка'; String get justifyWinWidth => 'Подравни във всяка колонка';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsBg extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsBn extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'ন সিবদ'; String get alignRight => 'ন সিবদ';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'রসর সযত'; String get justifyWinWidth => 'রসর সযত';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsBn extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsCs extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Zarovnat vpravo'; String get alignRight => 'Zarovnat vpravo';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Zarovnat do bloku'; String get justifyWinWidth => 'Zarovnat do bloku';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsCs extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsDa extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Align right'; String get alignRight => 'Align right';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justify win width'; String get justifyWinWidth => 'Justify win width';
@ -288,4 +291,7 @@ class FlutterQuillLocalizationsDa extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsDe extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Rechtsbündig ausrichten'; String get alignRight => 'Rechtsbündig ausrichten';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Blocksatz'; String get justifyWinWidth => 'Blocksatz';
@ -294,4 +297,7 @@ class FlutterQuillLocalizationsDe extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsEn extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Align right'; String get alignRight => 'Align right';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justify win width'; String get justifyWinWidth => 'Justify win width';
@ -290,6 +293,9 @@ class FlutterQuillLocalizationsEn extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }
/// The translations for English, as used in the United States (`en_US`). /// The translations for English, as used in the United States (`en_US`).

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsEs extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Alinear a la derecha'; String get alignRight => 'Alinear a la derecha';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justificar'; String get justifyWinWidth => 'Justificar';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsEs extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsFa extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'چیدمان راست'; String get alignRight => 'چیدمان راست';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'تضمین عرض پنجره'; String get justifyWinWidth => 'تضمین عرض پنجره';
@ -291,4 +294,7 @@ class FlutterQuillLocalizationsFa extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsFr extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Aligner à droite'; String get alignRight => 'Aligner à droite';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justifier'; String get justifyWinWidth => 'Justifier';
@ -296,4 +299,7 @@ class FlutterQuillLocalizationsFr extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsHe extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'יישור לימין'; String get alignRight => 'יישור לימין';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'יישור לרוחב החלון'; String get justifyWinWidth => 'יישור לרוחב החלון';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsHe extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsHi extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'ित कर'; String get alignRight => 'ित कर';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'जसिन च'; String get justifyWinWidth => 'जसिन च';
@ -291,4 +294,7 @@ class FlutterQuillLocalizationsHi extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsId extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Rata Kanan'; String get alignRight => 'Rata Kanan';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Rata Kanan dan Kiri'; String get justifyWinWidth => 'Rata Kanan dan Kiri';
@ -292,4 +295,7 @@ class FlutterQuillLocalizationsId extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsIt extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Allinea a destra'; String get alignRight => 'Allinea a destra';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Giustifica per larghezza finestra'; String get justifyWinWidth => 'Giustifica per larghezza finestra';
@ -292,4 +295,7 @@ class FlutterQuillLocalizationsIt extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsJa extends FlutterQuillLocalizations {
@override @override
String get alignRight => '右揃え'; String get alignRight => '右揃え';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => '両端揃え'; String get justifyWinWidth => '両端揃え';
@ -287,4 +290,7 @@ class FlutterQuillLocalizationsJa extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -5,7 +5,7 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations {
FlutterQuillLocalizationsKo([super.locale = 'ko']); FlutterQuillLocalizationsKo([super.locale = 'ko']);
@override @override
String get pasteLink => '링크를 붙여넣어 주세요.'; String get pasteLink => '링크를 붙여 넣어 주세요';
@override @override
String get ok => '확인'; String get ok => '확인';
@ -35,10 +35,10 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations {
String get zoom => '확대하기'; String get zoom => '확대하기';
@override @override
String get saved => '저장되었습니다.'; String get saved => '저장되었습니다';
@override @override
String get text => '텍스트'; String get text => '제목';
@override @override
String get resize => '크기조정'; String get resize => '크기조정';
@ -77,165 +77,168 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations {
String get video => '비디오'; String get video => '비디오';
@override @override
String get undo => 'Undo'; String get undo => '되돌리기';
@override @override
String get redo => 'Redo'; String get redo => '다시실행';
@override @override
String get fontFamily => 'Font family'; String get fontFamily => '글꼴';
@override @override
String get fontSize => 'Font size'; String get fontSize => '글자 크기';
@override @override
String get bold => 'Bold'; String get bold => '굵게';
@override @override
String get subscript => 'Subscript'; String get subscript => '아래 첨자';
@override @override
String get superscript => 'Superscript'; String get superscript => '위 첨자';
@override @override
String get italic => 'Italic'; String get italic => '기울이기';
@override @override
String get underline => 'Underline'; String get underline => '밑줄';
@override @override
String get strikeThrough => 'Strike through'; String get strikeThrough => '취소선';
@override @override
String get inlineCode => 'Inline code'; String get inlineCode => '인라인 코드';
@override @override
String get fontColor => 'Font color'; String get fontColor => '글자 색상';
@override @override
String get backgroundColor => 'Background color'; String get backgroundColor => '배경 색상';
@override @override
String get clearFormat => 'Clear format'; String get clearFormat => '서식 지우기';
@override @override
String get alignLeft => 'Align left'; String get alignLeft => '왼쪽 정렬';
@override @override
String get alignCenter => 'Align center'; String get alignCenter => '가운데 정렬';
@override @override
String get alignRight => 'Align right'; String get alignRight => '오른쪽 정렬';
@override @override
String get justifyWinWidth => 'Justify win width'; String get alignJustify => 'Align justify';
@override @override
String get textDirection => 'Text direction'; String get justifyWinWidth => '좌우로 정렬';
@override @override
String get headerStyle => 'Header style'; String get textDirection => '텍스트 방향';
@override @override
String get normal => 'Normal'; String get headerStyle => '헤더 스타일';
@override @override
String get heading1 => 'Heading 1'; String get normal => '일반 텍스트';
@override @override
String get heading2 => 'Heading 2'; String get heading1 => '제목 1';
@override @override
String get heading3 => 'Heading 3'; String get heading2 => '제목 2';
@override @override
String get heading4 => 'Heading 4'; String get heading3 => '제목 3';
@override @override
String get heading5 => 'Heading 5'; String get heading4 => '제목 4';
@override @override
String get heading6 => 'Heading 6'; String get heading5 => '제목 5';
@override @override
String get numberedList => 'Numbered list'; String get heading6 => '제목 6';
@override @override
String get bulletList => 'Bullet list'; String get numberedList => '번호 매기기 목록';
@override @override
String get checkedList => 'Checked list'; String get bulletList => '글머리 기호 목록';
@override @override
String get codeBlock => 'Code block'; String get checkedList => '체크리스트';
@override @override
String get quote => 'Quote'; String get codeBlock => '코드 블록';
@override @override
String get increaseIndent => 'Increase indent'; String get quote => '인용';
@override @override
String get decreaseIndent => 'Decrease indent'; String get increaseIndent => '들여쓰기 증가';
@override @override
String get insertURL => 'Insert URL'; String get decreaseIndent => '들여쓰기 감소';
@override @override
String get visitLink => 'Visit link'; String get insertURL => 'URL 삽입';
@override @override
String get enterLink => 'Enter link'; String get visitLink => '링크 방문';
@override @override
String get enterMedia => 'Enter media'; String get enterLink => '링크 입력';
@override @override
String get edit => 'Edit'; String get enterMedia => '미디어 입력';
@override @override
String get apply => 'Apply'; String get edit => '편집';
@override @override
String get hex => 'Hex'; String get apply => '적용';
@override @override
String get material => 'Material'; String get hex => 'Hex 값';
@override @override
String get color => 'Color'; String get material => 'Material 색상';
@override @override
String get findText => 'Find text'; String get color => '색상';
@override @override
String get moveToPreviousOccurrence => 'Move to previous occurrence'; String get findText => '찾기';
@override @override
String get moveToNextOccurrence => 'Move to next occurrence'; String get moveToPreviousOccurrence => '이전 위치로 이동';
@override @override
String get savedUsingTheNetwork => 'Saved using the network'; String get moveToNextOccurrence => '다음 위치로 이동';
@override @override
String get savedUsingLocalStorage => 'Saved using the local storage'; String get savedUsingTheNetwork => '네트워크를 통해 저장';
@override
String get savedUsingLocalStorage => '로컬 스토리지를 통해 저장';
@override @override
String theImageHasBeenSavedAt(String imagePath) { String theImageHasBeenSavedAt(String imagePath) {
return 'The image has been saved at: $imagePath'; return '이미지가 저장되었습니다 $imagePath';
} }
@override @override
String get errorWhileSavingImage => 'Error while saving image'; String get errorWhileSavingImage => '이미지를 저장하는데 실패했습니다';
@override @override
String get pleaseEnterTextForYourLink => "e.g., 'Learn more'"; String get pleaseEnterTextForYourLink => '링크 제목 입력';
@override @override
String get pleaseEnterTheLinkURL => "e.g., 'https://example.com'"; String get pleaseEnterTheLinkURL => "예시) 'https://example.com'";
@override @override
String get pleaseEnterAValidImageURL => 'Please enter a valid image URL'; String get pleaseEnterAValidImageURL => '유효한 이미지 URL을 입력하세요';
@override @override
String get pleaseEnterAValidVideoURL => '유효한 비디오 URL을 입력하세요'; String get pleaseEnterAValidVideoURL => '유효한 비디오 URL을 입력하세요';
@ -250,41 +253,44 @@ class FlutterQuillLocalizationsKo extends FlutterQuillLocalizations {
String get caseSensitivityAndWholeWordSearch => '대소문자 구분 및 전체 단어 검색'; String get caseSensitivityAndWholeWordSearch => '대소문자 구분 및 전체 단어 검색';
@override @override
String get caseSensitive => 'Case sensitive'; String get caseSensitive => '대소문자 구분';
@override @override
String get wholeWord => 'Whole word'; String get wholeWord => '전체 단어';
@override @override
String get insertImage => '이미지 삽입'; String get insertImage => '이미지 삽입';
@override @override
String get pickAPhotoFromYourGallery => 'Pick a photo from your gallery'; String get pickAPhotoFromYourGallery => '갤러리에서 이미지 선택';
@override
String get takeAPhotoUsingYourCamera => '카메라로 사진 촬영';
@override @override
String get takeAPhotoUsingYourCamera => 'Take a photo using your camera'; String get pasteAPhotoUsingALink => '이미지 링크 입력';
@override @override
String get pasteAPhotoUsingALink => 'Paste a photo using a link'; String get pickAVideoFromYourGallery => '갤러리에서 동영상 선택';
@override @override
String get pickAVideoFromYourGallery => 'Pick a video from your gallery'; String get recordAVideoUsingYourCamera => '카메라로 동영상 촬영';
@override @override
String get recordAVideoUsingYourCamera => 'Record a video using your camera'; String get pasteAVideoUsingALink => '동영상 링크 입력';
@override @override
String get pasteAVideoUsingALink => 'Paste a video using a link'; String get close => '닫기';
@override @override
String get close => 'Close'; String get searchSettings => '검색 설정';
@override @override
String get searchSettings => 'Search settings'; String get cut => '잘라내기';
@override @override
String get cut => 'Cut'; String get paste => '붙여넣기';
@override @override
String get paste => 'Paste'; String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsKu extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'بۆ ڕاست'; String get alignRight => 'بۆ ڕاست';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'پانی ڕێکبخە'; String get justifyWinWidth => 'پانی ڕێکبخە';
@ -291,6 +294,9 @@ class FlutterQuillLocalizationsKu extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }
/// The translations for Kurdish (`ku_CKB`). /// The translations for Kurdish (`ku_CKB`).

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsMs extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Align right'; String get alignRight => 'Align right';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justify win width'; String get justifyWinWidth => 'Justify win width';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsMs extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsNe extends FlutterQuillLocalizations {
@override @override
String get alignRight => ' पङिबद'; String get alignRight => ' पङिबद';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'जसिन च'; String get justifyWinWidth => 'जसिन च';
@ -294,4 +297,7 @@ class FlutterQuillLocalizationsNe extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsNl extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Align right'; String get alignRight => 'Align right';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justify win width'; String get justifyWinWidth => 'Justify win width';
@ -292,4 +295,7 @@ class FlutterQuillLocalizationsNl extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsNo extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Høyrejuster'; String get alignRight => 'Høyrejuster';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Rettferdiggjør bredden'; String get justifyWinWidth => 'Rettferdiggjør bredden';
@ -292,4 +295,7 @@ class FlutterQuillLocalizationsNo extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsPl extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Align right'; String get alignRight => 'Align right';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justify win width'; String get justifyWinWidth => 'Justify win width';
@ -289,4 +292,7 @@ class FlutterQuillLocalizationsPl extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsPt extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Align right'; String get alignRight => 'Align right';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justify win width'; String get justifyWinWidth => 'Justify win width';
@ -290,6 +293,9 @@ class FlutterQuillLocalizationsPt extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }
/// The translations for Portuguese, as used in Brazil (`pt_BR`). /// The translations for Portuguese, as used in Brazil (`pt_BR`).

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsRo extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Aliniază la dreapta'; String get alignRight => 'Aliniază la dreapta';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justifică lățimea ferestrei'; String get justifyWinWidth => 'Justifică lățimea ferestrei';
@ -293,6 +296,9 @@ class FlutterQuillLocalizationsRo extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }
/// The translations for Romanian Moldavian Moldovan, as used in Romania (`ro_RO`). /// The translations for Romanian Moldavian Moldovan, as used in Romania (`ro_RO`).

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsRu extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Выровнять по правому краю'; String get alignRight => 'Выровнять по правому краю';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Выровнять по ширине окна'; String get justifyWinWidth => 'Выровнять по ширине окна';
@ -293,4 +296,7 @@ class FlutterQuillLocalizationsRu extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSk extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Zarovnať vpravo'; String get alignRight => 'Zarovnať vpravo';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Zarovnať na šírku okna'; String get justifyWinWidth => 'Zarovnať na šírku okna';
@ -294,4 +297,7 @@ class FlutterQuillLocalizationsSk extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSr extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Poravnanje desno'; String get alignRight => 'Poravnanje desno';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Centriraj širinu prozora'; String get justifyWinWidth => 'Centriraj širinu prozora';
@ -291,4 +294,7 @@ class FlutterQuillLocalizationsSr extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSv extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Högerjustera'; String get alignRight => 'Högerjustera';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justera till fönsterbredd'; String get justifyWinWidth => 'Justera till fönsterbredd';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsSv extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsSw extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Pangilia Kulia'; String get alignRight => 'Pangilia Kulia';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Kuhalalisha Upana wa Ushindi'; String get justifyWinWidth => 'Kuhalalisha Upana wa Ushindi';
@ -289,4 +292,7 @@ class FlutterQuillLocalizationsSw extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsTk extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Saga deňleşdir'; String get alignRight => 'Saga deňleşdir';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Justify win width'; String get justifyWinWidth => 'Justify win width';
@ -288,4 +291,7 @@ class FlutterQuillLocalizationsTk extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsTr extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Sağa Hizala'; String get alignRight => 'Sağa Hizala';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Kenarlara Hizala'; String get justifyWinWidth => 'Kenarlara Hizala';
@ -289,4 +292,7 @@ class FlutterQuillLocalizationsTr extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsUk extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Вирівняти праворуч'; String get alignRight => 'Вирівняти праворуч';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Вирівняти за шириною вікна'; String get justifyWinWidth => 'Вирівняти за шириною вікна';
@ -294,4 +297,7 @@ class FlutterQuillLocalizationsUk extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsUr extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'دائیں ہم آہنگ ہوں'; String get alignRight => 'دائیں ہم آہنگ ہوں';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'جسٹیفائی ون چوڑائی'; String get justifyWinWidth => 'جسٹیفائی ون چوڑائی';
@ -293,4 +296,7 @@ class FlutterQuillLocalizationsUr extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsVi extends FlutterQuillLocalizations {
@override @override
String get alignRight => 'Căn phải'; String get alignRight => 'Căn phải';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => 'Căn đều chiều rộng'; String get justifyWinWidth => 'Căn đều chiều rộng';
@ -290,4 +293,7 @@ class FlutterQuillLocalizationsVi extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }

@ -127,6 +127,9 @@ class FlutterQuillLocalizationsZh extends FlutterQuillLocalizations {
@override @override
String get alignRight => '右对齐'; String get alignRight => '右对齐';
@override
String get alignJustify => 'Align justify';
@override @override
String get justifyWinWidth => '两端对齐'; String get justifyWinWidth => '两端对齐';
@ -287,6 +290,9 @@ class FlutterQuillLocalizationsZh extends FlutterQuillLocalizations {
@override @override
String get paste => 'Paste'; String get paste => 'Paste';
@override
String get insertTable => 'Insert table';
} }
/// The translations for Chinese, as used in China (`zh_CN`). /// The translations for Chinese, as used in China (`zh_CN`).

@ -41,6 +41,10 @@
"alignLeft": "Align left", "alignLeft": "Align left",
"alignCenter": "Align center", "alignCenter": "Align center",
"alignRight": "Align right", "alignRight": "Align right",
"alignJustify": "Align justify",
"@alignJustify": {
"description": "Justify the text over the full window width"
},
"justifyWinWidth": "Justify win width", "justifyWinWidth": "Justify win width",
"textDirection": "Text direction", "textDirection": "Text direction",
"headerStyle": "Header style", "headerStyle": "Header style",
@ -102,5 +106,6 @@
"close": "Close", "close": "Close",
"searchSettings": "Search settings", "searchSettings": "Search settings",
"cut": "Cut", "cut": "Cut",
"paste": "Paste" "paste": "Paste",
"insertTable": "Insert table"
} }

@ -1,6 +1,6 @@
{ {
"@@locale": "ko", "@@locale": "ko",
"pasteLink": "링크를 붙여넣어 주세요.", "pasteLink": "링크를 붙여 넣어 주세요",
"ok": "확인", "ok": "확인",
"selectColor": "색상 선택", "selectColor": "색상 선택",
"gallery": "갤러리", "gallery": "갤러리",
@ -10,8 +10,8 @@
"remove": "제거하기", "remove": "제거하기",
"save": "저장하기", "save": "저장하기",
"zoom": "확대하기", "zoom": "확대하기",
"saved": "저장되었습니다.", "saved": "저장되었습니다",
"text": "텍스트", "text": "제목",
"resize": "크기조정", "resize": "크기조정",
"width": "넓이", "width": "넓이",
"height": "높이", "height": "높이",
@ -24,55 +24,85 @@
"search": "검색", "search": "검색",
"camera": "카메라", "camera": "카메라",
"video": "비디오", "video": "비디오",
"undo": "Undo", "undo": "되돌리기",
"redo": "Redo", "redo": "다시실행",
"fontFamily": "Font family", "fontFamily": "글꼴",
"fontSize": "Font size", "fontSize": "글자 크기",
"bold": "Bold", "bold": "굵게",
"subscript": "Subscript", "subscript": "아래 첨자",
"superscript": "Superscript", "superscript": "위 첨자",
"italic": "Italic", "italic": "기울이기",
"underline": "Underline", "underline": "밑줄",
"strikeThrough": "Strike through", "strikeThrough": "취소선",
"inlineCode": "Inline code", "inlineCode": "인라인 코드",
"fontColor": "Font color", "fontColor": "글자 색상",
"backgroundColor": "Background color", "backgroundColor": "배경 색상",
"clearFormat": "Clear format", "clearFormat": "서식 지우기",
"alignLeft": "Align left", "alignLeft": "왼쪽 정렬",
"alignCenter": "Align center", "alignCenter": "가운데 정렬",
"alignRight": "Align right", "alignRight": "오른쪽 정렬",
"justifyWinWidth": "Justify win width", "justifyWinWidth": "좌우로 정렬",
"textDirection": "Text direction", "textDirection": "텍스트 방향",
"headerStyle": "Header style", "headerStyle": "헤더 스타일",
"numberedList": "Numbered list", "normal": "일반 텍스트",
"bulletList": "Bullet list", "heading1": "제목 1",
"checkedList": "Checked list", "heading2": "제목 2",
"codeBlock": "Code block", "heading3": "제목 3",
"quote": "Quote", "heading4": "제목 4",
"increaseIndent": "Increase indent", "heading5": "제목 5",
"decreaseIndent": "Decrease indent", "heading6": "제목 6",
"insertURL": "Insert URL", "numberedList": "번호 매기기 목록",
"visitLink": "Visit link", "bulletList": "글머리 기호 목록",
"enterLink": "Enter link", "checkedList": "체크리스트",
"enterMedia": "Enter media", "codeBlock": "코드 블록",
"edit": "Edit", "quote": "인용",
"apply": "Apply", "increaseIndent": "들여쓰기 증가",
"findText": "Find text", "decreaseIndent": "들여쓰기 감소",
"moveToPreviousOccurrence": "Move to previous occurrence", "insertURL": "URL 삽입",
"moveToNextOccurrence": "Move to next occurrence", "visitLink": "링크 방문",
"savedUsingTheNetwork": "Saved using the network", "enterLink": "링크 입력",
"savedUsingLocalStorage": "Saved using the local storage", "enterMedia": "미디어 입력",
"errorWhileSavingImage": "Error while saving image", "edit": "편집",
"pleaseEnterTextForYourLink": "e.g., 'Learn more'", "apply": "적용",
"pleaseEnterTheLinkURL": "e.g., 'https://example.com'", "hex": "Hex 값",
"pleaseEnterAValidImageURL": "Please enter a valid image URL", "material": "Material 색상",
"hex": "Hex", "color": "색상",
"material": "Material", "findText": "찾기",
"color": "Color", "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을 입력하세요", "pleaseEnterAValidVideoURL": "유효한 비디오 URL을 입력하세요",
"photo": "사진", "photo": "사진",
"image": "이미지", "image": "이미지",
"caseSensitivityAndWholeWordSearch": "대소문자 구분 및 전체 단어 검색", "caseSensitivityAndWholeWordSearch": "대소문자 구분 및 전체 단어 검색",
"insertImage": "이미지 삽입" "caseSensitive": "대소문자 구분",
"wholeWord": "전체 단어",
"insertImage": "이미지 삽입",
"pickAPhotoFromYourGallery": "갤러리에서 이미지 선택",
"takeAPhotoUsingYourCamera": "카메라로 사진 촬영",
"pasteAPhotoUsingALink": "이미지 링크 입력",
"pickAVideoFromYourGallery": "갤러리에서 동영상 선택",
"recordAVideoUsingYourCamera": "카메라로 동영상 촬영",
"pasteAVideoUsingALink": "동영상 링크 입력",
"close": "닫기",
"searchSettings": "검색 설정",
"cut": "잘라내기",
"paste": "붙여넣기"
} }

@ -1,15 +1,18 @@
{ {
"ar": [ "ar": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"bg": [ "bg": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -29,10 +32,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"bn": [ "bn": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -52,19 +57,23 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"cs": [ "cs": [
"alignJustify",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"da": [ "da": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -84,10 +93,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"de": [ "de": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -102,10 +113,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"en_US": [ "en_US": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -125,10 +138,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"es": [ "es": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -143,10 +158,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"fa": [ "fa": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -166,20 +183,24 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"fr": [ "fr": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"he": [ "he": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -199,10 +220,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"hi": [ "hi": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -222,10 +245,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"id": [ "id": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -245,10 +270,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"it": [ "it": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -268,10 +295,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ja": [ "ja": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -291,53 +320,41 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ko": [ "ko": [
"normal", "alignJustify",
"heading1", "insertTable"
"heading2",
"heading3",
"heading4",
"heading5",
"heading6",
"theImageHasBeenSavedAt",
"caseSensitive",
"wholeWord",
"pickAPhotoFromYourGallery",
"takeAPhotoUsingYourCamera",
"pasteAPhotoUsingALink",
"pickAVideoFromYourGallery",
"recordAVideoUsingYourCamera",
"pasteAVideoUsingALink",
"close",
"searchSettings",
"cut",
"paste"
], ],
"ku": [ "ku": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ku_CKB": [ "ku_CKB": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ms": [ "ms": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -357,19 +374,23 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ne": [ "ne": [
"alignJustify",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"nl": [ "nl": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -389,10 +410,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"no": [ "no": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -412,10 +435,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"pl": [ "pl": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -435,10 +460,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"pt": [ "pt": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -458,10 +485,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"pt_BR": [ "pt_BR": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -481,30 +510,36 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ro": [ "ro": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ro_RO": [ "ro_RO": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ru": [ "ru": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -519,19 +554,23 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"sk": [ "sk": [
"alignJustify",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"sr": [ "sr": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -551,20 +590,24 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"sv": [ "sv": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"sw": [ "sw": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -584,10 +627,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"tk": [ "tk": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -607,10 +652,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"tr": [ "tr": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -630,20 +677,24 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"uk": [ "uk": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"ur": [ "ur": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -663,10 +714,12 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"vi": [ "vi": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -686,30 +739,36 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"zh": [ "zh": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"zh_CN": [ "zh_CN": [
"alignJustify",
"theImageHasBeenSavedAt", "theImageHasBeenSavedAt",
"caseSensitive", "caseSensitive",
"wholeWord", "wholeWord",
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
], ],
"zh_HK": [ "zh_HK": [
"alignJustify",
"normal", "normal",
"heading1", "heading1",
"heading2", "heading2",
@ -729,6 +788,7 @@
"close", "close",
"searchSettings", "searchSettings",
"cut", "cut",
"paste" "paste",
"insertTable"
] ]
} }

@ -1,9 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import '../../extensions/quill_configurations_ext.dart'; import '../../extensions/quill_configurations_ext.dart';
import '../extensions/localizations.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 { class FlutterQuillLocalizationsWidget extends StatelessWidget {
const FlutterQuillLocalizationsWidget({ const FlutterQuillLocalizationsWidget({
required this.child, required this.child,

@ -1,8 +1,14 @@
class QuillControllerConfigurations { 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 /// Callback when the user pastes and data has not already been processed
/// ///
/// Return true if the paste operation was handled /// Return true if the paste operation was handled
final Future<bool> Function()? onClipboardPaste; final Future<bool> 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;
} }

@ -1,4 +1,4 @@
import 'package:flutter/widgets.dart' show Color; import 'package:flutter/material.dart';
import '../../../../../flutter_quill.dart'; import '../../../../../flutter_quill.dart';
@ -24,6 +24,7 @@ class QuillToolbarSearchButtonOptions extends QuillToolbarBaseButtonOptions<
super.iconButtonFactor, super.iconButtonFactor,
this.dialogBarrierColor, this.dialogBarrierColor,
this.customOnPressedCallback, this.customOnPressedCallback,
this.searchBarAlignment,
}); });
final QuillDialogTheme? dialogTheme; final QuillDialogTheme? dialogTheme;
@ -34,6 +35,8 @@ class QuillToolbarSearchButtonOptions extends QuillToolbarBaseButtonOptions<
/// By default we will show simple search dialog ui /// By default we will show simple search dialog ui
/// you can pass value to this callback to change this /// you can pass value to this callback to change this
final QuillToolbarSearchButtonOnPressedCallback? customOnPressedCallback; final QuillToolbarSearchButtonOnPressedCallback? customOnPressedCallback;
final AlignmentGeometry? searchBarAlignment;
} }
typedef QuillToolbarSearchButtonOnPressedCallback = Future<void> Function( typedef QuillToolbarSearchButtonOnPressedCallback = Future<void> Function(

@ -4,6 +4,12 @@ import 'package:flutter/widgets.dart'
import '../../../widgets/quill/embeds.dart'; import '../../../widgets/quill/embeds.dart';
import '../../../widgets/quill/quill_controller.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_dialog_theme.dart';
import '../../themes/quill_icon_theme.dart'; import '../../themes/quill_icon_theme.dart';
import 'simple_toolbar_button_options.dart'; import 'simple_toolbar_button_options.dart';
@ -62,6 +68,14 @@ enum HeaderStyleType {
bool get isButtons => this == HeaderStyleType.buttons; 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 /// The configurations for the toolbar widget of flutter quill
@immutable @immutable
class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties { class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties {
@ -112,6 +126,7 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties {
this.showClipboardPaste = true, this.showClipboardPaste = true,
this.linkStyleType = LinkStyleType.original, this.linkStyleType = LinkStyleType.original,
this.headerStyleType = HeaderStyleType.original, this.headerStyleType = HeaderStyleType.original,
this.searchButtonType = SearchButtonType.modern,
/// The decoration to use for the toolbar. /// The decoration to use for the toolbar.
super.decoration, super.decoration,
@ -218,6 +233,9 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties {
/// Defines which dialog is used for applying header attribute. /// Defines which dialog is used for applying header attribute.
final HeaderStyleType headerStyleType; final HeaderStyleType headerStyleType;
/// Define which button type should be used for the [showSearchButton]
final SearchButtonType searchButtonType;
@override @override
List<Object?> get props => [ List<Object?> get props => [
buttonOptions, buttonOptions,

@ -1,48 +1,49 @@
import 'package:html2md/html2md.dart' as html2md; import 'package:html2md/html2md.dart' as html2md;
import 'package:markdown/markdown.dart' as md; import 'package:markdown/markdown.dart' as md;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../../../markdown_quill.dart'; import '../../../markdown_quill.dart';
import '../../../quill_delta.dart'; import '../../../quill_delta.dart';
import '../../utils/delta_x_utils.dart';
@immutable @immutable
@experimental
class DeltaX { 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] /// Convert the HTML Raw string to [Delta]
/// ///
/// It will run using the following steps: /// It will run using the following steps:
/// ///
/// 1. Convert the html to markdown string using `html2md` package /// 1. Convert the html to markdown string using `html2md` package
/// 2. Convert the markdown string to quill delta json string /// 2. Convert the markdown string to [Delta] using [fromMarkdown]
/// 3. Decode the delta json string to [Delta]
///
/// 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 /// This api is **experimental** and designed to be used **internally** and shouldn't
/// used for real world applications /// used for **production applications**.
/// ///
@experimental @experimental
static Delta fromHtml(String html) { static Delta fromHtml(String htmlText) {
final markdown = html2md final markdownText = html2md.convert(
.convert( htmlText,
html, rules: [underlineRule, videoRule],
) styleOptions: {'emDelimiter': '*'},
.replaceAll('unsafe:', ''); ).replaceAll(
'unsafe:',
final mdDocument = md.Document(encodeHtml: false); '',
);
final mdToDelta = MarkdownToDelta(markdownDocument: mdDocument); return fromMarkdown(markdownText);
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,
// );
} }
} }

@ -177,26 +177,46 @@ class Document {
/// Only attributes applied to all characters within this range are /// Only attributes applied to all characters within this range are
/// included in the result. /// 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) { Style collectStyle(int index, int len) {
final res = queryChild(index); var res = queryChild(index);
Style rangeStyle;
if (len > 0) { if (len > 0) {
return (res.node as Line).collectStyle(res.offset, len); return (res.node as Line).collectStyle(res.offset, len);
} }
//
if (res.offset == 0) { 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 = <Attribute>{};
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) && if ((linkAttribute != null) &&
(linkAttribute.value != (linkAttribute.value !=
(res.node as Line) (res.node as Line)
.collectStyle(res.offset, len) .collectStyle(res.offset, len)
.attributes[Attribute.link.key] .attributes[Attribute.link.key]
?.value)) { ?.value)) {
return rangeStyle.removeAll({linkAttribute}); return style.removeAll({linkAttribute});
} }
return rangeStyle; return style;
} }
/// Returns all styles and Embed for each node within selection /// Returns all styles and Embed for each node within selection

@ -369,18 +369,18 @@ class AutoFormatMultipleLinksRule extends InsertRule {
r'https?:\/\/[\w\-]+(\.[\w\-]+)*(:\d+)?(\/[^\s]*)?'; r'https?:\/\/[\w\-]+(\.[\w\-]+)*(:\d+)?(\/[^\s]*)?';
/// It requires a valid link in one link /// It requires a valid link in one link
static final oneLineLinkRegExp = RegExp( RegExp get oneLineLinkRegExp => RegExp(
_oneLineLinkPattern, _oneLineLinkPattern,
caseSensitive: false, caseSensitive: false,
); );
/// It detect if there is a link in the text whatever if it in the middle etc /// 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 // Used to solve bug https://github.com/singerdmx/flutter-quill/issues/1432
static final detectLinkRegExp = RegExp( RegExp get detectLinkRegExp => RegExp(
_detectLinkPattern, _detectLinkPattern,
caseSensitive: false, caseSensitive: false,
); );
static final linkRegExp = oneLineLinkRegExp; RegExp get linkRegExp => oneLineLinkRegExp;
@override @override
Delta? applyRule( Delta? applyRule(

@ -82,6 +82,7 @@ class MarkdownToDelta extends Converter<String, Delta>
final _elementToInlineAttr = <String, ElementToAttributeConvertor>{ final _elementToInlineAttr = <String, ElementToAttributeConvertor>{
'em': (_) => [Attribute.italic], 'em': (_) => [Attribute.italic],
'u': (_) => [Attribute.underline],
'strong': (_) => [Attribute.bold], 'strong': (_) => [Attribute.bold],
'del': (_) => [Attribute.strikeThrough], 'del': (_) => [Attribute.strikeThrough],
'a': (element) => [LinkAttribute(element.attributes['href'])], 'a': (element) => [LinkAttribute(element.attributes['href'])],
@ -91,6 +92,7 @@ class MarkdownToDelta extends Converter<String, Delta>
final _elementToEmbed = <String, ElementToEmbeddableConvertor>{ final _elementToEmbed = <String, ElementToEmbeddableConvertor>{
'hr': (_) => horizontalRule, 'hr': (_) => horizontalRule,
'img': (elAttrs) => BlockEmbed.image(elAttrs['src'] ?? ''), 'img': (elAttrs) => BlockEmbed.image(elAttrs['src'] ?? ''),
'video': (elAttrs) => BlockEmbed.video(elAttrs['src'] ?? '')
}; };
var _delta = Delta(); var _delta = Delta();

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save