From 00c628437d73ee7327672d7028a5c37612cfd95e Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 13 Jul 2022 23:08:41 +0800 Subject: [PATCH] chore: (draft) support text node widget editing --- .../example/lib/plugin/text_node_widget.dart | 176 ++++++++++++++---- .../flowy_editor/lib/document/node.dart | 1 + 2 files changed, 142 insertions(+), 35 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart index 482e6855f3..66ed5e3779 100644 --- a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart +++ b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flowy_editor/flowy_editor.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; class TextNodeBuilder extends NodeWidgetBuilder { TextNodeBuilder.create({ @@ -11,41 +13,7 @@ class TextNodeBuilder extends NodeWidgetBuilder { @override Widget build(BuildContext buildContext) { - final richText = SelectableText.rich( - TextSpan( - text: node.attributes['content'] as String, - style: node.attributes.toTextStyle(), - ), - ); - - Widget? children; - if (node.children.isNotEmpty) { - children = Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: node.children - .map( - (e) => renderPlugins.buildWidget( - context: NodeWidgetContext( - buildContext: buildContext, - node: e, - editorState: editorState, - ), - ), - ) - .toList(), - ); - } - - if (children != null) { - return Column( - children: [ - richText, - children, - ], - ); - } else { - return richText; - } + return _TextNodeWidget(node: node, editorState: editorState); } } @@ -57,3 +25,141 @@ extension on Attributes { ); } } + +class _TextNodeWidget extends StatefulWidget { + final Node node; + final EditorState editorState; + + const _TextNodeWidget({ + Key? key, + required this.node, + required this.editorState, + }) : super(key: key); + + @override + State<_TextNodeWidget> createState() => __TextNodeWidgetState(); +} + +class __TextNodeWidgetState extends State<_TextNodeWidget> + implements TextInputClient { + Node get node => widget.node; + EditorState get editorState => widget.editorState; + String get content => node.attributes['content'] as String; + TextEditingValue get textEditingValue => TextEditingValue(text: content); + + TextInputConnection? _textInputConnection; + + @override + Widget build(BuildContext context) { + final editableRichText = ChangeNotifierProvider.value( + value: node, + builder: (_, __) => Consumer( + builder: ((context, value, child) => SelectableText.rich( + TextSpan( + text: content, + style: node.attributes.toTextStyle(), + ), + onTap: () { + _textInputConnection?.close(); + _textInputConnection = TextInput.attach( + this, + const TextInputConfiguration( + enableDeltaModel: false, + inputType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + ), + ); + _textInputConnection + ?..show() + ..setEditingState(textEditingValue); + }, + )), + ), + ); + + final child = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + editableRichText, + if (node.children.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: node.children + .map( + (e) => editorState.renderPlugins.buildWidget( + context: NodeWidgetContext( + buildContext: context, + node: e, + editorState: editorState, + ), + ), + ) + .toList(), + ), + ], + ); + return child; + } + + @override + void connectionClosed() { + // TODO: implement connectionClosed + } + + @override + // TODO: implement currentAutofillScope + AutofillScope? get currentAutofillScope => throw UnimplementedError(); + + @override + // TODO: implement currentTextEditingValue + TextEditingValue? get currentTextEditingValue => textEditingValue; + + @override + void insertTextPlaceholder(Size size) { + // TODO: implement insertTextPlaceholder + } + + @override + void performAction(TextInputAction action) { + // TODO: implement performAction + } + + @override + void performPrivateCommand(String action, Map data) { + // TODO: implement performPrivateCommand + } + + @override + void removeTextPlaceholder() { + // TODO: implement removeTextPlaceholder + } + + @override + void showAutocorrectionPromptRect(int start, int end) { + // TODO: implement showAutocorrectionPromptRect + } + + @override + void showToolbar() { + // TODO: implement showToolbar + } + + @override + void updateEditingValue(TextEditingValue value) { + debugPrint(value.text); + editorState.update( + node, + Attributes.from(node.attributes) + ..addAll( + { + 'content': value.text, + }, + ), + ); + } + + @override + void updateFloatingCursor(RawFloatingCursorPoint point) { + // TODO: implement updateFloatingCursor + } +} diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart index ad49d9c8a2..9eccdb7897 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart @@ -29,6 +29,7 @@ class Node extends ChangeNotifier with LinkedListEntry { factory Node.fromJson(Map json) { assert(json['type'] is String); + // TODO: check the type that not exist on plugins. final jType = json['type'] as String; final jChildren = json['children'] as List?; final jAttributes = json['attributes'] != null