fix: paste issue from ChatGPT (#1146)
* chore: remove diff issue * fix: deepcopy the children node when delete node * fix: auto scroller issue * fix: paste content from chatgpt
This commit is contained in:
parent
1ec9bb7f29
commit
4c1d2eca35
6 changed files with 312 additions and 226 deletions
|
|
@ -408,7 +408,8 @@ class Delta extends Iterable<TextOperation> {
|
||||||
if (op is TextInsert) {
|
if (op is TextInsert) {
|
||||||
return op.text;
|
return op.text;
|
||||||
}
|
}
|
||||||
throw Exception('diff() called $e');
|
assert(false, 'diff() called with non-insert operations');
|
||||||
|
return '';
|
||||||
}).join();
|
}).join();
|
||||||
return text;
|
return text;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ CommandShortcutEventHandler _convertToParagraphCommandHandler = (editorState) {
|
||||||
ParagraphBlockKeys.delta: delta.toJson(),
|
ParagraphBlockKeys.delta: delta.toJson(),
|
||||||
},
|
},
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
children: node.children,
|
children: node.children.map((e) => e.deepCopy()).toList(),
|
||||||
),
|
),
|
||||||
deepCopy: true,
|
deepCopy: true,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ class AutoScroller extends EdgeDraggingAutoScroller
|
||||||
if (direction != null && direction == AxisDirection.up) {
|
if (direction != null && direction == AxisDirection.up) {
|
||||||
return startAutoScrollIfNecessary(
|
return startAutoScrollIfNecessary(
|
||||||
offset & Size(1, edgeOffset),
|
offset & Size(1, edgeOffset),
|
||||||
duration: duration,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +46,6 @@ class AutoScroller extends EdgeDraggingAutoScroller
|
||||||
|
|
||||||
startAutoScrollIfNecessary(
|
startAutoScrollIfNecessary(
|
||||||
dragTarget,
|
dragTarget,
|
||||||
duration: duration,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,25 +13,24 @@ import 'package:flutter/material.dart';
|
||||||
export 'package:flutter/physics.dart' show Tolerance;
|
export 'package:flutter/physics.dart' show Tolerance;
|
||||||
|
|
||||||
/// Describes the aspects of a Scrollable widget to inform inherited widgets
|
/// Describes the aspects of a Scrollable widget to inform inherited widgets
|
||||||
/// like [ScrollBehavior] for decorating.
|
/// like [ScrollBehavior] for decorating or enumerate the properties of combined
|
||||||
// TODO(Piinks): Fix doc with 2DScrollable change.
|
/// Scrollables, such as [TwoDimensionalScrollable].
|
||||||
// or enumerate the properties of combined
|
|
||||||
// Scrollables, such as [TwoDimensionalScrollable].
|
|
||||||
///
|
///
|
||||||
/// Decorations like [GlowingOverscrollIndicator]s and [Scrollbar]s require
|
/// Decorations like [GlowingOverscrollIndicator]s and [Scrollbar]s require
|
||||||
/// information about the Scrollable in order to be initialized.
|
/// information about the Scrollable in order to be initialized.
|
||||||
@immutable
|
@immutable
|
||||||
class ScrollableDetails {
|
class ScrollableDetails {
|
||||||
/// Creates a set of details describing the [Scrollable]. The [direction]
|
/// Creates a set of details describing the [Scrollable].
|
||||||
/// cannot be null.
|
|
||||||
const ScrollableDetails({
|
const ScrollableDetails({
|
||||||
required this.direction,
|
required this.direction,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.physics,
|
this.physics,
|
||||||
@Deprecated('Migrate to decorationClipBehavior. '
|
@Deprecated(
|
||||||
|
'Migrate to decorationClipBehavior. '
|
||||||
'This property was deprecated so that its application is clearer. This clip '
|
'This property was deprecated so that its application is clearer. This clip '
|
||||||
'applies to decorators, and does not directly clip a scroll view. '
|
'applies to decorators, and does not directly clip a scroll view. '
|
||||||
'This feature was deprecated after v3.9.0-1.0.pre.')
|
'This feature was deprecated after v3.9.0-1.0.pre.',
|
||||||
|
)
|
||||||
Clip? clipBehavior,
|
Clip? clipBehavior,
|
||||||
Clip? decorationClipBehavior,
|
Clip? decorationClipBehavior,
|
||||||
}) : decorationClipBehavior = clipBehavior ?? decorationClipBehavior;
|
}) : decorationClipBehavior = clipBehavior ?? decorationClipBehavior;
|
||||||
|
|
@ -74,10 +73,12 @@ class ScrollableDetails {
|
||||||
final Clip? decorationClipBehavior;
|
final Clip? decorationClipBehavior;
|
||||||
|
|
||||||
/// Deprecated getter for [decorationClipBehavior].
|
/// Deprecated getter for [decorationClipBehavior].
|
||||||
@Deprecated('Migrate to decorationClipBehavior. '
|
@Deprecated(
|
||||||
|
'Migrate to decorationClipBehavior. '
|
||||||
'This property was deprecated so that its application is clearer. This clip '
|
'This property was deprecated so that its application is clearer. This clip '
|
||||||
'applies to decorators, and does not directly clip a scroll view. '
|
'applies to decorators, and does not directly clip a scroll view. '
|
||||||
'This feature was deprecated after v3.9.0-1.0.pre.')
|
'This feature was deprecated after v3.9.0-1.0.pre.',
|
||||||
|
)
|
||||||
Clip? get clipBehavior => decorationClipBehavior;
|
Clip? get clipBehavior => decorationClipBehavior;
|
||||||
|
|
||||||
/// Copy the current [ScrollableDetails] with the given values replacing the
|
/// Copy the current [ScrollableDetails] with the given values replacing the
|
||||||
|
|
@ -115,12 +116,8 @@ class ScrollableDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(
|
int get hashCode =>
|
||||||
direction,
|
Object.hash(direction, controller, physics, decorationClipBehavior);
|
||||||
controller,
|
|
||||||
physics,
|
|
||||||
decorationClipBehavior,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
|
@ -143,18 +140,15 @@ class ScrollableDetails {
|
||||||
///
|
///
|
||||||
/// The scroll velocity is controlled by the [velocityScalar]:
|
/// The scroll velocity is controlled by the [velocityScalar]:
|
||||||
///
|
///
|
||||||
/// velocity = <distance of overscroll> * [velocityScalar].
|
/// velocity = (distance of overscroll) * [velocityScalar].
|
||||||
class EdgeDraggingAutoScroller {
|
class EdgeDraggingAutoScroller {
|
||||||
/// Creates a auto scroller that scrolls the [scrollable].
|
/// Creates a auto scroller that scrolls the [scrollable].
|
||||||
EdgeDraggingAutoScroller(
|
EdgeDraggingAutoScroller(
|
||||||
this.scrollable, {
|
this.scrollable, {
|
||||||
this.onScrollViewScrolled,
|
this.onScrollViewScrolled,
|
||||||
this.velocityScalar = _kDefaultAutoScrollVelocityScalar,
|
required this.velocityScalar,
|
||||||
});
|
});
|
||||||
|
|
||||||
// An eyeballed value for a smooth scrolling experience.
|
|
||||||
static const double _kDefaultAutoScrollVelocityScalar = 7;
|
|
||||||
|
|
||||||
/// The [Scrollable] this auto scroller is scrolling.
|
/// The [Scrollable] this auto scroller is scrolling.
|
||||||
final ScrollableState scrollable;
|
final ScrollableState scrollable;
|
||||||
|
|
||||||
|
|
@ -165,10 +159,12 @@ class EdgeDraggingAutoScroller {
|
||||||
/// in between each scroll.
|
/// in between each scroll.
|
||||||
final VoidCallback? onScrollViewScrolled;
|
final VoidCallback? onScrollViewScrolled;
|
||||||
|
|
||||||
|
/// {@template flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
|
||||||
/// The velocity scalar per pixel over scroll.
|
/// The velocity scalar per pixel over scroll.
|
||||||
///
|
///
|
||||||
/// It represents how the velocity scale with the over scroll distance. The
|
/// It represents how the velocity scale with the over scroll distance. The
|
||||||
/// auto-scroll velocity = <distance of overscroll> * velocityScalar.
|
/// auto-scroll velocity = (distance of overscroll) * velocityScalar.
|
||||||
|
/// {@endtemplate}
|
||||||
final double velocityScalar;
|
final double velocityScalar;
|
||||||
|
|
||||||
late Rect _dragTargetRelatedToScrollOrigin;
|
late Rect _dragTargetRelatedToScrollOrigin;
|
||||||
|
|
@ -178,21 +174,17 @@ class EdgeDraggingAutoScroller {
|
||||||
bool _scrolling = false;
|
bool _scrolling = false;
|
||||||
|
|
||||||
double _offsetExtent(Offset offset, Axis scrollDirection) {
|
double _offsetExtent(Offset offset, Axis scrollDirection) {
|
||||||
switch (scrollDirection) {
|
return switch (scrollDirection) {
|
||||||
case Axis.horizontal:
|
Axis.horizontal => offset.dx,
|
||||||
return offset.dx;
|
Axis.vertical => offset.dy,
|
||||||
case Axis.vertical:
|
};
|
||||||
return offset.dy;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double _sizeExtent(Size size, Axis scrollDirection) {
|
double _sizeExtent(Size size, Axis scrollDirection) {
|
||||||
switch (scrollDirection) {
|
return switch (scrollDirection) {
|
||||||
case Axis.horizontal:
|
Axis.horizontal => size.width,
|
||||||
return size.width;
|
Axis.vertical => size.height,
|
||||||
case Axis.vertical:
|
};
|
||||||
return size.height;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AxisDirection get _axisDirection => scrollable.axisDirection;
|
AxisDirection get _axisDirection => scrollable.axisDirection;
|
||||||
|
|
@ -205,7 +197,7 @@ class EdgeDraggingAutoScroller {
|
||||||
///
|
///
|
||||||
/// If the scrollable is already scrolling, calling this method updates the
|
/// If the scrollable is already scrolling, calling this method updates the
|
||||||
/// previous dragTarget to the new value and continues scrolling if necessary.
|
/// previous dragTarget to the new value and continues scrolling if necessary.
|
||||||
void startAutoScrollIfNecessary(Rect dragTarget, {Duration? duration}) {
|
void startAutoScrollIfNecessary(Rect dragTarget) {
|
||||||
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
|
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
|
||||||
_dragTargetRelatedToScrollOrigin =
|
_dragTargetRelatedToScrollOrigin =
|
||||||
dragTarget.translate(deltaToOrigin.dx, deltaToOrigin.dy);
|
dragTarget.translate(deltaToOrigin.dx, deltaToOrigin.dy);
|
||||||
|
|
@ -214,7 +206,7 @@ class EdgeDraggingAutoScroller {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(!_scrolling);
|
assert(!_scrolling);
|
||||||
_scroll(duration: duration);
|
_scroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stop any ongoing auto scrolling.
|
/// Stop any ongoing auto scrolling.
|
||||||
|
|
@ -222,11 +214,13 @@ class EdgeDraggingAutoScroller {
|
||||||
_scrolling = false;
|
_scrolling = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _scroll({Duration? duration}) async {
|
Future<void> _scroll() async {
|
||||||
|
try {
|
||||||
final RenderBox scrollRenderBox =
|
final RenderBox scrollRenderBox =
|
||||||
scrollable.context.findRenderObject()! as RenderBox;
|
scrollable.context.findRenderObject()! as RenderBox;
|
||||||
|
final Matrix4 transform = scrollRenderBox.getTransformTo(null);
|
||||||
final Rect globalRect = MatrixUtils.transformRect(
|
final Rect globalRect = MatrixUtils.transformRect(
|
||||||
scrollRenderBox.getTransformTo(null),
|
transform,
|
||||||
Rect.fromLTWH(
|
Rect.fromLTWH(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
|
@ -234,6 +228,17 @@ class EdgeDraggingAutoScroller {
|
||||||
scrollRenderBox.size.height,
|
scrollRenderBox.size.height,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
final Rect transformedDragTarget = MatrixUtils.transformRect(
|
||||||
|
transform,
|
||||||
|
_dragTargetRelatedToScrollOrigin,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ((globalRect.size.width + precisionErrorTolerance) >=
|
||||||
|
transformedDragTarget.size.width &&
|
||||||
|
(globalRect.size.height + precisionErrorTolerance) >=
|
||||||
|
transformedDragTarget.size.height) {
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
_scrolling = true;
|
_scrolling = true;
|
||||||
double? newOffset;
|
double? newOffset;
|
||||||
|
|
@ -259,14 +264,17 @@ class EdgeDraggingAutoScroller {
|
||||||
case AxisDirection.up:
|
case AxisDirection.up:
|
||||||
case AxisDirection.left:
|
case AxisDirection.left:
|
||||||
if (proxyEnd > viewportEnd &&
|
if (proxyEnd > viewportEnd &&
|
||||||
scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
scrollable.position.pixels >
|
||||||
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
|
scrollable.position.minScrollExtent) {
|
||||||
|
final double overDrag =
|
||||||
|
math.min(proxyEnd - viewportEnd, overDragMax);
|
||||||
newOffset = math.max(
|
newOffset = math.max(
|
||||||
scrollable.position.minScrollExtent,
|
scrollable.position.minScrollExtent,
|
||||||
scrollable.position.pixels - overDrag,
|
scrollable.position.pixels - overDrag,
|
||||||
);
|
);
|
||||||
} else if (proxyStart < viewportStart &&
|
} else if (proxyStart < viewportStart &&
|
||||||
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
scrollable.position.pixels <
|
||||||
|
scrollable.position.maxScrollExtent) {
|
||||||
final double overDrag =
|
final double overDrag =
|
||||||
math.min(viewportStart - proxyStart, overDragMax);
|
math.min(viewportStart - proxyStart, overDragMax);
|
||||||
newOffset = math.min(
|
newOffset = math.min(
|
||||||
|
|
@ -277,7 +285,8 @@ class EdgeDraggingAutoScroller {
|
||||||
case AxisDirection.right:
|
case AxisDirection.right:
|
||||||
case AxisDirection.down:
|
case AxisDirection.down:
|
||||||
if (proxyStart < viewportStart &&
|
if (proxyStart < viewportStart &&
|
||||||
scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
scrollable.position.pixels >
|
||||||
|
scrollable.position.minScrollExtent) {
|
||||||
final double overDrag =
|
final double overDrag =
|
||||||
math.min(viewportStart - proxyStart, overDragMax);
|
math.min(viewportStart - proxyStart, overDragMax);
|
||||||
newOffset = math.max(
|
newOffset = math.max(
|
||||||
|
|
@ -285,8 +294,10 @@ class EdgeDraggingAutoScroller {
|
||||||
scrollable.position.pixels - overDrag,
|
scrollable.position.pixels - overDrag,
|
||||||
);
|
);
|
||||||
} else if (proxyEnd > viewportEnd &&
|
} else if (proxyEnd > viewportEnd &&
|
||||||
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
scrollable.position.pixels <
|
||||||
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
|
scrollable.position.maxScrollExtent) {
|
||||||
|
final double overDrag =
|
||||||
|
math.min(proxyEnd - viewportEnd, overDragMax);
|
||||||
newOffset = math.min(
|
newOffset = math.min(
|
||||||
scrollable.position.maxScrollExtent,
|
scrollable.position.maxScrollExtent,
|
||||||
scrollable.position.pixels + overDrag,
|
scrollable.position.pixels + overDrag,
|
||||||
|
|
@ -300,22 +311,18 @@ class EdgeDraggingAutoScroller {
|
||||||
_scrolling = false;
|
_scrolling = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
duration ??= Duration(milliseconds: (1000 / velocityScalar).round());
|
final Duration duration =
|
||||||
if (duration == Duration.zero) {
|
Duration(milliseconds: (1000 / velocityScalar).round());
|
||||||
scrollable.position.jumpTo(newOffset);
|
await scrollable.position
|
||||||
} else {
|
.animateTo(newOffset, duration: duration, curve: Curves.linear);
|
||||||
await scrollable.position.animateTo(
|
onScrollViewScrolled?.call();
|
||||||
newOffset,
|
|
||||||
duration: duration,
|
|
||||||
curve: Curves.linear,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onScrollViewScrolled != null) {
|
|
||||||
onScrollViewScrolled!();
|
|
||||||
}
|
|
||||||
if (_scrolling) {
|
if (_scrolling) {
|
||||||
await _scroll(duration: duration);
|
await _scroll();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint(e.toString());
|
||||||
|
} finally {
|
||||||
|
_scrolling = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -329,10 +336,83 @@ typedef ScrollIncrementCalculator = double Function(
|
||||||
ScrollIncrementDetails details,
|
ScrollIncrementDetails details,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// An [Action] that scrolls the [Scrollable] that encloses the current
|
/// Describes the type of scroll increment that will be performed by a
|
||||||
/// [primaryFocus] by the amount configured in the [ScrollIntent] given to it.
|
/// [ScrollAction] on a [Scrollable].
|
||||||
///
|
///
|
||||||
/// If a Scrollable cannot be found above the current [primaryFocus], the
|
/// This is used to configure a [ScrollIncrementDetails] object to pass to a
|
||||||
|
/// [ScrollIncrementCalculator] function on a [Scrollable].
|
||||||
|
///
|
||||||
|
/// {@template flutter.widgets.ScrollIncrementType.intent}
|
||||||
|
/// This indicates the *intent* of the scroll, not necessarily the size. Not all
|
||||||
|
/// scrollable areas will have the concept of a "line" or "page", but they can
|
||||||
|
/// respond to the different standard key bindings that cause scrolling, which
|
||||||
|
/// are bound to keys that people use to indicate a "line" scroll (e.g.
|
||||||
|
/// control-arrowDown keys) or a "page" scroll (e.g. pageDown key). It is
|
||||||
|
/// recommended that at least the relative magnitudes of the scrolls match
|
||||||
|
/// expectations.
|
||||||
|
/// {@endtemplate}
|
||||||
|
// enum ScrollIncrementType {
|
||||||
|
// /// Indicates that the [ScrollIncrementCalculator] should return the scroll
|
||||||
|
// /// distance it should move when the user requests to scroll by a "line".
|
||||||
|
// ///
|
||||||
|
// /// The distance a "line" scrolls refers to what should happen when the key
|
||||||
|
// /// binding for "scroll down/up by a line" is triggered. It's up to the
|
||||||
|
// /// [ScrollIncrementCalculator] function to decide what that means for a
|
||||||
|
// /// particular scrollable.
|
||||||
|
// line,
|
||||||
|
|
||||||
|
// /// Indicates that the [ScrollIncrementCalculator] should return the scroll
|
||||||
|
// /// distance it should move when the user requests to scroll by a "page".
|
||||||
|
// ///
|
||||||
|
// /// The distance a "page" scrolls refers to what should happen when the key
|
||||||
|
// /// binding for "scroll down/up by a page" is triggered. It's up to the
|
||||||
|
// /// [ScrollIncrementCalculator] function to decide what that means for a
|
||||||
|
// /// particular scrollable.
|
||||||
|
// page,
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// A details object that describes the type of scroll increment being requested
|
||||||
|
/// of a [ScrollIncrementCalculator] function, as well as the current metrics
|
||||||
|
// /// for the scrollable.
|
||||||
|
// class ScrollIncrementDetails {
|
||||||
|
// /// A const constructor for a [ScrollIncrementDetails].
|
||||||
|
// const ScrollIncrementDetails({required this.type, required this.metrics});
|
||||||
|
|
||||||
|
// /// The type of scroll this is (e.g. line, page, etc.).
|
||||||
|
// ///
|
||||||
|
// /// {@macro flutter.widgets.ScrollIncrementType.intent}
|
||||||
|
// final ScrollIncrementType type;
|
||||||
|
|
||||||
|
// /// The current metrics of the scrollable that is being scrolled.
|
||||||
|
// final ScrollMetrics metrics;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// An [Intent] that represents scrolling the nearest scrollable by an amount
|
||||||
|
/// appropriate for the [type] specified.
|
||||||
|
///
|
||||||
|
/// The actual amount of the scroll is determined by the
|
||||||
|
/// [Scrollable.incrementCalculator], or by its defaults if that is not
|
||||||
|
/// specified.
|
||||||
|
class ScrollIntent extends Intent {
|
||||||
|
/// Creates a const [ScrollIntent] that requests scrolling in the given
|
||||||
|
/// [direction], with the given [type].
|
||||||
|
const ScrollIntent({
|
||||||
|
required this.direction,
|
||||||
|
this.type = ScrollIncrementType.line,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The direction in which to scroll the scrollable containing the focused
|
||||||
|
/// widget.
|
||||||
|
final AxisDirection direction;
|
||||||
|
|
||||||
|
/// The type of scrolling that is intended.
|
||||||
|
final ScrollIncrementType type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Action] that scrolls the relevant [Scrollable] by the amount configured
|
||||||
|
/// in the [ScrollIntent] given to it.
|
||||||
|
///
|
||||||
|
/// If a Scrollable cannot be found above the given [BuildContext], the
|
||||||
/// [PrimaryScrollController] will be considered for default handling of
|
/// [PrimaryScrollController] will be considered for default handling of
|
||||||
/// [ScrollAction]s.
|
/// [ScrollAction]s.
|
||||||
///
|
///
|
||||||
|
|
@ -340,32 +420,28 @@ typedef ScrollIncrementCalculator = double Function(
|
||||||
/// for a [ScrollIntent.type] set to [ScrollIncrementType.page] is 80% of the
|
/// for a [ScrollIntent.type] set to [ScrollIncrementType.page] is 80% of the
|
||||||
/// size of the scroll window, and for [ScrollIncrementType.line], 50 logical
|
/// size of the scroll window, and for [ScrollIncrementType.line], 50 logical
|
||||||
/// pixels.
|
/// pixels.
|
||||||
class ScrollAction extends Action<ScrollIntent> {
|
class ScrollAction extends ContextAction<ScrollIntent> {
|
||||||
@override
|
@override
|
||||||
bool isEnabled(ScrollIntent intent) {
|
bool isEnabled(ScrollIntent intent, [BuildContext? context]) {
|
||||||
final FocusNode? focus = primaryFocus;
|
if (context == null) {
|
||||||
final bool contextIsValid = focus != null && focus.context != null;
|
return false;
|
||||||
if (contextIsValid) {
|
}
|
||||||
// Check for primary scrollable within the current context
|
if (Scrollable.maybeOf(context) != null) {
|
||||||
if (Scrollable.maybeOf(focus.context!) != null) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Check for fallback scrollable with context from PrimaryScrollController
|
|
||||||
final ScrollController? primaryScrollController =
|
final ScrollController? primaryScrollController =
|
||||||
PrimaryScrollController.maybeOf(focus.context!);
|
PrimaryScrollController.maybeOf(context);
|
||||||
return primaryScrollController != null &&
|
return (primaryScrollController != null) &&
|
||||||
primaryScrollController.hasClients;
|
primaryScrollController.hasClients;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the scroll increment for a single scroll request, for use when
|
/// Returns the scroll increment for a single scroll request, for use when
|
||||||
/// scrolling using a hardware keyboard.
|
/// scrolling using a hardware keyboard.
|
||||||
///
|
///
|
||||||
/// Must not be called when the position is null, or when any of the position
|
/// Must not be called when the position is null, or when any of the position
|
||||||
/// metrics (pixels, viewportDimension, maxScrollExtent, minScrollExtent) are
|
/// metrics (pixels, viewportDimension, maxScrollExtent, minScrollExtent) are
|
||||||
/// null. The type and state arguments must not be null, and the widget must
|
/// null. The widget must have already been laid out so that the position
|
||||||
/// have already been laid out so that the position fields are valid.
|
/// fields are valid.
|
||||||
static double _calculateScrollIncrement(
|
static double _calculateScrollIncrement(
|
||||||
ScrollableState state, {
|
ScrollableState state, {
|
||||||
ScrollIncrementType type = ScrollIncrementType.line,
|
ScrollIncrementType type = ScrollIncrementType.line,
|
||||||
|
|
@ -377,18 +453,13 @@ class ScrollAction extends Action<ScrollIntent> {
|
||||||
);
|
);
|
||||||
if (state.widget.incrementCalculator != null) {
|
if (state.widget.incrementCalculator != null) {
|
||||||
return state.widget.incrementCalculator!(
|
return state.widget.incrementCalculator!(
|
||||||
ScrollIncrementDetails(
|
ScrollIncrementDetails(type: type, metrics: state.position),
|
||||||
type: type,
|
|
||||||
metrics: state.position,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
switch (type) {
|
return switch (type) {
|
||||||
case ScrollIncrementType.line:
|
ScrollIncrementType.line => 50.0,
|
||||||
return 50.0;
|
ScrollIncrementType.page => 0.8 * state.position.viewportDimension,
|
||||||
case ScrollIncrementType.page:
|
};
|
||||||
return 0.8 * state.position.viewportDimension;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find out how much of an increment to move by, taking the different
|
/// Find out how much of an increment to move by, taking the different
|
||||||
|
|
@ -397,58 +468,22 @@ class ScrollAction extends Action<ScrollIntent> {
|
||||||
ScrollableState state,
|
ScrollableState state,
|
||||||
ScrollIntent intent,
|
ScrollIntent intent,
|
||||||
) {
|
) {
|
||||||
|
if (axisDirectionToAxis(intent.direction) ==
|
||||||
|
axisDirectionToAxis(state.axisDirection)) {
|
||||||
final double increment =
|
final double increment =
|
||||||
_calculateScrollIncrement(state, type: intent.type);
|
_calculateScrollIncrement(state, type: intent.type);
|
||||||
switch (intent.direction) {
|
return intent.direction == state.axisDirection ? increment : -increment;
|
||||||
case AxisDirection.down:
|
}
|
||||||
switch (state.axisDirection) {
|
|
||||||
case AxisDirection.up:
|
|
||||||
return -increment;
|
|
||||||
case AxisDirection.down:
|
|
||||||
return increment;
|
|
||||||
case AxisDirection.right:
|
|
||||||
case AxisDirection.left:
|
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
case AxisDirection.up:
|
|
||||||
switch (state.axisDirection) {
|
|
||||||
case AxisDirection.up:
|
|
||||||
return increment;
|
|
||||||
case AxisDirection.down:
|
|
||||||
return -increment;
|
|
||||||
case AxisDirection.right:
|
|
||||||
case AxisDirection.left:
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
case AxisDirection.left:
|
|
||||||
switch (state.axisDirection) {
|
|
||||||
case AxisDirection.right:
|
|
||||||
return -increment;
|
|
||||||
case AxisDirection.left:
|
|
||||||
return increment;
|
|
||||||
case AxisDirection.up:
|
|
||||||
case AxisDirection.down:
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
case AxisDirection.right:
|
|
||||||
switch (state.axisDirection) {
|
|
||||||
case AxisDirection.right:
|
|
||||||
return increment;
|
|
||||||
case AxisDirection.left:
|
|
||||||
return -increment;
|
|
||||||
case AxisDirection.up:
|
|
||||||
case AxisDirection.down:
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(ScrollIntent intent) {
|
void invoke(ScrollIntent intent, [BuildContext? context]) {
|
||||||
ScrollableState? state = Scrollable.maybeOf(primaryFocus!.context!);
|
assert(context != null, 'Cannot scroll without a context.');
|
||||||
|
ScrollableState? state = Scrollable.maybeOf(context!);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
final ScrollController primaryScrollController =
|
final ScrollController primaryScrollController =
|
||||||
PrimaryScrollController.of(primaryFocus!.context!);
|
PrimaryScrollController.of(context);
|
||||||
assert(() {
|
assert(() {
|
||||||
if (primaryScrollController.positions.length != 1) {
|
if (primaryScrollController.positions.length != 1) {
|
||||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
|
@ -472,29 +507,22 @@ class ScrollAction extends Action<ScrollIntent> {
|
||||||
return true;
|
return true;
|
||||||
}());
|
}());
|
||||||
|
|
||||||
if (primaryScrollController.position.context.notificationContext ==
|
final BuildContext? notificationContext =
|
||||||
null &&
|
primaryScrollController.position.context.notificationContext;
|
||||||
Scrollable.maybeOf(
|
if (notificationContext != null) {
|
||||||
primaryScrollController.position.context.notificationContext!,
|
state = Scrollable.maybeOf(notificationContext);
|
||||||
) ==
|
}
|
||||||
null) {
|
if (state == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state = Scrollable.maybeOf(
|
|
||||||
primaryScrollController.position.context.notificationContext!,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
assert(
|
assert(
|
||||||
state != null,
|
state.position.hasPixels,
|
||||||
'$ScrollAction was invoked on a context that has no scrollable parent',
|
|
||||||
);
|
|
||||||
assert(
|
|
||||||
state!.position.hasPixels,
|
|
||||||
'Scrollable must be laid out before it can be scrolled via a ScrollAction',
|
'Scrollable must be laid out before it can be scrolled via a ScrollAction',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Don't do anything if the user isn't allowed to scroll.
|
// Don't do anything if the user isn't allowed to scroll.
|
||||||
if (state!.resolvedPhysics != null &&
|
if (state.resolvedPhysics != null &&
|
||||||
!state.resolvedPhysics!.shouldAcceptUserOffset(state.position)) {
|
!state.resolvedPhysics!.shouldAcceptUserOffset(state.position)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,19 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
element.children.length == 1 &&
|
element.children.length == 1 &&
|
||||||
element.children.first.localName == HTMLTags.paragraph) {
|
element.children.first.localName == HTMLTags.paragraph) {
|
||||||
(delta, node) = _parseDeltaElement(element.children.first, type: type);
|
(delta, node) = _parseDeltaElement(element.children.first, type: type);
|
||||||
|
} else if (delta.isEmpty &&
|
||||||
|
element.children.isNotEmpty &&
|
||||||
|
element.children.first.localName == HTMLTags.paragraph) {
|
||||||
|
final paragraphElement = element.children.first;
|
||||||
|
(delta, _) = _parseDeltaElement(paragraphElement, type: type);
|
||||||
|
|
||||||
|
final remainingChildren = element.children.skip(1);
|
||||||
|
node = remainingChildren.expand((child) {
|
||||||
|
if (HTMLTags.specialElements.contains(child.localName)) {
|
||||||
|
return _parseSpecialElements(child, type: type);
|
||||||
|
}
|
||||||
|
return <Node>[];
|
||||||
|
}).toList();
|
||||||
}
|
}
|
||||||
return Node(
|
return Node(
|
||||||
type: type,
|
type: type,
|
||||||
|
|
@ -401,7 +414,8 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
for (final child in children) {
|
for (final child in children) {
|
||||||
if (child is dom.Element) {
|
if (child is dom.Element) {
|
||||||
if (child.children.isNotEmpty &&
|
if (child.children.isNotEmpty &&
|
||||||
HTMLTags.formattingElements.contains(child.localName) == false) {
|
HTMLTags.formattingElements.contains(child.localName) == false &&
|
||||||
|
HTMLTags.specialElements.contains(child.localName) == false) {
|
||||||
//rich editor for webs do this so handling that case for href <a href="https://www.google.com" rel="noopener noreferrer" target="_blank"><strong><em><u>demo</u></em></strong></a>
|
//rich editor for webs do this so handling that case for href <a href="https://www.google.com" rel="noopener noreferrer" target="_blank"><strong><em><u>demo</u></em></strong></a>
|
||||||
|
|
||||||
nodes.addAll(_parseElement(child.children, type: type));
|
nodes.addAll(_parseElement(child.children, type: type));
|
||||||
|
|
|
||||||
45
test/customer/html_document_test.dart
Normal file
45
test/customer/html_document_test.dart
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('customer html document test', () {
|
||||||
|
test('paste text from ChatGPT', () {
|
||||||
|
const html = '''
|
||||||
|
<meta charset='utf-8'><h2 data-start="283" data-end="314">Key Pain Points & Challenges</h2>
|
||||||
|
<ol data-start="316" data-end="4630">
|
||||||
|
<li data-start="316" data-end="870">
|
||||||
|
<p data-start="319" data-end="361"><strong data-start="319" data-end="359">Accuracy, Hallucinations & Omissions</strong></p>
|
||||||
|
<ul data-start="365" data-end="870">
|
||||||
|
<li data-start="365" data-end="569">
|
||||||
|
<p data-start="367" data-end="569">AI may mis-transcribe or “hallucinate” content (i.e. generate text that was never spoken). In high-stakes settings (e.g. medical, legal) this is especially risky. <span class="" data-state="closed"><span class="ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]" data-testid="webpage-citation-pill"><a href="https://apnews.com/article/90020cdf5fa16c79ca2e5b6c4c9bbb14?utm_source=chatgpt.com" target="_blank" rel="noopener" alt="https://apnews.com/article/90020cdf5fa16c79ca2e5b6c4c9bbb14?utm_source=chatgpt.com" class="flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"><span class="relative start-0 bottom-0 flex h-full w-full items-center"><span class="flex h-4 w-full items-center justify-between absolute"><span class="max-w-[15ch] grow truncate overflow-hidden text-center">Elite Asia</span><span class="-me-1 flex h-full items-center rounded-full px-1 text-[#8F8F8F]">+5</span></span><span class="flex h-4 w-full items-center justify-between"><span class="max-w-[15ch] grow truncate overflow-hidden text-center">AP News</span><span class="-me-1 flex h-full items-center rounded-full px-1 text-[#8F8F8F]">+5</span></span><span class="flex h-4 w-full items-center justify-between absolute"><span class="max-w-[15ch] grow truncate overflow-hidden text-center">WIRED</span><span class="-me-1 flex h-full items-center rounded-full px-1 text-[#8F8F8F]">+5</span></span></span></a></span></span></p>
|
||||||
|
</li>
|
||||||
|
<li data-start="573" data-end="743">
|
||||||
|
<p data-start="575" data-end="743">Technical jargon, acronyms, domain-specific vocabulary, accents, dialects, or overlapping speech still lead to high error rates. <span class="" data-state="closed"><span class="ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]" data-testid="webpage-citation-pill"><a href="https://go.verbit.ai/blog/automatic-transcription-the-pros-and-cons-of-ai-solutions/?utm_source=chatgpt.com" target="_blank" rel="noopener" alt="https://go.verbit.ai/blog/automatic-transcription-the-pros-and-cons-of-ai-solutions/?utm_source=chatgpt.com" class="flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"><span class="relative start-0 bottom-0 flex h-full w-full items-center"><span class="flex h-4 w-full items-center justify-between absolute"><span class="max-w-[15ch] grow truncate overflow-hidden text-center">blog.huddles.app</span><span class="-me-1 flex h-full items-center rounded-full px-1 text-[#8F8F8F]">+3</span></span><span class="flex h-4 w-full items-center justify-between"><span class="max-w-[15ch] grow truncate overflow-hidden text-center">go.verbit.ai</span><span class="-me-1 flex h-full items-center rounded-full px-1 text-[#8F8F8F]">+3</span></span><span class="flex h-4 w-full items-center justify-between absolute"><span class="max-w-[15ch] grow truncate overflow-hidden text-center">eff-inc.com</span><span class="-me-1 flex h-full items-center rounded-full px-1 text-[#8F8F8F]">+3</span></span></span></a></span></span></p>
|
||||||
|
</li>
|
||||||
|
<li data-start="747" data-end="870">
|
||||||
|
<p data-start="749" data-end="870">Silence, nonverbal cues, or pauses are sometimes incorrectly annotated or filled. <span class="" data-state="closed"><span class="ms-1 inline-flex max-w-full items-center relative top-[-0.094rem] animate-[show_150ms_ease-in]" data-testid="webpage-citation-pill"><a href="https://blog.huddles.app/what-are-the-limitations-of-ai-in-taking-meeting-notes/?utm_source=chatgpt.com" target="_blank" rel="noopener" alt="https://blog.huddles.app/what-are-the-limitations-of-ai-in-taking-meeting-notes/?utm_source=chatgpt.com" class="flex h-4.5 overflow-hidden rounded-xl px-2 text-[9px] font-medium transition-colors duration-150 ease-in-out text-token-text-secondary! bg-[#F4F4F4]! dark:bg-[#303030]!"><span class="relative start-0 bottom-0 flex h-full w-full items-center"><span class="flex h-4 w-full items-center justify-between"><span class="max-w-[15ch] grow truncate overflow-hidden text-center">blog.huddles.app</span><span class="-me-1 flex h-full items-center rounded-full px-1 text-[#8F8F8F]">+1</span></span></span></a></span></span></p></li></ul></li></ol>
|
||||||
|
''';
|
||||||
|
final document = htmlToDocument(html);
|
||||||
|
final children = document.root.children;
|
||||||
|
expect(children.length, 2);
|
||||||
|
// heading 2 - Key Pain Points & Challenges
|
||||||
|
expect(children[0].type, HeadingBlockKeys.type);
|
||||||
|
expect(children[0].delta!.toPlainText(), 'Key Pain Points & Challenges');
|
||||||
|
|
||||||
|
// numbered list - Accuracy, Hallucinations & Omissions
|
||||||
|
expect(children[1].type, NumberedListBlockKeys.type);
|
||||||
|
expect(
|
||||||
|
children[1].delta!.toPlainText(),
|
||||||
|
'Accuracy, Hallucinations & Omissions',
|
||||||
|
);
|
||||||
|
|
||||||
|
// its children - 3 bulleted list items
|
||||||
|
final children1 = children[1].children;
|
||||||
|
expect(children1.length, 3);
|
||||||
|
expect(
|
||||||
|
children1.every((child) => child.type == BulletedListBlockKeys.type),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue