diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..c9262307
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,23 @@
+name: flutter-quill CI
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: subosito/flutter-action@v2
+ with:
+ channel: 'stable'
+ - run: flutter --version
+ - run: flutter pub get
+ - run: flutter pub get -C flutter_quill_extensions
+ - run: flutter analyze
+ - run: flutter test
+ - run: flutter pub publish --dry-run
diff --git a/.gitignore b/.gitignore
index 08c3c879..6b87759f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@
.pub-cache/
.pub/
build/
+coverage/
# Android related
**/android/**/gradle-wrapper.jar
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4e8f538..f834734c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,31 +1,329 @@
+# [7.2.7]
+- Fix language code of Japan.
+
+# [7.2.6]
+- Style custom toolbar buttons like builtins.
+
+# [7.2.5]
+- Always use text cursor for editor on desktop.
+
+# [7.2.4]
+- Fixed keepStyleOnNewLine.
+
+# [7.2.3]
+- Get pixel ratio from view.
+
+# [7.2.2]
+- Prevent operations on stale editor state.
+
+# [7.2.1]
+- Add support for android keyboard content insertion.
+- Enhance color picker, enter hex color and color palette option.
+
+# [7.2.0]
+- Checkboxes, bullet points, and number points are now scaled based on the default paragraph font size.
+
+# [7.1.20]
+- Pass linestyle to embedded block.
+
+# [7.1.19]
+- Fix Rtl leading alignment problem.
+
+# [7.1.18]
+- Support flutter latest version.
+
+# [7.1.17+1]
+- Updates `device_info_plus` to version 9.0.0 to benefit from AGP 8 (see [changelog#900](https://pub.dev/packages/device_info_plus/changelog#900)).
+
+# [7.1.16]
+- Fixed subscript key from 'sup' to 'sub'.
+
+# [7.1.15]
+- Fixed a bug introduced in 7.1.7 where each section in `QuillToolbar` was displayed on its own line.
+
+# [7.1.14]
+- Add indents change for multiline selection.
+
+# [7.1.13]
+- Add custom recognizer.
+
+# [7.1.12]
+- Add superscript and subscript styles.
+
+# [7.1.11]
+- Add inserting indents for lines of list if text is selected.
+
+# [7.1.10]
+- Image embedding tweaks
+ - Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working.
+ - Implement image insert for web (image as base64)
+
+# [7.1.9]
+
+- Editor tweaks PR from [bambinoua](https://github.com/bambinoua).
+ - Shortcuts now working in Mac OS
+ - QuillDialogTheme is extended with new properties buttonStyle, linkDialogConstraints, imageDialogConstraints, isWrappable, runSpacing,
+ - Added LinkStyleButton2 with new LinkStyleDialog (similar to Quill implementation
+ - Conditinally use Row or Wrap for dialog's children.
+ - Update minimum Dart SDK version to 2.17.0 to use enum extensions.
+ - Use merging shortcuts and actions correclty (if the key combination is the same)
+
+# [7.1.8]
+- Dropdown tweaks
+ - Add itemHeight, itemPadding, defaultItemColor for customization of dropdown items.
+ - Remove alignment property as useless.
+ - Fix bugs with max width when width property is null.
+
+# [7.1.7]
+
+- Toolbar tweaks.
+ - Implement tooltips for embed CameraButton, VideoButton, FormulaButton, ImageButton.
+ - Extends customization for SelectAlignmentButton, QuillFontFamilyButton, QuillFontSizeButton adding padding, text style, alignment, width.
+ - Add renderFontFamilies to QuillFontFamilyButton to show font faces in dropdown.
+ - Add AxisDivider and its named constructors for for use in parent project.
+ - Export ToolbarButtons enum to allow specify tooltips for SelectAlignmentButton.
+ - Export QuillFontFamilyButton, SearchButton as they were not exported before.
+ - Deprecate items property in QuillFontFamilyButton, QuillFontSizeButton as the it can be built usinr rawItemsMap.
+ - Make onSelection QuillFontFamilyButton, QuillFontSizeButton omittable as no need to execute callback outside if controller is passed to widget.
+
+Now the package is more friendly for web projects.
+
+# [7.1.6]
+
+- Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets.
+
+# [7.1.5]
+
+- Add tooltips for toolbar buttons.
+
+# [7.1.4]
+
+- Fix inserting tab character in lists.
+
+# [7.1.3]
+
+- Fix ios cursor bug when word.length==1.
+
+# [7.1.2]
+
+- Fix non scrollable editor exception, when tapped under content.
+
+# [7.1.1]
+
+- customLinkPrefixes parameter - makes possible to open links with custom protoco.
+
+# [7.1.0]
+
+- Fix ordered list numeration with several lists in document.
+
+# [7.0.9]
+
+- Use const constructor for EmbedBuilder.
+
+# [7.0.8]
+
+- Fix IME position bug with scroller.
+
+# [7.0.7]
+
+- Add TextFieldTapRegion for contextMenu.
+
+# [7.0.6]
+
+- Fix line style loss on new line from non string.
+
+# [7.0.5]
+
+- Fix IME position bug for Mac and Windows.
+- Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac.
+
+# [7.0.4]
+
+- Have text selection span full line height for uneven sized text.
+
+# [7.0.3]
+
+- Fix ordered list numeration for lists with more than one level of list.
+
+# [7.0.2]
+
+- Allow widgets to override widget span properties.
+
+# [7.0.1]
+
+- Update i18n_extension dependency to version 8.0.0.
+
+# [7.0.0]
+
+- Breaking change: Tuples are no longer used. They have been replaced with a number of data classes.
+
+# [6.4.4]
+
+- Increased compatibility with Flutter widget tests.
+
+# [6.4.3]
+
+- Update dependencies (collection: 1.17.0, flutter_keyboard_visibility: 5.4.0, quiver: 3.2.1, tuple: 2.0.1, url_launcher: 6.1.9, characters: 1.2.1, i18n_extension: 7.0.0, device_info_plus: 8.1.0)
+
+# [6.4.2]
+
+- Replace `buildToolbar` with `contextMenuBuilder`.
+
+# [6.4.1]
+
+- Control the detect word boundary behaviour.
+
+# [6.4.0]
+
+- Use `axis` to make the toolbar vertical.
+- Use `toolbarIconCrossAlignment` to align the toolbar icons on the cross axis.
+- Breaking change: `QuillToolbar`'s parameter `toolbarHeight` was renamed to `toolbarSize`.
+
+# [6.3.5]
+
+- Ability to add custom shortcuts.
+
+# [6.3.4]
+
+- Update clipboard status prior to showing selected text overlay.
+
+# [6.3.3]
+
+- Fixed handling of mac intents.
+
+# [6.3.2]
+
+- Added `unknownEmbedBuilder` to QuillEditor.
+- Fix error style when input chinese japanese or korean.
+
+# [6.3.1]
+
+- Add color property to the basic factory function.
+
+# [6.3.0]
+
+- Support Flutter 3.7.
+
+# [6.2.2]
+
+- Fix: nextLine getter null where no assertion.
+
+# [6.2.1]
+
+- Revert "Align numerical and bullet lists along with text content".
+
+# [6.2.0]
+
+- Align numerical and bullet lists along with text content.
+
+# [6.1.12]
+
+- Apply i18n for default font dropdown option labels corresponding to 'Clear'.
+
+# [6.1.11]
+
+- Remove iOS hack for delaying focus calculation.
+
+# [6.1.10]
+
+- Delay focus calculation for iOS.
+
+# [6.1.9]
+
+- Bump keyboard show up wait to 1 sec.
+
+# [6.1.8]
+
+- Recalculate focus when showing keyboard.
+
+# [6.1.7]
+
+- Add czech localizations.
+
+# [6.1.6]
+
+- Upgrade i18n_extension to 6.0.0.
+
+# [6.1.5]
+
+- Fix formatting exception.
+
+# [6.1.4]
+
+- Add double quotes validation.
+
+# [6.1.3]
+
+- Revert "fix order list numbering (#988)".
+
+# [6.1.2]
+
+- Add typing shortcuts.
+
+# [6.1.1]
+
+- Fix order list numbering.
+
+# [6.1.0]
+
+- Add keyboard shortcuts for editor actions.
+
+# [6.0.10]
+
+- Upgrade device info plus to ^7.0.0.
+
+# [6.0.9]
+
+- Don't throw showAutocorrectionPromptRect not implemented. The function is called with every keystroke as a user is typing.
+
+# [6.0.8+1]
+
+- Fixes null pointer when setting documents.
+
+# [6.0.8]
+
+- Make QuillController.document mutable.
+
+# [6.0.7]
+
+- Allow disabling of selection toolbar.
+
# [6.0.6+1]
-* Revert 6.0.6
+
+- Revert 6.0.6.
# [6.0.6]
-* Fix wrong custom embed key.
+
+- Fix wrong custom embed key.
# [6.0.5]
-* Fixes toolbar buttons stealing focus from editor.
+
+- Fixes toolbar buttons stealing focus from editor.
# [6.0.4]
-* Bug fix for Type 'Uint8List' not found.
+
+- Bug fix for Type 'Uint8List' not found.
# [6.0.3]
-* Add ability to paste images.
+
+- Add ability to paste images.
# [6.0.2]
-* Address Dart Analysis issues.
+
+- Address Dart Analysis issues.
# [6.0.1]
-* Changed translation country code (zh_HK -> zh_hk) to lower case, which is required for i18n_extension used in flutter_quill.
-* Add localization in example's main to demonstrate translation.
-* Issue [Windows] selection's copy / paste tool bar not shown #861: add selection's copy / paste toolbar, escape to hide toolbar, mouse right click to show toolbar, ctrl-Y / ctrl-Z to undo / redo.
-* Image and video displayed in Windows platform caused screen flickering while selecting text, a sample_data_nomedia.json asset is added for Desktop to demonstrate the added features.
-* Known issue: keyboard action sometimes causes exception mentioned in Flutter's issue #106475 ([Windows] Keyboard shortcuts stop working after modifier key repeat flutter/flutter#106475).
-* Know issue: user needs to click the editor to get focus before toolbar is able to display.
+
+- Changed translation country code (zh_HK -> zh_hk) to lower case, which is required for i18n_extension used in flutter_quill.
+- Add localization in example's main to demonstrate translation.
+- Issue [Windows] selection's copy / paste tool bar not shown #861: add selection's copy / paste toolbar, escape to hide toolbar, mouse right click to show toolbar, ctrl-Y / ctrl-Z to undo / redo.
+- Image and video displayed in Windows platform caused screen flickering while selecting text, a sample_data_nomedia.json asset is added for Desktop to demonstrate the added features.
+- Known issue: keyboard action sometimes causes exception mentioned in Flutter's issue #106475 ([Windows] Keyboard shortcuts stop working after modifier key repeat flutter/flutter#106475).
+- Know issue: user needs to click the editor to get focus before toolbar is able to display.
# [6.0.0] BREAKING CHANGE
-* Removed embed (image, video & formula) blocks from the package to reduce app size.
+
+- Removed embed (image, video & formula) blocks from the package to reduce app size.
These blocks have been moved to the package `flutter_quill_extensions`, migrate by filling the `embedBuilders` and `embedButtons` parameters as follows:
@@ -43,699 +341,929 @@ QuillToolbar.basic(
);
```
-
# [5.4.2]
-* Upgrade i18n_extension.
+
+- Upgrade i18n_extension.
# [5.4.1]
-* Update German Translation.
+
+- Update German Translation.
# [5.4.0]
-* Added Formula Button (for maths support).
+
+- Added Formula Button (for maths support).
# [5.3.2]
-* Add more font family.
+
+- Add more font family.
# [5.3.1]
-* Enable search when text is not empty.
+
+- Enable search when text is not empty.
# [5.3.0]
-* Added search function.
+
+- Added search function.
# [5.2.11]
-* Remove default small color.
+
+- Remove default small color.
# [5.2.10]
-* Don't wrap the QuillEditor's child in the EditorTextSelectionGestureDetector if selection is disabled.
+
+- Don't wrap the QuillEditor's child in the EditorTextSelectionGestureDetector if selection is disabled.
# [5.2.9]
-* Added option to modify SelectHeaderStyleButton options.
-* Added option to click again on h1, h2, h3 button to go back to normal.
+
+- Added option to modify SelectHeaderStyleButton options.
+- Added option to click again on h1, h2, h3 button to go back to normal.
# [5.2.8]
-* Remove tooltip for LinkStyleButton.
-* Make link match regex case insensitive.
+
+- Remove tooltip for LinkStyleButton.
+- Make link match regex case insensitive.
# [5.2.7]
-* Add locale to QuillEditor.basic.
+
+- Add locale to QuillEditor.basic.
# [5.2.6]
-* Fix keyboard pops up when resizing the image.
+
+- Fix keyboard pops up when resizing the image.
# [5.2.5]
-* Upgrade youtube_player_flutter_quill to 8.2.2.
+
+- Upgrade youtube_player_flutter_quill to 8.2.2.
# [5.2.4]
-* Upgrade youtube_player_flutter_quill to 8.2.1.
+
+- Upgrade youtube_player_flutter_quill to 8.2.1.
# [5.2.3]
-* Flutter Quill Doesn't Work On iOS 16 or Xcode 14 Betas (Stored properties cannot be marked potentially unavailable with '@available').
+
+- Flutter Quill Doesn't Work On iOS 16 or Xcode 14 Betas (Stored properties cannot be marked potentially unavailable with '@available').
# [5.2.2]
-* Fix Web Unsupported operation: Platform._operatingSystem error.
+
+- Fix Web Unsupported operation: Platform.\_operatingSystem error.
# [5.2.1]
-* Rename QuillCustomIcon to QuillCustomButton.
+
+- Rename QuillCustomIcon to QuillCustomButton.
# [5.2.0]
-* Support font family selection.
+
+- Support font family selection.
# [5.1.1]
-* Update README.
+
+- Update README.
# [5.1.0]
-* Added CustomBlockEmbed and customElementsEmbedBuilder.
+
+- Added CustomBlockEmbed and customElementsEmbedBuilder.
# [5.0.5]
-* Upgrade device_info_plus to 4.0.0.
+
+- Upgrade device_info_plus to 4.0.0.
# [5.0.4]
-* Added onVideoInit callback for video documents.
+
+- Added onVideoInit callback for video documents.
# [5.0.3]
-* Update dependencies.
+
+- Update dependencies.
# [5.0.2]
-* Keep cursor position on checkbox tap.
+
+- Keep cursor position on checkbox tap.
# [5.0.1]
-* Fix static analysis errors.
+
+- Fix static analysis errors.
# [5.0.0]
-* Flutter 3.0.0 support.
+
+- Flutter 3.0.0 support.
# [4.2.3]
-* Ignore color:inherit and convert double to int for level.
+
+- Ignore color:inherit and convert double to int for level.
# [4.2.2]
-* Add clear option to font size dropdown.
+
+- Add clear option to font size dropdown.
# [4.2.1]
-* Refactor font size dropdown.
+
+- Refactor font size dropdown.
# [4.2.0]
-* Ensure selectionOverlay is available for showToolbar.
+
+- Ensure selectionOverlay is available for showToolbar.
# [4.1.9]
-* Using properly iconTheme colors.
+
+- Using properly iconTheme colors.
# [4.1.8]
-* Update font size dropdown.
+
+- Update font size dropdown.
# [4.1.7]
-* Convert FontSize to a Map to allow for named Font Size.
+
+- Convert FontSize to a Map to allow for named Font Size.
# [4.1.6]
-* Update quill_dropdown_button.dart.
+
+- Update quill_dropdown_button.dart.
# [4.1.5]
-* Add Font Size dropdown to the toolbar.
+
+- Add Font Size dropdown to the toolbar.
# [4.1.4]
-* New borderRadius for iconTheme.
+
+- New borderRadius for iconTheme.
# [4.1.3]
-* Fix selection handles show/hide after paste, backspace, copy.
+
+- Fix selection handles show/hide after paste, backspace, copy.
# [4.1.2]
-* Add full support for hardware keyboards (Chromebook, Android tablets, etc) that don't alter screen UI.
+
+- Add full support for hardware keyboards (Chromebook, Android tablets, etc) that don't alter screen UI.
# [4.1.1]
-* Added textSelectionControls field in QuillEditor.
+
+- Added textSelectionControls field in QuillEditor.
# [4.1.0]
-* Added Node to linkActionPickerDelegate.
+
+- Added Node to linkActionPickerDelegate.
# [4.0.12]
-* Add Persian(fa) language.
+
+- Add Persian(fa) language.
# [4.0.11]
-* Fix cut selection error in multi-node line.
+
+- Fix cut selection error in multi-node line.
# [4.0.10]
-* Fix vertical caret position bug.
+
+- Fix vertical caret position bug.
# [4.0.9]
-* Request keyboard focus when no child is found.
+
+- Request keyboard focus when no child is found.
# [4.0.8]
-* Fix blank lines do not display when --web-renderer=html.
+
+- Fix blank lines do not display when --web-renderer=html.
# [4.0.7]
-* Refactor getPlainText (better handling of blank lines and lines with multiple markups.
+
+- Refactor getPlainText (better handling of blank lines and lines with multiple markups.
# [4.0.6]
-* Bug fix for copying text with new lines.
+
+- Bug fix for copying text with new lines.
# [4.0.5]
-* Fixed casting null to Tuple2 when link dialog is dismissed without any input (e.g. barrier dismissed).
+
+- Fixed casting null to Tuple2 when link dialog is dismissed without any input (e.g. barrier dismissed).
# [4.0.4]
-* Bug fix for text direction rtl.
+
+- Bug fix for text direction rtl.
# [4.0.3]
-* Support text direction rtl.
+
+- Support text direction rtl.
# [4.0.2]
-* Clear toggled style on selection change.
+
+- Clear toggled style on selection change.
# [4.0.1]
-* Fix copy/cut/paste/selectAll not working.
+
+- Fix copy/cut/paste/selectAll not working.
# [4.0.0]
-* Upgrade for Flutter 2.10.
+
+- Upgrade for Flutter 2.10.
# [3.9.11]
-* Added Indonesian translation.
+
+- Added Indonesian translation.
# [3.9.10]
-* Fix for undoing a modification ending with an indented line.
+
+- Fix for undoing a modification ending with an indented line.
# [3.9.9]
-* iOS: Save image whose filename does not end with image file extension.
+
+- iOS: Save image whose filename does not end with image file extension.
# [3.9.8]
-* Added Urdu translation.
+
+- Added Urdu translation.
# [3.9.7]
-* Fix for clicking on the Link button without any text on a new line crashes.
+
+- Fix for clicking on the Link button without any text on a new line crashes.
# [3.9.6]
-* Apply locale to QuillEditor(contents).
+
+- Apply locale to QuillEditor(contents).
# [3.9.5]
-* Fix image pasting.
+
+- Fix image pasting.
# [3.9.4]
-* Hiding dialog after selecting action for image.
+
+- Hiding dialog after selecting action for image.
# [3.9.3]
-* Update ImageResizer for Android.
+
+- Update ImageResizer for Android.
# [3.9.2]
-* Copy image with its style.
+
+- Copy image with its style.
# [3.9.1]
-* Support resizing image.
+
+- Support resizing image.
# [3.9.0]
-* Image menu options for copy/remove.
+
+- Image menu options for copy/remove.
# [3.8.8]
-* Update set textEditingValue.
+
+- Update set textEditingValue.
# [3.8.7]
-* Fix checkbox not toggled correctly in toolbar button.
+
+- Fix checkbox not toggled correctly in toolbar button.
# [3.8.6]
-* Fix cursor position changes when checking/unchecking the checkbox.
+
+- Fix cursor position changes when checking/unchecking the checkbox.
# [3.8.5]
-* Fix _handleDragUpdate in _TextSelectionHandleOverlayState.
+
+- Fix \_handleDragUpdate in \_TextSelectionHandleOverlayState.
# [3.8.4]
-* Fix link dialog layout.
+
+- Fix link dialog layout.
# [3.8.3]
-* Fix for errors on a non scrollable editor.
+
+- Fix for errors on a non scrollable editor.
# [3.8.2]
-* Fix certain keys not working on web when editor is a child of a scroll view.
+
+- Fix certain keys not working on web when editor is a child of a scroll view.
# [3.8.1]
-* Refactor _QuillEditorState to QuillEditorState.
+
+- Refactor \_QuillEditorState to QuillEditorState.
# [3.8.0]
-* Support pasting with format.
+
+- Support pasting with format.
# [3.7.3]
-* Fix selection overlay for collapsed selection.
+
+- Fix selection overlay for collapsed selection.
# [3.7.2]
-* Reverted Embed toPlainText change.
+
+- Reverted Embed toPlainText change.
# [3.7.1]
-* Change Embed toPlainText to be empty string.
+
+- Change Embed toPlainText to be empty string.
# [3.7.0]
-* Replace Toolbar showHistory group with individual showRedo and showUndo.
+
+- Replace Toolbar showHistory group with individual showRedo and showUndo.
# [3.6.5]
-* Update Link dialogue for image/video.
+
+- Update Link dialogue for image/video.
# [3.6.4]
-* Link dialogue TextInputType.multiline.
+
+- Link dialogue TextInputType.multiline.
# [3.6.3]
-* Bug fix for link button text selection.
+
+- Bug fix for link button text selection.
# [3.6.2]
-* Improve link button.
+
+- Improve link button.
# [3.6.1]
-* Remove SnackBar 'What is entered is not a link'.
+
+- Remove SnackBar 'What is entered is not a link'.
# [3.6.0]
-* Allow link button to enter text.
+
+- Allow link button to enter text.
# [3.5.3]
-* Change link button behavior.
+
+- Change link button behavior.
# [3.5.2]
-* Bug fix for embed.
+
+- Bug fix for embed.
# [3.5.1]
-* Bug fix for platform util.
+
+- Bug fix for platform util.
# [3.5.0]
-* Removed redundant classes.
+
+- Removed redundant classes.
# [3.4.4]
-* Add more translations.
+
+- Add more translations.
# [3.4.3]
-* Preset link from attributes.
+
+- Preset link from attributes.
# [3.4.2]
-* Fix launch link edit mode.
+
+- Fix launch link edit mode.
# [3.4.1]
-* Placeholder effective in scrollable.
+
+- Placeholder effective in scrollable.
# [3.4.0]
-* Option to save image in read-only mode.
+
+- Option to save image in read-only mode.
# [3.3.1]
-* Pass any specified key in QuillEditor constructor to super.
+
+- Pass any specified key in QuillEditor constructor to super.
# [3.3.0]
-* Fixed Style toggle issue.
+
+- Fixed Style toggle issue.
# [3.2.1]
-* Added new translations.
+
+- Added new translations.
# [3.2.0]
-* Support multiple links insertion on the go.
+
+- Support multiple links insertion on the go.
# [3.1.1]
-* Add selection completed callback.
+
+- Add selection completed callback.
# [3.1.0]
-* Fixed image ontap functionality.
+
+- Fixed image ontap functionality.
# [3.0.4]
-* Add maxContentWidth constraint to editor.
+
+- Add maxContentWidth constraint to editor.
# [3.0.3]
-* Do not show caret on screen when the editor is not focused.
+
+- Do not show caret on screen when the editor is not focused.
# [3.0.2]
-* Fix launch link for read-only mode.
+
+- Fix launch link for read-only mode.
# [3.0.1]
-* Handle null value of Attribute.link.
+
+- Handle null value of Attribute.link.
# [3.0.0]
-* Launch link improvements.
-* Removed QuillSimpleViewer.
+
+- Launch link improvements.
+- Removed QuillSimpleViewer.
# [2.5.2]
-* Skip image when pasting.
+
+- Skip image when pasting.
# [2.5.1]
-* Bug fix for Desktop `Shift` + `Click` support.
+
+- Bug fix for Desktop `Shift` + `Click` support.
# [2.5.0]
-* Update checkbox list.
+
+- Update checkbox list.
# [2.4.1]
-* Desktop selection improvements.
+
+- Desktop selection improvements.
# [2.4.0]
-* Improve inline code style.
+
+- Improve inline code style.
# [2.3.3]
-* Improves selection rects to have consistent height regardless of individual segment text styles.
+
+- Improves selection rects to have consistent height regardless of individual segment text styles.
# [2.3.2]
-* Allow disabling floating cursor.
+
+- Allow disabling floating cursor.
# [2.3.1]
-* Preserve last newline character on delete.
+
+- Preserve last newline character on delete.
# [2.3.0]
-* Massive changes to support flutter 2.8.
+
+- Massive changes to support flutter 2.8.
# [2.2.2]
-* iOS - floating cursor.
+
+- iOS - floating cursor.
# [2.2.1]
-* Bug fix for imports supporting flutter 2.8.
+
+- Bug fix for imports supporting flutter 2.8.
# [2.2.0]
-* Support flutter 2.8.
+
+- Support flutter 2.8.
# [2.1.1]
-* Add methods of clearing editor and moving cursor.
+
+- Add methods of clearing editor and moving cursor.
# [2.1.0]
-* Add delete handler.
+
+- Add delete handler.
# [2.0.23]
-* Support custom replaceText handler.
+
+- Support custom replaceText handler.
# [2.0.22]
-* Fix attribute compare and fix font size parsing.
+
+- Fix attribute compare and fix font size parsing.
# [2.0.21]
-* Handle click on embed object.
+
+- Handle click on embed object.
# [2.0.20]
-* Improved UX/UI of Image widget.
+
+- Improved UX/UI of Image widget.
# [2.0.19]
-* When uploading a video, applying indicator.
+
+- When uploading a video, applying indicator.
# [2.0.18]
-* Make toolbar dividers optional.
+
+- Make toolbar dividers optional.
# [2.0.17]
-* Allow alignment of the toolbar icons to match WrapAlignment.
+
+- Allow alignment of the toolbar icons to match WrapAlignment.
# [2.0.16]
-* Add hide / show alignment buttons.
+
+- Add hide / show alignment buttons.
# [2.0.15]
-* Implement change cursor to SystemMouseCursors.click when hovering a link styled text.
+
+- Implement change cursor to SystemMouseCursors.click when hovering a link styled text.
# [2.0.14]
-* Enable customize the checkbox widget using DefaultListBlockStyle style.
+
+- Enable customize the checkbox widget using DefaultListBlockStyle style.
# [2.0.13]
-* Improve the scrolling performance by reducing the repaint areas.
+
+- Improve the scrolling performance by reducing the repaint areas.
# [2.0.12]
-* Fix the selection effect can't be seen as the textLine with background color.
+
+- Fix the selection effect can't be seen as the textLine with background color.
# [2.0.11]
-* Fix visibility of text selection handlers on scroll.
+
+- Fix visibility of text selection handlers on scroll.
# [2.0.10]
-* cursorConnt.color notify the text_line to repaint if it was disposed.
+
+- cursorConnt.color notify the text_line to repaint if it was disposed.
# [2.0.9]
-* Improve UX when trying to add a link.
+
+- Improve UX when trying to add a link.
# [2.0.8]
-* Adding translations to the toolbar.
+
+- Adding translations to the toolbar.
# [2.0.7]
-* Added theming options for toolbar icons and LinkDialog.
+
+- Added theming options for toolbar icons and LinkDialog.
# [2.0.6]
-* Avoid runtime error when placed inside TabBarView.
+
+- Avoid runtime error when placed inside TabBarView.
# [2.0.5]
-* Support inline code formatting.
+
+- Support inline code formatting.
# [2.0.4]
-* Enable history shortcuts for desktop.
+
+- Enable history shortcuts for desktop.
# [2.0.3]
-* Fix cursor when line contains image.
+
+- Fix cursor when line contains image.
# [2.0.2]
-* Address KeyboardListener class name conflict.
+
+- Address KeyboardListener class name conflict.
# [2.0.1]
-* Upgrade flutter_colorpicker to 0.5.0.
+
+- Upgrade flutter_colorpicker to 0.5.0.
# [2.0.0]
-* Text Alignment functions + Block Format standards.
+
+- Text Alignment functions + Block Format standards.
# [1.9.6]
-* Support putting QuillEditor inside a Scrollable view.
+
+- Support putting QuillEditor inside a Scrollable view.
# [1.9.5]
-* Skip image when pasting.
+
+- Skip image when pasting.
# [1.9.4]
-* Bug fix for cursor position when tapping at the end of line with image(s).
+
+- Bug fix for cursor position when tapping at the end of line with image(s).
# [1.9.3]
-* Bug fix when line only contains one image.
+
+- Bug fix when line only contains one image.
# [1.9.2]
-* Support for building custom inline styles.
+
+- Support for building custom inline styles.
# [1.9.1]
-* Cursor jumps to the most appropriate offset to display selection.
+
+- Cursor jumps to the most appropriate offset to display selection.
# [1.9.0]
-* Support inline image.
+
+- Support inline image.
# [1.8.3]
-* Updated quill_delta.
+
+- Updated quill_delta.
# [1.8.2]
-* Support mobile image alignment.
+
+- Support mobile image alignment.
# [1.8.1]
-* Support mobile custom size image.
+
+- Support mobile custom size image.
# [1.8.0]
-* Support entering link for image/video.
+
+- Support entering link for image/video.
# [1.7.3]
-* Bumps photo_view version.
+
+- Bumps photo_view version.
# [1.7.2]
-* Fix static analysis error.
+
+- Fix static analysis error.
# [1.7.1]
-* Support Youtube video.
+
+- Support Youtube video.
# [1.7.0]
-* Support video.
+
+- Support video.
# [1.6.4]
-* Bug fix for clear format button.
+
+- Bug fix for clear format button.
# [1.6.3]
-* Fixed dragging right handle scrolling issue.
+
+- Fixed dragging right handle scrolling issue.
# [1.6.2]
-* Fixed the position of the selection status drag handle.
+
+- Fixed the position of the selection status drag handle.
# [1.6.1]
-* Upgrade image_picker and flutter_colorpicker.
+
+- Upgrade image_picker and flutter_colorpicker.
# [1.6.0]
-* Support Multi Row Toolbar.
+
+- Support Multi Row Toolbar.
# [1.5.0]
-* Remove file_picker dependency.
+
+- Remove file_picker dependency.
# [1.4.1]
-* Remove filesystem_picker dependency.
+
+- Remove filesystem_picker dependency.
# [1.4.0]
-* Remove path_provider dependency.
+
+- Remove path_provider dependency.
# [1.3.4]
-* Add option to paintCursorAboveText.
+
+- Add option to paintCursorAboveText.
# [1.3.3]
-* Upgrade file_picker version.
+
+- Upgrade file_picker version.
# [1.3.2]
-* Fix copy/paste bug.
+
+- Fix copy/paste bug.
# [1.3.1]
-* New logo.
+
+- New logo.
# [1.3.0]
-* Support flutter 2.2.0.
+
+- Support flutter 2.2.0.
# [1.2.2]
-* Checkbox supports tapping.
+
+- Checkbox supports tapping.
# [1.2.1]
-* Indented position not holding while editing.
+
+- Indented position not holding while editing.
# [1.2.0]
-* Fix image button cancel causes crash.
+
+- Fix image button cancel causes crash.
# [1.1.8]
-* Fix height of empty line bug.
+
+- Fix height of empty line bug.
# [1.1.7]
-* Fix text selection in read-only mode.
+
+- Fix text selection in read-only mode.
# [1.1.6]
-* Remove universal_html dependency.
+
+- Remove universal_html dependency.
# [1.1.5]
-* Enable "Select", "Select All" and "Copy" in read-only mode.
+
+- Enable "Select", "Select All" and "Copy" in read-only mode.
# [1.1.4]
-* Fix text selection issue.
+
+- Fix text selection issue.
# [1.1.3]
-* Update example folder.
+
+- Update example folder.
# [1.1.2]
-* Add pedantic.
+
+- Add pedantic.
# [1.1.1]
-* Base64 image support.
+
+- Base64 image support.
# [1.1.0]
-* Support null safety.
+
+- Support null safety.
# [1.0.9]
-* Web support for raw editor and keyboard listener.
+
+- Web support for raw editor and keyboard listener.
# [1.0.8]
-* Support token attribute.
+
+- Support token attribute.
# [1.0.7]
-* Fix crash on web (dart:io).
+
+- Fix crash on web (dart:io).
# [1.0.6]
-* Add desktop support - WINDOWS, MACOS and LINUX.
+
+- Add desktop support - WINDOWS, MACOS and LINUX.
# [1.0.5]
-* Bug fix: Can not insert newline when Bold is toggled ON.
+
+- Bug fix: Can not insert newline when Bold is toggled ON.
# [1.0.4]
-* Upgrade photo_view to ^0.11.0.
+
+- Upgrade photo_view to ^0.11.0.
# [1.0.3]
-* Fix issue that text is not displayed while typing [WEB].
+
+- Fix issue that text is not displayed while typing [WEB].
# [1.0.2]
-* Update toolbar in sample home page.
+
+- Update toolbar in sample home page.
# [1.0.1]
-* Fix static analysis errors.
+
+- Fix static analysis errors.
# [1.0.0]
-* Support flutter 2.0.
+
+- Support flutter 2.0.
# [1.0.0-dev.2]
-* Improve link handling for tel, mailto and etc.
+
+- Improve link handling for tel, mailto and etc.
# [1.0.0-dev.1]
-* Upgrade prerelease SDK & Bump for master.
+
+- Upgrade prerelease SDK & Bump for master.
# [0.3.5]
-* Fix for cursor focus issues when keyboard is on.
+
+- Fix for cursor focus issues when keyboard is on.
# [0.3.4]
-* Improve link handling for tel, mailto and etc.
+
+- Improve link handling for tel, mailto and etc.
# [0.3.3]
-* More fix on cursor focus issue when keyboard is on.
+
+- More fix on cursor focus issue when keyboard is on.
# [0.3.2]
-* Fix cursor focus issue when keyboard is on.
+
+- Fix cursor focus issue when keyboard is on.
# [0.3.1]
-* cursor focus when keyboard is on.
+
+- cursor focus when keyboard is on.
# [0.3.0]
-* Line Height calculated based on font size.
+
+- Line Height calculated based on font size.
# [0.2.12]
-* Support placeholder.
+
+- Support placeholder.
# [0.2.11]
-* Fix static analysis error.
+
+- Fix static analysis error.
# [0.2.10]
-* Update TextInputConfiguration autocorrect to true in stable branch.
+
+- Update TextInputConfiguration autocorrect to true in stable branch.
# [0.2.9]
-* Update TextInputConfiguration autocorrect to true.
+
+- Update TextInputConfiguration autocorrect to true.
# [0.2.8]
-* Support display local image besides network image in stable branch.
+
+- Support display local image besides network image in stable branch.
# [0.2.7]
-* Support display local image besides network image.
+
+- Support display local image besides network image.
# [0.2.6]
-* Fix cursor after pasting.
+
+- Fix cursor after pasting.
# [0.2.5]
-* Toggle text/background color button in toolbar.
+
+- Toggle text/background color button in toolbar.
# [0.2.4]
-* Support the use of custom icon size in toolbar.
+
+- Support the use of custom icon size in toolbar.
# [0.2.3]
-* Support custom styles and image on local device storage without uploading.
+
+- Support custom styles and image on local device storage without uploading.
# [0.2.2]
-* Update git repo.
+
+- Update git repo.
# [0.2.1]
-* Fix static analysis error.
+
+- Fix static analysis error.
# [0.2.0]
-* Add checked/unchecked list button in toolbar.
+
+- Add checked/unchecked list button in toolbar.
# [0.1.8]
-* Support font and size attributes.
+
+- Support font and size attributes.
# [0.1.7]
-* Support checked/unchecked list.
+
+- Support checked/unchecked list.
# [0.1.6]
-* Fix getExtentEndpointForSelection.
+
+- Fix getExtentEndpointForSelection.
# [0.1.5]
-* Support text alignment.
+
+- Support text alignment.
# [0.1.4]
-* Handle url with trailing spaces.
+
+- Handle url with trailing spaces.
# [0.1.3]
-* Handle cursor position change when undo/redo.
+
+- Handle cursor position change when undo/redo.
# [0.1.2]
-* Handle more text colors.
+
+- Handle more text colors.
# [0.1.1]
-* Fix cursor issue when undo.
+
+- Fix cursor issue when undo.
# [0.1.0]
-* Fix insert image.
+
+- Fix insert image.
# [0.0.9]
-* Handle rgba color.
+
+- Handle rgba color.
# [0.0.8]
-* Fix launching url.
+
+- Fix launching url.
# [0.0.7]
-* Handle multiple image inserts.
+
+- Handle multiple image inserts.
# [0.0.6]
-* More toolbar functionality.
+
+- More toolbar functionality.
# [0.0.5]
-* Update example.
+
+- Update example.
# [0.0.4]
-* Update example.
+
+- Update example.
# [0.0.3]
-* Update home page meta data.
+
+- Update home page meta data.
# [0.0.2]
-* Support image upload and launch url in read-only mode.
+
+- Support image upload and launch url in read-only mode.
# [0.0.1]
-* Rich text editor based on Quill Delta.
+
+- Rich text editor based on Quill Delta.
diff --git a/README.md b/README.md
index 06fcd9e8..3c3ca406 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
A rich text editor for Flutter
@@ -20,20 +20,35 @@
[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
+---
FlutterQuill is a rich text editor and a [Quill] component for [Flutter].
This library is a WYSIWYG editor built for the modern mobile platform, with web compatibility under development. Check out our [Youtube Playlist] or [Code Introduction] to take a detailed walkthrough of the code base. You can join our [Slack Group] for discussion.
-Demo App: https://bulletjournal.us/home/index.html
+Demo App: [BULLET JOURNAL](https://bulletjournal.us/home/index.html)
+
+Pub: [FlutterQuill]
-Pub: https://pub.dev/packages/flutter_quill
+## Demo
+
+
+
+
+
+
+
+
+
+
+
+---
## Usage
See the `example` directory for a minimal example of how to use FlutterQuill. You typically just need to instantiate a controller:
-```
+```dart
QuillController _controller = QuillController.basic();
```
@@ -54,6 +69,7 @@ Column(
],
)
```
+
Check out [Sample Page] for advanced usage.
## Input / Output
@@ -63,9 +79,9 @@ This library uses [Quill] as an internal data format.
* Use `_controller.document.toDelta()` to extract the deltas.
* Use `_controller.document.toPlainText()` to extract plain text.
-FlutterQuill provides some JSON serialisation support, so that you can save and open documents. To save a document as JSON, do something like the following:
+FlutterQuill provides some JSON serialization support, so that you can save and open documents. To save a document as JSON, do something like the following:
-```
+```dart
var json = jsonEncode(_controller.document.toDelta().toJson());
```
@@ -73,11 +89,12 @@ You can then write this to storage.
To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this:
-```
-var myJSON = jsonDecode(incomingJSONText);
+```dart
+var myJSON = jsonDecode(r'{"insert":"hello\n"}');
_controller = QuillController(
document: Document.fromJson(myJSON),
- selection: TextSelection.collapsed(offset: 0));
+ selection: TextSelection.collapsed(offset: 0),
+ );
```
## Web
@@ -85,38 +102,44 @@ _controller = QuillController(
For web development, use `flutter config --enable-web` for flutter or use [ReactQuill] for React.
It is required to provide `EmbedBuilder`, e.g. [defaultEmbedBuildersWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L99).
-Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L241).
+Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L317).
## Desktop
-It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L221).
+It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L297).
## Configuration
-The `QuillToolbar` class lets you customise which formatting options are available.
+The `QuillToolbar` class lets you customize which formatting options are available.
[Sample Page] provides sample code for advanced usage and configuration.
### Font Size
-Within the editor toolbar, a drop-down with font-sizing capabilities is available. This can be enabled or disabled with `showFontSize`.
+
+Within the editor toolbar, a drop-down with font-sizing capabilities is available. This can be enabled or disabled with `showFontSize`.
When enabled, the default font-size values can be modified via _optional_ `fontSizeValues`. `fontSizeValues` accepts a `Map` consisting of a `String` title for the font size and a `String` value for the font size. Example:
-```
+
+```dart
fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'}
```
-Font size can be cleared with a value of `0`, for example:
-```
+Font size can be cleared with a value of `0`, for example:
+
+```dart
fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '0'}
```
### Font Family
+
To use your own fonts, update your [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) and pass in `fontFamilyValues`. More details at [this change](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa), [this article](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) and [this](https://www.flutterbeads.com/change-font-family-flutter/).
### Custom Buttons
+
You may add custom buttons to the _end_ of the toolbar, via the `customButtons` option, which is a `List` of `QuillCustomButton`.
To add an Icon, we should use a new QuillCustomButton class
-```
+
+```dart
QuillCustomButton(
icon:Icons.ac_unit,
onTap: () {
@@ -126,7 +149,8 @@ To add an Icon, we should use a new QuillCustomButton class
```
Each `QuillCustomButton` is used as part of the `customButtons` option as follows:
-```
+
+```dart
QuillToolbar.basic(
(...),
customButtons: [
@@ -153,16 +177,15 @@ QuillToolbar.basic(
]
```
-
## Embed Blocks
As of version 6.0, embed blocks are not provided by default as part of this package. Instead, this package provides an interface to all the user to provide there own implementations for embed blocks. Implementations for image, video and formula embed blocks is proved in a separate package [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions).
-Provide a list of embed
+Provide a list of embed
### Using the embed blocks from `flutter_quill_extensions`
-```
+```dart
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
QuillEditor.basic(
@@ -176,12 +199,11 @@ QuillToolbar.basic(
);
```
-
-
### Custom Size Image for Mobile
Define `mobileWidth`, `mobileHeight`, `mobileMargin`, `mobileAlignment` as follows:
-```
+
+```dart
{
"insert": {
"image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
@@ -219,7 +241,7 @@ After that, we need to map this "notes" type into a widget. In that case, I used
Don't forget to add this method to the `QuillEditor` after that!
```dart
-class NotesEmbedBuilder implements EmbedBuilder {
+class NotesEmbedBuilder extends EmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future Function(BuildContext context, {Document? document}) addEditNote;
@@ -233,6 +255,7 @@ class NotesEmbedBuilder implements EmbedBuilder {
QuillController controller,
Embed node,
bool readOnly,
+ bool inline,
) {
final notes = NotesBlockEmbed(node.value.data).document;
@@ -297,7 +320,7 @@ Future _addEditNote(BuildContext context, {Document? document}) async {
final length = controller.selection.extentOffset - index;
if (isEditing) {
- final offset = getEmbedNode(controller, controller.selection.start).item1;
+ final offset = getEmbedNode(controller, controller.selection.start).offset;
controller.replaceText(
offset, 1, block, TextSelection.collapsed(offset: offset));
} else {
@@ -312,9 +335,8 @@ And voila, we have a custom widget inside of the rich text editor!
-> For more info and a video example, see the [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877)
-
-> For more details, check out [this YouTube video](https://youtu.be/pI5p5j7cfHc)
+> 1. For more info and a video example, see the [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877)
+> 2. For more details, check out [this YouTube video](https://youtu.be/pI5p5j7cfHc)
### Translation
@@ -325,13 +347,15 @@ QuillToolbar(locale: Locale('fr'), ...)
QuillEditor(locale: Locale('fr'), ...)
```
-Currently, translations are available for these 22 locales:
+Currently, translations are available for these 27 locales:
* `Locale('en')`
* `Locale('ar')`
+* `Locale('cs')`
* `Locale('de')`
* `Locale('da')`
* `Locale('fr')`
+* `Locale('he')`
* `Locale('zh', 'cn')`
* `Locale('zh', 'hk')`
* `Locale('ko')`
@@ -344,29 +368,44 @@ Currently, translations are available for these 22 locales:
* `Locale('pl')`
* `Locale('vi')`
* `Locale('id')`
+* `Locale('it')`
+* `Locale('ms')`
* `Locale('nl')`
* `Locale('no')`
* `Locale('fa')`
* `Locale('hi')`
* `Locale('sr')`
+* `Locale('ja')`
#### Contributing to translations
+
The translation file is located at [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations!
----
+## Conversion to HTML
-
-
-
-
+Having your document stored in Quill Delta format is sometimes not enough. Often you'll need to convert
+it to other formats such as HTML in order to publish it, or send an email. One option is to use
+[vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document
+to HTML. This package has full support for all Quill operations - including images, videos, formulas,
+tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter.
+It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html)
+Typescript/Javascript package.
+## Testing
-
-
-
-
+To aid in testing applications using the editor an extension to the flutter `WidgetTester` is provided which includes methods to simplify interacting with the editor in test cases.
+
+Import the test utilities in your test file:
+
+```dart
+import 'package:flutter_quill/flutter_quill_test.dart';
+```
+and then enter text using `quillEnterText`:
+```dart
+await tester.quillEnterText(find.byType(QuillEditor), 'test\n');
+```
## Sponsors
@@ -375,10 +414,6 @@ The translation file is located at [toolbar.i18n.dart](lib/src/translations/tool
"https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
width="150px" height="150px">
----
-
-[Chinese Documentation](./doc_cn.md)
-
[Quill]: https://quilljs.com/docs/formats
[Flutter]: https://github.com/flutter/flutter
[FlutterQuill]: https://pub.dev/packages/flutter_quill
@@ -387,3 +422,7 @@ The translation file is located at [toolbar.i18n.dart](lib/src/translations/tool
[Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g
[Sample Page]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart
[Code Introduction]: https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md
+
+
+
+[中文文档](./doc_cn.md)
diff --git a/doc_cn.md b/doc_cn.md
index 2bf9de93..0286abf3 100644
--- a/doc_cn.md
+++ b/doc_cn.md
@@ -1,4 +1,4 @@
-
+
支持 Flutter 平台的富文本编辑器
@@ -20,24 +20,41 @@
[github-forks-badge]: https://img.shields.io/github/forks/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff
[github-forks-link]: https://github.com/singerdmx/flutter-quill/network/members
+[原文档](./README.md)
-FlutterQuill 是一个富文本编辑器,同样也是 [Quill] 在 [Flutter] 的版本。
+---
+
+`FlutterQuill` 是一个富文本编辑器,也是 [Quill](https://quilljs.com/docs/formats) 在 [Flutter](https://github.com/flutter/flutter) 的版本
+
+该库是为移动平台构建的『所见即所得』的富文本编辑器,同时我们还正在对 `Web` 平台进行兼容。查看我们的 [Youtube 播放列表](https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2) 或 [代码介绍](https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md) 以了解代码的详细内容。你可以加入我们的 [Slack Group](https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g) 来进行讨论
+
+示例 `App` : [BULLET JOURNAL](https://bulletjournal.us/home/index.html)
+
+`Pub` : [FlutterQuill](https://pub.dev/packages/flutter_quill)
-该库是为移动平台构建的 “ 所见即所得 ” 的富文本编辑器,同时我们还正在对 Web 平台进行兼容。查看我们的 [Youtube 播放列表] 或 [代码介绍] 以了解代码的详细内容。你可以加入我们的 [Slack Group] 来进行讨论。
+## 效果展示
+
+
+
+
+
-Demo App: https://bulletjournal.us/home/index.html
+
+
+
+
-Pub: https://pub.dev/packages/flutter_quill
+---
## 用法
-查看 `示例` 目录来学习 FlutterQuill 最简单的使用方法,你通常只需要实例化一个控制器:
+查看 `示例` 目录来学习 `FlutterQuill` 最简单的使用方法,你通常只需要一个控制器实例:
-```
+```dart
QuillController _controller = QuillController.basic();
```
-然后在你的 App 中嵌入工具栏和编辑器,例如:
+然后在你的 `App` 中嵌入工具栏 `QuillToolbar` 和编辑器 `QuillEditor` ,如:
```dart
Column(
@@ -47,75 +64,88 @@ Column(
child: Container(
child: QuillEditor.basic(
controller: _controller,
- readOnly: false, // true for view only mode
+ readOnly: false, // 为 true 时只读
),
),
)
],
)
```
-查看 [示例页面] 以了解高级用户。
-## 输入 / 输出
+查看 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart) 查看高级用法
-该库使用 [Quill] 作为内部数据格式。
+## 保存和读取
-* 使用 `_controller.document.toDelta()` 获取增量。
-* 使用 `_controller.document.toPlainText()` 获取纯文本。
+该库使用 [Quill 格式](https://quilljs.com/docs/formats) 作为内部数据格式
-FlutterQuill 提供了一些 JSON 序列化支持,以便您可以保存和打开文档。 要将文档保存为 JSON 类型,请执行以下操作:
+* 使用 `_controller.document.toDelta()` 获取 [Delta 格式](https://quilljs.com/docs/delta/)
+* 使用 `_controller.document.toPlainText()` 获取纯文本
-```
+`FlutterQuill` 提供了一些 `JSON` 序列化支持,以便你保存和打开文档
+
+要将文档转化为 `JSON` 类型,请执行以下操作:
+
+```dart
var json = jsonEncode(_controller.document.toDelta().toJson());
```
-然后你就可以将其存储。
-
-想要 FlutterQuill 编辑器使用你之前存储的 JSON 数据,请执行以下操作:
+要将 `FlutterQuill` 使用之前存储的 `JSON` 数据,请执行以下操作:
-```
-var myJSON = jsonDecode(incomingJSONText);
+```dart
+var myJSON = jsonDecode(r'{"insert":"hello\n"}');
_controller = QuillController(
document: Document.fromJson(myJSON),
- selection: TextSelection.collapsed(offset: 0));
+ selection: TextSelection.collapsed(offset: 0),
+ );
```
-## Web
-对于 web 开发,请执行 `flutter config --enable-web` 来获取对 flutter 的支持或使用 [ReactQuill] 获取对 React 的支持。
+## Web 端
+
+对于 `Web` 开发,请执行 `flutter config --enable-web` 来获取 `Flutter` 的支持,或使用 [ReactQuill](https://github.com/zenoamaro/react-quill) 获取对 `React` 的支持
-进行 Web 开发需要提供 `EmbedBuilder`, 参考:[defaultEmbedBuilderWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L29).
-进行 Web 开发还需要提供 `webImagePickImpl`, 参考: [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L225).
+进行 `Web` 开发需要提供 `EmbedBuilder` ,参见 [defaultEmbedBuilderWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L29)
+进行 `Web` 开发还需要提供 `webImagePickImpl` ,参见 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L225)
-## Desktop
+## 桌面端
-在桌面端进行工具栏按钮开发,需要提供 `filePickImpl`。参考: [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L205).
+进行桌面端工具栏按钮开发需要提供 `filePickImpl` ,参见 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L205)
## 配置
-`QuillToolbar` 类允许您自定义可用的格式选项。[示例页面] 提供了高级使用和配置的示例代码。
+`QuillToolbar` 类允许你自定义可用的格式选项,参见 [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart) 提供了高级使用和配置的示例代码
### 字号
-在编辑器工具栏中,提供了具有字号功能的下拉菜单。 这可以通过 `showFontSize` 启用或禁用。
-启用后,可以通过*可选的* `fontSizeValues` 属性修改默认字号。 `fontSizeValues` 接受一个 `Map`,其中包含一个 `String` 类型的标题和一个 `String` 类型的字号。 例子:
-```
-fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'}
+在工具栏中提供了选择字号的下拉菜单,可通过 `showFontSize` 来启用或禁用
+
+启用后,可以通过 *可选的* `fontSizeValues` 属性修改默认字号
+
+`fontSizeValues` 接收一个 `Map`,其中包含一个 `String` 类型的标题和一个 `String` 类型的字号,如:
+
+```dart
+fontSizeValues: const {'小字号': '8', '中字号': '24.5', '大字号': '46'}
```
字体大小可以使用 `0` 值清除,例如:
-```
-fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '0'}
+
+```dart
+fontSizeValues: const {'小字号': '8', '中字号': '24.5', '大字号': '46', '清除': '0'}
```
### 字体
-想要使用你自己的字体,请更新你的 [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) 并且传入 `fontFamilyValues`。详情内容请查看 [this change](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa), [this article](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) 和 [this](https://www.flutterbeads.com/change-font-family-flutter/)。
+
+想要使用你自己的字体,请更新你的 [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) 并且传入 `fontFamilyValues`
+
+详见 [这个 Commit](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa) 和 [这篇文章](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) 以及 [这个教程](https://www.flutterbeads.com/change-font-family-flutter/)
### 自定义按钮
-您可以通过 `customButtons` 可选参数将自定义按钮添加到工具栏的*末尾*,该参数接收的了行是 `QuillCustomButton` 的 `List`。
-要添加一个 Icon,我们应该实例化一个新的新的 `QuillCustomButton`
-```
+你可以通过 `customButtons` 可选参数将自定义按钮添加到工具栏的 *末尾* ,该参数接收 `QuillCustomButton` 的 `List`
+
+要添加一个 `Icon` ,我们应该实例化一个新的 `QuillCustomButton`
+
+```dart
QuillCustomButton(
icon:Icons.ac_unit,
onTap: () {
@@ -124,8 +154,9 @@ fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '
),
```
-每个 `QuillCustomButton` 都是 `customButtons` 可选参数的一部分,如下所示:
-```
+每个 `QuillCustomButton` 都是 `customButtons` 可选参数的一部分,如:
+
+```dart
QuillToolbar.basic(
(...),
customButtons: [
@@ -135,14 +166,12 @@ QuillToolbar.basic(
debugPrint('snowflake1');
}
),
-
QuillCustomButton(
icon:Icons.ac_unit,
onTap: () {
debugPrint('snowflake2');
}
),
-
QuillCustomButton(
icon:Icons.ac_unit,
onTap: () {
@@ -152,10 +181,33 @@ QuillToolbar.basic(
]
```
+## 嵌入块
+
+自 `6.0` 版本,本库不默认支持嵌入块,反之本库提供接口给所有用户来创建所需的嵌入块。
+
+若需要图片、视频、公式块的支持,请查看独立库 [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions)
+
+### 根据 `flutter_quill_extensions` 使用图片、视频、公式等自定义嵌入块
+
+```dart
+import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
+
+QuillEditor.basic(
+ controller: controller,
+ embedBuilders: FlutterQuillEmbeds.builders(),
+);
+
+QuillToolbar.basic(
+ controller: controller,
+ embedButtons: FlutterQuillEmbeds.buttons(),
+);
+```
+
### 移动端上自定义图片尺寸
定义`mobileWidth`、`mobileHeight`、`mobileMargin`、`mobileAlignment`如下:
-```
+
+```dart
{
"insert": {
"image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
@@ -167,13 +219,14 @@ QuillToolbar.basic(
```
### 自定义嵌入块
-有时您想在文本中添加一些自定义内容或者是自定义小部件。 比如向文本添加注释,或者在文本编辑器中添加的任何自定义内容。
-您唯一需要做的就是添加一个 `CustomBlockEmbed` 并将其映射到 `customElementsEmbedBuilder` 中,以将自定义块内的数据转换为一个 widget!
+有时你想在文本中添加一些自定义内容或者是自定义小部件
-例子:
+比如向文本添加注释,或者在文本编辑器中添加的任何自定义内容
-从 `CustomBlockEmbed` 开始,我们在这里扩展它并添加对 'Note' widget 的方法,这就是 `Document`,`flutter_quill` 使用它来呈现富文本。
+你唯一需要做的就是添加一个 `CustomBlockEmbed` 并将其映射到 `customElementsEmbedBuilder` 中,以将自定义块内的数据转换为一个 `Widget` ,如:
+
+先从 `CustomBlockEmbed` `extent` 出一个 `NotesBlockEmbed` 类,并添加两个方法以返回 `Document` 用以 `flutter_quill` 渲染富文本
```dart
class NotesBlockEmbed extends CustomBlockEmbed {
@@ -188,43 +241,54 @@ class NotesBlockEmbed extends CustomBlockEmbed {
}
```
-然后,我们需要将这个 “notes” 类型映射到 widget 中。在例子中,我使用 `ListTile` 来显示它,使用 `onTap` 方法俩编辑内容,另外不要忘记将此方法添加到 `QuillEditor` 中。
+然后,我们需要将这个 `notes` 类型映射到其想渲染出的 `Widget` 中
+
+在这里我们使用 `ListTile` 来渲染它,并使用 `onTap` 方法来编辑内容,最后不要忘记将此方法添加到 `QuillEditor` 中
```dart
-Widget customElementsEmbedBuilder(
- BuildContext context,
- QuillController controller,
- CustomBlockEmbed block,
- bool readOnly,
- void Function(GlobalKey videoContainerKey)? onVideoInit,
-) {
- switch (block.type) {
- case 'notes':
- final notes = NotesBlockEmbed(block.data).document;
-
- return Material(
- color: Colors.transparent,
- child: ListTile(
- title: Text(
- notes.toPlainText().replaceAll('\n', ' '),
- maxLines: 3,
- overflow: TextOverflow.ellipsis,
- ),
- leading: const Icon(Icons.notes),
- onTap: () => _addEditNote(context, document: notes),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- side: const BorderSide(color: Colors.grey),
- ),
+class NotesEmbedBuilder extends EmbedBuilder {
+ NotesEmbedBuilder({required this.addEditNote});
+
+ Future Function(BuildContext context, {Document? document}) addEditNote;
+
+ @override
+ String get key => 'notes';
+
+ @override
+ Widget build(
+ BuildContext context,
+ QuillController controller,
+ Embed node,
+ bool readOnly,
+ bool inline,
+ ) {
+ final notes = NotesBlockEmbed(node.value.data).document;
+
+ return Material(
+ color: Colors.transparent,
+ child: ListTile(
+ title: Text(
+ notes.toPlainText().replaceAll('\n', ' '),
+ maxLines: 3,
+ overflow: TextOverflow.ellipsis,
),
- );
- default:
- return const SizedBox();
+ leading: const Icon(Icons.notes),
+ onTap: () => addEditNote(context, document: notes),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ side: const BorderSide(color: Colors.grey),
+ ),
+ ),
+ );
}
}
```
-然后,编写一个方法来添加/编辑内容,`showDialog` 方法显示 Quill 编辑器以编辑内容,用户编辑完成后,需要检查文档是否有内容,如果有内容,在 `CustomBlockEmbed` 中添加/编辑 `NotesBlockEmbed`(注意,如果没有在 `NotesBlockEmbed` 中传递 `CustomBlockEmbed` ,编辑将不会生效)。
+最后我们编写一个方法来添加或编辑内容
+
+`showDialog` 方法先显示 `Quill` 编辑器以让用户编辑内容,编辑完成后,我们需要检查文档是否有内容,若有则在 `BlockEmbed.custom` 传入添加或编辑了的 `NotesBlockEmbed`
+
+注意,如果我们没有在 `BlockEmbed.custom` 传如我们所自定义的 `CustomBlockEmbed` ,那么编辑将不会生效
```dart
Future _addEditNote(BuildContext context, {Document? document}) async {
@@ -265,7 +329,7 @@ Future _addEditNote(BuildContext context, {Document? document}) async {
final length = controller.selection.extentOffset - index;
if (isEditing) {
- final offset = getEmbedNode(controller, controller.selection.start).item1;
+ final offset = getEmbedNode(controller, controller.selection.start).offset;
controller.replaceText(
offset, 1, block, TextSelection.collapsed(offset: offset));
} else {
@@ -274,34 +338,35 @@ Future _addEditNote(BuildContext context, {Document? document}) async {
}
```
-这样我们就成功的在富文本编辑器中添加了一个自定义小组件。
+这样我们就成功的在富文本编辑器中添加了一个自定义小组件
-> 更多信息和视频示例,请参阅 [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877)
-
-> 有关更多详细信息,请查看 [this YouTube video](https://youtu.be/pI5p5j7cfHc)
+> 1. 更多信息和视频示例,请参阅 [这个特性的 PR](https://github.com/singerdmx/flutter-quill/pull/877)
+> 2. 有关更多详细信息,请查看 [这个 Youtube 视频](https://youtu.be/pI5p5j7cfHc)
### 翻译
-该库为 quill 工具栏和编辑器提供翻译,除非您设置自己的语言环境,否则它将遵循系统语言环境:
+该库为 `QuillToolbar` 和 `QuillEditor` 提供了部分翻译,且若你未设置自己的语言环境,则它将使用系统语言环境:
```dart
QuillToolbar(locale: Locale('fr'), ...)
QuillEditor(locale: Locale('fr'), ...)
```
-目前,可提供以下 22 种语言环境的翻译:
+目前,可提供以下 27 种语言环境的翻译:
* `Locale('en')`
* `Locale('ar')`
+* `Locale('cs')`
* `Locale('de')`
* `Locale('da')`
* `Locale('fr')`
-* `Locale('zh', 'CN')`
-* `Locale('zh', 'HK')`
+* `Locale('he')`
+* `Locale('zh', 'cn')`
+* `Locale('zh', 'hk')`
* `Locale('ko')`
* `Locale('ru')`
* `Locale('es')`
@@ -312,42 +377,38 @@ QuillEditor(locale: Locale('fr'), ...)
* `Locale('pl')`
* `Locale('vi')`
* `Locale('id')`
+* `Locale('it')`
+* `Locale('ms')`
* `Locale('nl')`
* `Locale('no')`
* `Locale('fa')`
* `Locale('hi')`
* `Locale('sr')`
+* `Locale('jp')`
#### 贡献翻译
-翻译文件位于 [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart)。 随意贡献您自己的翻译,只需复制英文翻译映射并将值替换为您的翻译。 然后打开一个拉取请求,这样每个人都可以从您的翻译中受益!
----
+翻译文件位于 [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart)
-
-
-
-
+随意贡献你自己的翻译,只需复制英文翻译映射并将值替换为你的翻译即可
+然后打开一个拉取请求,这样每个人都可以从你的翻译中受益!
-
-
-
-
+### 转化至 HTML
+
+将你的文档转为 `Quill Delta` 格式有时还不够,通常你需要将其转化为其他如 `HTML` 格式来分发他,或作为邮件发出
+一个方案是使用 [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) `Flutter` 包来转化至 `HTML` 格式。此包支持所以的 `Quill` 操作,包含图片、视频、公式、表格和注释
+转化过程可以在 `vanilla Dart` 如服务器端或 `CLI` 执行,也可在 `Flutter` 中执行
-## 帮助
+其是流行且成熟的 [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) `Typescript/Javascript` 包的 `Dart` 部分
+
+---
+
+## 赞助
-
-[Quill]: https://quilljs.com/docs/formats
-[Flutter]: https://github.com/flutter/flutter
-[FlutterQuill]: https://pub.dev/packages/flutter_quill
-[ReactQuill]: https://github.com/zenoamaro/react-quill
-[Youtube 播放列表]: https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2
-[Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g
-[示例页面]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart
-[代码介绍]: https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index f2872cf4..4f8d4d24 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 9.0
+ 11.0
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 1e8c3c90..88359b22 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '9.0'
+# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 1cce8762..3d83c0c2 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 50;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -210,6 +210,7 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -241,6 +242,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -351,7 +353,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -433,7 +435,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -482,7 +484,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 55f5c308..b8fc1f1d 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -45,5 +45,7 @@
CADisableMinimumFrameDurationOnPhone
+ UIApplicationSupportsIndirectInputEvents
+
diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart
index fcff61f2..9e3bcb02 100644
--- a/example/lib/pages/home_page.dart
+++ b/example/lib/pages/home_page.dart
@@ -12,11 +12,16 @@ import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
-import 'package:tuple/tuple.dart';
import '../universal_ui/universal_ui.dart';
import 'read_only_page.dart';
+enum _SelectionType {
+ none,
+ word,
+ // line,
+}
+
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
@@ -25,6 +30,14 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State {
QuillController? _controller;
final FocusNode _focusNode = FocusNode();
+ Timer? _selectAllTimer;
+ _SelectionType _selectionType = _SelectionType.none;
+
+ @override
+ void dispose() {
+ _selectAllTimer?.cancel();
+ super.dispose();
+ }
@override
void initState() {
@@ -78,29 +91,74 @@ class _HomePageState extends State {
color: Colors.grey.shade800,
child: _buildMenuBar(context),
),
- body: RawKeyboardListener(
- focusNode: FocusNode(),
- onKey: (event) {
- if (event.data.isControlPressed && event.character == 'b') {
- if (_controller!
- .getSelectionStyle()
- .attributes
- .keys
- .contains('bold')) {
- _controller!
- .formatSelection(Attribute.clone(Attribute.bold, null));
- } else {
- _controller!.formatSelection(Attribute.bold);
- }
- }
- },
- child: _buildWelcomeEditor(context),
- ),
+ body: _buildWelcomeEditor(context),
);
}
+ bool _onTripleClickSelection() {
+ final controller = _controller!;
+
+ _selectAllTimer?.cancel();
+ _selectAllTimer = null;
+
+ // If you want to select all text after paragraph, uncomment this line
+ // if (_selectionType == _SelectionType.line) {
+ // final selection = TextSelection(
+ // baseOffset: 0,
+ // extentOffset: controller.document.length,
+ // );
+
+ // controller.updateSelection(selection, ChangeSource.REMOTE);
+
+ // _selectionType = _SelectionType.none;
+
+ // return true;
+ // }
+
+ if (controller.selection.isCollapsed) {
+ _selectionType = _SelectionType.none;
+ }
+
+ if (_selectionType == _SelectionType.none) {
+ _selectionType = _SelectionType.word;
+ _startTripleClickTimer();
+ return false;
+ }
+
+ if (_selectionType == _SelectionType.word) {
+ final child = controller.document.queryChild(
+ controller.selection.baseOffset,
+ );
+ final offset = child.node?.documentOffset ?? 0;
+ final length = child.node?.length ?? 0;
+
+ final selection = TextSelection(
+ baseOffset: offset,
+ extentOffset: offset + length,
+ );
+
+ controller.updateSelection(selection, ChangeSource.REMOTE);
+
+ // _selectionType = _SelectionType.line;
+
+ _selectionType = _SelectionType.none;
+
+ _startTripleClickTimer();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ void _startTripleClickTimer() {
+ _selectAllTimer = Timer(const Duration(milliseconds: 900), () {
+ _selectionType = _SelectionType.none;
+ });
+ }
+
Widget _buildWelcomeEditor(BuildContext context) {
- var quillEditor = QuillEditor(
+ Widget quillEditor = QuillEditor(
controller: _controller!,
scrollController: ScrollController(),
scrollable: true,
@@ -108,9 +166,13 @@ class _HomePageState extends State {
autoFocus: false,
readOnly: false,
placeholder: 'Add content',
+ enableSelectionToolbar: isMobile(),
expands: false,
padding: EdgeInsets.zero,
onImagePaste: _onImagePaste,
+ onTapUp: (details, p1) {
+ return _onTripleClickSelection();
+ },
customStyles: DefaultStyles(
h1: DefaultTextBlockStyle(
const TextStyle(
@@ -119,8 +181,8 @@ class _HomePageState extends State {
height: 1.15,
fontWeight: FontWeight.w300,
),
- const Tuple2(16, 0),
- const Tuple2(0, 0),
+ const VerticalSpacing(16, 0),
+ const VerticalSpacing(0, 0),
null),
sizeSmall: const TextStyle(fontSize: 9),
),
@@ -140,6 +202,9 @@ class _HomePageState extends State {
placeholder: 'Add content',
expands: false,
padding: EdgeInsets.zero,
+ onTapUp: (details, p1) {
+ return _onTripleClickSelection();
+ },
customStyles: DefaultStyles(
h1: DefaultTextBlockStyle(
const TextStyle(
@@ -148,12 +213,15 @@ class _HomePageState extends State {
height: 1.15,
fontWeight: FontWeight.w300,
),
- const Tuple2(16, 0),
- const Tuple2(0, 0),
+ const VerticalSpacing(16, 0),
+ const VerticalSpacing(0, 0),
null),
sizeSmall: const TextStyle(fontSize: 9),
),
- embedBuilders: defaultEmbedBuildersWeb);
+ embedBuilders: [
+ ...defaultEmbedBuildersWeb,
+ NotesEmbedBuilder(addEditNote: _addEditNote),
+ ]);
}
var toolbar = QuillToolbar.basic(
controller: _controller!,
@@ -347,6 +415,7 @@ class _HomePageState extends State {
}
void _readOnly() {
+ Navigator.pop(super.context);
Navigator.push(
super.context,
MaterialPageRoute(
@@ -402,7 +471,8 @@ class _HomePageState extends State {
final length = controller.selection.extentOffset - index;
if (isEditing) {
- final offset = getEmbedNode(controller, controller.selection.start).item1;
+ final offset =
+ getEmbedNode(controller, controller.selection.start).offset;
controller.replaceText(
offset, 1, block, TextSelection.collapsed(offset: offset));
} else {
@@ -411,7 +481,7 @@ class _HomePageState extends State {
}
}
-class NotesEmbedBuilder implements EmbedBuilder {
+class NotesEmbedBuilder extends EmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future Function(BuildContext context, {Document? document}) addEditNote;
@@ -425,6 +495,8 @@ class NotesEmbedBuilder implements EmbedBuilder {
QuillController controller,
Embed node,
bool readOnly,
+ bool inline,
+ TextStyle textStyle,
) {
final notes = NotesBlockEmbed(node.value.data).document;
diff --git a/example/lib/universal_ui/universal_ui.dart b/example/lib/universal_ui/universal_ui.dart
index 443255a4..91344afb 100644
--- a/example/lib/universal_ui/universal_ui.dart
+++ b/example/lib/universal_ui/universal_ui.dart
@@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:universal_html/html.dart' as html;
-import 'package:youtube_player_flutter_quill/youtube_player_flutter_quill.dart';
+import 'package:youtube_player_flutter/youtube_player_flutter.dart';
import '../widgets/responsive_widget.dart';
import 'fake_ui.dart' if (dart.library.html) 'real_ui.dart' as ui_instance;
@@ -27,7 +27,7 @@ class UniversalUI {
var ui = UniversalUI();
-class ImageEmbedBuilderWeb implements EmbedBuilder {
+class ImageEmbedBuilderWeb extends EmbedBuilder {
@override
String get key => BlockEmbed.imageType;
@@ -37,6 +37,8 @@ class ImageEmbedBuilderWeb implements EmbedBuilder {
QuillController controller,
Embed node,
bool readOnly,
+ bool inline,
+ TextStyle textStyle,
) {
final imageUrl = node.value.data;
if (isImageBase64(imageUrl)) {
@@ -44,8 +46,12 @@ class ImageEmbedBuilderWeb implements EmbedBuilder {
return const SizedBox();
}
final size = MediaQuery.of(context).size;
- UniversalUI().platformViewRegistry.registerViewFactory(
- imageUrl, (viewId) => html.ImageElement()..src = imageUrl);
+ UniversalUI().platformViewRegistry.registerViewFactory(imageUrl, (viewId) {
+ return html.ImageElement()
+ ..src = imageUrl
+ ..style.height = 'auto'
+ ..style.width = 'auto';
+ });
return Padding(
padding: EdgeInsets.only(
right: ResponsiveWidget.isMediumScreen(context)
@@ -64,13 +70,19 @@ class ImageEmbedBuilderWeb implements EmbedBuilder {
}
}
-class VideoEmbedBuilderWeb implements EmbedBuilder {
+class VideoEmbedBuilderWeb extends EmbedBuilder {
@override
String get key => BlockEmbed.videoType;
@override
- Widget build(BuildContext context, QuillController controller, Embed node,
- bool readOnly) {
+ Widget build(
+ BuildContext context,
+ QuillController controller,
+ Embed node,
+ bool readOnly,
+ bool inline,
+ TextStyle textStyle,
+ ) {
var videoUrl = node.value.data;
if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) {
final youtubeID = YoutubePlayer.convertUrlToId(videoUrl);
diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc
index 8ef02f26..5f64f84a 100644
--- a/example/linux/my_application.cc
+++ b/example/linux/my_application.cc
@@ -27,7 +27,7 @@ static void my_application_activate(GApplication* application) {
// in case the window manager does more exotic layout, e.g. tiling.
// If running on Wayland assume the header bar will work (may need changing
// if future cases occur).
- gboolean use_header_bar = TRUE;
+ gboolean use_header_bar = FALSE;
#ifdef GDK_WINDOWING_X11
GdkScreen *screen = gtk_window_get_screen(window);
if (GDK_IS_X11_SCREEN(screen)) {
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
index 886c66ec..85987338 100644
--- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,9 +5,9 @@
import FlutterMacOS
import Foundation
-import device_info_plus_macos
+import device_info_plus
import pasteboard
-import path_provider_macos
+import path_provider_foundation
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index f271d0b7..caaa4ab9 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -29,8 +29,8 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.4
path_provider: ^2.0.9
- filesystem_picker: ^2.0.0
- file_picker: ^4.6.1
+ filesystem_picker: ^3.1.0
+ file_picker: ^5.2.2
flutter_quill:
path: ../
flutter_quill_extensions:
diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc
index 3ac0062f..a922e84e 100644
--- a/example/windows/runner/Runner.rc
+++ b/example/windows/runner/Runner.rc
@@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico"
// Version
//
-#ifdef FLUTTER_BUILD_NUMBER
-#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
-#define VERSION_AS_NUMBER 1,0,0
+#define VERSION_AS_NUMBER 1,0,0,0
#endif
-#ifdef FLUTTER_BUILD_NAME
-#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md
index 13187808..176876c7 100644
--- a/flutter_quill_extensions/CHANGELOG.md
+++ b/flutter_quill_extensions/CHANGELOG.md
@@ -1,3 +1,26 @@
+## 0.3.3
+* Fix a prototype bug which was bring by [PR #1230](https://github.com/singerdmx/flutter-quill/pull/1230#issuecomment-1560597099)
+
+## 0.3.2
+* Updated dependencies to support intl 0.18
+
+## 0.3.1
+* Image embedding tweaks
+ * Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working.
+ * Implement image insert for web (image as base64)
+
+## 0.3.0
+
+* Added support for adding custom tooltips to toolbar buttons
+
+## 0.2.0
+
+* Allow widgets to override widget span properties [b7951b0](https://github.com/singerdmx/flutter-quill/commit/b7951b02c9086ea42e7aad6d78e6c9b0297562e5)
+* Remove tuples [3e9452e](https://github.com/singerdmx/flutter-quill/commit/3e9452e675e8734ff50364c5f7b5d34088d5ff05)
+* Remove transparent color of ImageVideoUtils dialog [74544bd](https://github.com/singerdmx/flutter-quill/commit/74544bd945a9d212ca1e8d6b3053dbecee22b720)
+* Migrate to `youtube_player_flutter` from `youtube_player_flutter_quill`
+* Updates to forumla button [5228f38](https://github.com/singerdmx/flutter-quill/commit/5228f389ba6f37d61d445cfe138c19fcf8766d71)
+
## 0.1.0
* Initial release
diff --git a/flutter_quill_extensions/lib/embeds/builders.dart b/flutter_quill_extensions/lib/embeds/builders.dart
index c443cc59..b16b5845 100644
--- a/flutter_quill_extensions/lib/embeds/builders.dart
+++ b/flutter_quill_extensions/lib/embeds/builders.dart
@@ -7,15 +7,17 @@ import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/translations.dart';
import 'package:gallery_saver/gallery_saver.dart';
import 'package:math_keyboard/math_keyboard.dart';
-import 'package:tuple/tuple.dart';
+import 'package:universal_html/html.dart' as html;
+import '../shims/dart_ui_fake.dart'
+ if (dart.library.html) '../shims/dart_ui_real.dart' as ui;
import 'utils.dart';
import 'widgets/image.dart';
import 'widgets/image_resizer.dart';
import 'widgets/video_app.dart';
import 'widgets/youtube_video_app.dart';
-class ImageEmbedBuilder implements EmbedBuilder {
+class ImageEmbedBuilder extends EmbedBuilder {
@override
String get key => BlockEmbed.imageType;
@@ -25,12 +27,14 @@ class ImageEmbedBuilder implements EmbedBuilder {
QuillController controller,
base.Embed node,
bool readOnly,
+ bool inline,
+ TextStyle textStyle,
) {
assert(!kIsWeb, 'Please provide image EmbedBuilder for Web');
var image;
final imageUrl = standardizeImageUrl(node.value.data);
- Tuple2? _widthHeight;
+ OptionalSize? _imageSize;
final style = node.style.attributes['style'];
if (base.isMobile() && style != null) {
final _attrs = base.parseKeyValuePairs(style.value.toString(), {
@@ -46,7 +50,7 @@ class ImageEmbedBuilder implements EmbedBuilder {
'mobileWidth and mobileHeight must be specified');
final w = double.parse(_attrs[Attribute.mobileWidth]!);
final h = double.parse(_attrs[Attribute.mobileHeight]!);
- _widthHeight = Tuple2(w, h);
+ _imageSize = OptionalSize(w, h);
final m = _attrs[Attribute.mobileMargin] == null
? 0.0
: double.parse(_attrs[Attribute.mobileMargin]!);
@@ -57,9 +61,9 @@ class ImageEmbedBuilder implements EmbedBuilder {
}
}
- if (_widthHeight == null) {
+ if (_imageSize == null) {
image = imageByUrl(imageUrl);
- _widthHeight = Tuple2((image as Image).width, image.height);
+ _imageSize = OptionalSize((image as Image).width, image.height);
}
if (!readOnly && base.isMobile()) {
@@ -87,10 +91,10 @@ class ImageEmbedBuilder implements EmbedBuilder {
controller
..skipRequestKeyboard = true
..formatText(
- res.item1, 1, StyleAttribute(attr));
+ res.offset, 1, StyleAttribute(attr));
},
- imageWidth: _widthHeight?.item1,
- imageHeight: _widthHeight?.item2,
+ imageWidth: _imageSize?.width,
+ imageHeight: _imageSize?.height,
maxWidth: _screenSize.width,
maxHeight: _screenSize.height);
});
@@ -103,10 +107,10 @@ class ImageEmbedBuilder implements EmbedBuilder {
onPressed: () {
final imageNode =
getEmbedNode(controller, controller.selection.start)
- .item2;
+ .value;
final imageUrl = imageNode.value.data;
controller.copiedImageUrl =
- Tuple2(imageUrl, getImageStyleString(controller));
+ ImageUrl(imageUrl, getImageStyleString(controller));
Navigator.pop(context);
},
);
@@ -117,7 +121,7 @@ class ImageEmbedBuilder implements EmbedBuilder {
onPressed: () {
final offset =
getEmbedNode(controller, controller.selection.start)
- .item1;
+ .offset;
controller.replaceText(offset, 1, '',
TextSelection.collapsed(offset: offset));
Navigator.pop(context);
@@ -145,7 +149,43 @@ class ImageEmbedBuilder implements EmbedBuilder {
}
}
-class VideoEmbedBuilder implements EmbedBuilder {
+class ImageEmbedBuilderWeb extends EmbedBuilder {
+ ImageEmbedBuilderWeb({this.constraints})
+ : assert(kIsWeb, 'ImageEmbedBuilderWeb is only for web platform');
+
+ final BoxConstraints? constraints;
+
+ @override
+ String get key => BlockEmbed.imageType;
+
+ @override
+ Widget build(
+ BuildContext context,
+ QuillController controller,
+ Embed node,
+ bool readOnly,
+ bool inline,
+ TextStyle textStyle,
+ ) {
+ final imageUrl = node.value.data;
+
+ ui.platformViewRegistry.registerViewFactory(imageUrl, (viewId) {
+ return html.ImageElement()
+ ..src = imageUrl
+ ..style.height = 'auto'
+ ..style.width = 'auto';
+ });
+
+ return ConstrainedBox(
+ constraints: constraints ?? BoxConstraints.loose(const Size(200, 200)),
+ child: HtmlElementView(
+ viewType: imageUrl,
+ ),
+ );
+ }
+}
+
+class VideoEmbedBuilder extends EmbedBuilder {
VideoEmbedBuilder({this.onVideoInit});
final void Function(GlobalKey videoContainerKey)? onVideoInit;
@@ -159,6 +199,8 @@ class VideoEmbedBuilder implements EmbedBuilder {
QuillController controller,
base.Embed node,
bool readOnly,
+ bool inline,
+ TextStyle textStyle,
) {
assert(!kIsWeb, 'Please provide video EmbedBuilder for Web');
@@ -176,7 +218,7 @@ class VideoEmbedBuilder implements EmbedBuilder {
}
}
-class FormulaEmbedBuilder implements EmbedBuilder {
+class FormulaEmbedBuilder extends EmbedBuilder {
@override
String get key => BlockEmbed.formulaType;
@@ -186,6 +228,8 @@ class FormulaEmbedBuilder implements EmbedBuilder {
QuillController controller,
base.Embed node,
bool readOnly,
+ bool inline,
+ TextStyle textStyle,
) {
assert(!kIsWeb, 'Please provide formula EmbedBuilder for Web');
diff --git a/flutter_quill_extensions/lib/embeds/embed_types.dart b/flutter_quill_extensions/lib/embeds/embed_types.dart
index 814b77b6..6a48f066 100644
--- a/flutter_quill_extensions/lib/embeds/embed_types.dart
+++ b/flutter_quill_extensions/lib/embeds/embed_types.dart
@@ -1,4 +1,5 @@
import 'dart:io';
+import 'dart:typed_data';
import 'package:flutter/material.dart';
@@ -18,3 +19,28 @@ enum MediaPickSetting {
Camera,
Video,
}
+
+typedef MediaFileUrl = String;
+typedef MediaFilePicker = Future Function(QuillMediaType mediaType);
+typedef MediaPickedCallback = Future Function(QuillFile file);
+
+enum QuillMediaType { image, video }
+
+extension QuillMediaTypeX on QuillMediaType {
+ bool get isImage => this == QuillMediaType.image;
+ bool get isVideo => this == QuillMediaType.video;
+}
+
+/// Represents a file data which returned by file picker.
+class QuillFile {
+ QuillFile({
+ required this.name,
+ this.path = '',
+ Uint8List? bytes,
+ }) : assert(name.isNotEmpty),
+ bytes = bytes ?? Uint8List(0);
+
+ final String name;
+ final String path;
+ final Uint8List bytes;
+}
diff --git a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart
index bb906f30..5ec8e28e 100644
--- a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart
+++ b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart
@@ -19,6 +19,7 @@ class CameraButton extends StatelessWidget {
this.webVideoPickImpl,
this.cameraPickSettingSelector,
this.iconTheme,
+ this.tooltip,
Key? key,
}) : super(key: key);
@@ -42,6 +43,7 @@ class CameraButton extends StatelessWidget {
final MediaPickSettingSelector? cameraPickSettingSelector;
final QuillIconTheme? iconTheme;
+ final String? tooltip;
@override
Widget build(BuildContext context) {
@@ -53,6 +55,7 @@ class CameraButton extends StatelessWidget {
return QuillIconButton(
icon: Icon(icon, size: iconSize, color: iconColor),
+ tooltip: tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: iconSize * 1.77,
diff --git a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart
index fb0ab679..5c7c5684 100644
--- a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart
+++ b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart
@@ -9,6 +9,7 @@ class FormulaButton extends StatelessWidget {
this.fillColor,
this.iconTheme,
this.dialogTheme,
+ this.tooltip,
Key? key,
}) : super(key: key);
@@ -23,6 +24,7 @@ class FormulaButton extends StatelessWidget {
final QuillIconTheme? iconTheme;
final QuillDialogTheme? dialogTheme;
+ final String? tooltip;
@override
Widget build(BuildContext context) {
@@ -34,6 +36,7 @@ class FormulaButton extends StatelessWidget {
return QuillIconButton(
icon: Icon(icon, size: iconSize, color: iconColor),
+ tooltip: tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: iconSize * 1.77,
diff --git a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart
index 5cc51aff..d05d851d 100644
--- a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart
+++ b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart
@@ -17,6 +17,7 @@ class ImageButton extends StatelessWidget {
this.mediaPickSettingSelector,
this.iconTheme,
this.dialogTheme,
+ this.tooltip,
Key? key,
}) : super(key: key);
@@ -38,6 +39,7 @@ class ImageButton extends StatelessWidget {
final QuillIconTheme? iconTheme;
final QuillDialogTheme? dialogTheme;
+ final String? tooltip;
@override
Widget build(BuildContext context) {
@@ -49,6 +51,7 @@ class ImageButton extends StatelessWidget {
return QuillIconButton(
icon: Icon(icon, size: iconSize, color: iconColor),
+ tooltip: tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: iconSize * 1.77,
diff --git a/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart b/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart
index a72f1996..aef7c9e7 100644
--- a/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart
+++ b/flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart
@@ -80,7 +80,6 @@ class ImageVideoUtils {
context: context,
builder: (ctx) => AlertDialog(
contentPadding: EdgeInsets.zero,
- backgroundColor: Colors.transparent,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
diff --git a/flutter_quill_extensions/lib/embeds/toolbar/media_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/media_button.dart
new file mode 100644
index 00000000..837ca825
--- /dev/null
+++ b/flutter_quill_extensions/lib/embeds/toolbar/media_button.dart
@@ -0,0 +1,452 @@
+//import 'dart:io';
+import 'dart:math' as math;
+import 'dart:ui';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_quill/extensions.dart';
+import 'package:flutter_quill/flutter_quill.dart' hide Text;
+import 'package:flutter_quill/translations.dart';
+import 'package:image_picker/image_picker.dart';
+
+import '../embed_types.dart';
+
+/// Widget which combines [ImageButton] and [VideButton] widgets. This widget
+/// has more customization and uses dialog similar to one which is used
+/// on [http://quilljs.com].
+class MediaButton extends StatelessWidget {
+ const MediaButton({
+ required this.controller,
+ required this.icon,
+ this.type = QuillMediaType.image,
+ this.iconSize = kDefaultIconSize,
+ this.fillColor,
+ this.mediaFilePicker = _defaultMediaPicker,
+ this.onMediaPickedCallback,
+ this.iconTheme,
+ this.dialogTheme,
+ this.tooltip,
+ this.childrenSpacing = 16.0,
+ this.labelText,
+ this.hintText,
+ this.submitButtonText,
+ this.submitButtonSize,
+ this.galleryButtonText,
+ this.linkButtonText,
+ this.autovalidateMode = AutovalidateMode.disabled,
+ Key? key,
+ this.validationMessage,
+ }) : assert(type == QuillMediaType.image,
+ 'Video selection is not supported yet'),
+ super(key: key);
+
+ final QuillController controller;
+ final IconData icon;
+ final double iconSize;
+ final Color? fillColor;
+ final QuillMediaType type;
+ final QuillIconTheme? iconTheme;
+ final QuillDialogTheme? dialogTheme;
+ final String? tooltip;
+ final MediaFilePicker mediaFilePicker;
+ final MediaPickedCallback? onMediaPickedCallback;
+
+ /// The margin between child widgets in the dialog.
+ final double childrenSpacing;
+
+ /// The text of label in link add mode.
+ final String? labelText;
+
+ /// The hint text for link [TextField].
+ final String? hintText;
+
+ /// The text of the submit button.
+ final String? submitButtonText;
+
+ /// The size of dialog buttons.
+ final Size? submitButtonSize;
+
+ /// The text of the gallery button [MediaSourceSelectorDialog].
+ final String? galleryButtonText;
+
+ /// The text of the link button [MediaSourceSelectorDialog].
+ final String? linkButtonText;
+
+ final AutovalidateMode autovalidateMode;
+ final String? validationMessage;
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final iconColor = iconTheme?.iconUnselectedColor ?? theme.iconTheme.color;
+ final iconFillColor =
+ iconTheme?.iconUnselectedFillColor ?? fillColor ?? theme.canvasColor;
+
+ return QuillIconButton(
+ icon: Icon(icon, size: iconSize, color: iconColor),
+ tooltip: tooltip,
+ highlightElevation: 0,
+ hoverElevation: 0,
+ size: iconSize * 1.77,
+ fillColor: iconFillColor,
+ borderRadius: iconTheme?.borderRadius ?? 2,
+ onPressed: () => _onPressedHandler(context),
+ );
+ }
+
+ Future _onPressedHandler(BuildContext context) async {
+ if (onMediaPickedCallback != null) {
+ final mediaSource = await showDialog(
+ context: context,
+ builder: (_) => MediaSourceSelectorDialog(
+ dialogTheme: dialogTheme,
+ galleryButtonText: galleryButtonText,
+ linkButtonText: linkButtonText,
+ ),
+ );
+ if (mediaSource != null) {
+ if (mediaSource == MediaPickSetting.Gallery) {
+ await _pickImage();
+ } else {
+ _inputLink(context);
+ }
+ }
+ } else {
+ _inputLink(context);
+ }
+ }
+
+ Future _pickImage() async {
+ if (!(kIsWeb || isMobile() || isDesktop())) {
+ throw UnsupportedError(
+ 'Unsupported target platform: ${defaultTargetPlatform.name}');
+ }
+
+ final mediaFileUrl = await _pickMediaFileUrl();
+
+ if (mediaFileUrl != null) {
+ final index = controller.selection.baseOffset;
+ final length = controller.selection.extentOffset - index;
+ controller.replaceText(
+ index, length, BlockEmbed.image(mediaFileUrl), null);
+ }
+ }
+
+ Future _pickMediaFileUrl() async {
+ final mediaFile = await mediaFilePicker(type);
+ return mediaFile != null ? onMediaPickedCallback?.call(mediaFile) : null;
+ }
+
+ void _inputLink(BuildContext context) {
+ showDialog(
+ context: context,
+ builder: (_) => MediaLinkDialog(
+ dialogTheme: dialogTheme,
+ labelText: labelText,
+ hintText: hintText,
+ buttonText: submitButtonText,
+ buttonSize: submitButtonSize,
+ childrenSpacing: childrenSpacing,
+ autovalidateMode: autovalidateMode,
+ validationMessage: validationMessage,
+ ),
+ ).then(_linkSubmitted);
+ }
+
+ void _linkSubmitted(String? value) {
+ if (value != null && value.isNotEmpty) {
+ final index = controller.selection.baseOffset;
+ final length = controller.selection.extentOffset - index;
+ final data =
+ type.isImage ? BlockEmbed.image(value) : BlockEmbed.video(value);
+ controller.replaceText(index, length, data, null);
+ }
+ }
+}
+
+/// Provides a dialog for input link to media resource.
+class MediaLinkDialog extends StatefulWidget {
+ const MediaLinkDialog({
+ Key? key,
+ this.link,
+ this.dialogTheme,
+ this.childrenSpacing = 16.0,
+ this.labelText,
+ this.hintText,
+ this.buttonText,
+ this.buttonSize,
+ this.autovalidateMode = AutovalidateMode.disabled,
+ this.validationMessage,
+ }) : assert(childrenSpacing > 0),
+ super(key: key);
+
+ final String? link;
+ final QuillDialogTheme? dialogTheme;
+
+ /// The margin between child widgets in the dialog.
+ final double childrenSpacing;
+
+ /// The text of label in link add mode.
+ final String? labelText;
+
+ /// The hint text for link [TextField].
+ final String? hintText;
+
+ /// The text of the submit button.
+ final String? buttonText;
+
+ /// The size of dialog buttons.
+ final Size? buttonSize;
+
+ final AutovalidateMode autovalidateMode;
+ final String? validationMessage;
+
+ @override
+ State createState() => _MediaLinkDialogState();
+}
+
+class _MediaLinkDialogState extends State {
+ final _linkFocus = FocusNode();
+ final _linkController = TextEditingController();
+
+ @override
+ void dispose() {
+ _linkFocus.dispose();
+ _linkController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final constraints = widget.dialogTheme?.linkDialogConstraints ??
+ () {
+ final mediaQuery = MediaQuery.of(context);
+ final maxWidth =
+ kIsWeb ? mediaQuery.size.width / 4 : mediaQuery.size.width - 80;
+ return BoxConstraints(maxWidth: maxWidth, maxHeight: 80);
+ }();
+
+ final buttonStyle = widget.buttonSize != null
+ ? Theme.of(context)
+ .elevatedButtonTheme
+ .style
+ ?.copyWith(fixedSize: MaterialStatePropertyAll(widget.buttonSize))
+ : widget.dialogTheme?.buttonStyle;
+
+ final isWrappable = widget.dialogTheme?.isWrappable ?? false;
+
+ final children = [
+ Text(widget.labelText ?? 'Enter media'.i18n),
+ UtilityWidgets.maybeWidget(
+ enabled: !isWrappable,
+ wrapper: (child) => Expanded(
+ child: child,
+ ),
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: widget.childrenSpacing),
+ child: TextFormField(
+ controller: _linkController,
+ focusNode: _linkFocus,
+ style: widget.dialogTheme?.inputTextStyle,
+ keyboardType: TextInputType.url,
+ textInputAction: TextInputAction.done,
+ decoration: InputDecoration(
+ labelStyle: widget.dialogTheme?.labelTextStyle,
+ hintText: widget.hintText,
+ ),
+ autofocus: true,
+ autovalidateMode: widget.autovalidateMode,
+ validator: _validateLink,
+ onChanged: _linkChanged,
+ ),
+ ),
+ ),
+ ElevatedButton(
+ onPressed: _canPress() ? _submitLink : null,
+ style: buttonStyle,
+ child: Text(widget.buttonText ?? 'Ok'.i18n),
+ ),
+ ];
+
+ return Dialog(
+ backgroundColor: widget.dialogTheme?.dialogBackgroundColor,
+ shape: widget.dialogTheme?.shape ??
+ DialogTheme.of(context).shape ??
+ RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
+ child: ConstrainedBox(
+ constraints: constraints,
+ child: Padding(
+ padding:
+ widget.dialogTheme?.linkDialogPadding ?? const EdgeInsets.all(16),
+ child: isWrappable
+ ? Wrap(
+ alignment: WrapAlignment.center,
+ crossAxisAlignment: WrapCrossAlignment.center,
+ runSpacing: widget.dialogTheme?.runSpacing ?? 0.0,
+ children: children,
+ )
+ : Row(
+ children: children,
+ ),
+ ),
+ ),
+ );
+ }
+
+ bool _canPress() => _validateLink(_linkController.text) == null;
+
+ void _linkChanged(String value) {
+ setState(() {
+ _linkController.text = value;
+ });
+ }
+
+ void _submitLink() => Navigator.pop(context, _linkController.text);
+
+ String? _validateLink(String? value) {
+ if ((value?.isEmpty ?? false) ||
+ !AutoFormatMultipleLinksRule.linkRegExp.hasMatch(value!)) {
+ return widget.validationMessage ?? 'That is not a valid URL';
+ }
+
+ return null;
+ }
+}
+
+/// Media souce selector.
+class MediaSourceSelectorDialog extends StatelessWidget {
+ const MediaSourceSelectorDialog({
+ Key? key,
+ this.dialogTheme,
+ this.galleryButtonText,
+ this.linkButtonText,
+ }) : super(key: key);
+
+ final QuillDialogTheme? dialogTheme;
+
+ /// The text of the gallery button [MediaSourceSelectorDialog].
+ final String? galleryButtonText;
+
+ /// The text of the link button [MediaSourceSelectorDialog].
+ final String? linkButtonText;
+
+ @override
+ Widget build(BuildContext context) {
+ final constraints = dialogTheme?.mediaSelectorDialogConstraints ??
+ () {
+ final mediaQuery = MediaQuery.of(context);
+ double maxWidth, maxHeight;
+ if (kIsWeb) {
+ maxWidth = mediaQuery.size.width / 7;
+ maxHeight = mediaQuery.size.height / 7;
+ } else {
+ maxWidth = mediaQuery.size.width - 80;
+ maxHeight = maxWidth / 2;
+ }
+ return BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight);
+ }();
+
+ final shape = dialogTheme?.shape ??
+ DialogTheme.of(context).shape ??
+ RoundedRectangleBorder(borderRadius: BorderRadius.circular(4));
+
+ return Dialog(
+ backgroundColor: dialogTheme?.dialogBackgroundColor,
+ shape: shape,
+ child: ConstrainedBox(
+ constraints: constraints,
+ child: Padding(
+ padding: dialogTheme?.mediaSelectorDialogPadding ??
+ const EdgeInsets.all(16),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: TextButtonWithIcon(
+ icon: Icons.collections,
+ label: galleryButtonText ?? 'Gallery'.i18n,
+ onPressed: () =>
+ Navigator.pop(context, MediaPickSetting.Gallery),
+ ),
+ ),
+ const SizedBox(width: 10),
+ Expanded(
+ child: TextButtonWithIcon(
+ icon: Icons.link,
+ label: linkButtonText ?? 'Link'.i18n,
+ onPressed: () =>
+ Navigator.pop(context, MediaPickSetting.Link),
+ ),
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class TextButtonWithIcon extends StatelessWidget {
+ const TextButtonWithIcon({
+ required this.label,
+ required this.icon,
+ required this.onPressed,
+ this.textStyle,
+ Key? key,
+ }) : super(key: key);
+
+ final String label;
+ final IconData icon;
+ final VoidCallback onPressed;
+ final TextStyle? textStyle;
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ final scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+ final gap = scale <= 1 ? 8.0 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
+ final buttonStyle = TextButtonTheme.of(context).style;
+ final shape = buttonStyle?.shape?.resolve({}) ??
+ const RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(Radius.circular(4)));
+ return Material(
+ shape: shape,
+ textStyle: textStyle ??
+ theme.textButtonTheme.style?.textStyle?.resolve({}) ??
+ theme.textTheme.labelLarge,
+ elevation: buttonStyle?.elevation?.resolve({}) ?? 0,
+ child: InkWell(
+ customBorder: shape,
+ onTap: onPressed,
+ child: Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(icon),
+ SizedBox(height: gap),
+ Flexible(child: Text(label)),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+/// Default file picker.
+Future _defaultMediaPicker(QuillMediaType mediaType) async {
+ final pickedFile = mediaType.isImage
+ ? await ImagePicker().pickImage(source: ImageSource.gallery)
+ : await ImagePicker().pickVideo(source: ImageSource.gallery);
+
+ if (pickedFile != null) {
+ return QuillFile(
+ name: pickedFile.name,
+ path: pickedFile.path,
+ bytes: await pickedFile.readAsBytes(),
+ );
+ }
+
+ return null;
+}
diff --git a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart
index e6193622..e5c1ab73 100644
--- a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart
+++ b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart
@@ -17,6 +17,7 @@ class VideoButton extends StatelessWidget {
this.mediaPickSettingSelector,
this.iconTheme,
this.dialogTheme,
+ this.tooltip,
Key? key,
}) : super(key: key);
@@ -38,6 +39,7 @@ class VideoButton extends StatelessWidget {
final QuillIconTheme? iconTheme;
final QuillDialogTheme? dialogTheme;
+ final String? tooltip;
@override
Widget build(BuildContext context) {
@@ -49,6 +51,7 @@ class VideoButton extends StatelessWidget {
return QuillIconButton(
icon: Icon(icon, size: iconSize, color: iconColor),
+ tooltip: tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: iconSize * 1.77,
diff --git a/flutter_quill_extensions/lib/embeds/widgets/image.dart b/flutter_quill_extensions/lib/embeds/widgets/image.dart
index d4df2a4c..658c5050 100644
--- a/flutter_quill_extensions/lib/embeds/widgets/image.dart
+++ b/flutter_quill_extensions/lib/embeds/widgets/image.dart
@@ -21,7 +21,7 @@ String getImageStyleString(QuillController controller) {
final String? s = controller
.getAllSelectionStyles()
.firstWhere((s) => s.attributes.containsKey(Attribute.style.key),
- orElse: () => Style())
+ orElse: Style.new)
.attributes[Attribute.style.key]
?.value;
return s ?? '';
diff --git a/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart b/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart
index 8032b30f..02e53fbe 100644
--- a/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart
+++ b/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart
@@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:url_launcher/url_launcher.dart';
-import 'package:youtube_player_flutter_quill/youtube_player_flutter_quill.dart';
+import 'package:youtube_player_flutter/youtube_player_flutter.dart';
class YoutubeVideoApp extends StatefulWidget {
const YoutubeVideoApp(
diff --git a/flutter_quill_extensions/lib/flutter_quill_extensions.dart b/flutter_quill_extensions/lib/flutter_quill_extensions.dart
index 7ef38328..fdbc54b2 100644
--- a/flutter_quill_extensions/lib/flutter_quill_extensions.dart
+++ b/flutter_quill_extensions/lib/flutter_quill_extensions.dart
@@ -15,23 +15,33 @@ export 'embeds/toolbar/camera_button.dart';
export 'embeds/toolbar/formula_button.dart';
export 'embeds/toolbar/image_button.dart';
export 'embeds/toolbar/image_video_utils.dart';
+export 'embeds/toolbar/media_button.dart';
export 'embeds/toolbar/video_button.dart';
export 'embeds/utils.dart';
class FlutterQuillEmbeds {
- static List builders(
- {void Function(GlobalKey videoContainerKey)? onVideoInit}) =>
+ static List builders({
+ void Function(GlobalKey videoContainerKey)? onVideoInit,
+ }) =>
[
ImageEmbedBuilder(),
VideoEmbedBuilder(onVideoInit: onVideoInit),
FormulaEmbedBuilder(),
];
+ static List webBuilders() => [
+ ImageEmbedBuilderWeb(),
+ ];
+
static List buttons({
bool showImageButton = true,
bool showVideoButton = true,
bool showCameraButton = true,
bool showFormulaButton = false,
+ String? imageButtonTooltip,
+ String? videoButtonTooltip,
+ String? cameraButtonTooltip,
+ String? formulaButtonTooltip,
OnImagePickCallback? onImagePickCallback,
OnVideoPickCallback? onVideoPickCallback,
MediaPickSettingSelector? mediaPickSettingSelector,
@@ -39,54 +49,58 @@ class FlutterQuillEmbeds {
FilePickImpl? filePickImpl,
WebImagePickImpl? webImagePickImpl,
WebVideoPickImpl? webVideoPickImpl,
- }) {
- return [
- if (showImageButton)
- (controller, toolbarIconSize, iconTheme, dialogTheme) => ImageButton(
- icon: Icons.image,
- iconSize: toolbarIconSize,
- controller: controller,
- onImagePickCallback: onImagePickCallback,
- filePickImpl: filePickImpl,
- webImagePickImpl: webImagePickImpl,
- mediaPickSettingSelector: mediaPickSettingSelector,
- iconTheme: iconTheme,
- dialogTheme: dialogTheme,
- ),
- if (showVideoButton)
- (controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton(
- icon: Icons.movie_creation,
- iconSize: toolbarIconSize,
- controller: controller,
- onVideoPickCallback: onVideoPickCallback,
- filePickImpl: filePickImpl,
- webVideoPickImpl: webImagePickImpl,
- mediaPickSettingSelector: mediaPickSettingSelector,
- iconTheme: iconTheme,
- dialogTheme: dialogTheme,
- ),
- if ((onImagePickCallback != null || onVideoPickCallback != null) &&
- showCameraButton)
- (controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton(
- icon: Icons.photo_camera,
- iconSize: toolbarIconSize,
- controller: controller,
- onImagePickCallback: onImagePickCallback,
- onVideoPickCallback: onVideoPickCallback,
- filePickImpl: filePickImpl,
- webImagePickImpl: webImagePickImpl,
- webVideoPickImpl: webVideoPickImpl,
- cameraPickSettingSelector: cameraPickSettingSelector,
- iconTheme: iconTheme,
- ),
- if (showFormulaButton)
- (controller, toolbarIconSize, iconTheme, dialogTheme) => FormulaButton(
- icon: Icons.functions,
- iconSize: toolbarIconSize,
- controller: controller,
- iconTheme: iconTheme,
- dialogTheme: dialogTheme,
- )
- ];
- }
+ }) =>
+ [
+ if (showImageButton)
+ (controller, toolbarIconSize, iconTheme, dialogTheme) => ImageButton(
+ icon: Icons.image,
+ iconSize: toolbarIconSize,
+ tooltip: imageButtonTooltip,
+ controller: controller,
+ onImagePickCallback: onImagePickCallback,
+ filePickImpl: filePickImpl,
+ webImagePickImpl: webImagePickImpl,
+ mediaPickSettingSelector: mediaPickSettingSelector,
+ iconTheme: iconTheme,
+ dialogTheme: dialogTheme,
+ ),
+ if (showVideoButton)
+ (controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton(
+ icon: Icons.movie_creation,
+ iconSize: toolbarIconSize,
+ tooltip: videoButtonTooltip,
+ controller: controller,
+ onVideoPickCallback: onVideoPickCallback,
+ filePickImpl: filePickImpl,
+ webVideoPickImpl: webImagePickImpl,
+ mediaPickSettingSelector: mediaPickSettingSelector,
+ iconTheme: iconTheme,
+ dialogTheme: dialogTheme,
+ ),
+ if ((onImagePickCallback != null || onVideoPickCallback != null) &&
+ showCameraButton)
+ (controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton(
+ icon: Icons.photo_camera,
+ iconSize: toolbarIconSize,
+ tooltip: cameraButtonTooltip,
+ controller: controller,
+ onImagePickCallback: onImagePickCallback,
+ onVideoPickCallback: onVideoPickCallback,
+ filePickImpl: filePickImpl,
+ webImagePickImpl: webImagePickImpl,
+ webVideoPickImpl: webVideoPickImpl,
+ cameraPickSettingSelector: cameraPickSettingSelector,
+ iconTheme: iconTheme,
+ ),
+ if (showFormulaButton)
+ (controller, toolbarIconSize, iconTheme, dialogTheme) =>
+ FormulaButton(
+ icon: Icons.functions,
+ iconSize: toolbarIconSize,
+ tooltip: formulaButtonTooltip,
+ controller: controller,
+ iconTheme: iconTheme,
+ dialogTheme: dialogTheme,
+ )
+ ];
}
diff --git a/flutter_quill_extensions/lib/shims/dart_ui_fake.dart b/flutter_quill_extensions/lib/shims/dart_ui_fake.dart
new file mode 100644
index 00000000..baaf9ebd
--- /dev/null
+++ b/flutter_quill_extensions/lib/shims/dart_ui_fake.dart
@@ -0,0 +1,23 @@
+// ignore_for_file: avoid_classes_with_only_static_members, camel_case_types, lines_longer_than_80_chars
+
+import 'package:universal_html/html.dart' as html;
+
+// Fake interface for the logic that this package needs from (web-only) dart:ui.
+// This is conditionally exported so the analyzer sees these methods as available.
+
+typedef PlatroformViewFactory = html.Element Function(int viewId);
+
+/// Shim for web_ui engine.PlatformViewRegistry
+/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L62
+class platformViewRegistry {
+ /// Shim for registerViewFactory
+ /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L72
+ static dynamic registerViewFactory(
+ String viewTypeId, PlatroformViewFactory viewFactory) {}
+}
+
+/// Shim for web_ui engine.AssetManager
+/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/assets.dart#L12
+class webOnlyAssetManager {
+ static dynamic getAssetUrl(String asset) {}
+}
diff --git a/flutter_quill_extensions/lib/shims/dart_ui_real.dart b/flutter_quill_extensions/lib/shims/dart_ui_real.dart
new file mode 100644
index 00000000..69c06ee2
--- /dev/null
+++ b/flutter_quill_extensions/lib/shims/dart_ui_real.dart
@@ -0,0 +1 @@
+export 'dart:ui';
diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml
index 01a85962..f1b77e8c 100644
--- a/flutter_quill_extensions/pubspec.yaml
+++ b/flutter_quill_extensions/pubspec.yaml
@@ -1,30 +1,28 @@
name: flutter_quill_extensions
description: Embed extensions for flutter_quill including image, video, formula and etc.
-version: 0.1.0
+version: 0.3.3
homepage: https://bulletjournal.us/home/index.html
repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions
environment:
- sdk: ">=2.12.0 <3.0.0"
+ sdk: ">=2.17.0 <4.0.0"
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
- flutter_quill: ^6.0.0
+ flutter_quill: ^7.2.1
image_picker: ^0.8.5+3
photo_view: ^0.14.0
video_player: ^2.4.2
- youtube_player_flutter_quill: ^8.2.2
+ youtube_player_flutter: ^8.1.1
gallery_saver: ^2.3.2
- math_keyboard: ^0.1.6
- string_validator: ^0.3.0
-
-#dependency_overrides:
-# flutter_quill:
-# path: ../
+ math_keyboard: ^0.2.0
+ string_validator: ^1.0.0
+ universal_html: ^2.2.1
+ url_launcher: ^6.1.9
dev_dependencies:
flutter_test:
diff --git a/lib/extensions.dart b/lib/extensions.dart
index 12e961c2..b7c2b0af 100644
--- a/lib/extensions.dart
+++ b/lib/extensions.dart
@@ -4,3 +4,4 @@ export 'src/models/documents/nodes/leaf.dart' hide Text;
export 'src/models/rules/insert.dart';
export 'src/utils/platform.dart';
export 'src/utils/string.dart';
+export 'src/utils/widgets.dart';
diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart
index eae6c5de..bfc666f1 100644
--- a/lib/flutter_quill.dart
+++ b/lib/flutter_quill.dart
@@ -2,11 +2,18 @@ library flutter_quill;
export 'src/models/documents/attribute.dart';
export 'src/models/documents/document.dart';
+export 'src/models/documents/nodes/block.dart';
export 'src/models/documents/nodes/embeddable.dart';
export 'src/models/documents/nodes/leaf.dart';
+export 'src/models/documents/nodes/line.dart';
export 'src/models/documents/nodes/node.dart';
export 'src/models/documents/style.dart';
export 'src/models/quill_delta.dart';
+export 'src/models/structs/doc_change.dart';
+export 'src/models/structs/image_url.dart';
+export 'src/models/structs/offset_value.dart';
+export 'src/models/structs/optional_size.dart';
+export 'src/models/structs/vertical_spacing.dart';
export 'src/models/themes/quill_custom_button.dart';
export 'src/models/themes/quill_dialog_theme.dart';
export 'src/models/themes/quill_icon_theme.dart';
@@ -18,3 +25,4 @@ export 'src/widgets/embeds.dart';
export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction;
export 'src/widgets/style_widgets/style_widgets.dart';
export 'src/widgets/toolbar.dart';
+export 'src/widgets/toolbar/enum.dart';
diff --git a/lib/flutter_quill_test.dart b/lib/flutter_quill_test.dart
new file mode 100644
index 00000000..988e4e82
--- /dev/null
+++ b/lib/flutter_quill_test.dart
@@ -0,0 +1,3 @@
+library flutter_quill_test;
+
+export 'src/test/widget_tester_extension.dart';
diff --git a/lib/src/models/documents/attribute.dart b/lib/src/models/documents/attribute.dart
index 2ec7d7f2..c8d40360 100644
--- a/lib/src/models/documents/attribute.dart
+++ b/lib/src/models/documents/attribute.dart
@@ -19,6 +19,8 @@ class Attribute {
static final Map _registry = LinkedHashMap.of({
Attribute.bold.key: Attribute.bold,
+ Attribute.subscript.key: Attribute.subscript,
+ Attribute.superscript.key: Attribute.superscript,
Attribute.italic.key: Attribute.italic,
Attribute.small.key: Attribute.small,
Attribute.underline.key: Attribute.underline,
@@ -42,10 +44,18 @@ class Attribute {
Attribute.style.key: Attribute.style,
Attribute.token.key: Attribute.token,
Attribute.script.key: Attribute.script,
+ Attribute.image.key: Attribute.image,
+ Attribute.video.key: Attribute.video,
});
static const BoldAttribute bold = BoldAttribute();
+ static final ScriptAttribute subscript =
+ ScriptAttribute(ScriptAttributes.sub);
+
+ static final ScriptAttribute superscript =
+ ScriptAttribute(ScriptAttributes.sup);
+
static const ItalicAttribute italic = ItalicAttribute();
static const SmallAttribute small = SmallAttribute();
@@ -90,7 +100,7 @@ class Attribute {
static const TokenAttribute token = TokenAttribute('');
- static const ScriptAttribute script = ScriptAttribute('');
+ static final ScriptAttribute script = ScriptAttribute(null);
static const String mobileWidth = 'mobileWidth';
@@ -100,8 +110,14 @@ class Attribute {
static const String mobileAlignment = 'mobileAlignment';
+ static const ImageAttribute image = ImageAttribute(null);
+
+ static const VideoAttribute video = VideoAttribute(null);
+
static final Set inlineKeys = {
Attribute.bold.key,
+ Attribute.subscript.key,
+ Attribute.superscript.key,
Attribute.italic.key,
Attribute.small.key,
Attribute.underline.key,
@@ -138,6 +154,11 @@ class Attribute {
Attribute.blockQuote.key,
});
+ static final Set embedKeys = {
+ Attribute.image.key,
+ Attribute.video.key,
+ };
+
static const Attribute h1 = HeaderAttribute(level: 1);
static const Attribute h2 = HeaderAttribute(level: 2);
@@ -345,8 +366,26 @@ class TokenAttribute extends Attribute {
const TokenAttribute(String val) : super('token', AttributeScope.IGNORE, val);
}
-// `script` is supposed to be inline attribute but it is not supported yet
-class ScriptAttribute extends Attribute {
- const ScriptAttribute(String val)
- : super('script', AttributeScope.IGNORE, val);
+class ScriptAttribute extends Attribute {
+ ScriptAttribute(ScriptAttributes? val)
+ : super('script', AttributeScope.INLINE, val?.value);
+}
+
+enum ScriptAttributes {
+ sup('super'),
+ sub('sub');
+
+ const ScriptAttributes(this.value);
+
+ final String value;
+}
+
+class ImageAttribute extends Attribute {
+ const ImageAttribute(String? url)
+ : super('image', AttributeScope.EMBEDS, url);
+}
+
+class VideoAttribute extends Attribute {
+ const VideoAttribute(String? url)
+ : super('video', AttributeScope.EMBEDS, url);
}
diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart
index e3e2237f..597d9025 100644
--- a/lib/src/models/documents/document.dart
+++ b/lib/src/models/documents/document.dart
@@ -1,9 +1,11 @@
import 'dart:async';
-import 'package:tuple/tuple.dart';
-
import '../quill_delta.dart';
import '../rules/rule.dart';
+import '../structs/doc_change.dart';
+import '../structs/history_changed.dart';
+import '../structs/offset_value.dart';
+import '../structs/segment_leaf_node.dart';
import 'attribute.dart';
import 'history.dart';
import 'nodes/block.dart';
@@ -50,13 +52,12 @@ class Document {
_rules.setCustomRules(customRules);
}
- final StreamController> _observer =
- StreamController.broadcast();
+ final StreamController _observer = StreamController.broadcast();
final History _history = History();
- /// Stream of [Change]s applied to this document.
- Stream> get changes => _observer.stream;
+ /// Stream of [DocChange]s applied to this document.
+ Stream get changes => _observer.stream;
/// Inserts [data] in this document at specified [index].
///
@@ -158,7 +159,7 @@ class Document {
}
/// Returns all styles for each node within selection
- List> collectAllIndividualStyles(int index, int len) {
+ List> collectAllIndividualStyles(int index, int len) {
final res = queryChild(index);
return (res.node as Line).collectAllIndividualStyles(res.offset, len);
}
@@ -169,6 +170,12 @@ class Document {
return (res.node as Line).collectAllStyles(res.offset, len);
}
+ /// Returns all styles for any character within the specified text range.
+ List> collectAllStylesWithOffset(int index, int len) {
+ final res = queryChild(index);
+ return (res.node as Line).collectAllStylesWithOffsets(res.offset, len);
+ }
+
/// Returns plain text within the specified text range.
String getPlainText(int index, int len) {
final res = queryChild(index);
@@ -216,19 +223,15 @@ class Document {
}
/// Given offset, find its leaf node in document
- Tuple2 querySegmentLeafNode(int offset) {
+ SegmentLeafNode querySegmentLeafNode(int offset) {
final result = queryChild(offset);
if (result.node == null) {
- return const Tuple2(null, null);
+ return const SegmentLeafNode(null, null);
}
final line = result.node as Line;
final segmentResult = line.queryChild(result.offset, false);
- if (segmentResult.node == null) {
- return Tuple2(line, null);
- }
- final segment = segmentResult.node as Leaf;
- return Tuple2(line, segment);
+ return SegmentLeafNode(line, segmentResult.node as Leaf?);
}
/// Composes [change] Delta into this document.
@@ -276,16 +279,16 @@ class Document {
if (_delta != _root.toDelta()) {
throw 'Compose failed';
}
- final change = Tuple3(originalDelta, delta, changeSource);
+ final change = DocChange(originalDelta, delta, changeSource);
_observer.add(change);
_history.handleDocChange(change);
}
- Tuple2 undo() {
+ HistoryChanged undo() {
return _history.undo(this);
}
- Tuple2 redo() {
+ HistoryChanged redo() {
return _history.redo(this);
}
diff --git a/lib/src/models/documents/history.dart b/lib/src/models/documents/history.dart
index d406505e..fa87700c 100644
--- a/lib/src/models/documents/history.dart
+++ b/lib/src/models/documents/history.dart
@@ -1,6 +1,6 @@
-import 'package:tuple/tuple.dart';
-
import '../quill_delta.dart';
+import '../structs/doc_change.dart';
+import '../structs/history_changed.dart';
import 'document.dart';
class History {
@@ -32,12 +32,12 @@ class History {
///record delay
final int interval;
- void handleDocChange(Tuple3 change) {
+ void handleDocChange(DocChange docChange) {
if (ignoreChange) return;
- if (!userOnly || change.item3 == ChangeSource.LOCAL) {
- record(change.item2, change.item1);
+ if (!userOnly || docChange.source == ChangeSource.LOCAL) {
+ record(docChange.change, docChange.before);
} else {
- transform(change.item2);
+ transform(docChange.change);
}
}
@@ -85,9 +85,9 @@ class History {
}
}
- Tuple2 _change(Document doc, List source, List dest) {
+ HistoryChanged _change(Document doc, List source, List dest) {
if (source.isEmpty) {
- return const Tuple2(false, 0);
+ return const HistoryChanged(false, 0);
}
final delta = source.removeLast();
// look for insert or delete
@@ -107,14 +107,14 @@ class History {
ignoreChange = true;
doc.compose(delta, ChangeSource.LOCAL);
ignoreChange = false;
- return Tuple2(true, len);
+ return HistoryChanged(true, len);
}
- Tuple2 undo(Document doc) {
+ HistoryChanged undo(Document doc) {
return _change(doc, stack.undo, stack.redo);
}
- Tuple2 redo(Document doc) {
+ HistoryChanged redo(Document doc) {
return _change(doc, stack.redo, stack.undo);
}
}
diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart
index 3e3bbb04..42306edb 100644
--- a/lib/src/models/documents/nodes/line.dart
+++ b/lib/src/models/documents/nodes/line.dart
@@ -1,9 +1,9 @@
import 'dart:math' as math;
import 'package:collection/collection.dart';
-import 'package:tuple/tuple.dart';
import '../../quill_delta.dart';
+import '../../structs/offset_value.dart';
import '../attribute.dart';
import '../style.dart';
import 'block.dart';
@@ -381,7 +381,7 @@ class Line extends Container {
}
final remaining = len - local;
- if (remaining > 0) {
+ if (remaining > 0 && nextLine != null) {
final rest = nextLine!.collectStyle(0, remaining);
_handle(rest);
}
@@ -391,10 +391,10 @@ class Line extends Container {
/// Returns each node segment's offset in selection
/// with its corresponding style as a list
- List> collectAllIndividualStyles(int offset, int len,
+ List> collectAllIndividualStyles(int offset, int len,
{int beg = 0}) {
final local = math.min(length - offset, len);
- final result = >[];
+ final result = >[];
final data = queryChild(offset, true);
var node = data.node as Leaf?;
@@ -402,12 +402,12 @@ class Line extends Container {
var pos = 0;
if (node is Text) {
pos = node.length - data.offset;
- result.add(Tuple2(beg, node.style));
+ result.add(OffsetValue(beg, node.style));
}
while (!node!.isLast && pos < local) {
node = node.next as Leaf;
if (node is Text) {
- result.add(Tuple2(pos + beg, node.style));
+ result.add(OffsetValue(pos + beg, node.style));
pos += node.length;
}
}
@@ -416,7 +416,7 @@ class Line extends Container {
// TODO: add line style and parent's block style
final remaining = len - local;
- if (remaining > 0) {
+ if (remaining > 0 && nextLine != null) {
final rest =
nextLine!.collectAllIndividualStyles(0, remaining, beg: local);
result.addAll(rest);
@@ -450,7 +450,7 @@ class Line extends Container {
}
final remaining = len - local;
- if (remaining > 0) {
+ if (remaining > 0 && nextLine != null) {
final rest = nextLine!.collectAllStyles(0, remaining);
result.addAll(rest);
}
@@ -458,6 +458,44 @@ class Line extends Container {
return result;
}
+ /// Returns all styles for any character within the specified text range.
+ List> collectAllStylesWithOffsets(
+ int offset,
+ int len, {
+ int beg = 0,
+ }) {
+ final local = math.min(length - offset, len);
+ final result = >[];
+
+ final data = queryChild(offset, true);
+ var node = data.node as Leaf?;
+ if (node != null) {
+ var pos = 0;
+ pos = node.length - data.offset;
+ result.add(OffsetValue(node.documentOffset, node.style, node.length));
+ while (!node!.isLast && pos < local) {
+ node = node.next as Leaf;
+ result.add(OffsetValue(node.documentOffset, node.style, node.length));
+ pos += node.length;
+ }
+ }
+
+ result.add(OffsetValue(documentOffset, style, length));
+ if (parent is Block) {
+ final block = parent as Block;
+ result.add(OffsetValue(block.documentOffset, block.style, block.length));
+ }
+
+ final remaining = len - local;
+ if (remaining > 0 && nextLine != null) {
+ final rest =
+ nextLine!.collectAllStylesWithOffsets(0, remaining, beg: local);
+ result.addAll(rest);
+ }
+
+ return result;
+ }
+
/// Returns plain text within the specified text range.
String getPlainText(int offset, int len) {
final plainText = StringBuffer();
@@ -501,7 +539,7 @@ class Line extends Container {
}
}
- if (_len > 0) {
+ if (_len > 0 && nextLine != null) {
_len = nextLine!._getPlainText(0, _len, plainText);
}
}
diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart
index ef77e77a..44124fbc 100644
--- a/lib/src/models/rules/insert.dart
+++ b/lib/src/models/rules/insert.dart
@@ -1,5 +1,3 @@
-import 'package:tuple/tuple.dart';
-
import '../../models/documents/document.dart';
import '../documents/attribute.dart';
import '../documents/nodes/embeddable.dart';
@@ -37,26 +35,26 @@ class PreserveLineStyleOnSplitRule extends InsertRule {
final itr = DeltaIterator(document);
final before = itr.skip(index);
- if (before == null ||
- before.data is! String ||
- (before.data as String).endsWith('\n')) {
+ if (before == null) {
return null;
}
- final after = itr.next();
- if (after.data is! String || (after.data as String).startsWith('\n')) {
+ if (before.data is String && (before.data as String).endsWith('\n')) {
return null;
}
- final text = after.data as String;
+ final after = itr.next();
+ if (after.data is String && (after.data as String).startsWith('\n')) {
+ return null;
+ }
final delta = Delta()..retain(index + (len ?? 0));
- if (text.contains('\n')) {
+ if (after.data is String && (after.data as String).contains('\n')) {
assert(after.isPlain);
delta.insert('\n');
return delta;
}
final nextNewLine = _getNextNewLine(itr);
- final attributes = nextNewLine.item1?.attributes;
+ final attributes = nextNewLine.operation?.attributes;
return delta..insert('\n', attributes);
}
@@ -85,8 +83,8 @@ class PreserveBlockStyleOnInsertRule extends InsertRule {
// Look for the next newline.
final nextNewLine = _getNextNewLine(itr);
- final lineStyle =
- Style.fromJson(nextNewLine.item1?.attributes ?? {});
+ final lineStyle = Style.fromJson(
+ nextNewLine.operation?.attributes ?? {});
final blockStyle = lineStyle.getBlocksExceptHeader();
// Are we currently in a block? If not then ignore.
@@ -126,8 +124,8 @@ class PreserveBlockStyleOnInsertRule extends InsertRule {
// Reset style of the original newline character if needed.
if (resetStyle.isNotEmpty) {
delta
- ..retain(nextNewLine.item2!)
- ..retain((nextNewLine.item1!.data as String).indexOf('\n'))
+ ..retain(nextNewLine.skipped!)
+ ..retain((nextNewLine.operation!.data as String).indexOf('\n'))
..retain(1, resetStyle);
}
@@ -188,9 +186,10 @@ class AutoExitBlockRule extends InsertRule {
// Keep looking for the next newline character to see if it shares the same
// block style as `cur`.
final nextNewLine = _getNextNewLine(itr);
- if (nextNewLine.item1 != null &&
- nextNewLine.item1!.attributes != null &&
- Style.fromJson(nextNewLine.item1!.attributes).getBlockExceptHeader() ==
+ if (nextNewLine.operation != null &&
+ nextNewLine.operation!.attributes != null &&
+ Style.fromJson(nextNewLine.operation!.attributes)
+ .getBlockExceptHeader() ==
blockStyle) {
// We are not at the end of this block, ignore.
return null;
@@ -477,7 +476,7 @@ class PreserveInlineStylesRule extends InsertRule {
}
final itr = DeltaIterator(document);
- final prev = itr.skip(index);
+ final prev = itr.skip(len == 0 ? index : index + 1);
if (prev == null ||
prev.data is! String ||
(prev.data as String).contains('\n')) {
@@ -524,15 +523,22 @@ class CatchAllInsertRule extends InsertRule {
}
}
-Tuple2 _getNextNewLine(DeltaIterator iterator) {
+_NextNewLine _getNextNewLine(DeltaIterator iterator) {
Operation op;
for (var skipped = 0; iterator.hasNext; skipped += op.length!) {
op = iterator.next();
final lineBreak =
(op.data is String ? op.data as String? : '')!.indexOf('\n');
if (lineBreak >= 0) {
- return Tuple2(op, skipped);
+ return _NextNewLine(op, skipped);
}
}
- return const Tuple2(null, null);
+ return const _NextNewLine(null, null);
+}
+
+class _NextNewLine {
+ const _NextNewLine(this.operation, this.skipped);
+
+ final Operation? operation;
+ final int? skipped;
}
diff --git a/lib/src/models/structs/doc_change.dart b/lib/src/models/structs/doc_change.dart
new file mode 100644
index 00000000..d2772a59
--- /dev/null
+++ b/lib/src/models/structs/doc_change.dart
@@ -0,0 +1,19 @@
+import '../documents/document.dart';
+import '../quill_delta.dart';
+
+class DocChange {
+ DocChange(
+ this.before,
+ this.change,
+ this.source,
+ );
+
+ /// Document state before [change].
+ final Delta before;
+
+ /// Change delta applied to the document.
+ final Delta change;
+
+ /// The source of this change.
+ final ChangeSource source;
+}
diff --git a/lib/src/models/structs/history_changed.dart b/lib/src/models/structs/history_changed.dart
new file mode 100644
index 00000000..abb61567
--- /dev/null
+++ b/lib/src/models/structs/history_changed.dart
@@ -0,0 +1,9 @@
+class HistoryChanged {
+ const HistoryChanged(
+ this.changed,
+ this.len,
+ );
+
+ final bool changed;
+ final int? len;
+}
diff --git a/lib/src/models/structs/image_url.dart b/lib/src/models/structs/image_url.dart
new file mode 100644
index 00000000..097e199b
--- /dev/null
+++ b/lib/src/models/structs/image_url.dart
@@ -0,0 +1,9 @@
+class ImageUrl {
+ const ImageUrl(
+ this.url,
+ this.styleString,
+ );
+
+ final String url;
+ final String styleString;
+}
diff --git a/lib/src/models/structs/offset_value.dart b/lib/src/models/structs/offset_value.dart
new file mode 100644
index 00000000..58275458
--- /dev/null
+++ b/lib/src/models/structs/offset_value.dart
@@ -0,0 +1,6 @@
+class OffsetValue {
+ OffsetValue(this.offset, this.value, [this.length]);
+ final int offset;
+ final int? length;
+ final T value;
+}
diff --git a/lib/src/models/structs/optional_size.dart b/lib/src/models/structs/optional_size.dart
new file mode 100644
index 00000000..a887b468
--- /dev/null
+++ b/lib/src/models/structs/optional_size.dart
@@ -0,0 +1,14 @@
+class OptionalSize {
+ OptionalSize(
+ this.width,
+ this.height,
+ );
+
+ /// If non-null, requires the child to have exactly this width.
+ /// If null, the child is free to choose its own width.
+ final double? width;
+
+ /// If non-null, requires the child to have exactly this height.
+ /// If null, the child is free to choose its own height.
+ final double? height;
+}
diff --git a/lib/src/models/structs/segment_leaf_node.dart b/lib/src/models/structs/segment_leaf_node.dart
new file mode 100644
index 00000000..43921b93
--- /dev/null
+++ b/lib/src/models/structs/segment_leaf_node.dart
@@ -0,0 +1,9 @@
+import '../documents/nodes/leaf.dart';
+import '../documents/nodes/line.dart';
+
+class SegmentLeafNode {
+ const SegmentLeafNode(this.line, this.leaf);
+
+ final Line? line;
+ final Leaf? leaf;
+}
diff --git a/lib/src/models/structs/vertical_spacing.dart b/lib/src/models/structs/vertical_spacing.dart
new file mode 100644
index 00000000..54f76f7c
--- /dev/null
+++ b/lib/src/models/structs/vertical_spacing.dart
@@ -0,0 +1,9 @@
+class VerticalSpacing {
+ const VerticalSpacing(
+ this.top,
+ this.bottom,
+ );
+
+ final double top;
+ final double bottom;
+}
diff --git a/lib/src/models/themes/quill_custom_button.dart b/lib/src/models/themes/quill_custom_button.dart
index aadd28e8..6f5b5365 100644
--- a/lib/src/models/themes/quill_custom_button.dart
+++ b/lib/src/models/themes/quill_custom_button.dart
@@ -1,14 +1,26 @@
import 'package:flutter/material.dart';
class QuillCustomButton {
- const QuillCustomButton({this.icon, this.onTap, this.child});
+ const QuillCustomButton({
+ this.icon,
+ this.iconColor,
+ this.onTap,
+ this.tooltip,
+ this.child,
+ });
///The icon widget
final IconData? icon;
+ ///The icon color;
+ final Color? iconColor;
+
///The function when the icon is tapped
final VoidCallback? onTap;
///The customButton placeholder
final Widget? child;
+
+ /// The button tooltip.
+ final String? tooltip;
}
diff --git a/lib/src/models/themes/quill_dialog_theme.dart b/lib/src/models/themes/quill_dialog_theme.dart
index 795d35d5..1552c5f5 100644
--- a/lib/src/models/themes/quill_dialog_theme.dart
+++ b/lib/src/models/themes/quill_dialog_theme.dart
@@ -1,8 +1,21 @@
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-class QuillDialogTheme {
- QuillDialogTheme(
- {this.labelTextStyle, this.inputTextStyle, this.dialogBackgroundColor});
+/// Used to configure the dialog's look and feel.
+class QuillDialogTheme with Diagnosticable {
+ const QuillDialogTheme({
+ this.labelTextStyle,
+ this.inputTextStyle,
+ this.dialogBackgroundColor,
+ this.shape,
+ this.buttonStyle,
+ this.linkDialogConstraints,
+ this.linkDialogPadding = const EdgeInsets.all(16),
+ this.mediaSelectorDialogConstraints,
+ this.mediaSelectorDialogPadding = const EdgeInsets.all(16),
+ this.isWrappable = false,
+ this.runSpacing = 8.0,
+ }) : assert(runSpacing >= 0);
///The text style to use for the label shown in the link-input dialog
final TextStyle? labelTextStyle;
@@ -10,6 +23,105 @@ class QuillDialogTheme {
///The text style to use for the input text shown in the link-input dialog
final TextStyle? inputTextStyle;
- ///The background color for the [LinkDialog()]
+ ///The background color for the Quill dialog
final Color? dialogBackgroundColor;
+
+ /// The shape of this dialog's border.
+ ///
+ /// Defines the dialog's [Material.shape].
+ ///
+ /// The default shape is a [RoundedRectangleBorder] with a radius of 4.0
+ final ShapeBorder? shape;
+
+ /// Constrains for [LinkStyleDialog].
+ final BoxConstraints? linkDialogConstraints;
+
+ /// The padding for content of [LinkStyleDialog].
+ final EdgeInsetsGeometry linkDialogPadding;
+
+ /// Constrains for [MediaSourceSelectorDialog].
+ final BoxConstraints? mediaSelectorDialogConstraints;
+
+ /// The padding for content of [MediaSourceSelectorDialog].
+ final EdgeInsetsGeometry mediaSelectorDialogPadding;
+
+ /// Customizes this button's appearance.
+ final ButtonStyle? buttonStyle;
+
+ /// Whether dialog's children are wrappred with [Wrap] instead of [Row].
+ final bool isWrappable;
+
+ /// How much space to place between the runs themselves in the cross axis.
+ ///
+ /// Make sense if [isWrappable] is `true`.
+ ///
+ /// Defaults to 0.0.
+ final double runSpacing;
+
+ QuillDialogTheme copyWith({
+ TextStyle? labelTextStyle,
+ TextStyle? inputTextStyle,
+ Color? dialogBackgroundColor,
+ ShapeBorder? shape,
+ ButtonStyle? buttonStyle,
+ BoxConstraints? linkDialogConstraints,
+ EdgeInsetsGeometry? linkDialogPadding,
+ BoxConstraints? imageDialogConstraints,
+ EdgeInsetsGeometry? mediaDialogPadding,
+ bool? isWrappable,
+ double? runSpacing,
+ }) {
+ return QuillDialogTheme(
+ labelTextStyle: labelTextStyle ?? this.labelTextStyle,
+ inputTextStyle: inputTextStyle ?? this.inputTextStyle,
+ dialogBackgroundColor:
+ dialogBackgroundColor ?? this.dialogBackgroundColor,
+ shape: shape ?? this.shape,
+ buttonStyle: buttonStyle ?? this.buttonStyle,
+ linkDialogConstraints:
+ linkDialogConstraints ?? this.linkDialogConstraints,
+ linkDialogPadding: linkDialogPadding ?? this.linkDialogPadding,
+ mediaSelectorDialogConstraints:
+ imageDialogConstraints ?? mediaSelectorDialogConstraints,
+ mediaSelectorDialogPadding:
+ mediaDialogPadding ?? mediaSelectorDialogPadding,
+ isWrappable: isWrappable ?? this.isWrappable,
+ runSpacing: runSpacing ?? this.runSpacing,
+ );
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (other.runtimeType != runtimeType) {
+ return false;
+ }
+ return other is QuillDialogTheme &&
+ other.labelTextStyle == labelTextStyle &&
+ other.inputTextStyle == inputTextStyle &&
+ other.dialogBackgroundColor == dialogBackgroundColor &&
+ other.shape == shape &&
+ other.buttonStyle == buttonStyle &&
+ other.linkDialogConstraints == linkDialogConstraints &&
+ other.linkDialogPadding == linkDialogPadding &&
+ other.mediaSelectorDialogConstraints ==
+ mediaSelectorDialogConstraints &&
+ other.mediaSelectorDialogPadding == mediaSelectorDialogPadding &&
+ other.isWrappable == isWrappable &&
+ other.runSpacing == runSpacing;
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ labelTextStyle,
+ inputTextStyle,
+ dialogBackgroundColor,
+ shape,
+ buttonStyle,
+ linkDialogConstraints,
+ linkDialogPadding,
+ mediaSelectorDialogConstraints,
+ mediaSelectorDialogPadding,
+ isWrappable,
+ runSpacing,
+ );
}
diff --git a/lib/src/test/widget_tester_extension.dart b/lib/src/test/widget_tester_extension.dart
new file mode 100644
index 00000000..21bb75ab
--- /dev/null
+++ b/lib/src/test/widget_tester_extension.dart
@@ -0,0 +1,60 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../widgets/editor.dart';
+import '../widgets/raw_editor.dart';
+
+/// Extends
+extension QuillEnterText on WidgetTester {
+ /// Give the QuillEditor widget specified by [finder] the focus.
+ Future quillGiveFocus(Finder finder) {
+ return TestAsyncUtils.guard(() async {
+ final editor = state(
+ find.descendant(
+ of: finder,
+ matching:
+ find.byType(QuillEditor, skipOffstage: finder.skipOffstage),
+ matchRoot: true),
+ );
+ editor.widget.focusNode.requestFocus();
+ await pump();
+ expect(editor.widget.focusNode.hasFocus, isTrue);
+ });
+ }
+
+ /// Give the QuillEditor widget specified by [finder] the focus and update its
+ /// editing value with [text], as if it had been provided by the onscreen
+ /// keyboard.
+ ///
+ /// The widget specified by [finder] must be a [QuillEditor] or have a
+ /// [QuillEditor] descendant. For example `find.byType(QuillEditor)`.
+ Future quillEnterText(Finder finder, String text) async {
+ return TestAsyncUtils.guard(() async {
+ await quillGiveFocus(finder);
+ await quillUpdateEditingValue(finder, text);
+ await idle();
+ });
+ }
+
+ /// Update the text editing value of the QuillEditor widget specified by
+ /// [finder] with [text], as if it had been provided by the onscreen keyboard.
+ ///
+ /// The widget specified by [finder] must already have focus and be a
+ /// [QuillEditor] or have a [QuillEditor] descendant. For example
+ /// `find.byType(QuillEditor)`.
+ Future quillUpdateEditingValue(Finder finder, String text) async {
+ return TestAsyncUtils.guard(() async {
+ final editor = state(
+ find.descendant(
+ of: finder,
+ matching: find.byType(RawEditor, skipOffstage: finder.skipOffstage),
+ matchRoot: true),
+ );
+ testTextInput.updateEditingValue(TextEditingValue(
+ text: text,
+ selection: TextSelection.collapsed(
+ offset: editor.textEditingValue.text.length)));
+ await idle();
+ });
+ }
+}
diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart
index d0af83f0..be92116c 100644
--- a/lib/src/translations/toolbar.i18n.dart
+++ b/lib/src/translations/toolbar.i18n.dart
@@ -35,6 +35,42 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
+ 'Hex': 'Hex',
+ 'Material': 'Material',
+ 'Color': 'Color',
},
'en_us': {
'Paste a link': 'Paste a link',
@@ -68,39 +104,113 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
+ 'Hex': 'Hex',
+ 'Material': 'Material',
+ 'Color': 'Color',
},
'ar': {
'Paste a link': 'نسخ الرابط',
'Ok': 'نعم',
'Select Color': 'اختار اللون',
- 'Gallery': 'الصور',
+ 'Gallery': 'المعرض',
'Link': 'الرابط',
'Please first select some text to transform into a link.':
'يرجى اختيار نص للتحويل إلى رابط',
'Open': 'فتح',
- 'Copy': 'ينسخ',
+ 'Copy': 'نسخ',
'Remove': 'إزالة',
- 'Save': 'يحفظ',
+ 'Save': 'حفظ',
'Zoom': 'تكبير',
- 'Saved': 'أنقذ',
- 'Text': 'Text',
- 'What is entered is not a link': 'What is entered is not a link',
- 'Resize': 'Resize',
- 'Width': 'Width',
- 'Height': 'Height',
- 'Size': 'Size',
- 'Small': 'Small',
- 'Large': 'Large',
- 'Huge': 'Huge',
- 'Clear': 'Clear',
- 'Font': 'Font',
- 'Search': 'Search',
- 'matches': 'matches',
- 'showing match': 'showing match',
- 'Prev': 'Prev',
- 'Next': 'Next',
- 'Camera': 'Camera',
- 'Video': 'Video',
+ 'Saved': 'تم الحفظ',
+ 'Text': 'نص',
+ 'What is entered is not a link': 'ما تم ادخاله ليس رابط',
+ 'Resize': 'تحجيم',
+ 'Width': 'عرض',
+ 'Height': 'ارتفاع',
+ 'Size': 'حجم',
+ 'Small': 'صغير',
+ 'Large': 'كبير',
+ 'Huge': 'ضخم',
+ 'Clear': 'تنظيف',
+ 'Font': 'خط',
+ 'Search': 'بحث',
+ 'matches': 'تطابق',
+ 'showing match': 'عرض التطابق',
+ 'Prev': 'سابق',
+ 'Next': 'تالي',
+ 'Camera': 'كاميرا',
+ 'Video': 'فيديو',
+ 'Undo': 'تراجع',
+ 'Redo': 'تقدم',
+ 'Font family': 'عائلة الخط',
+ 'Font size': 'حجم الخط',
+ 'Bold': 'عريض',
+ 'Subscript': 'نص سفلي',
+ 'Superscript': 'نص علوي',
+ 'Italic': 'مائل',
+ 'Underline': 'تحته خط',
+ 'Strike through': 'داخله خط',
+ 'Inline code': 'كود بوسط السطر',
+ 'Font color': 'لون الخط',
+ 'Background color': 'لون الخلفية',
+ 'Clear format': 'تنظيف التنسيق',
+ 'Align left': 'محاذاة اليسار',
+ 'Align center': 'محاذاة الوسط',
+ 'Align right': 'محاذاة اليمين',
+ // i think it should be 'Justify with width'
+ // it is wrong in all properties
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'اتجاه النص',
+ 'Header style': 'ستايل العنوان',
+ 'Numbered list': 'قائمة مرقمة',
+ 'Bullet list': 'قائمة منقطة',
+ 'Checked list': 'قائمة للمهام',
+ 'Code block': 'كود كامل',
+ 'Quote': 'اقتباس',
+ 'Increase indent': 'زيادة الهامش',
+ 'Decrease indent': 'تنقيص الهامش',
+ 'Insert URL': 'ادخل عنوان رابط',
+ 'Visit link': 'زيارة الرابط',
+ 'Enter link': 'ادخل رابط',
+ 'Enter media': 'ادخل وسائط',
+ 'Edit': 'تعديل',
+ 'Apply': 'تطبيق',
+ 'Hex': 'Hex',
+ 'Material': 'Material',
+ 'Color': 'اللون',
},
'da': {
'Paste a link': 'Indsæt link',
@@ -134,6 +244,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'de': {
'Paste a link': 'Link hinzufügen',
@@ -168,6 +311,39 @@ extension Localization on String {
'Next': 'Nächster',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'fr': {
'Paste a link': 'Coller un lien',
@@ -178,29 +354,62 @@ extension Localization on String {
'Please first select some text to transform into a link.':
"Veuillez d'abord sélectionner un texte à transformer en lien.",
'Open': 'Ouverte',
- 'Copy': 'Copie',
+ 'Copy': 'Copier',
'Remove': 'Supprimer',
'Save': 'Sauvegarder',
'Zoom': 'Zoom',
'Saved': 'Enregistrée',
'Text': 'Text',
- 'What is entered is not a link': 'What is entered is not a link',
- 'Resize': 'Resize',
- 'Width': 'Width',
- 'Height': 'Height',
- 'Size': 'Size',
- 'Small': 'Small',
- 'Large': 'Large',
- 'Huge': 'Huge',
- 'Clear': 'Clear',
- 'Font': 'Font',
- 'Search': 'Search',
- 'matches': 'matches',
- 'showing match': 'showing match',
- 'Prev': 'Prev',
- 'Next': 'Next',
- 'Camera': 'Camera',
- 'Video': 'Video',
+ 'What is entered is not a link': "Ce qui est saisi n'est pas un lien",
+ 'Resize': 'Redimensionner',
+ 'Width': 'Largeur',
+ 'Height': 'Hauteur',
+ 'Size': 'Taille',
+ 'Small': 'Petit',
+ 'Large': 'Grand',
+ 'Huge': 'Énorme',
+ 'Clear': 'Supprimer la mise en forme',
+ 'Font': 'Police',
+ 'Search': 'Rechercher',
+ 'matches': 'correspondances',
+ 'showing match': 'voir la correspondance',
+ 'Prev': 'Précédent',
+ 'Next': 'Suivant',
+ 'Camera': 'Caméra',
+ 'Video': 'Vidéo',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'zh_cn': {
'Paste a link': '粘贴链接',
@@ -209,7 +418,7 @@ extension Localization on String {
'Gallery': '相簿',
'Link': '链接',
'Please first select some text to transform into a link.':
- '请先选择一些要转化为链接的文本',
+ '请先选择需转化为链接的文本',
'Open': '打开',
'Copy': '复制',
'Remove': '移除',
@@ -222,18 +431,183 @@ extension Localization on String {
'Width': '宽度',
'Height': '高度',
'Size': '文字大小',
- 'Small': 'Small',
- 'Large': 'Large',
- 'Huge': 'Huge',
- 'Clear': 'Clear',
- 'Font': 'Font',
- 'Search': 'Search',
- 'matches': 'matches',
- 'showing match': 'showing match',
- 'Prev': 'Prev',
- 'Next': 'Next',
- 'Camera': 'Camera',
- 'Video': 'Video',
+ 'Small': '小字号',
+ 'Large': '大字号',
+ 'Huge': '超大字号',
+ 'Clear': '清除',
+ 'Font': '字体',
+ 'Search': '搜索',
+ 'matches': '结果',
+ 'showing match': '显示结果',
+ 'Prev': '上一个',
+ 'Next': '下一个',
+ 'Camera': '拍照',
+ 'Video': '录像',
+ 'Undo': '撤销',
+ 'Redo': '重做',
+ 'Font family': '字体',
+ 'Font size': '字号',
+ 'Bold': '粗体',
+ 'Subscript': '下标',
+ 'Superscript': '上标',
+ 'Italic': '斜体',
+ 'Underline': '下划线',
+ 'Strike through': '删除线',
+ 'Inline code': '内联代码',
+ 'Font color': '字体颜色',
+ 'Background color': '背景颜色',
+ 'Clear format': '清除格式',
+ 'Align left': '左对齐',
+ 'Align center': '居中对齐',
+ 'Align right': '右对齐',
+ 'Justify win width': '两端对齐',
+ 'Text direction': '文本方向',
+ 'Header style': '标题样式',
+ 'Numbered list': '有序列表',
+ 'Bullet list': '无序列表',
+ 'Checked list': '任务列表',
+ 'Code block': '代码块',
+ 'Quote': '引言',
+ 'Increase indent': '增加缩进',
+ 'Decrease indent': '减少缩进',
+ 'Insert URL': '插入链接',
+ 'Visit link': '访问链接',
+ 'Enter link': '输入链接',
+ 'Enter media': '输入媒体',
+ 'Edit': '编辑',
+ 'Apply': '应用',
+ },
+ 'zh_hk': {
+ 'Paste a link': '貼上連結',
+ 'Ok': '確定',
+ 'Select Color': '選擇顏色',
+ 'Gallery': '圖片庫',
+ 'Link': '連結',
+ 'Please first select some text to transform into a link.':
+ '請選擇文字以轉換為連結。',
+ 'Open': '開啓',
+ 'Copy': '複製',
+ 'Remove': '移除',
+ 'Save': '儲存',
+ 'Zoom': '放大',
+ 'Saved': '已儲存',
+ 'Text': '文字',
+ 'What is entered is not a link': '輸入的不是連結',
+ 'Resize': '變更大小',
+ 'Width': '寛',
+ 'Height': '高',
+ 'Size': '大小',
+ 'Small': '小',
+ 'Large': '大',
+ 'Huge': '超大',
+ 'Clear': '清除',
+ 'Font': '字型',
+ 'Search': '搜尋',
+ 'matches': '符合',
+ 'showing match': '顯示符合',
+ 'Prev': '上一個',
+ 'Next': '下一個',
+ 'Camera': '相機',
+ 'Video': '錄影',
+ 'Undo': '撤銷',
+ 'Redo': '重做',
+ 'Font family': '字體',
+ 'Font size': '字號',
+ 'Bold': '粗體',
+ 'Subscript': '下標',
+ 'Superscript': '上標',
+ 'Italic': '斜體',
+ 'Underline': '下劃線',
+ 'Strike through': '刪除線',
+ 'Inline code': '內聯代碼',
+ 'Font color': '字體顏色',
+ 'Background color': '背景顏色',
+ 'Clear format': '清除格式',
+ 'Align left': '左對齊',
+ 'Align center': '居中對齊',
+ 'Align right': '右對齊',
+ 'Justify win width': '兩端對齊',
+ 'Text direction': '文本方向',
+ 'Header style': '標題樣式',
+ 'Numbered list': '有序列表',
+ 'Bullet list': '無序列表',
+ 'Checked list': '任務列表',
+ 'Code block': '代碼塊',
+ 'Quote': '引言',
+ 'Increase indent': '增加縮進',
+ 'Decrease indent': '減少縮進',
+ 'Insert URL': '插入鏈接',
+ 'Visit link': '訪問鏈接',
+ 'Enter link': '輸入鏈接',
+ 'Enter media': '輸入媒體',
+ 'Edit': '編輯',
+ 'Apply': '應用',
+ },
+ 'ja': {
+ 'Paste a link': 'リンクをペースト',
+ 'Ok': '完了',
+ 'Select Color': '色を選択',
+ 'Gallery': '写真集',
+ 'Link': 'リンク',
+ 'Please first select some text to transform into a link.':
+ 'まずリンクに変換する文字を選択してください.',
+ 'Open': '開く',
+ 'Copy': 'コピー',
+ 'Remove': '削除',
+ 'Save': '保存',
+ 'Zoom': '拡大',
+ 'Saved': '保存済み',
+ 'Text': '文字',
+ 'What is entered is not a link': '入力されたのはリンクではありません',
+ 'Resize': 'サイズを調整',
+ 'Width': '幅',
+ 'Height': '高さ',
+ 'Size': 'サイズ',
+ 'Small': '小さい',
+ 'Large': '大きい',
+ 'Huge': 'でっかい',
+ 'Clear': 'クリア',
+ 'Font': 'フォント',
+ 'Search': '検索',
+ 'matches': '結果',
+ 'showing match': '結果を表示',
+ 'Prev': '前へ',
+ 'Next': '次へ',
+ 'Camera': 'カメラ',
+ 'Video': 'ビデオ',
+ 'Undo': '取り消し',
+ 'Redo': 'やり直し',
+ 'Font family': 'フォントファミリー',
+ 'Font size': 'フォントサイズ',
+ 'Bold': '太字',
+ 'Subscript': '下付き',
+ 'Superscript': '上付き',
+ 'Italic': '斜体',
+ 'Underline': '下線',
+ 'Strike through': '取り消し線',
+ 'Inline code': 'インラインコード',
+ 'Font color': 'フォントカラー',
+ 'Background color': 'ベースカラー',
+ 'Clear format': 'クリアフォーマット',
+ 'Align left': '左揃え',
+ 'Align center': 'センターアライメント',
+ 'Align right': '右揃え',
+ 'Justify win width': '両端揃え',
+ 'Text direction': '文字方向',
+ 'Header style': 'タイトルスタイル',
+ 'Numbered list': '順序付きリスト',
+ 'Bullet list': '順序無しリスト',
+ 'Checked list': 'チェックボックス',
+ 'Code block': 'コード',
+ 'Quote': '引用',
+ 'Increase indent': 'インデントを増やす',
+ 'Decrease indent': 'インデントを減らす',
+ 'Insert URL': 'ハイパーリンクを挿入',
+ 'Visit link': 'ハイパーリンクを訪問',
+ 'Enter link': 'ハイパーリンクを輸入',
+ 'Enter media': 'ミディアムを輸入',
+ 'Edit': '編集',
+ 'Apply': '応用',
},
'ko': {
'Paste a link': '링크를 붙여넣어 주세요.',
@@ -251,21 +625,55 @@ extension Localization on String {
'Saved': '저장되었습니다.',
'Text': '텍스트',
'What is entered is not a link': '입력한 내용은 링크가 아닙니다.',
- 'Resize': 'Resize',
- 'Width': 'Width',
- 'Height': 'Height',
- 'Small': 'Small',
- 'Large': 'Large',
- 'Huge': 'Huge',
- 'Clear': 'Clear',
- 'Font': 'Font',
- 'Search': 'Search',
- 'matches': 'matches',
- 'showing match': 'showing match',
- 'Prev': 'Prev',
- 'Next': 'Next',
- 'Camera': 'Camera',
- 'Video': 'Video',
+ 'Resize': '크기조정',
+ 'Width': '넓이',
+ 'Height': '높이',
+ 'Size': '크기',
+ 'Small': '작게',
+ 'Large': '크게',
+ 'Huge': '매우크게',
+ 'Clear': '초기화',
+ 'Font': '글꼴',
+ 'Search': '검색',
+ 'matches': '결과',
+ 'showing match': '결과 보기',
+ 'Prev': '이전',
+ 'Next': '다음',
+ 'Camera': '카메라',
+ 'Video': '비디오',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'ru': {
'Paste a link': 'Вставить ссылку',
@@ -299,6 +707,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'es': {
'Paste a link': 'Pega un enlace',
@@ -333,6 +774,39 @@ extension Localization on String {
'Next': 'Siguiente',
'Camera': 'Cámara',
'Video': 'Vídeo',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'tr': {
'Paste a link': 'Bağlantıyı Yapıştır',
@@ -351,21 +825,54 @@ extension Localization on String {
'Text': 'Text',
'What is entered is not a link': 'What is entered is not a link',
'Resize': 'Resize',
- 'Width': 'Width',
- 'Height': 'Height',
- 'Size': 'Size',
- 'Small': 'Small',
- 'Large': 'Large',
- 'Huge': 'Huge',
- 'Clear': 'Clear',
- 'Font': 'Font',
- 'Search': 'Search',
+ 'Width': 'Genişlik',
+ 'Height': 'Yükseklik',
+ 'Size': 'Boyut',
+ 'Small': 'Küçük',
+ 'Large': 'Büyük',
+ 'Huge': 'Daha Büyük',
+ 'Clear': 'Temizle',
+ 'Font': 'Yazı tipi',
+ 'Search': 'Ara',
'matches': 'matches',
'showing match': 'showing match',
'Prev': 'Prev',
- 'Next': 'Next',
- 'Camera': 'Camera',
+ 'Next': 'Devam',
+ 'Camera': 'Kamera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'uk': {
'Paste a link': 'Вставити посилання',
@@ -399,6 +906,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'pt': {
'Paste a link': 'Colar um link',
@@ -433,6 +973,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'pt_br': {
'Paste a link': 'Colar um link',
@@ -461,12 +1034,45 @@ extension Localization on String {
'Clear': 'Limpar',
'Font': 'Fonte',
'Search': 'Buscar',
- 'matches': 'matches',
- 'showing match': 'showing match',
+ 'matches': 'resultado(s)',
+ 'showing match': 'mostrando resultado',
'Prev': 'Anterior',
'Next': 'Próximo',
- 'Camera': 'Camera',
+ 'Camera': 'Câmera',
'Video': 'Vídeo',
+ 'Undo': 'Desfazer',
+ 'Redo': 'Refazer',
+ 'Font family': 'Fonte',
+ 'Font size': 'Tamanho da fonte',
+ 'Bold': 'Negrito',
+ 'Subscript': 'Subscrito',
+ 'Superscript': 'Sobrescrito',
+ 'Italic': 'Itálico',
+ 'Underline': 'Sublinhado',
+ 'Strike through': 'Tachado',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Cor da fonte',
+ 'Background color': 'Cor do fundo',
+ 'Clear format': 'Limpar formatação',
+ 'Align left': 'Texto à esquerda',
+ 'Align center': 'Centralizar',
+ 'Align right': 'Texto à direita',
+ 'Justify win width': 'Justificado',
+ 'Text direction': 'Direção do texto',
+ 'Header style': 'Estilo de cabeçalho',
+ 'Numbered list': 'Numeração',
+ 'Bullet list': 'Marcadores',
+ 'Checked list': 'Lista de verificação',
+ 'Code block': 'Code block',
+ 'Quote': 'Citação',
+ 'Increase indent': 'Aumentar recuo',
+ 'Decrease indent': 'Diminuir recuo',
+ 'Insert URL': 'Inserir URL',
+ 'Visit link': 'Visitar link',
+ 'Enter link': 'Inserir link',
+ 'Enter media': 'Inserir mídia',
+ 'Edit': 'Editar',
+ 'Apply': 'Aplicar',
},
'pl': {
'Paste a link': 'Wklej link',
@@ -501,6 +1107,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'vi': {
'Paste a link': 'Chèn liên kết',
@@ -535,6 +1174,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'ur': {
'Paste a link': 'لنک پیسٹ کریں',
@@ -568,6 +1240,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'id': {
'Paste a link': 'Tempel tautan',
@@ -601,6 +1306,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'no': {
'Paste a link': 'Lim inn lenke',
@@ -634,10 +1372,43 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'fa': {
'Paste a link': 'جایگذاری لینک',
- 'Ok': 'اوکی',
+ 'Ok': 'تایید',
'Select Color': 'انتخاب رنگ',
'Gallery': 'گالری',
'Link': 'لینک',
@@ -650,7 +1421,7 @@ extension Localization on String {
'Zoom': 'بزرگنمایی',
'Saved': 'ذخیره شد',
'Text': 'متن',
- 'What is entered is not a link': 'ورودی وارد شده لینک نمی باشد',
+ 'What is entered is not a link': 'لینک وارد شده معتبر نمی باشد',
'Resize': 'تغییر اندازه',
'Width': 'عرض',
'Height': 'طول',
@@ -667,6 +1438,39 @@ extension Localization on String {
'Next': 'بعدی',
'Camera': 'دوربین',
'Video': 'ویدیو',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'hi': {
'Paste a link': 'लिंक पेस्ट करें',
@@ -700,6 +1504,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'nl': {
'Paste a link': 'Plak een link',
@@ -733,39 +1570,39 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
- },
- 'zh_hk': {
- 'Paste a link': '貼上連結',
- 'Ok': '確定',
- 'Select Color': '選擇顏色',
- 'Gallery': '圖片庫',
- 'Link': '連結',
- 'Please first select some text to transform into a link.':
- '請選擇文字以轉換為連結。',
- 'Open': '開啓',
- 'Copy': '複製',
- 'Remove': '移除',
- 'Save': '儲存',
- 'Zoom': '放大',
- 'Saved': '已儲存',
- 'Text': '文字',
- 'What is entered is not a link': '輸入的不是連結',
- 'Resize': '變更大小',
- 'Width': '寛',
- 'Height': '高',
- 'Size': '大小',
- 'Small': '小',
- 'Large': '大',
- 'Huge': '超大',
- 'Clear': '清除',
- 'Font': '字型',
- 'Search': '搜尋',
- 'matches': '符合',
- 'showing match': '顯示符合',
- 'Prev': '上一個',
- 'Next': '下一個',
- 'Camera': '相機',
- 'Video': '錄影',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
'sr': {
'Paste a link': 'Nalepi vezu',
@@ -799,6 +1636,305 @@ extension Localization on String {
'Next': 'Next',
'Camera': 'Camera',
'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
+ },
+ 'cs': {
+ 'Paste a link': 'Vložte odkaz',
+ 'Ok': 'Ok',
+ 'Select Color': 'Vyberte barvu',
+ 'Gallery': 'Galerie',
+ 'Link': 'Odkaz',
+ 'Please first select some text to transform into a link.':
+ 'Nejprve vyberte nějaký text, který chcete převést na odkaz.',
+ 'Open': 'Otevřít',
+ 'Copy': 'Kopírovat',
+ 'Remove': 'Odstranit',
+ 'Save': 'Uložit',
+ 'Zoom': 'Přiblížit',
+ 'Saved': 'Uloženo',
+ 'Text': 'Text',
+ 'What is entered is not a link': 'Zadaný vstup není odkaz',
+ 'Resize': 'Změnit velikost',
+ 'Width': 'Šířka',
+ 'Height': 'Výška',
+ 'Size': 'Velikost',
+ 'Small': 'Malý',
+ 'Large': 'Velký',
+ 'Huge': 'Obrovský',
+ 'Clear': 'Smazat',
+ 'Font': 'Písmo',
+ 'Search': 'Hledat',
+ 'matches': 'odpovídá',
+ 'showing match': 'zobrazuje odpovídající',
+ 'Prev': 'Předchozí',
+ 'Next': 'Další',
+ 'Camera': 'Kamera',
+ 'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
+ },
+ 'he': {
+ 'Paste a link': 'הדבק את הלינק',
+ 'Ok': 'אוקי',
+ 'Select Color': 'בחר צבע',
+ 'Gallery': 'גלריה',
+ 'Link': 'לינק',
+ 'Please first select some text to transform into a link.':
+ 'בבקשה תחילה בחר טקסט להפיכה ללינק',
+ 'Open': 'פתח',
+ 'Copy': 'העתק',
+ 'Remove': 'מחק',
+ 'Save': 'שמור',
+ 'Zoom': 'זום',
+ 'Saved': 'נשמר',
+ 'Text': 'טקסט',
+ 'What is entered is not a link': 'מה שהוכנס הוא לא לינק',
+ 'Resize': 'שנה גודל',
+ 'Width': 'רוחב',
+ 'Height': 'גובה',
+ 'Size': 'גודל',
+ 'Small': 'קטן',
+ 'Large': 'גדול',
+ 'Huge': 'ענק',
+ 'Clear': 'מחוק',
+ 'Font': 'פונט',
+ 'Search': 'חפש',
+ 'matches': 'תוצאות',
+ 'showing match': 'מציג תוצאות',
+ 'Prev': 'הקודם',
+ 'Next': 'הבא',
+ 'Camera': 'מצלמה',
+ 'Video': 'וידאו',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
+ },
+ 'ms': {
+ 'Paste a link': 'Tampal Pautan',
+ 'Ok': 'Ok',
+ 'Select Color': 'Pilih Warna',
+ 'Gallery': 'Galeri',
+ 'Link': 'Pautan',
+ 'Please first select some text to transform into a link.':
+ 'Sila pilih beberapa patah perkataan'
+ ' untuk diubah menjadi pautan.',
+ 'Open': 'Buka',
+ 'Copy': 'Salin',
+ 'Remove': 'Buang',
+ 'Save': 'Simpan',
+ 'Zoom': 'Zum',
+ 'Saved': 'Telah Disimpan',
+ 'Text': 'Perkataan',
+ 'What is entered is not a link': 'Apa yang diisi bukan pautan',
+ 'Resize': 'Ubah saiz',
+ 'Width': 'Lebar',
+ 'Height': 'Tinggi',
+ 'Size': 'Saiz',
+ 'Small': 'Kecil',
+ 'Large': 'Besar',
+ 'Huge': 'Amat Besar',
+ 'Clear': 'Padam',
+ 'Font': 'Fon',
+ 'Search': 'Carian',
+ 'matches': 'padanan',
+ 'showing match': 'menunjukkan padanan',
+ 'Prev': 'Sebelum',
+ 'Next': 'Seterusnya',
+ 'Camera': 'Kamera',
+ 'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
+ },
+ 'it': {
+ 'Paste a link': 'Incolla un collegamento',
+ 'Ok': 'Ok',
+ 'Select Color': 'Seleziona Colore',
+ 'Gallery': 'Galleria',
+ 'Link': 'Collegamento',
+ 'Please first select some text to transform into a link.':
+ 'Per prima cosa seleziona del testo da trasformare in un link.',
+ 'Open': 'Apri',
+ 'Copy': 'Copia',
+ 'Remove': 'Rimuovi',
+ 'Save': 'Salva',
+ 'Zoom': 'Ingrandisci',
+ 'Saved': 'Salvato',
+ 'Text': 'Testo',
+ 'What is entered is not a link':
+ 'Ciò che viene inserito non è un collegamento',
+ 'Resize': 'Ridimensiona',
+ 'Width': 'Larghezza',
+ 'Height': 'Altezza',
+ 'Size': 'Dimensione',
+ 'Small': 'Piccolo',
+ 'Large': 'Largo',
+ 'Huge': 'Enorme',
+ 'Clear': 'Cancella',
+ 'Font': 'Font',
+ 'Search': 'Ricerca',
+ 'matches': 'corrispondenze',
+ 'showing match': 'visualizza corrispondenza',
+ 'Prev': 'Prec',
+ 'Next': 'Succ',
+ 'Camera': 'Camera',
+ 'Video': 'Video',
+ 'Undo': 'Undo',
+ 'Redo': 'Redo',
+ 'Font family': 'Font family',
+ 'Font size': 'Font size',
+ 'Bold': 'Bold',
+ 'Subscript': 'Subscript',
+ 'Superscript': 'Superscript',
+ 'Italic': 'Italic',
+ 'Underline': 'Underline',
+ 'Strike through': 'Strike through',
+ 'Inline code': 'Inline code',
+ 'Font color': 'Font color',
+ 'Background color': 'Background color',
+ 'Clear format': 'Clear format',
+ 'Align left': 'Align left',
+ 'Align center': 'Align center',
+ 'Align right': 'Align right',
+ 'Justify win width': 'Justify win width',
+ 'Text direction': 'Text direction',
+ 'Header style': 'Header style',
+ 'Numbered list': 'Numbered list',
+ 'Bullet list': 'Bullet list',
+ 'Checked list': 'Checked list',
+ 'Code block': 'Code block',
+ 'Quote': 'Quote',
+ 'Increase indent': 'Increase indent',
+ 'Decrease indent': 'Decrease indent',
+ 'Insert URL': 'Insert URL',
+ 'Visit link': 'Visit link',
+ 'Enter link': 'Enter link',
+ 'Enter media': 'Enter media',
+ 'Edit': 'Edit',
+ 'Apply': 'Apply',
},
};
diff --git a/lib/src/utils/cast.dart b/lib/src/utils/cast.dart
new file mode 100644
index 00000000..9bc50fdb
--- /dev/null
+++ b/lib/src/utils/cast.dart
@@ -0,0 +1 @@
+T? castOrNull(dynamic x) => x is T ? x : null;
diff --git a/lib/src/utils/embeds.dart b/lib/src/utils/embeds.dart
index 7ee2dd0d..db693ba8 100644
--- a/lib/src/utils/embeds.dart
+++ b/lib/src/utils/embeds.dart
@@ -1,11 +1,10 @@
import 'dart:math';
-import 'package:tuple/tuple.dart';
-
import '../models/documents/nodes/leaf.dart';
+import '../models/structs/offset_value.dart';
import '../widgets/controller.dart';
-Tuple2 getEmbedNode(QuillController controller, int offset) {
+OffsetValue