mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #609 from LucasXu0/feat/flowy_editor
combine render plugins and editor state
This commit is contained in:
commit
a4c66c4db0
6
.github/workflows/dart_test.yml
vendored
6
.github/workflows/dart_test.yml
vendored
@ -8,6 +8,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
|
- 'feat/flowy_editor'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
@ -71,3 +72,8 @@ jobs:
|
|||||||
flutter pub get
|
flutter pub get
|
||||||
flutter test
|
flutter test
|
||||||
|
|
||||||
|
- name: Run FlowyEditor tests
|
||||||
|
working-directory: frontend/app_flowy/packages/flowy_editor
|
||||||
|
run: |
|
||||||
|
flutter pub get
|
||||||
|
flutter test
|
@ -20,7 +20,7 @@
|
|||||||
"subtype": "with-checkbox",
|
"subtype": "with-checkbox",
|
||||||
"text-type": "heading1",
|
"text-type": "heading1",
|
||||||
"font-size": 30,
|
"font-size": 30,
|
||||||
"content": "bbbbbbbbbbbbbbbbbbbbbbb",
|
"content": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||||
"checkbox": false
|
"checkbox": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -92,13 +92,12 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final data = Map<String, Object>.from(json.decode(snapshot.data!));
|
final data = Map<String, Object>.from(json.decode(snapshot.data!));
|
||||||
final stateTree = StateTree.fromJson(data);
|
final document = StateTree.fromJson(data);
|
||||||
return renderPlugins.buildWidget(
|
final editorState = EditorState(
|
||||||
context: NodeWidgetContext(
|
document: document,
|
||||||
buildContext: context,
|
renderPlugins: renderPlugins,
|
||||||
node: stateTree.root,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
return editorState.build(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -5,55 +5,74 @@ import 'package:provider/provider.dart';
|
|||||||
class ImageNodeBuilder extends NodeWidgetBuilder {
|
class ImageNodeBuilder extends NodeWidgetBuilder {
|
||||||
ImageNodeBuilder.create({
|
ImageNodeBuilder.create({
|
||||||
required super.node,
|
required super.node,
|
||||||
required super.renderPlugins,
|
required super.editorState,
|
||||||
}) : super.create();
|
}) : super.create();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext buildContext) {
|
||||||
|
return _ImageNodeWidget(
|
||||||
|
node: node,
|
||||||
|
editorState: editorState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ImageNodeWidget extends StatelessWidget {
|
||||||
|
final Node node;
|
||||||
|
final EditorState editorState;
|
||||||
|
|
||||||
|
const _ImageNodeWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.node,
|
||||||
|
required this.editorState,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
String get src => node.attributes['image_src'] as String;
|
String get src => node.attributes['image_src'] as String;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext buildContext) {
|
Widget build(BuildContext context) {
|
||||||
Future.delayed(const Duration(seconds: 5), () {
|
return GestureDetector(
|
||||||
node.updateAttributes({
|
child: ChangeNotifierProvider.value(
|
||||||
'image_src':
|
|
||||||
"https://images.pexels.com/photos/9995076/pexels-photo-9995076.png?cs=srgb&dl=pexels-temmuz-uzun-9995076.jpg&fm=jpg&w=640&h=400"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return ChangeNotifierProvider.value(
|
|
||||||
value: node,
|
value: node,
|
||||||
builder: (context, child) {
|
builder: (_, __) => Consumer<Node>(
|
||||||
return Consumer<Node>(
|
builder: ((context, value, child) => _build(context)),
|
||||||
builder: (context, value, child) {
|
),
|
||||||
return _build(context);
|
),
|
||||||
|
onTap: () {
|
||||||
|
editorState.update(
|
||||||
|
node,
|
||||||
|
Attributes.from(node.attributes)
|
||||||
|
..addAll(
|
||||||
|
{
|
||||||
|
'image_src':
|
||||||
|
"https://images.pexels.com/photos/9995076/pexels-photo-9995076.png?cs=srgb&dl=pexels-temmuz-uzun-9995076.jpg&fm=jpg&w=640&h=400",
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _build(BuildContext buildContext) {
|
Widget _build(BuildContext context) {
|
||||||
final image = Image.network(src);
|
return Column(
|
||||||
Widget? children;
|
children: [
|
||||||
if (node.children.isNotEmpty) {
|
Image.network(src),
|
||||||
children = Column(
|
if (node.children.isNotEmpty)
|
||||||
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: node.children
|
children: node.children
|
||||||
.map(
|
.map(
|
||||||
(e) => renderPlugins.buildWidget(
|
(e) => editorState.renderPlugins.buildWidget(
|
||||||
context: NodeWidgetContext(buildContext: buildContext, node: e),
|
context: NodeWidgetContext(
|
||||||
|
buildContext: context,
|
||||||
|
node: e,
|
||||||
|
editorState: editorState,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
),
|
||||||
}
|
|
||||||
if (children != null) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
image,
|
|
||||||
children,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import 'package:flowy_editor/flowy_editor.dart';
|
|||||||
class TextNodeBuilder extends NodeWidgetBuilder {
|
class TextNodeBuilder extends NodeWidgetBuilder {
|
||||||
TextNodeBuilder.create({
|
TextNodeBuilder.create({
|
||||||
required super.node,
|
required super.node,
|
||||||
required super.renderPlugins,
|
required super.editorState,
|
||||||
}) : super.create();
|
}) : super.create();
|
||||||
|
|
||||||
String get content => node.attributes['content'] as String;
|
String get content => node.attributes['content'] as String;
|
||||||
@ -25,7 +25,11 @@ class TextNodeBuilder extends NodeWidgetBuilder {
|
|||||||
children: node.children
|
children: node.children
|
||||||
.map(
|
.map(
|
||||||
(e) => renderPlugins.buildWidget(
|
(e) => renderPlugins.buildWidget(
|
||||||
context: NodeWidgetContext(buildContext: buildContext, node: e),
|
context: NodeWidgetContext(
|
||||||
|
buildContext: buildContext,
|
||||||
|
node: e,
|
||||||
|
editorState: editorState,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
class TextWithCheckBoxNodeBuilder extends NodeWidgetBuilder {
|
class TextWithCheckBoxNodeBuilder extends NodeWidgetBuilder {
|
||||||
TextWithCheckBoxNodeBuilder.create({
|
TextWithCheckBoxNodeBuilder.create({
|
||||||
required super.node,
|
required super.node,
|
||||||
required super.renderPlugins,
|
required super.editorState,
|
||||||
}) : super.create();
|
}) : super.create();
|
||||||
|
|
||||||
// TODO: check the type
|
// TODO: check the type
|
||||||
@ -13,11 +13,16 @@ class TextWithCheckBoxNodeBuilder extends NodeWidgetBuilder {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext buildContext) {
|
Widget build(BuildContext buildContext) {
|
||||||
return Row(
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Checkbox(value: isCompleted, onChanged: (value) {}),
|
Checkbox(value: isCompleted, onChanged: (value) {}),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: renderPlugins.buildWidget(
|
child: renderPlugins.buildWidget(
|
||||||
context: NodeWidgetContext(buildContext: buildContext, node: node),
|
context: NodeWidgetContext(
|
||||||
|
buildContext: buildContext,
|
||||||
|
node: node,
|
||||||
|
editorState: editorState,
|
||||||
|
),
|
||||||
withSubtype: false,
|
withSubtype: false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -185,5 +185,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.17.3 <3.0.0"
|
dart: ">=2.17.0 <3.0.0"
|
||||||
flutter: ">=1.17.0"
|
flutter: ">=1.17.0"
|
||||||
|
@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.3 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
|
@ -84,6 +84,20 @@ class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
|||||||
return childAtIndex(path.first)?.childAtPath(path.sublist(1));
|
return childAtIndex(path.first)?.childAtPath(path.sublist(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Path path([Path previous = const []]) {
|
||||||
|
if (parent == null) {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
var index = 0;
|
||||||
|
for (var child in parent!.children) {
|
||||||
|
if (child == this) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
return parent!.path([index, ...previous]);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void insertAfter(Node entry) {
|
void insertAfter(Node entry) {
|
||||||
entry.parent = parent;
|
entry.parent = parent;
|
||||||
|
@ -4,7 +4,9 @@ import 'package:flowy_editor/document/path.dart';
|
|||||||
class StateTree {
|
class StateTree {
|
||||||
final Node root;
|
final Node root;
|
||||||
|
|
||||||
StateTree({required this.root});
|
StateTree({
|
||||||
|
required this.root,
|
||||||
|
});
|
||||||
|
|
||||||
factory StateTree.fromJson(Attributes json) {
|
factory StateTree.fromJson(Attributes json) {
|
||||||
assert(json['document'] is Map);
|
assert(json['document'] is Map);
|
||||||
|
@ -24,7 +24,10 @@ class TextOperation {
|
|||||||
|
|
||||||
int _hashAttributes(Attributes attributes) {
|
int _hashAttributes(Attributes attributes) {
|
||||||
return Object.hashAllUnordered(
|
return Object.hashAllUnordered(
|
||||||
attributes.entries.map((e) => Object.hash(e.key, e.value)));
|
attributes.entries.map(
|
||||||
|
(e) => Object.hash(e.key, e.value),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextInsert extends TextOperation {
|
class TextInsert extends TextOperation {
|
||||||
@ -145,7 +148,8 @@ class _OpIterator {
|
|||||||
int _index = 0;
|
int _index = 0;
|
||||||
int _offset = 0;
|
int _offset = 0;
|
||||||
|
|
||||||
_OpIterator(List<TextOperation> operations) : _operations = UnmodifiableListView(operations);
|
_OpIterator(List<TextOperation> operations)
|
||||||
|
: _operations = UnmodifiableListView(operations);
|
||||||
|
|
||||||
bool get hasNext {
|
bool get hasNext {
|
||||||
return peekLength() < _maxInt;
|
return peekLength() < _maxInt;
|
||||||
@ -186,16 +190,23 @@ class _OpIterator {
|
|||||||
_offset += length;
|
_offset += length;
|
||||||
}
|
}
|
||||||
if (nextOp is TextDelete) {
|
if (nextOp is TextDelete) {
|
||||||
return TextDelete(length: length);
|
return TextDelete(
|
||||||
|
length: length,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextOp is TextRetain) {
|
if (nextOp is TextRetain) {
|
||||||
return TextRetain(length: length, attributes: nextOp.attributes);
|
return TextRetain(
|
||||||
|
length: length,
|
||||||
|
attributes: nextOp.attributes,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextOp is TextInsert) {
|
if (nextOp is TextInsert) {
|
||||||
return TextInsert(
|
return TextInsert(
|
||||||
nextOp.content.substring(offset, offset + length), nextOp.attributes);
|
nextOp.content.substring(offset, offset + length),
|
||||||
|
nextOp.attributes,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextRetain(length: _maxInt);
|
return TextRetain(length: _maxInt);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
|
|
||||||
import './text_delta.dart';
|
import './text_delta.dart';
|
||||||
import './node.dart';
|
import './node.dart';
|
||||||
|
|
||||||
class TextNode extends Node {
|
class TextNode extends Node {
|
||||||
final Delta delta;
|
final Delta delta;
|
||||||
|
|
||||||
TextNode(
|
TextNode({
|
||||||
{required super.type,
|
required super.type,
|
||||||
required super.children,
|
required super.children,
|
||||||
required super.attributes,
|
required super.attributes,
|
||||||
required this.delta});
|
required this.delta,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,52 @@
|
|||||||
|
import 'package:flowy_editor/document/node.dart';
|
||||||
import 'package:flowy_editor/operation/operation.dart';
|
import 'package:flowy_editor/operation/operation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import './document/state_tree.dart';
|
import './document/state_tree.dart';
|
||||||
import './document/selection.dart';
|
import './document/selection.dart';
|
||||||
import './operation/operation.dart';
|
import './operation/operation.dart';
|
||||||
import './operation/transaction.dart';
|
import './operation/transaction.dart';
|
||||||
|
import './render/render_plugins.dart';
|
||||||
|
|
||||||
class EditorState {
|
class EditorState {
|
||||||
final StateTree document;
|
final StateTree document;
|
||||||
|
final RenderPlugins renderPlugins;
|
||||||
Selection? cursorSelection;
|
Selection? cursorSelection;
|
||||||
|
|
||||||
EditorState({
|
EditorState({
|
||||||
required this.document,
|
required this.document,
|
||||||
|
required this.renderPlugins,
|
||||||
});
|
});
|
||||||
|
|
||||||
apply(Transaction transaction) {
|
/// TODO: move to a better place.
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return renderPlugins.buildWidget(
|
||||||
|
context: NodeWidgetContext(
|
||||||
|
buildContext: context,
|
||||||
|
node: document.root,
|
||||||
|
editorState: this,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply(Transaction transaction) {
|
||||||
for (final op in transaction.operations) {
|
for (final op in transaction.operations) {
|
||||||
_applyOperation(op);
|
_applyOperation(op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to a better place.
|
||||||
|
void update(
|
||||||
|
Node node,
|
||||||
|
Attributes attributes,
|
||||||
|
) {
|
||||||
|
_applyOperation(UpdateOperation(
|
||||||
|
path: node.path(),
|
||||||
|
attributes: attributes,
|
||||||
|
oldAttributes: node.attributes,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
_applyOperation(Operation op) {
|
_applyOperation(Operation op) {
|
||||||
if (op is InsertOperation) {
|
if (op is InsertOperation) {
|
||||||
document.insert(op.path, op.value);
|
document.insert(op.path, op.value);
|
||||||
|
@ -5,3 +5,6 @@ export 'package:flowy_editor/document/node.dart';
|
|||||||
export 'package:flowy_editor/document/path.dart';
|
export 'package:flowy_editor/document/path.dart';
|
||||||
export 'package:flowy_editor/render/render_plugins.dart';
|
export 'package:flowy_editor/render/render_plugins.dart';
|
||||||
export 'package:flowy_editor/render/node_widget_builder.dart';
|
export 'package:flowy_editor/render/node_widget_builder.dart';
|
||||||
|
export 'package:flowy_editor/operation/transaction.dart';
|
||||||
|
export 'package:flowy_editor/operation/operation.dart';
|
||||||
|
export 'package:flowy_editor/editor_state.dart';
|
||||||
|
@ -2,9 +2,7 @@ import 'package:flowy_editor/document/path.dart';
|
|||||||
import 'package:flowy_editor/document/node.dart';
|
import 'package:flowy_editor/document/node.dart';
|
||||||
|
|
||||||
abstract class Operation {
|
abstract class Operation {
|
||||||
|
|
||||||
Operation invert();
|
Operation invert();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class InsertOperation extends Operation {
|
class InsertOperation extends Operation {
|
||||||
@ -18,9 +16,11 @@ class InsertOperation extends Operation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return DeleteOperation(path: path, removedValue: value);
|
return DeleteOperation(
|
||||||
|
path: path,
|
||||||
|
removedValue: value,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpdateOperation extends Operation {
|
class UpdateOperation extends Operation {
|
||||||
@ -36,9 +36,12 @@ class UpdateOperation extends Operation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return UpdateOperation(path: path, attributes: oldAttributes, oldAttributes: attributes);
|
return UpdateOperation(
|
||||||
|
path: path,
|
||||||
|
attributes: oldAttributes,
|
||||||
|
oldAttributes: attributes,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeleteOperation extends Operation {
|
class DeleteOperation extends Operation {
|
||||||
@ -52,7 +55,9 @@ class DeleteOperation extends Operation {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Operation invert() {
|
Operation invert() {
|
||||||
return InsertOperation(path: path, value: removedValue);
|
return InsertOperation(
|
||||||
|
path: path,
|
||||||
|
value: removedValue,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import './operation.dart';
|
import './operation.dart';
|
||||||
|
|
||||||
class Transaction {
|
class Transaction {
|
||||||
final List<Operation> operations = [];
|
final List<Operation> operations;
|
||||||
|
Transaction([this.operations = const []]);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
|
import 'package:flowy_editor/document/node.dart';
|
||||||
|
import 'package:flowy_editor/render/render_plugins.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../document/node.dart';
|
|
||||||
import '../render/render_plugins.dart';
|
|
||||||
|
|
||||||
class NodeWidgetBuilder<T extends Node> {
|
class NodeWidgetBuilder<T extends Node> {
|
||||||
|
final EditorState editorState;
|
||||||
final T node;
|
final T node;
|
||||||
final RenderPlugins renderPlugins;
|
|
||||||
|
|
||||||
NodeWidgetBuilder.create({required this.node, required this.renderPlugins});
|
RenderPlugins get renderPlugins => editorState.renderPlugins;
|
||||||
|
|
||||||
Widget call(BuildContext buildContext) => build(buildContext);
|
NodeWidgetBuilder.create({
|
||||||
|
required this.editorState,
|
||||||
|
required this.node,
|
||||||
|
});
|
||||||
|
|
||||||
/// Render the current [Node]
|
/// Render the current [Node]
|
||||||
/// and the layout style of [Node.Children].
|
/// and the layout style of [Node.Children].
|
||||||
Widget build(BuildContext buildContext) => throw UnimplementedError();
|
Widget build(BuildContext buildContext) => throw UnimplementedError();
|
||||||
|
|
||||||
|
Widget call(BuildContext buildContext) => build(buildContext);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../document/node.dart';
|
import '../document/node.dart';
|
||||||
import 'node_widget_builder.dart';
|
import './node_widget_builder.dart';
|
||||||
|
import 'package:flowy_editor/editor_state.dart';
|
||||||
|
|
||||||
class NodeWidgetContext {
|
class NodeWidgetContext {
|
||||||
BuildContext buildContext;
|
final BuildContext buildContext;
|
||||||
Node node;
|
final Node node;
|
||||||
NodeWidgetContext({required this.buildContext, required this.node});
|
final EditorState editorState;
|
||||||
|
|
||||||
|
NodeWidgetContext({
|
||||||
|
required this.buildContext,
|
||||||
|
required this.node,
|
||||||
|
required this.editorState,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef NodeWidgetBuilderF<T extends Node, A extends NodeWidgetBuilder> = A
|
typedef NodeWidgetBuilderF<T extends Node, A extends NodeWidgetBuilder> = A
|
||||||
Function({
|
Function({
|
||||||
required T node,
|
required T node,
|
||||||
required RenderPlugins renderPlugins,
|
required EditorState editorState,
|
||||||
});
|
});
|
||||||
|
|
||||||
// unused
|
// unused
|
||||||
@ -56,8 +63,10 @@ class RenderPlugins {
|
|||||||
name += '/${node.subtype}';
|
name += '/${node.subtype}';
|
||||||
}
|
}
|
||||||
final nodeWidgetBuilder = _nodeWidgetBuilder(name);
|
final nodeWidgetBuilder = _nodeWidgetBuilder(name);
|
||||||
return nodeWidgetBuilder(node: context.node, renderPlugins: this)(
|
return nodeWidgetBuilder(
|
||||||
context.buildContext);
|
node: context.node,
|
||||||
|
editorState: context.editorState,
|
||||||
|
)(context.buildContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeWidgetBuilderF _nodeWidgetBuilder(String name) {
|
NodeWidgetBuilderF _nodeWidgetBuilder(String name) {
|
||||||
|
@ -4,7 +4,7 @@ version: 0.0.1
|
|||||||
homepage:
|
homepage:
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.3 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
flutter: ">=1.17.0"
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -74,9 +74,7 @@ void main() {
|
|||||||
expect(a.compose(b), expected);
|
expect(a.compose(b), expected);
|
||||||
});
|
});
|
||||||
test('retain + insert', () {
|
test('retain + insert', () {
|
||||||
final a = Delta().retain(1, {
|
final a = Delta().retain(1, {'color': 'blue'});
|
||||||
'color': 'blue'
|
|
||||||
});
|
|
||||||
final b = Delta().insert('B');
|
final b = Delta().insert('B');
|
||||||
final expected = Delta().insert('B').retain(1, {
|
final expected = Delta().insert('B').retain(1, {
|
||||||
'color': 'blue',
|
'color': 'blue',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flowy_editor/document/node.dart';
|
import 'package:flowy_editor/document/node.dart';
|
||||||
import 'package:flowy_editor/document/state_tree.dart';
|
import 'package:flowy_editor/document/state_tree.dart';
|
||||||
@ -20,7 +21,7 @@ void main() {
|
|||||||
expect(stateTree.root.toJson(), data['document']);
|
expect(stateTree.root.toJson(), data['document']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('search node in state tree', () async {
|
test('search node by Path in state tree', () async {
|
||||||
final String response = await rootBundle.loadString('assets/document.json');
|
final String response = await rootBundle.loadString('assets/document.json');
|
||||||
final data = Map<String, Object>.from(json.decode(response));
|
final data = Map<String, Object>.from(json.decode(response));
|
||||||
final stateTree = StateTree.fromJson(data);
|
final stateTree = StateTree.fromJson(data);
|
||||||
@ -30,6 +31,18 @@ void main() {
|
|||||||
expect(textType != null, true);
|
expect(textType != null, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('search node by Self in state tree', () async {
|
||||||
|
final String response = await rootBundle.loadString('assets/document.json');
|
||||||
|
final data = Map<String, Object>.from(json.decode(response));
|
||||||
|
final stateTree = StateTree.fromJson(data);
|
||||||
|
final checkBoxNode = stateTree.root.childAtPath([1, 0]);
|
||||||
|
expect(checkBoxNode != null, true);
|
||||||
|
final textType = checkBoxNode!.attributes['text-type'];
|
||||||
|
expect(textType != null, true);
|
||||||
|
final path = checkBoxNode.path([]);
|
||||||
|
expect(pathEquals(path, [1, 0]), true);
|
||||||
|
});
|
||||||
|
|
||||||
test('insert node in state tree', () async {
|
test('insert node in state tree', () async {
|
||||||
final String response = await rootBundle.loadString('assets/document.json');
|
final String response = await rootBundle.loadString('assets/document.json');
|
||||||
final data = Map<String, Object>.from(json.decode(response));
|
final data = Map<String, Object>.from(json.decode(response));
|
||||||
|
Loading…
Reference in New Issue
Block a user