|
@@ -4,12 +4,9 @@ import 'package:flowy_editor/document/selection.dart';
|
|
|
import 'package:flowy_editor/document/text_delta.dart';
|
|
|
import 'package:flowy_editor/editor_state.dart';
|
|
|
import 'package:flowy_editor/document/path.dart';
|
|
|
-import 'package:flowy_editor/operation/transaction_builder.dart';
|
|
|
import 'package:flowy_editor/render/node_widget_builder.dart';
|
|
|
import 'package:flowy_editor/render/render_plugins.dart';
|
|
|
import 'package:flowy_editor/render/rich_text/rich_text_style.dart';
|
|
|
-import 'package:flowy_editor/infra/flowy_svg.dart';
|
|
|
-import 'package:flowy_editor/extensions/object_extensions.dart';
|
|
|
import 'package:flowy_editor/render/selection/selectable.dart';
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
@@ -56,37 +53,21 @@ class FlowyRichText extends StatefulWidget {
|
|
|
|
|
|
class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|
|
final _textKey = GlobalKey();
|
|
|
- final _decorationKey = GlobalKey();
|
|
|
|
|
|
- EditorState get _editorState => widget.editorState;
|
|
|
- TextNode get _textNode => widget.textNode;
|
|
|
RenderParagraph get _renderParagraph =>
|
|
|
_textKey.currentContext?.findRenderObject() as RenderParagraph;
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
- final attributes = _textNode.attributes;
|
|
|
- // TODO: use factory method ??
|
|
|
- if (attributes.list == 'todo') {
|
|
|
- // return _buildTodoListRichText(context);
|
|
|
- } else if (attributes.list == 'bullet') {
|
|
|
- // return _buildBulletedListRichText(context);
|
|
|
- } else if (attributes.quote == true) {
|
|
|
- return _buildQuotedRichText(context);
|
|
|
- } else if (attributes.heading != null) {
|
|
|
- // return _buildHeadingRichText(context);
|
|
|
- } else if (attributes.number != null) {
|
|
|
- // return _buildNumberListRichText(context);
|
|
|
- }
|
|
|
return _buildRichText(context);
|
|
|
}
|
|
|
|
|
|
@override
|
|
|
- Position start() => Position(path: _textNode.path, offset: 0);
|
|
|
+ Position start() => Position(path: widget.textNode.path, offset: 0);
|
|
|
|
|
|
@override
|
|
|
- Position end() =>
|
|
|
- Position(path: _textNode.path, offset: _textNode.toRawString().length);
|
|
|
+ Position end() => Position(
|
|
|
+ path: widget.textNode.path, offset: widget.textNode.toRawString().length);
|
|
|
|
|
|
@override
|
|
|
Rect getCursorRectInPosition(Position position) {
|
|
@@ -108,23 +89,22 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|
|
Position getPositionInOffset(Offset start) {
|
|
|
final offset = _renderParagraph.globalToLocal(start);
|
|
|
final baseOffset = _renderParagraph.getPositionForOffset(offset).offset;
|
|
|
- return Position(path: _textNode.path, offset: baseOffset);
|
|
|
+ return Position(path: widget.textNode.path, offset: baseOffset);
|
|
|
}
|
|
|
|
|
|
@override
|
|
|
List<Rect> getRectsInSelection(Selection selection) {
|
|
|
assert(pathEquals(selection.start.path, selection.end.path) &&
|
|
|
- pathEquals(selection.start.path, _textNode.path));
|
|
|
+ pathEquals(selection.start.path, widget.textNode.path));
|
|
|
|
|
|
final textSelection = TextSelection(
|
|
|
baseOffset: selection.start.offset,
|
|
|
extentOffset: selection.end.offset,
|
|
|
);
|
|
|
- final baseRect = frontWidgetRect();
|
|
|
- return _renderParagraph.getBoxesForSelection(textSelection).map((box) {
|
|
|
- final rect = box.toRect();
|
|
|
- return rect.translate(baseRect.centerRight.dx, 0);
|
|
|
- }).toList();
|
|
|
+ return _renderParagraph
|
|
|
+ .getBoxesForSelection(textSelection)
|
|
|
+ .map((box) => box.toRect())
|
|
|
+ .toList();
|
|
|
}
|
|
|
|
|
|
@override
|
|
@@ -134,7 +114,7 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|
|
final baseOffset = _renderParagraph.getPositionForOffset(localStart).offset;
|
|
|
final extentOffset = _renderParagraph.getPositionForOffset(localEnd).offset;
|
|
|
return Selection.single(
|
|
|
- path: _textNode.path,
|
|
|
+ path: widget.textNode.path,
|
|
|
startOffset: baseOffset,
|
|
|
endOffset: extentOffset,
|
|
|
);
|
|
@@ -144,18 +124,29 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|
|
return _buildSingleRichText(context);
|
|
|
}
|
|
|
|
|
|
+ Widget _buildSingleRichText(BuildContext context) {
|
|
|
+ final textSpan = _textSpan;
|
|
|
+ return RichText(
|
|
|
+ key: _textKey,
|
|
|
+ text: widget.textSpanDecorator != null
|
|
|
+ ? widget.textSpanDecorator!(textSpan)
|
|
|
+ : textSpan,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // unused now.
|
|
|
Widget _buildRichTextWithChildren(BuildContext context) {
|
|
|
return Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
children: [
|
|
|
_buildSingleRichText(context),
|
|
|
- ..._textNode.children
|
|
|
+ ...widget.textNode.children
|
|
|
.map(
|
|
|
- (child) => _editorState.renderPlugins.buildWidget(
|
|
|
+ (child) => widget.editorState.renderPlugins.buildWidget(
|
|
|
context: NodeWidgetContext(
|
|
|
buildContext: context,
|
|
|
node: child,
|
|
|
- editorState: _editorState,
|
|
|
+ editorState: widget.editorState,
|
|
|
),
|
|
|
),
|
|
|
)
|
|
@@ -164,115 +155,8 @@ class _FlowyRichTextState extends State<FlowyRichText> with Selectable {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _buildSingleRichText(BuildContext context) {
|
|
|
- return RichText(
|
|
|
- key: _textKey,
|
|
|
- text: widget.textSpanDecorator != null
|
|
|
- ? widget.textSpanDecorator!(_decorateTextSpanWithGlobalStyle)
|
|
|
- : _decorateTextSpanWithGlobalStyle,
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- Widget _buildTodoListRichText(BuildContext context) {
|
|
|
- final name = _textNode.attributes.todo ? 'check' : 'uncheck';
|
|
|
- return Row(
|
|
|
- crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
- children: [
|
|
|
- GestureDetector(
|
|
|
- child: FlowySvg(
|
|
|
- key: _decorationKey,
|
|
|
- name: name,
|
|
|
- ),
|
|
|
- onTap: () => TransactionBuilder(_editorState)
|
|
|
- ..updateNode(_textNode, {
|
|
|
- 'todo': !_textNode.attributes.todo,
|
|
|
- })
|
|
|
- ..commit(),
|
|
|
- ),
|
|
|
- _buildRichText(context),
|
|
|
- ],
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- Widget _buildBulletedListRichText(BuildContext context) {
|
|
|
- return Row(
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
- children: [
|
|
|
- FlowySvg(
|
|
|
- key: _decorationKey,
|
|
|
- name: 'point',
|
|
|
- ),
|
|
|
- _buildRichText(context),
|
|
|
- ],
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- Widget _buildNumberListRichText(BuildContext context) {
|
|
|
- return Row(
|
|
|
- crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
- children: [
|
|
|
- FlowySvg(
|
|
|
- key: _decorationKey,
|
|
|
- number: _textNode.attributes.number,
|
|
|
- ),
|
|
|
- _buildRichText(context),
|
|
|
- ],
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- Widget _buildQuotedRichText(BuildContext context) {
|
|
|
- return Row(
|
|
|
- crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
- children: [
|
|
|
- FlowySvg(
|
|
|
- key: _decorationKey,
|
|
|
- name: 'quote',
|
|
|
- ),
|
|
|
- _buildRichText(context),
|
|
|
- ],
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- Widget _buildHeadingRichText(BuildContext context) {
|
|
|
- // TODO: customize
|
|
|
- return Column(
|
|
|
- children: [
|
|
|
- const Padding(padding: EdgeInsets.only(top: 5)),
|
|
|
- _buildRichText(context),
|
|
|
- const Padding(padding: EdgeInsets.only(top: 5)),
|
|
|
- ],
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- Rect frontWidgetRect() {
|
|
|
- // FIXME: find a more elegant way to solve this situation.
|
|
|
- final renderBox = _decorationKey.currentContext
|
|
|
- ?.findRenderObject()
|
|
|
- ?.unwrapOrNull<RenderBox>();
|
|
|
- if (renderBox != null) {
|
|
|
- return renderBox.localToGlobal(Offset.zero) & renderBox.size;
|
|
|
- }
|
|
|
- return Rect.zero;
|
|
|
- }
|
|
|
-
|
|
|
- TextSpan get _decorateTextSpanWithGlobalStyle => TextSpan(
|
|
|
- children: _textSpan.children
|
|
|
- ?.whereType<TextSpan>()
|
|
|
- .map(
|
|
|
- (span) => TextSpan(
|
|
|
- text: span.text,
|
|
|
- style: span.style?.copyWith(
|
|
|
- fontSize: _textNode.attributes.fontSize,
|
|
|
- color: _textNode.attributes.quoteColor,
|
|
|
- ),
|
|
|
- recognizer: span.recognizer,
|
|
|
- ),
|
|
|
- )
|
|
|
- .toList(),
|
|
|
- );
|
|
|
-
|
|
|
TextSpan get _textSpan => TextSpan(
|
|
|
- children: _textNode.delta.operations
|
|
|
+ children: widget.textNode.delta.operations
|
|
|
.whereType<TextInsert>()
|
|
|
.map((insert) => RichTextStyle(
|
|
|
attributes: insert.attributes ?? {},
|