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) {
|
||||
return op.text;
|
||||
}
|
||||
throw Exception('diff() called $e');
|
||||
assert(false, 'diff() called with non-insert operations');
|
||||
return '';
|
||||
}).join();
|
||||
return text;
|
||||
}).toList();
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ CommandShortcutEventHandler _convertToParagraphCommandHandler = (editorState) {
|
|||
ParagraphBlockKeys.delta: delta.toJson(),
|
||||
},
|
||||
textDirection: textDirection,
|
||||
children: node.children,
|
||||
children: node.children.map((e) => e.deepCopy()).toList(),
|
||||
),
|
||||
deepCopy: true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ class AutoScroller extends EdgeDraggingAutoScroller
|
|||
if (direction != null && direction == AxisDirection.up) {
|
||||
return startAutoScrollIfNecessary(
|
||||
offset & Size(1, edgeOffset),
|
||||
duration: duration,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +46,6 @@ class AutoScroller extends EdgeDraggingAutoScroller
|
|||
|
||||
startAutoScrollIfNecessary(
|
||||
dragTarget,
|
||||
duration: duration,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,25 +13,24 @@ import 'package:flutter/material.dart';
|
|||
export 'package:flutter/physics.dart' show Tolerance;
|
||||
|
||||
/// Describes the aspects of a Scrollable widget to inform inherited widgets
|
||||
/// like [ScrollBehavior] for decorating.
|
||||
// TODO(Piinks): Fix doc with 2DScrollable change.
|
||||
// or enumerate the properties of combined
|
||||
// Scrollables, such as [TwoDimensionalScrollable].
|
||||
/// like [ScrollBehavior] for decorating or enumerate the properties of combined
|
||||
/// Scrollables, such as [TwoDimensionalScrollable].
|
||||
///
|
||||
/// Decorations like [GlowingOverscrollIndicator]s and [Scrollbar]s require
|
||||
/// information about the Scrollable in order to be initialized.
|
||||
@immutable
|
||||
class ScrollableDetails {
|
||||
/// Creates a set of details describing the [Scrollable]. The [direction]
|
||||
/// cannot be null.
|
||||
/// Creates a set of details describing the [Scrollable].
|
||||
const ScrollableDetails({
|
||||
required this.direction,
|
||||
this.controller,
|
||||
this.physics,
|
||||
@Deprecated('Migrate to decorationClipBehavior. '
|
||||
'This property was deprecated so that its application is clearer. This clip '
|
||||
'applies to decorators, and does not directly clip a scroll view. '
|
||||
'This feature was deprecated after v3.9.0-1.0.pre.')
|
||||
@Deprecated(
|
||||
'Migrate to decorationClipBehavior. '
|
||||
'This property was deprecated so that its application is clearer. This clip '
|
||||
'applies to decorators, and does not directly clip a scroll view. '
|
||||
'This feature was deprecated after v3.9.0-1.0.pre.',
|
||||
)
|
||||
Clip? clipBehavior,
|
||||
Clip? decorationClipBehavior,
|
||||
}) : decorationClipBehavior = clipBehavior ?? decorationClipBehavior;
|
||||
|
|
@ -74,10 +73,12 @@ class ScrollableDetails {
|
|||
final Clip? decorationClipBehavior;
|
||||
|
||||
/// Deprecated getter for [decorationClipBehavior].
|
||||
@Deprecated('Migrate to decorationClipBehavior. '
|
||||
'This property was deprecated so that its application is clearer. This clip '
|
||||
'applies to decorators, and does not directly clip a scroll view. '
|
||||
'This feature was deprecated after v3.9.0-1.0.pre.')
|
||||
@Deprecated(
|
||||
'Migrate to decorationClipBehavior. '
|
||||
'This property was deprecated so that its application is clearer. This clip '
|
||||
'applies to decorators, and does not directly clip a scroll view. '
|
||||
'This feature was deprecated after v3.9.0-1.0.pre.',
|
||||
)
|
||||
Clip? get clipBehavior => decorationClipBehavior;
|
||||
|
||||
/// Copy the current [ScrollableDetails] with the given values replacing the
|
||||
|
|
@ -115,12 +116,8 @@ class ScrollableDetails {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
direction,
|
||||
controller,
|
||||
physics,
|
||||
decorationClipBehavior,
|
||||
);
|
||||
int get hashCode =>
|
||||
Object.hash(direction, controller, physics, decorationClipBehavior);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
|
|
@ -143,18 +140,15 @@ class ScrollableDetails {
|
|||
///
|
||||
/// The scroll velocity is controlled by the [velocityScalar]:
|
||||
///
|
||||
/// velocity = <distance of overscroll> * [velocityScalar].
|
||||
/// velocity = (distance of overscroll) * [velocityScalar].
|
||||
class EdgeDraggingAutoScroller {
|
||||
/// Creates a auto scroller that scrolls the [scrollable].
|
||||
EdgeDraggingAutoScroller(
|
||||
this.scrollable, {
|
||||
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.
|
||||
final ScrollableState scrollable;
|
||||
|
||||
|
|
@ -165,10 +159,12 @@ class EdgeDraggingAutoScroller {
|
|||
/// in between each scroll.
|
||||
final VoidCallback? onScrollViewScrolled;
|
||||
|
||||
/// {@template flutter.widgets.EdgeDraggingAutoScroller.velocityScalar}
|
||||
/// The velocity scalar per pixel over scroll.
|
||||
///
|
||||
/// 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;
|
||||
|
||||
late Rect _dragTargetRelatedToScrollOrigin;
|
||||
|
|
@ -178,21 +174,17 @@ class EdgeDraggingAutoScroller {
|
|||
bool _scrolling = false;
|
||||
|
||||
double _offsetExtent(Offset offset, Axis scrollDirection) {
|
||||
switch (scrollDirection) {
|
||||
case Axis.horizontal:
|
||||
return offset.dx;
|
||||
case Axis.vertical:
|
||||
return offset.dy;
|
||||
}
|
||||
return switch (scrollDirection) {
|
||||
Axis.horizontal => offset.dx,
|
||||
Axis.vertical => offset.dy,
|
||||
};
|
||||
}
|
||||
|
||||
double _sizeExtent(Size size, Axis scrollDirection) {
|
||||
switch (scrollDirection) {
|
||||
case Axis.horizontal:
|
||||
return size.width;
|
||||
case Axis.vertical:
|
||||
return size.height;
|
||||
}
|
||||
return switch (scrollDirection) {
|
||||
Axis.horizontal => size.width,
|
||||
Axis.vertical => size.height,
|
||||
};
|
||||
}
|
||||
|
||||
AxisDirection get _axisDirection => scrollable.axisDirection;
|
||||
|
|
@ -205,7 +197,7 @@ class EdgeDraggingAutoScroller {
|
|||
///
|
||||
/// If the scrollable is already scrolling, calling this method updates the
|
||||
/// 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;
|
||||
_dragTargetRelatedToScrollOrigin =
|
||||
dragTarget.translate(deltaToOrigin.dx, deltaToOrigin.dy);
|
||||
|
|
@ -214,7 +206,7 @@ class EdgeDraggingAutoScroller {
|
|||
return;
|
||||
}
|
||||
assert(!_scrolling);
|
||||
_scroll(duration: duration);
|
||||
_scroll();
|
||||
}
|
||||
|
||||
/// Stop any ongoing auto scrolling.
|
||||
|
|
@ -222,100 +214,115 @@ class EdgeDraggingAutoScroller {
|
|||
_scrolling = false;
|
||||
}
|
||||
|
||||
Future<void> _scroll({Duration? duration}) async {
|
||||
final RenderBox scrollRenderBox =
|
||||
scrollable.context.findRenderObject()! as RenderBox;
|
||||
final Rect globalRect = MatrixUtils.transformRect(
|
||||
scrollRenderBox.getTransformTo(null),
|
||||
Rect.fromLTWH(
|
||||
0,
|
||||
0,
|
||||
scrollRenderBox.size.width,
|
||||
scrollRenderBox.size.height,
|
||||
),
|
||||
);
|
||||
|
||||
_scrolling = true;
|
||||
double? newOffset;
|
||||
const double overDragMax = 20.0;
|
||||
|
||||
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
|
||||
final Offset viewportOrigin =
|
||||
globalRect.topLeft.translate(deltaToOrigin.dx, deltaToOrigin.dy);
|
||||
final double viewportStart =
|
||||
_offsetExtent(viewportOrigin, _scrollDirection);
|
||||
final double viewportEnd =
|
||||
viewportStart + _sizeExtent(globalRect.size, _scrollDirection);
|
||||
|
||||
final double proxyStart = _offsetExtent(
|
||||
_dragTargetRelatedToScrollOrigin.topLeft,
|
||||
_scrollDirection,
|
||||
);
|
||||
final double proxyEnd = _offsetExtent(
|
||||
_dragTargetRelatedToScrollOrigin.bottomRight,
|
||||
_scrollDirection,
|
||||
);
|
||||
switch (_axisDirection) {
|
||||
case AxisDirection.up:
|
||||
case AxisDirection.left:
|
||||
if (proxyEnd > viewportEnd &&
|
||||
scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.max(
|
||||
scrollable.position.minScrollExtent,
|
||||
scrollable.position.pixels - overDrag,
|
||||
);
|
||||
} else if (proxyStart < viewportStart &&
|
||||
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
||||
final double overDrag =
|
||||
math.min(viewportStart - proxyStart, overDragMax);
|
||||
newOffset = math.min(
|
||||
scrollable.position.maxScrollExtent,
|
||||
scrollable.position.pixels + overDrag,
|
||||
);
|
||||
}
|
||||
case AxisDirection.right:
|
||||
case AxisDirection.down:
|
||||
if (proxyStart < viewportStart &&
|
||||
scrollable.position.pixels > scrollable.position.minScrollExtent) {
|
||||
final double overDrag =
|
||||
math.min(viewportStart - proxyStart, overDragMax);
|
||||
newOffset = math.max(
|
||||
scrollable.position.minScrollExtent,
|
||||
scrollable.position.pixels - overDrag,
|
||||
);
|
||||
} else if (proxyEnd > viewportEnd &&
|
||||
scrollable.position.pixels < scrollable.position.maxScrollExtent) {
|
||||
final double overDrag = math.min(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.min(
|
||||
scrollable.position.maxScrollExtent,
|
||||
scrollable.position.pixels + overDrag,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (newOffset == null ||
|
||||
(newOffset - scrollable.position.pixels).abs() < 1.0) {
|
||||
// Drag should not trigger scroll.
|
||||
_scrolling = false;
|
||||
return;
|
||||
}
|
||||
duration ??= Duration(milliseconds: (1000 / velocityScalar).round());
|
||||
if (duration == Duration.zero) {
|
||||
scrollable.position.jumpTo(newOffset);
|
||||
} else {
|
||||
await scrollable.position.animateTo(
|
||||
newOffset,
|
||||
duration: duration,
|
||||
curve: Curves.linear,
|
||||
Future<void> _scroll() async {
|
||||
try {
|
||||
final RenderBox scrollRenderBox =
|
||||
scrollable.context.findRenderObject()! as RenderBox;
|
||||
final Matrix4 transform = scrollRenderBox.getTransformTo(null);
|
||||
final Rect globalRect = MatrixUtils.transformRect(
|
||||
transform,
|
||||
Rect.fromLTWH(
|
||||
0,
|
||||
0,
|
||||
scrollRenderBox.size.width,
|
||||
scrollRenderBox.size.height,
|
||||
),
|
||||
);
|
||||
final Rect transformedDragTarget = MatrixUtils.transformRect(
|
||||
transform,
|
||||
_dragTargetRelatedToScrollOrigin,
|
||||
);
|
||||
}
|
||||
|
||||
if (onScrollViewScrolled != null) {
|
||||
onScrollViewScrolled!();
|
||||
}
|
||||
if (_scrolling) {
|
||||
await _scroll(duration: duration);
|
||||
if ((globalRect.size.width + precisionErrorTolerance) >=
|
||||
transformedDragTarget.size.width &&
|
||||
(globalRect.size.height + precisionErrorTolerance) >=
|
||||
transformedDragTarget.size.height) {
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
_scrolling = true;
|
||||
double? newOffset;
|
||||
const double overDragMax = 20.0;
|
||||
|
||||
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
|
||||
final Offset viewportOrigin =
|
||||
globalRect.topLeft.translate(deltaToOrigin.dx, deltaToOrigin.dy);
|
||||
final double viewportStart =
|
||||
_offsetExtent(viewportOrigin, _scrollDirection);
|
||||
final double viewportEnd =
|
||||
viewportStart + _sizeExtent(globalRect.size, _scrollDirection);
|
||||
|
||||
final double proxyStart = _offsetExtent(
|
||||
_dragTargetRelatedToScrollOrigin.topLeft,
|
||||
_scrollDirection,
|
||||
);
|
||||
final double proxyEnd = _offsetExtent(
|
||||
_dragTargetRelatedToScrollOrigin.bottomRight,
|
||||
_scrollDirection,
|
||||
);
|
||||
switch (_axisDirection) {
|
||||
case AxisDirection.up:
|
||||
case AxisDirection.left:
|
||||
if (proxyEnd > viewportEnd &&
|
||||
scrollable.position.pixels >
|
||||
scrollable.position.minScrollExtent) {
|
||||
final double overDrag =
|
||||
math.min(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.max(
|
||||
scrollable.position.minScrollExtent,
|
||||
scrollable.position.pixels - overDrag,
|
||||
);
|
||||
} else if (proxyStart < viewportStart &&
|
||||
scrollable.position.pixels <
|
||||
scrollable.position.maxScrollExtent) {
|
||||
final double overDrag =
|
||||
math.min(viewportStart - proxyStart, overDragMax);
|
||||
newOffset = math.min(
|
||||
scrollable.position.maxScrollExtent,
|
||||
scrollable.position.pixels + overDrag,
|
||||
);
|
||||
}
|
||||
case AxisDirection.right:
|
||||
case AxisDirection.down:
|
||||
if (proxyStart < viewportStart &&
|
||||
scrollable.position.pixels >
|
||||
scrollable.position.minScrollExtent) {
|
||||
final double overDrag =
|
||||
math.min(viewportStart - proxyStart, overDragMax);
|
||||
newOffset = math.max(
|
||||
scrollable.position.minScrollExtent,
|
||||
scrollable.position.pixels - overDrag,
|
||||
);
|
||||
} else if (proxyEnd > viewportEnd &&
|
||||
scrollable.position.pixels <
|
||||
scrollable.position.maxScrollExtent) {
|
||||
final double overDrag =
|
||||
math.min(proxyEnd - viewportEnd, overDragMax);
|
||||
newOffset = math.min(
|
||||
scrollable.position.maxScrollExtent,
|
||||
scrollable.position.pixels + overDrag,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (newOffset == null ||
|
||||
(newOffset - scrollable.position.pixels).abs() < 1.0) {
|
||||
// Drag should not trigger scroll.
|
||||
_scrolling = false;
|
||||
return;
|
||||
}
|
||||
final Duration duration =
|
||||
Duration(milliseconds: (1000 / velocityScalar).round());
|
||||
await scrollable.position
|
||||
.animateTo(newOffset, duration: duration, curve: Curves.linear);
|
||||
onScrollViewScrolled?.call();
|
||||
if (_scrolling) {
|
||||
await _scroll();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
} finally {
|
||||
_scrolling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -329,10 +336,83 @@ typedef ScrollIncrementCalculator = double Function(
|
|||
ScrollIncrementDetails details,
|
||||
);
|
||||
|
||||
/// An [Action] that scrolls the [Scrollable] that encloses the current
|
||||
/// [primaryFocus] by the amount configured in the [ScrollIntent] given to it.
|
||||
/// Describes the type of scroll increment that will be performed by a
|
||||
/// [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
|
||||
/// [ScrollAction]s.
|
||||
///
|
||||
|
|
@ -340,23 +420,19 @@ typedef ScrollIncrementCalculator = double Function(
|
|||
/// for a [ScrollIntent.type] set to [ScrollIncrementType.page] is 80% of the
|
||||
/// size of the scroll window, and for [ScrollIncrementType.line], 50 logical
|
||||
/// pixels.
|
||||
class ScrollAction extends Action<ScrollIntent> {
|
||||
class ScrollAction extends ContextAction<ScrollIntent> {
|
||||
@override
|
||||
bool isEnabled(ScrollIntent intent) {
|
||||
final FocusNode? focus = primaryFocus;
|
||||
final bool contextIsValid = focus != null && focus.context != null;
|
||||
if (contextIsValid) {
|
||||
// Check for primary scrollable within the current context
|
||||
if (Scrollable.maybeOf(focus.context!) != null) {
|
||||
return true;
|
||||
}
|
||||
// Check for fallback scrollable with context from PrimaryScrollController
|
||||
final ScrollController? primaryScrollController =
|
||||
PrimaryScrollController.maybeOf(focus.context!);
|
||||
return primaryScrollController != null &&
|
||||
primaryScrollController.hasClients;
|
||||
bool isEnabled(ScrollIntent intent, [BuildContext? context]) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (Scrollable.maybeOf(context) != null) {
|
||||
return true;
|
||||
}
|
||||
final ScrollController? primaryScrollController =
|
||||
PrimaryScrollController.maybeOf(context);
|
||||
return (primaryScrollController != null) &&
|
||||
primaryScrollController.hasClients;
|
||||
}
|
||||
|
||||
/// Returns the scroll increment for a single scroll request, for use when
|
||||
|
|
@ -364,8 +440,8 @@ class ScrollAction extends Action<ScrollIntent> {
|
|||
///
|
||||
/// Must not be called when the position is null, or when any of the position
|
||||
/// metrics (pixels, viewportDimension, maxScrollExtent, minScrollExtent) are
|
||||
/// null. The type and state arguments must not be null, and the widget must
|
||||
/// have already been laid out so that the position fields are valid.
|
||||
/// null. The widget must have already been laid out so that the position
|
||||
/// fields are valid.
|
||||
static double _calculateScrollIncrement(
|
||||
ScrollableState state, {
|
||||
ScrollIncrementType type = ScrollIncrementType.line,
|
||||
|
|
@ -377,18 +453,13 @@ class ScrollAction extends Action<ScrollIntent> {
|
|||
);
|
||||
if (state.widget.incrementCalculator != null) {
|
||||
return state.widget.incrementCalculator!(
|
||||
ScrollIncrementDetails(
|
||||
type: type,
|
||||
metrics: state.position,
|
||||
),
|
||||
ScrollIncrementDetails(type: type, metrics: state.position),
|
||||
);
|
||||
}
|
||||
switch (type) {
|
||||
case ScrollIncrementType.line:
|
||||
return 50.0;
|
||||
case ScrollIncrementType.page:
|
||||
return 0.8 * state.position.viewportDimension;
|
||||
}
|
||||
return switch (type) {
|
||||
ScrollIncrementType.line => 50.0,
|
||||
ScrollIncrementType.page => 0.8 * state.position.viewportDimension,
|
||||
};
|
||||
}
|
||||
|
||||
/// Find out how much of an increment to move by, taking the different
|
||||
|
|
@ -397,58 +468,22 @@ class ScrollAction extends Action<ScrollIntent> {
|
|||
ScrollableState state,
|
||||
ScrollIntent intent,
|
||||
) {
|
||||
final double increment =
|
||||
_calculateScrollIncrement(state, type: intent.type);
|
||||
switch (intent.direction) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (axisDirectionToAxis(intent.direction) ==
|
||||
axisDirectionToAxis(state.axisDirection)) {
|
||||
final double increment =
|
||||
_calculateScrollIncrement(state, type: intent.type);
|
||||
return intent.direction == state.axisDirection ? increment : -increment;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@override
|
||||
void invoke(ScrollIntent intent) {
|
||||
ScrollableState? state = Scrollable.maybeOf(primaryFocus!.context!);
|
||||
void invoke(ScrollIntent intent, [BuildContext? context]) {
|
||||
assert(context != null, 'Cannot scroll without a context.');
|
||||
ScrollableState? state = Scrollable.maybeOf(context!);
|
||||
if (state == null) {
|
||||
final ScrollController primaryScrollController =
|
||||
PrimaryScrollController.of(primaryFocus!.context!);
|
||||
PrimaryScrollController.of(context);
|
||||
assert(() {
|
||||
if (primaryScrollController.positions.length != 1) {
|
||||
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||
|
|
@ -472,29 +507,22 @@ class ScrollAction extends Action<ScrollIntent> {
|
|||
return true;
|
||||
}());
|
||||
|
||||
if (primaryScrollController.position.context.notificationContext ==
|
||||
null &&
|
||||
Scrollable.maybeOf(
|
||||
primaryScrollController.position.context.notificationContext!,
|
||||
) ==
|
||||
null) {
|
||||
final BuildContext? notificationContext =
|
||||
primaryScrollController.position.context.notificationContext;
|
||||
if (notificationContext != null) {
|
||||
state = Scrollable.maybeOf(notificationContext);
|
||||
}
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
state = Scrollable.maybeOf(
|
||||
primaryScrollController.position.context.notificationContext!,
|
||||
);
|
||||
}
|
||||
assert(
|
||||
state != null,
|
||||
'$ScrollAction was invoked on a context that has no scrollable parent',
|
||||
);
|
||||
assert(
|
||||
state!.position.hasPixels,
|
||||
state.position.hasPixels,
|
||||
'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.
|
||||
if (state!.resolvedPhysics != null &&
|
||||
if (state.resolvedPhysics != null &&
|
||||
!state.resolvedPhysics!.shouldAcceptUserOffset(state.position)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -366,6 +366,19 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
|||
element.children.length == 1 &&
|
||||
element.children.first.localName == HTMLTags.paragraph) {
|
||||
(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(
|
||||
type: type,
|
||||
|
|
@ -401,7 +414,8 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
|||
for (final child in children) {
|
||||
if (child is dom.Element) {
|
||||
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>
|
||||
|
||||
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