From e9d8dc9657b73d628550dbef1ce3c6a05bbde8df Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 17:50:32 +0800 Subject: [PATCH 1/9] feat: increase line spacing --- .../flowy_editor/lib/infra/flowy_svg.dart | 9 +++ .../render/rich_text/bulleted_list_text.dart | 51 +++++++------ .../lib/render/rich_text/checkbox_text.dart | 76 ++++++++++--------- .../render/rich_text/default_selectable.dart | 12 ++- .../lib/render/rich_text/heading_text.dart | 22 +++--- .../render/rich_text/number_list_text.dart | 55 ++++++++------ .../lib/render/rich_text/quoted_text.dart | 58 +++++++------- .../lib/render/rich_text/rich_text.dart | 22 +++--- .../lib/render/rich_text/rich_text_style.dart | 28 ++++++- 9 files changed, 198 insertions(+), 135 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart b/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart index d38fe2d16d..d40a198b1a 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/infra/flowy_svg.dart @@ -8,15 +8,24 @@ class FlowySvg extends StatelessWidget { this.size = const Size(20, 20), this.color, this.number, + this.padding, }) : super(key: key); final String? name; final Size size; final Color? color; final int? number; + final EdgeInsets? padding; @override Widget build(BuildContext context) { + return Padding( + padding: padding ?? const EdgeInsets.all(0), + child: _buildSvg(), + ); + } + + Widget _buildSvg() { if (name != null) { return SizedBox.fromSize( size: size, diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/bulleted_list_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/bulleted_list_text.dart index b962f63f3d..2607be26ed 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/bulleted_list_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/bulleted_list_text.dart @@ -43,38 +43,45 @@ class BulletedListTextNodeWidget extends StatefulWidget { class _BulletedListTextNodeWidgetState extends State with Selectable, DefaultSelectable { + @override + final iconKey = GlobalKey(); + final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text'); - final leftPadding = 20.0; + final _iconSize = 20.0; + final _iconRightPadding = 5.0; @override Selectable get forward => _richTextKey.currentState as Selectable; - @override - Offset get baseOffset { - return Offset(leftPadding, 0); - } - @override Widget build(BuildContext context) { + final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding; + return SizedBox( - width: maxTextNodeWidth, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowySvg( - size: Size.square(leftPadding), - name: 'point', - ), - Expanded( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'List', - textNode: widget.textNode, - editorState: widget.editorState, + width: defaultMaxTextNodeWidth, + child: Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowySvg( + key: iconKey, + size: Size.square(_iconSize), + padding: + EdgeInsets.only(top: topPadding, right: _iconRightPadding), + name: 'point', ), - ), - ], + Expanded( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'List', + textNode: widget.textNode, + editorState: widget.editorState, + ), + ), + ], + ), ), ); } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart index e5b02eb32d..890f80fd54 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart @@ -41,19 +41,17 @@ class CheckboxNodeWidget extends StatefulWidget { class _CheckboxNodeWidgetState extends State with Selectable, DefaultSelectable { - final _richTextKey = GlobalKey(debugLabel: 'checkbox_text'); + @override + final iconKey = GlobalKey(); - final leftPadding = 20.0; + final _richTextKey = GlobalKey(debugLabel: 'checkbox_text'); + final _iconSize = 20.0; + final _iconRightPadding = 5.0; @override Selectable get forward => _richTextKey.currentState as Selectable; - @override - Offset get baseOffset { - return Offset(leftPadding, 0); - } - @override Widget build(BuildContext context) { if (widget.textNode.children.isEmpty) { @@ -65,37 +63,43 @@ class _CheckboxNodeWidgetState extends State Widget _buildWithSingle(BuildContext context) { final check = widget.textNode.attributes.check; + final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding; return SizedBox( - width: maxTextNodeWidth, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - child: FlowySvg( - size: Size.square(leftPadding), - name: check ? 'check' : 'uncheck', - ), - onTap: () { - debugPrint('[Checkbox] onTap...'); - TransactionBuilder(widget.editorState) - ..updateNode(widget.textNode, { - StyleKey.checkbox: !check, - }) - ..commit(); - }, + width: defaultMaxTextNodeWidth, + child: Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + child: FlowySvg( + key: iconKey, + size: Size.square(_iconSize), + padding: EdgeInsets.only( + top: topPadding, right: _iconRightPadding), + name: check ? 'check' : 'uncheck', + ), + onTap: () { + debugPrint('[Checkbox] onTap...'); + TransactionBuilder(widget.editorState) + ..updateNode(widget.textNode, { + StyleKey.checkbox: !check, + }) + ..commit(); + }, + ), + Expanded( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'To-do', + textNode: widget.textNode, + textSpanDecorator: _textSpanDecorator, + editorState: widget.editorState, + ), + ), + ], ), - Expanded( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'To-do', - textNode: widget.textNode, - textSpanDecorator: _textSpanDecorator, - editorState: widget.editorState, - ), - ), - ], - ), - ); + )); } Widget _buildWithChildren(BuildContext context) { diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/default_selectable.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/default_selectable.dart index 21cc5108f3..4fbe9c1521 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/default_selectable.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/default_selectable.dart @@ -6,7 +6,17 @@ import 'package:flutter/material.dart'; mixin DefaultSelectable { Selectable get forward; - Offset get baseOffset; + GlobalKey? get iconKey; + + Offset get baseOffset { + if (iconKey != null) { + final renderBox = iconKey!.currentContext?.findRenderObject(); + if (renderBox is RenderBox) { + return Offset(renderBox.size.width, 0); + } + } + return Offset.zero; + } Position getPositionInOffset(Offset start) => forward.getPositionInOffset(start); diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/heading_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/heading_text.dart index f74064fac6..c010ad4833 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/heading_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/heading_text.dart @@ -41,9 +41,11 @@ class HeadingTextNodeWidget extends StatefulWidget { class _HeadingTextNodeWidgetState extends State with Selectable, DefaultSelectable { + @override + GlobalKey? get iconKey => null; + final _richTextKey = GlobalKey(debugLabel: 'heading_text'); - final topPadding = 5.0; - final bottomPadding = 2.0; + final _topPadding = 5.0; @override Selectable get forward => @@ -51,18 +53,18 @@ class _HeadingTextNodeWidgetState extends State @override Offset get baseOffset { - return Offset(0, topPadding); + return Offset(0, _topPadding); } @override Widget build(BuildContext context) { - return SizedBox( - width: maxTextNodeWidth, - child: Padding( - padding: EdgeInsets.only( - top: topPadding, - bottom: bottomPadding, - ), + return Padding( + padding: EdgeInsets.only( + top: _topPadding, + bottom: defaultLinePadding, + ), + child: SizedBox( + width: defaultMaxTextNodeWidth, child: FlowyRichText( key: _richTextKey, placeholderText: 'Heading', diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/number_list_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/number_list_text.dart index 65b41e8e9b..4ffd587470 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/number_list_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/number_list_text.dart @@ -43,39 +43,44 @@ class NumberListTextNodeWidget extends StatefulWidget { class _NumberListTextNodeWidgetState extends State with Selectable, DefaultSelectable { + @override + final iconKey = GlobalKey(); + final _richTextKey = GlobalKey(debugLabel: 'number_list_text'); - final leftPadding = 20.0; + final _iconSize = 20.0; + final _iconRightPadding = 5.0; @override Selectable get forward => _richTextKey.currentState as Selectable; - @override - Offset get baseOffset { - return Offset(leftPadding, 0); - } - @override Widget build(BuildContext context) { - return SizedBox( - width: maxTextNodeWidth, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowySvg( - size: Size.square(leftPadding), - number: widget.textNode.attributes.number, + final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding; + return Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: SizedBox( + width: defaultMaxTextNodeWidth, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowySvg( + key: iconKey, + size: Size.square(_iconSize), + padding: + EdgeInsets.only(top: topPadding, right: _iconRightPadding), + number: widget.textNode.attributes.number, + ), + Expanded( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'List', + textNode: widget.textNode, + editorState: widget.editorState, + ), + ), + ], ), - Expanded( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'List', - textNode: widget.textNode, - editorState: widget.editorState, - ), - ), - ], - ), - ); + )); } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/quoted_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/quoted_text.dart index 0bb259de14..09004f7f9d 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/quoted_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/quoted_text.dart @@ -42,48 +42,50 @@ class QuotedTextNodeWidget extends StatefulWidget { class _QuotedTextNodeWidgetState extends State with Selectable, DefaultSelectable { + @override + final iconKey = GlobalKey(); + final _richTextKey = GlobalKey(debugLabel: 'quoted_text'); - final leftPadding = 20.0; + final _iconSize = 20.0; + final _iconRightPadding = 5.0; @override Selectable get forward => _richTextKey.currentState as Selectable; - @override - Offset get baseOffset { - return Offset(leftPadding, 0); - } - @override Widget build(BuildContext context) { + final topPadding = RichTextStyle.fromTextNode(widget.textNode).topPadding; return SizedBox( - width: maxTextNodeWidth, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowySvg( - size: Size( - leftPadding, - _quoteHeight, - ), - name: 'quote', + width: defaultMaxTextNodeWidth, + child: Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowySvg( + key: iconKey, + size: Size(_iconSize, _quoteHeight), + padding: + EdgeInsets.only(top: topPadding, right: _iconRightPadding), + name: 'quote', + ), + Expanded( + child: FlowyRichText( + key: _richTextKey, + placeholderText: 'Quote', + textNode: widget.textNode, + editorState: widget.editorState, + ), + ), + ], ), - Expanded( - child: FlowyRichText( - key: _richTextKey, - placeholderText: 'Quote', - textNode: widget.textNode, - editorState: widget.editorState, - ), - ), - ], - ), - ); + )); } double get _quoteHeight { final lines = widget.textNode.toRawString().characters.where((c) => c == '\n').length; - return (lines + 1) * leftPadding; + return (lines + 1) * _iconSize; } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text.dart index b6d79a2358..bfb4c217a7 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text.dart @@ -42,26 +42,26 @@ class RichTextNodeWidget extends StatefulWidget { class _RichTextNodeWidgetState extends State with Selectable, DefaultSelectable { + @override + GlobalKey? get iconKey => null; + final _richTextKey = GlobalKey(debugLabel: 'rich_text'); - final leftPadding = 20.0; @override Selectable get forward => _richTextKey.currentState as Selectable; - @override - Offset get baseOffset { - return Offset.zero; - } - @override Widget build(BuildContext context) { return SizedBox( - width: maxTextNodeWidth, - child: FlowyRichText( - key: _richTextKey, - textNode: widget.textNode, - editorState: widget.editorState, + width: defaultMaxTextNodeWidth, + child: Padding( + padding: EdgeInsets.only(bottom: defaultLinePadding), + child: FlowyRichText( + key: _richTextKey, + textNode: widget.textNode, + editorState: widget.editorState, + ), ), ); } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart index 2fb12d68ac..e39aed9aa2 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart @@ -1,4 +1,5 @@ import 'package:flowy_editor/document/attributes.dart'; +import 'package:flowy_editor/document/node.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -60,7 +61,8 @@ class StyleKey { } // TODO: customize -double maxTextNodeWidth = 780.0; +double defaultMaxTextNodeWidth = 780.0; +double defaultLinePadding = 8.0; double baseFontSize = 16.0; // TODO: customize. Map headingToFontSize = { @@ -176,12 +178,33 @@ class RichTextStyle { RichTextStyle({ required this.attributes, required this.text, + this.height = 1.5, }); + RichTextStyle.fromTextNode(TextNode textNode) + : this(attributes: textNode.attributes, text: textNode.toRawString()); + final Attributes attributes; final String text; + final double height; - TextSpan toTextSpan() { + TextSpan toTextSpan() => _toTextSpan(height); + + double get topPadding { + if (height == 1.0) { + return 0; + } + // TODO: Need to be optimized. + final painter = + TextPainter(text: _toTextSpan(height), textDirection: TextDirection.ltr) + ..layout(); + final basePainter = + TextPainter(text: _toTextSpan(null), textDirection: TextDirection.ltr) + ..layout(); + return painter.height - basePainter.height; + } + + TextSpan _toTextSpan(double? height) { return TextSpan( text: text, style: TextStyle( @@ -191,6 +214,7 @@ class RichTextStyle { color: _textColor, decoration: _textDecoration, background: _background, + height: height, ), recognizer: _recognizer, ); From b7cb4b647ddbb48a8928b44b32d716c9b95a1a63 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 17:52:11 +0800 Subject: [PATCH 2/9] fix: the editor loses focus occasionally --- .../packages/flowy_editor/lib/service/selection_service.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart index ecf8caf817..1786eca05f 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart @@ -306,6 +306,9 @@ class _FlowySelectionState extends State return; } + editorState.service.keyboardService?.enable(); + editorState.service.scrollService?.enable(); + panEndOffset = details.globalPosition; final dy = editorState.service.scrollService?.dy; var panStartOffsetWithScrollDyGap = panStartOffset!; From 8fa55cfa08f3fe3203476c6f7510c40a1769216d Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 18:19:11 +0800 Subject: [PATCH 3/9] feat: text insert and replace with selection styles --- .../lib/operation/transaction_builder.dart | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart index 8fa67687c2..55ca336bfb 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'dart:math'; import 'package:flowy_editor/document/attributes.dart'; import 'package:flowy_editor/document/node.dart'; @@ -105,7 +106,20 @@ class TransactionBuilder { insertText(TextNode node, int index, String content, [Attributes? attributes]) { - textEdit(node, () => Delta().retain(index).insert(content, attributes)); + textEdit( + node, + () => Delta().retain(index).insert( + content, + attributes ?? + (index == 0 + ? null + : node.delta + .slice(max(index - 1, 0), index) + .operations + .first + .attributes), + ), + ); afterSelection = Selection.collapsed( Position(path: node.path, offset: index + content.length)); } @@ -121,10 +135,19 @@ class TransactionBuilder { Selection.collapsed(Position(path: node.path, offset: index)); } - replaceText(TextNode node, int index, int length, String content) { + replaceText(TextNode node, int index, int length, String content, + [Attributes? attributes]) { textEdit( node, - () => Delta().retain(index).delete(length).insert(content), + () => Delta().retain(index).delete(length).insert( + content, + attributes ?? + node.delta + .slice(index, index + length) + .operations + .first + .attributes, + ), ); afterSelection = Selection.collapsed( Position( From 4223324689d0e65604f9569dd166960690ce9a98 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 18:54:33 +0800 Subject: [PATCH 4/9] fix: cursor height error --- .../flowy_editor/lib/render/rich_text/flowy_rich_text.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart index 882f98c4d5..81d9159d40 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart @@ -41,6 +41,8 @@ class _FlowyRichTextState extends State with Selectable { final _textKey = GlobalKey(); final _placeholderTextKey = GlobalKey(); + final lineHeight = 1.5; + RenderParagraph get _renderParagraph => _textKey.currentContext?.findRenderObject() as RenderParagraph; @@ -145,6 +147,7 @@ class _FlowyRichTextState extends State with Selectable { ? Colors.transparent : Colors.grey, fontSize: baseFontSize, + height: lineHeight, ), ), ], @@ -200,6 +203,7 @@ class _FlowyRichTextState extends State with Selectable { .map((insert) => RichTextStyle( attributes: insert.attributes ?? {}, text: insert.content, + height: lineHeight, ).toTextSpan()) .toList(growable: false), ); From 1391d202a971216537a5b690f0223b3455d7ac76 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 19:03:55 +0800 Subject: [PATCH 5/9] fix: could not remove text style when pressing enter in empty text node --- .../lib/operation/transaction_builder.dart | 17 +++++---- .../lib/render/rich_text/rich_text_style.dart | 1 + ...er_without_shift_in_text_node_handler.dart | 35 +++++++++++++------ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart index 55ca336bfb..2898f7113a 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart @@ -137,17 +137,16 @@ class TransactionBuilder { replaceText(TextNode node, int index, int length, String content, [Attributes? attributes]) { + var newAttributes = attributes; + if (attributes == null) { + final ops = node.delta.slice(index, index + length).operations; + if (ops.isNotEmpty) { + newAttributes = ops.first.attributes; + } + } textEdit( node, - () => Delta().retain(index).delete(length).insert( - content, - attributes ?? - node.delta - .slice(index, index + length) - .operations - .first - .attributes, - ), + () => Delta().retain(index).delete(length).insert(content, newAttributes), ); afterSelection = Selection.collapsed( Position( diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart index e39aed9aa2..c44fd8dac1 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/rich_text_style.dart @@ -51,6 +51,7 @@ class StyleKey { ]; static List globalStyleKeys = [ + StyleKey.subtype, StyleKey.heading, StyleKey.checkbox, StyleKey.bulletedList, diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart index d2d3b14450..3dd0eef2df 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart @@ -67,16 +67,31 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler = // If selection is collapsed and position.start.offset == 0, // insert a empty text node before. if (selection.isCollapsed && selection.start.offset == 0) { - final afterSelection = Selection.collapsed( - Position(path: textNode.path.next, offset: 0), - ); - TransactionBuilder(editorState) - ..insertNode( - textNode.path, - TextNode.empty(), - ) - ..afterSelection = afterSelection - ..commit(); + if (textNode.toRawString().isEmpty) { + final afterSelection = Selection.collapsed( + Position(path: textNode.path, offset: 0), + ); + TransactionBuilder(editorState) + ..updateNode( + textNode, + Attributes.fromIterable( + StyleKey.globalStyleKeys, + value: (_) => null, + )) + ..afterSelection = afterSelection + ..commit(); + } else { + final afterSelection = Selection.collapsed( + Position(path: textNode.path.next, offset: 0), + ); + TransactionBuilder(editorState) + ..insertNode( + textNode.path, + TextNode.empty(), + ) + ..afterSelection = afterSelection + ..commit(); + } return KeyEventResult.handled; } From 0650c40d9d86c288534b88ce0c8fc318ec66dc52 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 19:11:17 +0800 Subject: [PATCH 6/9] fix: checkbox error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pressing Enter after a checked-off item, the new checkbox is also checked off. it should be unchecked when it’s newly created. --- .../enter_without_shift_in_text_node_handler.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart index 3dd0eef2df..51e593a20b 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart @@ -100,6 +100,13 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler = final needCopyAttributes = StyleKey.globalStyleKeys .where((key) => key != StyleKey.heading) .contains(textNode.subtype); + Attributes attributes = {}; + if (needCopyAttributes) { + attributes = Attributes.from(textNode.attributes); + if (attributes.check) { + attributes[StyleKey.checkbox] = false; + } + } final afterSelection = Selection.collapsed( Position(path: textNode.path.next, offset: 0), ); @@ -107,8 +114,7 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler = ..insertNode( textNode.path.next, textNode.copyWith( - attributes: - needCopyAttributes ? Attributes.from(textNode.attributes) : {}, + attributes: attributes, delta: textNode.delta.slice(selection.end.offset), ), ) From 3e256be0b9d5435bef51a577f8894ebfb15b424d Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 19:19:35 +0800 Subject: [PATCH 7/9] fix: checkbox placeholder error --- .../flowy_editor/lib/render/rich_text/checkbox_text.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart index 890f80fd54..073c339ed6 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart @@ -94,6 +94,7 @@ class _CheckboxNodeWidgetState extends State placeholderText: 'To-do', textNode: widget.textNode, textSpanDecorator: _textSpanDecorator, + placeholderTextSpanDecorator: _textSpanDecorator, editorState: widget.editorState, ), ), From 215587a50703ff0e7103f3eae041e076c1aa5e57 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 19:37:46 +0800 Subject: [PATCH 8/9] chore: format code --- .../lib/operation/transaction_builder.dart | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart index 2898f7113a..cececec924 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart @@ -106,18 +106,19 @@ class TransactionBuilder { insertText(TextNode node, int index, String content, [Attributes? attributes]) { + var newAttributes = attributes; + if (index != 0 && attributes == null) { + newAttributes = node.delta + .slice(max(index - 1, 0), index) + .operations + .first + .attributes; + } textEdit( node, () => Delta().retain(index).insert( content, - attributes ?? - (index == 0 - ? null - : node.delta - .slice(max(index - 1, 0), index) - .operations - .first - .attributes), + newAttributes, ), ); afterSelection = Selection.collapsed( From 6a27c490aad0edc90bf0dcfb785f49304a895aa0 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 9 Aug 2022 19:43:28 +0800 Subject: [PATCH 9/9] fix: selection error in edge --- .../flowy_editor/lib/service/selection_service.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart index 1786eca05f..9ae0082001 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart @@ -339,9 +339,10 @@ class _FlowySelectionState extends State start: isDownward ? start : end, end: isDownward ? end : start); debugPrint('[_onPanUpdate] isDownward = $isDownward, $selection'); editorState.updateCursorSelection(selection); + + _scrollUpOrDownIfNeeded(panEndOffset!, isDownward); } - _scrollUpOrDownIfNeeded(panEndOffset!); _showDebugLayerIfNeeded(); } @@ -466,7 +467,7 @@ class _FlowySelectionState extends State return NodeIterator(stateTree, startNode, endNode).toList(); } - void _scrollUpOrDownIfNeeded(Offset offset) { + void _scrollUpOrDownIfNeeded(Offset offset, bool isDownward) { final dy = editorState.service.scrollService?.dy; if (dy == null) { assert(false, 'Dy could not be null'); @@ -478,10 +479,10 @@ class _FlowySelectionState extends State /// TODO: It is necessary to calculate the relative speed /// according to the gap and move forward more gently. final distance = 10.0; - if (offset.dy <= topLimit) { + if (offset.dy <= topLimit && !isDownward) { // up editorState.service.scrollService?.scrollTo(dy - distance); - } else if (offset.dy >= bottomLimit) { + } else if (offset.dy >= bottomLimit && isDownward) { //down editorState.service.scrollService?.scrollTo(dy + distance); }