Skip to content

Commit 42d830f

Browse files
AtlasAutocodeDouglas Ward
and
Douglas Ward
authored
Add tests for PreserveInlineStylesRule and fix link editing. Other minor fixes. (singerdmx#2058)
* Value setting Stateful toolbar buttons derive from base class * Removed deprecated functions * Move clipboard actions to QuillController * Add: Clipboard toolbar buttons * Translation Justify * Translation alignJustify * Fix: Translation en-US * Misc fixes --------- Co-authored-by: Douglas Ward <[email protected]>
1 parent faf8f55 commit 42d830f

File tree

5 files changed

+366
-72
lines changed

5 files changed

+366
-72
lines changed

lib/src/controller/quill_controller.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:meta/meta.dart' show experimental;
88
import '../../quill_delta.dart';
99
import '../common/structs/image_url.dart';
1010
import '../common/structs/offset_value.dart';
11+
import '../common/utils/embeds.dart';
1112
import '../delta/delta_diff.dart';
1213
import '../delta/delta_x.dart';
1314
import '../document/attribute.dart';
@@ -515,6 +516,12 @@ class QuillController extends ChangeNotifier {
515516
Future<bool> clipboardPaste({void Function()? updateEditor}) async {
516517
if (readOnly || !selection.isValid) return true;
517518

519+
final pasteUsingInternalImageSuccess = await _pasteInternalImage();
520+
if (pasteUsingInternalImageSuccess) {
521+
updateEditor?.call();
522+
return true;
523+
}
524+
518525
final pasteUsingHtmlSuccess = await _pasteHTML();
519526
if (pasteUsingHtmlSuccess) {
520527
updateEditor?.call();
@@ -574,6 +581,34 @@ class QuillController extends ChangeNotifier {
574581
);
575582
}
576583

584+
/// Return true if can paste internal image
585+
Future<bool> _pasteInternalImage() async {
586+
final copiedImageUrl = _copiedImageUrl;
587+
if (copiedImageUrl != null) {
588+
final index = selection.baseOffset;
589+
final length = selection.extentOffset - index;
590+
replaceText(
591+
index,
592+
length,
593+
BlockEmbed.image(copiedImageUrl.url),
594+
null,
595+
);
596+
if (copiedImageUrl.styleString.isNotEmpty) {
597+
formatText(
598+
getEmbedNode(this, index + 1).offset,
599+
1,
600+
StyleAttribute(copiedImageUrl.styleString),
601+
);
602+
}
603+
_copiedImageUrl = null;
604+
await Clipboard.setData(
605+
const ClipboardData(text: ''),
606+
);
607+
return true;
608+
}
609+
return false;
610+
}
611+
577612
/// Return true if can paste using HTML
578613
Future<bool> _pasteHTML() async {
579614
final clipboardService = ClipboardServiceProvider.instance;

lib/src/editor/raw_editor/raw_editor_state.dart

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import 'package:flutter/scheduler.dart' show SchedulerBinding;
1111
import 'package:flutter/services.dart'
1212
show
1313
Clipboard,
14-
ClipboardData,
1514
HardwareKeyboard,
1615
KeyDownEvent,
1716
LogicalKeyboardKey,
@@ -24,7 +23,6 @@ import '../../common/structs/horizontal_spacing.dart';
2423
import '../../common/structs/offset_value.dart';
2524
import '../../common/structs/vertical_spacing.dart';
2625
import '../../common/utils/cast.dart';
27-
import '../../common/utils/embeds.dart';
2826
import '../../common/utils/platform.dart';
2927
import '../../controller/quill_controller.dart';
3028
import '../../delta/delta_diff.dart';
@@ -155,31 +153,6 @@ class QuillRawEditorState extends EditorState
155153
return;
156154
}
157155

158-
// When image copied internally in the editor
159-
final copiedImageUrl = controller.copiedImageUrl;
160-
if (copiedImageUrl != null) {
161-
final index = textEditingValue.selection.baseOffset;
162-
final length = textEditingValue.selection.extentOffset - index;
163-
controller.replaceText(
164-
index,
165-
length,
166-
BlockEmbed.image(copiedImageUrl.url),
167-
null,
168-
);
169-
if (copiedImageUrl.styleString.isNotEmpty) {
170-
controller.formatText(
171-
getEmbedNode(controller, index + 1).offset,
172-
1,
173-
StyleAttribute(copiedImageUrl.styleString),
174-
);
175-
}
176-
controller.copiedImageUrl = null;
177-
await Clipboard.setData(
178-
const ClipboardData(text: ''),
179-
);
180-
return;
181-
}
182-
183156
if (await controller.clipboardPaste()) {
184157
bringIntoView(textEditingValue.selection.extent);
185158
return;
@@ -909,6 +882,7 @@ class QuillRawEditorState extends EditorState
909882
for (final attr in style.values) {
910883
controller.formatSelection(attr);
911884
}
885+
controller.formatSelection(attribute);
912886
}
913887
}
914888

lib/src/editor/widgets/text/text_line.dart

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -931,10 +931,15 @@ class RenderEditableTextLine extends RenderEditableBox {
931931

932932
@override
933933
TextPosition? getPositionAbove(TextPosition position) {
934-
/// Move up by fraction of the default font height, larger font sizes need larger offset
935-
for (var offset = -0.5;; offset -= 0.25) {
936-
final pos = _getPosition(position, offset);
937-
if (pos != position || offset <= -2.0) {
934+
double? maxOffset;
935+
double limit() => maxOffset ??=
936+
_body!.semanticBounds.height / preferredLineHeight(position) + 1;
937+
bool checkLimit(double offset) => offset < 4.0 ? false : offset > limit();
938+
939+
/// Move up by fraction of the default font height, larger font sizes need larger offset, embed images need larger offset
940+
for (var offset = 0.5;; offset += offset < 4 ? 0.25 : 1.0) {
941+
final pos = _getPosition(position, -offset);
942+
if (pos?.offset != position.offset || checkLimit(offset)) {
938943
return pos;
939944
}
940945
}

lib/src/rules/insert.dart

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -558,35 +558,43 @@ class PreserveInlineStylesRule extends InsertRule {
558558

559559
final documentDelta = document.toDelta();
560560
final itr = DeltaIterator(documentDelta);
561+
len ??= 0;
561562
var prev = itr.skip(len == 0 ? index : index + 1);
563+
var excludeLinkAtLineStart = false;
562564

563-
if (prev == null || prev.data is! String) return null;
564-
565-
/// Trap for simple insertions at start of line
565+
/// Process simple insertions at start of line
566566
if (len == 0) {
567-
final prevData = prev.data as String;
568-
if (prevData.endsWith('\n')) {
569-
/// If current line is empty get attributes from a prior line
570-
final currLine = itr.next();
571-
final currData =
572-
currLine.data is String ? currLine.data as String : null;
573-
if (currData?.isEmpty == true || currData?.startsWith('\n') == true) {
574-
if (prevData.trimRight().isEmpty) {
575-
final back =
576-
DeltaIterator(documentDelta).skip(index - prevData.length);
577-
if (back != null && back.data is String) {
578-
prev = back;
567+
final currLine = itr.next();
568+
569+
/// Trap for previous is not text with attributes
570+
if (prev?.data is! String) {
571+
prev = currLine;
572+
excludeLinkAtLineStart = true;
573+
} else {
574+
final prevData = prev!.data as String;
575+
if (prevData.endsWith('\n')) {
576+
/// If current line is empty get attributes from a prior line
577+
final currData =
578+
currLine.data is String ? currLine.data as String : null;
579+
if (currData?.startsWith('\n') == true) {
580+
if (prevData.trimRight().isEmpty) {
581+
final back =
582+
DeltaIterator(documentDelta).skip(index - prevData.length);
583+
if (back != null && back.data is String) {
584+
prev = back;
585+
}
579586
}
587+
} else {
588+
prev = currLine;
589+
excludeLinkAtLineStart = true;
580590
}
581-
} else {
582-
prev = currLine;
583591
}
584592
}
585593
}
586594

587595
final attributes = <String, dynamic>{};
588-
if (prev.attributes != null) {
589-
for (final entry in prev.attributes!.entries) {
596+
if (prev?.attributes != null) {
597+
for (final entry in prev!.attributes!.entries) {
590598
if (Attribute.inlineKeys.contains(entry.key)) {
591599
attributes[entry.key] = entry.value;
592600
}
@@ -596,29 +604,12 @@ class PreserveInlineStylesRule extends InsertRule {
596604
return null;
597605
}
598606

599-
final text = data;
600-
if (attributes.isEmpty || !attributes.containsKey(Attribute.link.key)) {
601-
return Delta()
602-
..retain(index + (len ?? 0))
603-
..insert(text, attributes);
607+
if (excludeLinkAtLineStart) {
608+
attributes.remove(Attribute.link.key);
604609
}
605-
606-
attributes.remove(Attribute.link.key);
607-
final delta = Delta()
608-
..retain(index + (len ?? 0))
609-
..insert(text, attributes.isEmpty ? null : attributes);
610-
final next = itr.next();
611-
612-
final nextAttributes = next.attributes ?? const <String, dynamic>{};
613-
if (!nextAttributes.containsKey(Attribute.link.key)) {
614-
return delta;
615-
}
616-
if (attributes[Attribute.link.key] == nextAttributes[Attribute.link.key]) {
617-
return Delta()
618-
..retain(index + (len ?? 0))
619-
..insert(text, attributes);
620-
}
621-
return delta;
610+
return Delta()
611+
..retain(index + len)
612+
..insert(data, attributes.isEmpty ? null : attributes);
622613
}
623614
}
624615

0 commit comments

Comments
 (0)