mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
docs: extending.md
This commit is contained in:
parent
2c1b82a916
commit
93565269f0
@ -78,8 +78,10 @@ flutter run
|
|||||||
|
|
||||||
## How to extends
|
## How to extends
|
||||||
### Extending a custom components
|
### Extending a custom components
|
||||||
Please look at [extending.md](documentation/extending.md) for more details.
|
Please refer to [extending.md](documentation/extending.md#extending-a-custom-component) for more details.
|
||||||
|
|
||||||
|
### Extending a custom shortcut event
|
||||||
|
Please refer to [extending.md](documentation/extending.md#extending-a-custom-shortcut-event) for more details.
|
||||||
## Examples
|
## Examples
|
||||||
* Extends a custom component.
|
* Extends a custom component.
|
||||||
* [Checkbox Text](https://github.com/LucasXu0/AppFlowy/blob/documentation/flowy_editor/frontend/app_flowy/packages/flowy_editor/lib/src/render/rich_text/checkbox_text.dart) - Showing how to extend new styles based on existing rich text components.
|
* [Checkbox Text](https://github.com/LucasXu0/AppFlowy/blob/documentation/flowy_editor/frontend/app_flowy/packages/flowy_editor/lib/src/render/rich_text/checkbox_text.dart) - Showing how to extend new styles based on existing rich text components.
|
||||||
|
@ -24,7 +24,7 @@ Widget build(BuildContext context) {
|
|||||||
|
|
||||||
Nothing will happen after typing `_xxx_`.
|
Nothing will happen after typing `_xxx_`.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Next, we will create a function to handler underscore input.
|
Next, we will create a function to handler underscore input.
|
||||||
|
|
||||||
@ -113,26 +113,167 @@ Widget build(BuildContext context) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[Complete code example]()
|
[Complete code example]()
|
||||||
|
|
||||||
## Extending a custom component
|
## Extending a custom component
|
||||||
we will use a simple example to describe how to quickly extend custom component.
|
we will use a simple example to describe how to quickly extend custom component.
|
||||||
/// 1. define your custom type in example.json
|
|
||||||
/// For example I need to define an image plugin, then I define type equals
|
For example, we want to render an image from network.
|
||||||
/// "image", and add "image_src" into "attributes".
|
|
||||||
/// {
|
To start with, we build the simplest example, just a blank document.
|
||||||
/// "type": "image",
|
|
||||||
/// "attributes", { "image_src": "https://s1.ax1x.com/2022/07/28/vCgz1x.png" }
|
```dart
|
||||||
/// }
|
@override
|
||||||
/// 2. create a class extends [NodeWidgetBuilder]
|
Widget build(BuildContext context) {
|
||||||
/// 3. override the function `Widget build(NodeWidgetContext<Node> context)`
|
return Scaffold(
|
||||||
/// and return a widget to render. The returned widget should be
|
body: Container(
|
||||||
/// a StatefulWidget and mixin with [Selectable].
|
alignment: Alignment.topCenter,
|
||||||
///
|
child: FlowyEditor(
|
||||||
/// 4. override the getter `nodeValidator`
|
editorState: EditorState.empty(),
|
||||||
/// to verify the data structure in [Node].
|
keyEventHandlers: const [],
|
||||||
/// 5. register the plugin with `type` to `flowy_editor` in `main.dart`.
|
),
|
||||||
/// 6. Congratulations!
|
),
|
||||||
1. define your custom `type`.
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, we choose a unique string for the type of the custom node and use `network_image` in this case. And we add `network_image_src` to the `attributes` to describe the link of the image.
|
||||||
|
|
||||||
|
> For the definition of the [Node](), please refer to this [link]().
|
||||||
|
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"type": "network_image",
|
||||||
|
"attributes": {
|
||||||
|
"network_image_src": "https://docs.flutter.dev/assets/images/dash/dash-fainting.gif"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, we create a class that inherits [NodeWidgetBuilder](). As shown in the autoprompt, we need to implement two functions, one that returns a widget and the other that verifies the correctness of the [Node]().
|
||||||
|
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
|
||||||
|
@override
|
||||||
|
Widget build(NodeWidgetContext<Node> context) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
NodeValidator<Node> get nodeValidator => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, let's implement a simple image widget based on `Image`.
|
||||||
|
|
||||||
|
**It is important to note that the `State` of the returned `Widget` must be with [Selectable]().**
|
||||||
|
|
||||||
|
> For the definition of the [Selectable](), please refer to this [link]().
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class _NetworkImageNodeWidget extends StatefulWidget {
|
||||||
|
const _NetworkImageNodeWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.node,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Node node;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_NetworkImageNodeWidget> createState() =>
|
||||||
|
__NetworkImageNodeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class __NetworkImageNodeWidgetState extends State<_NetworkImageNodeWidget>
|
||||||
|
with Selectable {
|
||||||
|
RenderBox get _renderBox => context.findRenderObject() as RenderBox;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Image.network(
|
||||||
|
widget.node.attributes['network_image_src'],
|
||||||
|
height: 200,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) =>
|
||||||
|
loadingProgress == null ? child : const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position start() => Position(path: widget.node.path, offset: 0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position end() => Position(path: widget.node.path, offset: 1);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position getPositionInOffset(Offset start) => end();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Rect> getRectsInSelection(Selection selection) =>
|
||||||
|
[Offset.zero & _renderBox.size];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Selection getSelectionInRange(Offset start, Offset end) => Selection.single(
|
||||||
|
path: widget.node.path,
|
||||||
|
startOffset: 0,
|
||||||
|
endOffset: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, we return `_NetworkImageNodeWidget` in the `build` function of `NetworkImageNodeWidgetBuilder` and register `NetworkImageNodeWidgetBuilder` into `AppFlowyEditor`.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
|
||||||
|
@override
|
||||||
|
Widget build(NodeWidgetContext<Node> context) {
|
||||||
|
return _NetworkImageNodeWidget(
|
||||||
|
key: context.node.key,
|
||||||
|
node: context.node,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
NodeValidator<Node> get nodeValidator => (node) {
|
||||||
|
return node.type == 'network_image' &&
|
||||||
|
node.attributes['network_image_src'] is String;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final editorState = EditorState(
|
||||||
|
document: StateTree.empty()
|
||||||
|
..insert(
|
||||||
|
[0],
|
||||||
|
[
|
||||||
|
TextNode.empty(),
|
||||||
|
Node.fromJson({
|
||||||
|
'type': 'network_image',
|
||||||
|
'attributes': {
|
||||||
|
'network_image_src':
|
||||||
|
'https://docs.flutter.dev/assets/images/dash/dash-fainting.gif'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return FlowyEditor(
|
||||||
|
editorState: editorState,
|
||||||
|
customBuilders: {
|
||||||
|
'network_image': NetworkImageNodeWidgetBuilder(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[Complete code example]()
|
Binary file not shown.
After Width: | Height: | Size: 4.2 MiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
|
||||||
|
@override
|
||||||
|
Widget build(NodeWidgetContext<Node> context) {
|
||||||
|
return _NetworkImageNodeWidget(
|
||||||
|
key: context.node.key,
|
||||||
|
node: context.node,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
NodeValidator<Node> get nodeValidator => (node) {
|
||||||
|
return node.type == 'network_image' &&
|
||||||
|
node.attributes['network_image_src'] is String;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NetworkImageNodeWidget extends StatefulWidget {
|
||||||
|
const _NetworkImageNodeWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.node,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Node node;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_NetworkImageNodeWidget> createState() =>
|
||||||
|
__NetworkImageNodeWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class __NetworkImageNodeWidgetState extends State<_NetworkImageNodeWidget>
|
||||||
|
with Selectable {
|
||||||
|
RenderBox get _renderBox => context.findRenderObject() as RenderBox;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Image.network(
|
||||||
|
widget.node.attributes['network_image_src'],
|
||||||
|
height: 200,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) =>
|
||||||
|
loadingProgress == null ? child : const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position start() => Position(path: widget.node.path, offset: 0);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position end() => Position(path: widget.node.path, offset: 1);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Position getPositionInOffset(Offset start) => end();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Rect> getRectsInSelection(Selection selection) =>
|
||||||
|
[Offset.zero & _renderBox.size];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Selection getSelectionInRange(Offset start, Offset end) => Selection.single(
|
||||||
|
path: widget.node.path,
|
||||||
|
startOffset: 0,
|
||||||
|
endOffset: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user