Merge branch 'feat/flowy_editor' into feat/text-delta-to-text-span

This commit is contained in:
Vincent Chan
2022-07-18 16:56:47 +08:00
7 changed files with 133 additions and 98 deletions

View File

@ -1,9 +1,7 @@
{ {
"document": { "document": {
"type": "text", "type": "editor",
"attributes": { "attributes": {},
"content": "TITLE"
},
"children": [ "children": [
{ {
"type": "text", "type": "text",

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:example/plugin/document_node_widget.dart';
import 'package:example/plugin/image_node_widget.dart'; import 'package:example/plugin/image_node_widget.dart';
import 'package:example/plugin/text_node_widget.dart'; import 'package:example/plugin/text_node_widget.dart';
import 'package:example/plugin/text_with_check_box_node_widget.dart'; import 'package:example/plugin/text_with_check_box_node_widget.dart';
@ -56,23 +57,16 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
final RenderPlugins renderPlugins = RenderPlugins(); final RenderPlugins renderPlugins = RenderPlugins();
late EditorState _editorState;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
renderPlugins renderPlugins
..register( ..register('editor', EditorNodeWidgetBuilder.create)
'text', ..register('text', TextNodeBuilder.create)
TextNodeBuilder.create, ..register('image', ImageNodeBuilder.create)
) ..register('text/with-checkbox', TextWithCheckBoxNodeBuilder.create);
..register(
'image',
ImageNodeBuilder.create,
)
..register(
'text/with-checkbox',
TextWithCheckBoxNodeBuilder.create,
);
} }
@override @override
@ -83,37 +77,23 @@ class _MyHomePageState extends State<MyHomePage> {
// the App.build method, and use it to set our appbar title. // the App.build method, and use it to set our appbar title.
title: Text(widget.title), title: Text(widget.title),
), ),
body: Column( body: FutureBuilder<String>(
crossAxisAlignment: CrossAxisAlignment.start, future: rootBundle.loadString('assets/document.json'),
children: [ builder: (context, snapshot) {
FutureBuilder<String>( if (!snapshot.hasData) {
future: rootBundle.loadString('assets/document.json'), return const Center(
builder: (context, snapshot) { child: CircularProgressIndicator(),
if (!snapshot.hasData) { );
return const Center( } else {
child: CircularProgressIndicator(), final data = Map<String, Object>.from(json.decode(snapshot.data!));
); final document = StateTree.fromJson(data);
} else { _editorState = EditorState(
final data = document: document,
Map<String, Object>.from(json.decode(snapshot.data!)); renderPlugins: renderPlugins,
final document = StateTree.fromJson(data); );
print(document.root.toString()); return _editorState.build(context);
final editorState = EditorState( }
document: document, },
renderPlugins: renderPlugins,
);
return editorState.build(context);
}
},
),
SizedBox(
height: 50,
width: MediaQuery.of(context).size.width,
child: Container(
color: Colors.red,
),
)
],
), ),
); );
} }

View File

@ -0,0 +1,50 @@
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flutter/material.dart';
class EditorNodeWidgetBuilder extends NodeWidgetBuilder {
EditorNodeWidgetBuilder.create({
required super.editorState,
required super.node,
}) : super.create();
@override
Widget build(BuildContext buildContext) {
return SingleChildScrollView(
child: _EditorNodeWidget(
node: node,
editorState: editorState,
),
);
}
}
class _EditorNodeWidget extends StatelessWidget {
final Node node;
final EditorState editorState;
const _EditorNodeWidget({
Key? key,
required this.node,
required this.editorState,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: node.children
.map(
(e) => editorState.renderPlugins.buildWidget(
context: NodeWidgetContext(
buildContext: context,
node: e,
editorState: editorState,
),
),
)
.toList(),
),
);
}
}

View File

@ -1,7 +1,5 @@
import 'package:flowy_editor/flowy_editor.dart'; import 'package:flowy_editor/flowy_editor.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flowy_editor/document/attributes.dart';
class ImageNodeBuilder extends NodeWidgetBuilder { class ImageNodeBuilder extends NodeWidgetBuilder {
ImageNodeBuilder.create({ ImageNodeBuilder.create({
@ -33,12 +31,7 @@ class _ImageNodeWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
child: ChangeNotifierProvider.value( child: _build(context),
value: node,
builder: (_, __) => Consumer<Node>(
builder: ((context, value, child) => _build(context)),
),
),
onTap: () { onTap: () {
editorState.update(node, { editorState.update(node, {
'image_src': 'image_src':

View File

@ -3,7 +3,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flowy_editor/flowy_editor.dart'; import 'package:flowy_editor/flowy_editor.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:flowy_editor/document/attributes.dart'; import 'package:flowy_editor/document/attributes.dart';
class TextNodeBuilder extends NodeWidgetBuilder { class TextNodeBuilder extends NodeWidgetBuilder {
@ -98,7 +97,7 @@ class _TextNodeWidget extends StatefulWidget {
class __TextNodeWidgetState extends State<_TextNodeWidget> class __TextNodeWidgetState extends State<_TextNodeWidget>
implements TextInputClient { implements TextInputClient {
Node get node => widget.node; TextNode get node => widget.node as TextNode;
EditorState get editorState => widget.editorState; EditorState get editorState => widget.editorState;
TextEditingValue get textEditingValue => const TextEditingValue(); TextEditingValue get textEditingValue => const TextEditingValue();
@ -106,47 +105,39 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider.value( return Column(
value: node, crossAxisAlignment: CrossAxisAlignment.start,
builder: (_, __) => Consumer<Node>( children: [
builder: ((context, value, child) { SelectableText.rich(
final textNode = value as TextNode; TextSpan(
return Column( children: node.toTextSpans(),
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ onTap: () {
SelectableText.rich( _textInputConnection?.close();
TextSpan( _textInputConnection = TextInput.attach(
children: textNode.toTextSpans(), this,
), const TextInputConfiguration(
onTap: () { enableDeltaModel: false,
_textInputConnection?.close(); inputType: TextInputType.multiline,
_textInputConnection = TextInput.attach( textCapitalization: TextCapitalization.sentences,
this,
const TextInputConfiguration(
enableDeltaModel: false,
inputType: TextInputType.multiline,
textCapitalization: TextCapitalization.sentences,
),
);
_textInputConnection
?..show()
..setEditingState(textEditingValue);
},
), ),
if (node.children.isNotEmpty) );
...node.children.map( _textInputConnection
(e) => editorState.renderPlugins.buildWidget( ?..show()
context: NodeWidgetContext( ..setEditingState(textEditingValue);
buildContext: context, },
node: e, ),
editorState: editorState, if (node.children.isNotEmpty)
), ...node.children.map(
), (e) => editorState.renderPlugins.buildWidget(
) context: NodeWidgetContext(
], buildContext: context,
); node: e,
}), editorState: editorState,
), ),
),
)
],
); );
} }

View File

@ -2,12 +2,15 @@ import 'package:flowy_editor/editor_state.dart';
import 'package:flowy_editor/document/node.dart'; import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/render/render_plugins.dart'; import 'package:flowy_editor/render/render_plugins.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
typedef NodeValidator<T extends Node> = bool Function(T node); typedef NodeValidator<T extends Node> = bool Function(T node);
class NodeWidgetBuilder<T extends Node> { class NodeWidgetBuilder<T extends Node> {
final EditorState editorState; final EditorState editorState;
final T node; final T node;
bool rebuildOnNodeChanged;
NodeValidator<T>? nodeValidator; NodeValidator<T>? nodeValidator;
RenderPlugins get renderPlugins => editorState.renderPlugins; RenderPlugins get renderPlugins => editorState.renderPlugins;
@ -15,6 +18,7 @@ class NodeWidgetBuilder<T extends Node> {
NodeWidgetBuilder.create({ NodeWidgetBuilder.create({
required this.editorState, required this.editorState,
required this.node, required this.node,
this.rebuildOnNodeChanged = true,
}); });
/// Render the current [Node] /// Render the current [Node]
@ -29,6 +33,23 @@ class NodeWidgetBuilder<T extends Node> {
throw Exception( throw Exception(
'Node validate failure, node = { type: ${node.type}, attributes: ${node.attributes} }'); 'Node validate failure, node = { type: ${node.type}, attributes: ${node.attributes} }');
} }
return build(buildContext);
if (rebuildOnNodeChanged) {
return _buildNodeChangeNotifier(buildContext);
} else {
return build(buildContext);
}
}
Widget _buildNodeChangeNotifier(BuildContext buildContext) {
return ChangeNotifierProvider.value(
value: node,
builder: (_, __) => Consumer<T>(
builder: ((context, value, child) {
debugPrint('Node changed, and rebuilding...');
return build(context);
}),
),
);
} }
} }

View File

@ -11,6 +11,8 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
provider: ^6.0.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter