mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge branch 'feat/flowy_editor' into feat/text-delta-to-text-span
This commit is contained in:
@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"document": {
|
"document": {
|
||||||
"type": "text",
|
"type": "editor",
|
||||||
"attributes": {
|
"attributes": {},
|
||||||
"content": "TITLE"
|
|
||||||
},
|
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -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,10 +77,7 @@ 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,
|
|
||||||
children: [
|
|
||||||
FutureBuilder<String>(
|
|
||||||
future: rootBundle.loadString('assets/document.json'),
|
future: rootBundle.loadString('assets/document.json'),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
@ -94,27 +85,16 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final data =
|
final data = Map<String, Object>.from(json.decode(snapshot.data!));
|
||||||
Map<String, Object>.from(json.decode(snapshot.data!));
|
|
||||||
final document = StateTree.fromJson(data);
|
final document = StateTree.fromJson(data);
|
||||||
print(document.root.toString());
|
_editorState = EditorState(
|
||||||
final editorState = EditorState(
|
|
||||||
document: document,
|
document: document,
|
||||||
renderPlugins: renderPlugins,
|
renderPlugins: renderPlugins,
|
||||||
);
|
);
|
||||||
return editorState.build(context);
|
return _editorState.build(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(
|
|
||||||
height: 50,
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
child: Container(
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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':
|
||||||
|
@ -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,17 +105,12 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider.value(
|
|
||||||
value: node,
|
|
||||||
builder: (_, __) => Consumer<Node>(
|
|
||||||
builder: ((context, value, child) {
|
|
||||||
final textNode = value as TextNode;
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SelectableText.rich(
|
SelectableText.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
children: textNode.toTextSpans(),
|
children: node.toTextSpans(),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_textInputConnection?.close();
|
_textInputConnection?.close();
|
||||||
@ -145,9 +139,6 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -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} }');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rebuildOnNodeChanged) {
|
||||||
|
return _buildNodeChangeNotifier(buildContext);
|
||||||
|
} else {
|
||||||
return build(buildContext);
|
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);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user