Merge pull request #2 from singerdmx/master

Merging master from origin
pull/756/head
shiju80 3 years ago committed by GitHub
commit 19055de317
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      CHANGELOG.md
  2. 1
      README.md
  3. 2
      lib/src/models/documents/document.dart
  4. 8
      lib/src/models/rules/insert.dart
  5. 16
      lib/src/widgets/editor.dart
  6. 47
      lib/src/widgets/embeds/default_embed_builder.dart
  7. 31
      lib/src/widgets/embeds/image.dart
  8. 7
      lib/src/widgets/raw_editor.dart
  9. 7
      lib/src/widgets/toolbar.dart
  10. 4
      pubspec.yaml

@ -1,3 +1,9 @@
# [3.9.6]
* Apply locale to QuillEditor(contents).
# [3.9.5]
* Fix image pasting.
# [3.9.4] # [3.9.4]
* Hiding dialog after selecting action for image. * Hiding dialog after selecting action for image.

@ -114,6 +114,7 @@ Define `mobileWidth`, `mobileHeight`, `mobileMargin`, `mobileAlignment` as follo
The package offers translations for the quill toolbar, it will follow the system locale unless you set your own locale with: The package offers translations for the quill toolbar, it will follow the system locale unless you set your own locale with:
``` ```
QuillToolbar(locale: Locale('fr'), ...) QuillToolbar(locale: Locale('fr'), ...)
QuillEditor(locale: Locale('fr'), ...)
``` ```
Currently, translations are available for these locales: Currently, translations are available for these locales:
* `Locale('en')` * `Locale('en')`

@ -270,7 +270,7 @@ class Document {
for (var i = 0; i < ops.length; i++) { for (var i = 0; i < ops.length; i++) {
final op = ops[i]; final op = ops[i];
res.push(op); res.push(op);
_autoAppendNewlineAfterEmbeddable(i, ops, op, res, 'video'); _autoAppendNewlineAfterEmbeddable(i, ops, op, res, BlockEmbed.videoType);
} }
return res; return res;
} }

@ -2,6 +2,7 @@ import 'package:tuple/tuple.dart';
import '../../models/documents/document.dart'; import '../../models/documents/document.dart';
import '../documents/attribute.dart'; import '../documents/attribute.dart';
import '../documents/nodes/embeddable.dart';
import '../documents/style.dart'; import '../documents/style.dart';
import '../quill_delta.dart'; import '../quill_delta.dart';
import 'rule.dart'; import 'rule.dart';
@ -239,6 +240,7 @@ class ResetLineFormatOnNewLineRule extends InsertRule {
} }
/// Handles all format operations which manipulate embeds. /// Handles all format operations which manipulate embeds.
/// This rule wraps line breaks around video, not image.
class InsertEmbedsRule extends InsertRule { class InsertEmbedsRule extends InsertRule {
const InsertEmbedsRule(); const InsertEmbedsRule();
@ -249,6 +251,12 @@ class InsertEmbedsRule extends InsertRule {
return null; return null;
} }
assert(data is Map);
if (!(data as Map).containsKey(BlockEmbed.videoType)) {
return null;
}
final delta = Delta()..retain(index + (len ?? 0)); final delta = Delta()..retain(index + (len ?? 0));
final itr = DeltaIterator(document); final itr = DeltaIterator(document);
final prev = itr.skip(index), cur = itr.next(); final prev = itr.skip(index), cur = itr.next();

@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:i18n_extension/i18n_widget.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../models/documents/document.dart'; import '../models/documents/document.dart';
@ -169,6 +170,7 @@ class QuillEditor extends StatefulWidget {
this.embedBuilder = defaultEmbedBuilder, this.embedBuilder = defaultEmbedBuilder,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder, this.customStyleBuilder,
this.locale,
this.floatingCursorDisabled = false, this.floatingCursorDisabled = false,
Key? key}) Key? key})
: super(key: key); : super(key: key);
@ -339,6 +341,10 @@ class QuillEditor extends StatefulWidget {
final EmbedBuilder embedBuilder; final EmbedBuilder embedBuilder;
final CustomStyleBuilder? customStyleBuilder; final CustomStyleBuilder? customStyleBuilder;
/// The locale to use for the editor toolbar, defaults to system locale
/// and more https://github.com/singerdmx/flutter-quill#translation-of-toolbar
final Locale? locale;
/// Delegate function responsible for showing menu with link actions on /// Delegate function responsible for showing menu with link actions on
/// mobile platforms (iOS, Android). /// mobile platforms (iOS, Android).
/// ///
@ -452,10 +458,12 @@ class QuillEditorState extends State<QuillEditor>
floatingCursorDisabled: widget.floatingCursorDisabled, floatingCursorDisabled: widget.floatingCursorDisabled,
); );
final editor = _selectionGestureDetectorBuilder.build( final editor = I18n(
behavior: HitTestBehavior.translucent, initialLocale: widget.locale,
child: child, child: _selectionGestureDetectorBuilder.build(
); behavior: HitTestBehavior.translucent,
child: child,
));
if (kIsWeb) { if (kIsWeb) {
// Intercept RawKeyEvent on Web to prevent it from propagating to parents // Intercept RawKeyEvent on Web to prevent it from propagating to parents

@ -1,5 +1,3 @@
import 'dart:math';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -7,8 +5,8 @@ import 'package:gallery_saver/gallery_saver.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../../models/documents/attribute.dart'; import '../../models/documents/attribute.dart';
import '../../models/documents/nodes/embeddable.dart';
import '../../models/documents/nodes/leaf.dart' as leaf; import '../../models/documents/nodes/leaf.dart' as leaf;
import '../../models/documents/style.dart';
import '../../translations/toolbar.i18n.dart'; import '../../translations/toolbar.i18n.dart';
import '../../utils/platform.dart'; import '../../utils/platform.dart';
import '../../utils/string.dart'; import '../../utils/string.dart';
@ -24,7 +22,7 @@ Widget defaultEmbedBuilder(BuildContext context, QuillController controller,
Tuple2<double?, double?>? _widthHeight; Tuple2<double?, double?>? _widthHeight;
switch (node.value.type) { switch (node.value.type) {
case 'image': case BlockEmbed.imageType:
final imageUrl = standardizeImageUrl(node.value.data); final imageUrl = standardizeImageUrl(node.value.data);
var image; var image;
final style = node.style.attributes['style']; final style = node.style.attributes['style'];
@ -76,9 +74,10 @@ Widget defaultEmbedBuilder(BuildContext context, QuillController controller,
final _screenSize = MediaQuery.of(context).size; final _screenSize = MediaQuery.of(context).size;
return ImageResizer( return ImageResizer(
onImageResize: (w, h) { onImageResize: (w, h) {
final res = _getImageNode(controller); final res = getImageNode(
controller, controller.selection.start);
final attr = replaceStyleString( final attr = replaceStyleString(
_getImageStyleString(controller), w, h); getImageStyleString(controller), w, h);
controller.formatText( controller.formatText(
res.item1, 1, StyleAttribute(attr)); res.item1, 1, StyleAttribute(attr));
}, },
@ -94,10 +93,12 @@ Widget defaultEmbedBuilder(BuildContext context, QuillController controller,
color: Colors.cyanAccent, color: Colors.cyanAccent,
text: 'Copy'.i18n, text: 'Copy'.i18n,
onPressed: () { onPressed: () {
final imageNode = _getImageNode(controller).item2; final imageNode =
getImageNode(controller, controller.selection.start)
.item2;
final imageUrl = imageNode.value.data; final imageUrl = imageNode.value.data;
controller.copiedImageUrl = controller.copiedImageUrl =
Tuple2(imageUrl, _getImageStyleString(controller)); Tuple2(imageUrl, getImageStyleString(controller));
Navigator.pop(context); Navigator.pop(context);
}, },
); );
@ -106,7 +107,9 @@ Widget defaultEmbedBuilder(BuildContext context, QuillController controller,
color: Colors.red.shade200, color: Colors.red.shade200,
text: 'Remove'.i18n, text: 'Remove'.i18n,
onPressed: () { onPressed: () {
final offset = _getImageNode(controller).item1; final offset =
getImageNode(controller, controller.selection.start)
.item1;
controller.replaceText(offset, 1, '', controller.replaceText(offset, 1, '',
TextSelection.collapsed(offset: offset)); TextSelection.collapsed(offset: offset));
Navigator.pop(context); Navigator.pop(context);
@ -131,7 +134,7 @@ Widget defaultEmbedBuilder(BuildContext context, QuillController controller,
// We provide option menu for mobile platform excluding base64 image // We provide option menu for mobile platform excluding base64 image
return _menuOptionsForReadonlyImage(context, imageUrl, image); return _menuOptionsForReadonlyImage(context, imageUrl, image);
case 'video': case BlockEmbed.videoType:
final videoUrl = node.value.data; final videoUrl = node.value.data;
if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) {
return YoutubeVideoApp( return YoutubeVideoApp(
@ -147,30 +150,6 @@ Widget defaultEmbedBuilder(BuildContext context, QuillController controller,
} }
} }
String _getImageStyleString(QuillController controller) {
final String? s = controller
.getAllSelectionStyles()
.firstWhere((s) => s.attributes.containsKey(Attribute.style.key),
orElse: () => Style())
.attributes[Attribute.style.key]
?.value;
return s ?? '';
}
Tuple2<int, leaf.Embed> _getImageNode(QuillController controller) {
var offset = controller.selection.start;
var imageNode = controller.queryNode(offset);
if (imageNode == null || !(imageNode is leaf.Embed)) {
offset = max(0, offset - 1);
imageNode = controller.queryNode(offset);
}
if (imageNode != null && imageNode is leaf.Embed) {
return Tuple2(offset, imageNode);
}
return throw 'Image node not found by offset $offset';
}
Widget _menuOptionsForReadonlyImage( Widget _menuOptionsForReadonlyImage(
BuildContext context, String imageUrl, Image image) { BuildContext context, String imageUrl, Image image) {
return GestureDetector( return GestureDetector(

@ -1,14 +1,45 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io' as io; import 'dart:io' as io;
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:string_validator/string_validator.dart'; import 'package:string_validator/string_validator.dart';
import 'package:tuple/tuple.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/nodes/leaf.dart';
import '../../models/documents/style.dart';
import '../controller.dart';
bool isImageBase64(String imageUrl) { bool isImageBase64(String imageUrl) {
return !imageUrl.startsWith('http') && isBase64(imageUrl); return !imageUrl.startsWith('http') && isBase64(imageUrl);
} }
Tuple2<int, Embed> getImageNode(QuillController controller, int offset) {
var offset = controller.selection.start;
var imageNode = controller.queryNode(offset);
if (imageNode == null || !(imageNode is Embed)) {
offset = max(0, offset - 1);
imageNode = controller.queryNode(offset);
}
if (imageNode != null && imageNode is Embed) {
return Tuple2(offset, imageNode);
}
return throw 'Image node not found by offset $offset';
}
String getImageStyleString(QuillController controller) {
final String? s = controller
.getAllSelectionStyles()
.firstWhere((s) => s.attributes.containsKey(Attribute.style.key),
orElse: () => Style())
.attributes[Attribute.style.key]
?.value;
return s ?? '';
}
Image imageByUrl(String imageUrl, Image imageByUrl(String imageUrl,
{double? width, {double? width,
double? height, double? height,

@ -26,6 +26,7 @@ import 'default_styles.dart';
import 'delegate.dart'; import 'delegate.dart';
import 'editor.dart'; import 'editor.dart';
import 'embeds/default_embed_builder.dart'; import 'embeds/default_embed_builder.dart';
import 'embeds/image.dart';
import 'keyboard_listener.dart'; import 'keyboard_listener.dart';
import 'link.dart'; import 'link.dart';
import 'proxy.dart'; import 'proxy.dart';
@ -927,8 +928,10 @@ class RawEditorState extends EditorState
widget.controller widget.controller
.replaceText(index, length, BlockEmbed.image(copied.item1), null); .replaceText(index, length, BlockEmbed.image(copied.item1), null);
if (copied.item2.isNotEmpty) { if (copied.item2.isNotEmpty) {
widget.controller widget.controller.formatText(
.formatText(index + 1, 1, StyleAttribute(copied.item2)); getImageNode(widget.controller, index + 1).item1,
1,
StyleAttribute(copied.item2));
} }
widget.controller.copiedImageUrl = null; widget.controller.copiedImageUrl = null;
await Clipboard.setData(const ClipboardData(text: '')); await Clipboard.setData(const ClipboardData(text: ''));

@ -424,12 +424,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
final FilePickImpl? filePickImpl; final FilePickImpl? filePickImpl;
///The locale to use for the editor toolbar, defaults to system locale /// The locale to use for the editor toolbar, defaults to system locale
///Currently the supported locales are:
/// * Locale('en')
/// * Locale('de')
/// * Locale('fr')
/// * Locale('zh', 'CN')
/// and more https://github.com/singerdmx/flutter-quill#translation-of-toolbar /// and more https://github.com/singerdmx/flutter-quill#translation-of-toolbar
final Locale? locale; final Locale? locale;

@ -1,6 +1,6 @@
name: flutter_quill name: flutter_quill
description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us)
version: 3.9.4 version: 3.9.6
#author: bulletjournal #author: bulletjournal
homepage: https://bulletjournal.us/home/index.html homepage: https://bulletjournal.us/home/index.html
repository: https://github.com/singerdmx/flutter-quill repository: https://github.com/singerdmx/flutter-quill
@ -30,7 +30,7 @@ dependencies:
characters: ^1.1.0 characters: ^1.1.0
youtube_player_flutter: ^8.0.0 youtube_player_flutter: ^8.0.0
diff_match_patch: ^0.4.1 diff_match_patch: ^0.4.1
i18n_extension: ^4.1.3 i18n_extension: ^4.2.0
gallery_saver: ^2.3.2 gallery_saver: ^2.3.2
dev_dependencies: dev_dependencies:

Loading…
Cancel
Save