Fix image pasting

pull/650/head
X Code 3 years ago
parent 9fc2df7cba
commit 94c6a1dcbc
  1. 2
      lib/src/models/documents/document.dart
  2. 8
      lib/src/models/rules/insert.dart
  3. 47
      lib/src/widgets/embeds/default_embed_builder.dart
  4. 31
      lib/src/widgets/embeds/image.dart
  5. 7
      lib/src/widgets/raw_editor.dart

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

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

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

@ -1,14 +1,45 @@
import 'dart:convert';
import 'dart:io' as io;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.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) {
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,
{double? width,
double? height,

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

Loading…
Cancel
Save