doc: Editorial updates to the ReadMe for AppFlowyEditor (#900)

docs: Editorial updates to the ReadMe for AppFlowyEditor
This commit is contained in:
Eric Phillips 2022-09-02 00:18:57 -07:00 committed by GitHub
parent 1b9f096e5a
commit 9a01f90aee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 82 deletions

View File

@ -26,21 +26,27 @@ and the Flutter guide for
## Key Features
* Allow you to build rich, intuitive editors
* Design and modify it your way by customizing components, shortcut events, and many more coming soon including menu options and themes
* [Test-covered](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md) and maintained by AppFlowy's core team along with a community of more than 1,000 builders
* Build rich, intuitive editors
* Design and modify an ever expanding list of customizable features including
* components (such as form input controls, numbered lists, and rich text widgets)
* shortcut events
* menu options (**coming soon!**)
* themes (**coming soon!**)
* [Test-coverage](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md) and on-going maintenance by AppFlowy's core team and community of more than 1,000 builders
## Getting Started
## Getting started
Add the AppFlowy editor [Flutter package](https://docs.flutter.dev/development/packages-and-plugins/using-packages) to your environment.
```shell
flutter pub add appflowy_editor
flutter pub get
```
## How to use
## Creating Your First Editor
Start by creating a new empty AppFlowyEditor object.
Let's create a new AppFlowyEditor object
```dart
final editorState = EditorState.empty(); // an empty state
final editor = AppFlowyEditor(
@ -50,7 +56,8 @@ final editor = AppFlowyEditor(
);
```
You can also create an editor from a JSON file
You can also create an editor from a JSON object in order to configure your initial state.
```dart
final json = ...;
final editorState = EditorState(StateTree.fromJson(data));
@ -61,37 +68,43 @@ final editor = AppFlowyEditor(
);
```
To get a sense for how you might use it, run this example:
To get a sense for how the AppFlowy Editor works, run our example:
```shell
git clone https://github.com/AppFlowy-IO/AppFlowy.git
cd frontend/app_flowy/packages/appflowy_editor/example
flutter run
```
## Customizing Your Editor
## How to customize
### Customize a component
Please refer to [customizing a component](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-component) for more details.
### Customizing Components
Please refer to our documentation on customizing AppFlowy for a detailed discussion about [customizing components](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-component).
### Customize a shortcut event
Please refer to [customizing a shortcut event](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-shortcut-event) for more details.
Below are some examples of component customizations:
## More Examples
* Customize a component
* [Checkbox Text](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart) shows you how to extend new styles based on existing rich text components
* [Image](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) teaches you how to extend a new node and render it
* And more examples on [rich-text plugins](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text)
* Customize a shortcut event
* [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) shows you how to make text bold/italic/underline/strikethrough through shortcut keys
* [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
* Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)
* [Checkbox Text](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart) demonstrates how to extend new styles based on existing rich text components
* [Image](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) demonstrates how to extend a new node and render it
* See further examples of [rich-text plugins](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text)
### Customizing Shortcut Events
Please refer to our documentation on customizing AppFlowy for a detailed discussion about [customizing shortcut events](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md#customize-a-shortcut-event).
Below are some examples of shortcut event customizations:
* [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
* [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
* Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)
## Glossary
Please refer to the API documentation.
## Contributing
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated. Please look at [CONTRIBUTING.md](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/contributing-to-appflowy) for details.
Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.
Please look at [CONTRIBUTING.md](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/contributing-to-appflowy) for details.
## License
Distributed under the AGPLv3 License. See LICENSE for more information.
Distributed under the AGPLv3 License. See [LICENSE](https://github.com/AppFlowy-IO/AppFlowy-Docs/blob/main/LICENSE) for more information.

View File

@ -1,12 +1,12 @@
# How to customize ...
# Customizing Editor Features
## Customize a shortcut event
## Customizing a Shortcut Event
We will use a simple example to illustrate how to quickly add a shortcut event.
For example, typing `_xxx_` will be converted into _xxx_.
In this example, text that starts and ends with an underscore ( \_ ) character will be rendered in italics for emphasis. So typing `_xxx_` will automatically be converted into _xxx_.
Let's start with a blank document.
Let's start with a blank document:
```dart
@override
@ -27,7 +27,7 @@ At this point, nothing magic will happen after typing `_xxx_`.
![Before](./images/customizing_a_shortcut_event_before.gif)
Next, we will create a function to handle an underscore input.
To implement our shortcut event we will create a function to handle an underscore input.
```dart
import 'package:appflowy_editor/appflowy_editor.dart';
@ -35,23 +35,25 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
// Since we only need to handler the input of `underscore`.
// All inputs except `underscore` will be ignored directly.
// Since we only need to handle the input of an 'underscore' character,
// all inputs except `underscore` will be ignored immediately.
if (event.logicalKey != LogicalKeyboardKey.underscore) {
return KeyEventResult.ignored;
}
};
```
Then, we need to determine if the currently selected node is `TextNode` and the selection is collapsed.
Then, we need to determine if the currently selected node is a `TextNode` and if the selection is collapsed.
If so, we will continue.
```dart
// ...
FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
// ...
// Obtaining the selection and selected nodes of the current document through `selectionService`.
// And determine whether the selection is collapsed and whether the selected node is a text node.
// Obtain the selection and selected nodes of the current document through the 'selectionService'
// to determine whether the selection is collapsed and whether the selected node is a text node.
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
@ -60,11 +62,11 @@ FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
}
```
Now, we start dealing with underscore.
Now, we deal with handling the underscore.
Look for the position of the previous underscore and
1. return, if not found.
2. if found, the text wrapped in between two underscores will be displayed in italic.
1. if one is _not_ found, return without doing anything.
2. if one is found, the text enclosed within the two underscores will be formatted to display in italics.
```dart
// ...
@ -73,14 +75,14 @@ FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
final textNode = textNodes.first;
final text = textNode.toRawString();
// Determine if `underscore` already exists in the text node
// Determine if an 'underscore' already exists in the text node
final previousUnderscore = text.indexOf('_');
if (previousUnderscore == -1) {
return KeyEventResult.ignored;
}
// Delete the previous `underscore`,
// update the style of the text surrounded by two underscores to `italic`,
// Delete the previous 'underscore',
// update the style of the text surrounded by the two underscores to 'italic',
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, previousUnderscore, 1)
@ -99,7 +101,7 @@ FlowyKeyEventHandler underscoreToItalicHandler = (editorState, event) {
};
```
So far, the 'underscore handler' function is done and the only task left is to inject it into the AppFlowyEditor.
Now our 'underscore handler' function is done and the only task left is to inject it into the AppFlowyEditor.
```dart
@override
@ -120,14 +122,15 @@ Widget build(BuildContext context) {
![After](./images/customizing_a_shortcut_event_after.gif)
[Complete code example]()
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart) file of this example.
## Customize a component
We will use a simple example to showcase how to quickly add a custom component.
For example, we want to render an image from the network.
## Customizing a Component
We will use a simple example to show how to quickly add a custom component.
To start with, let's create an empty document by running commands as follows:
In this example we will render an image from the network.
Let's start with a blank document:
```dart
@override
@ -144,9 +147,9 @@ Widget build(BuildContext context) {
}
```
Next, we choose a unique string for your custom node's type. We use `network_image` in this case. And we add `network_image_src` to the `attributes` to describe the link of the image.
Next, we will choose a unique string for your custom node's type.
> For the definition of the [Node](), please refer to this [link]().
We'll use `network_image` in this case. And we add `network_image_src` to the `attributes` to describe the link of the image.
```JSON
{
@ -157,9 +160,9 @@ Next, we choose a unique string for your custom node's type. We use `network_ima
}
```
Then, we create a class that inherits [NodeWidgetBuilder](). As shown in the autoprompt, we need to implement two functions:
Then, we create a class that inherits [NodeWidgetBuilder](../lib/src/service/render_plugin_service.dart). As shown in the autoprompt, we need to implement two functions:
1. one returns a widget
2. the other verifies the correctness of the [Node]().
2. the other verifies the correctness of the [Node](../lib/src/document/node.dart).
```dart
@ -179,9 +182,7 @@ class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
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]().
Note that the `State` object that is returned by the `Widget` must implement [Selectable](../lib/src/render/selection/selectable.dart) using the `with` keyword.
```dart
class _NetworkImageNodeWidget extends StatefulWidget {
@ -236,7 +237,7 @@ class __NetworkImageNodeWidgetState extends State<_NetworkImageNodeWidget>
}
```
Finally, we return `_NetworkImageNodeWidget` in the `build` function of `NetworkImageNodeWidgetBuilder` and register `NetworkImageNodeWidgetBuilder` into `AppFlowyEditor`.
Finally, we return `_NetworkImageNodeWidget` in the `build` function of `NetworkImageNodeWidgetBuilder`...
```dart
class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
@ -256,6 +257,8 @@ class NetworkImageNodeWidgetBuilder extends NodeWidgetBuilder {
}
```
... and register `NetworkImageNodeWidgetBuilder` in the `AppFlowyEditor`.
```dart
final editorState = EditorState(
document: StateTree.empty()
@ -281,6 +284,6 @@ return AppFlowyEditor(
);
```
![](./images/customizing_a_component.gif)
![Whew!](./images/customizing_a_component.gif)
[Here you can check out the complete code file of this example]()
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example.

View File

@ -1,24 +1,33 @@
# Testing
> The directory structure of test files is consistent with the code files, making it easy for us to map a file with the corresponding test and check if the test is updated
The directory structure of test files mirrors that of the code files, making it easy for us to map a file with the corresponding test and check if the test is updated.
## Testing Functions
For an overview of testing best practices in Flutter applications, please refer to Flutter's [introduction to widget testing](https://docs.flutter.dev/cookbook/testing/widget/introduction) as well as their [introduction to unit testing](https://docs.flutter.dev/cookbook/testing/unit/introduction).
There you will learn how to do such things as such as simulate a click as well as leverage the `test` and `expect` functions.
## Testing Basic Editor Functions
The example code below shows how to construct a document that will be used in our testing.
**Construct a document for testing**
```dart
const text = 'Welcome to Appflowy 😁';
// Get the instance of editor.
// Get the instance of the editor.
final editor = tester.editor;
// Insert empty text node.
// Insert an empty text node.
editor.insertEmptyTextNode();
// Insert text node with string.
// Insert a text node with the text string we defined earlier.
editor.insertTextNode(text);
// Insert text node with heading style.
// Insert the same text, but with the heading style.
editor.insertTextNode(text, attributes: {
StyleKey.subtype: StyleKey.heading,
StyleKey.heading: StyleKey.h1,
});
// Insert text node with bulleted list style and bold style.
// Insert our text with the bulleted list style and the bold style.
// If you want to modify the style of the inserted text, you need to use the Delta parameter.
editor.insertTextNode(
'',
attributes: {
@ -30,66 +39,76 @@ editor.insertTextNode(
);
```
**The `startTesting` function must be called before testing**.
The `startTesting` function of the editor must be called before you begin your test.
```dart
await editor.startTesting();
```
**Get the number of nodes in the document**
Get the number of nodes in the document.
```dart
final length = editor.documentLength;
print(length);
```
**Get the node of a defined path**
Get the node of a defined path. In this case we are getting the first node of the document which is the text "Welcome to Appflowy 😁".
```dart
final firstTextNode = editor.nodeAtPath([0]) as TextNode;
```
**Update selection**
Update the [Selection](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/document/selection.dart) so that our text "Welcome to Appflowy 😁" is selected. We will start our selection from the beginning of the string.
```dart
await editor.updateSelection(
Selection.single(path: firstTextNode.path, startOffset: 0),
);
```
**Get the selection**
Get the current selection.
```dart
final selection = editor.documentSelection;
print(selection);
```
**Simulate shortcut event inputs**
Next we will simulate the input of a shortcut key being pressed that will select all the text.
```dart
// Command + A.
// Meta + A.
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isMetaPressed: true);
// Command + shift + S.
// Meta + shift + S.
await editor.pressLogicKey(
LogicalKeyboardKey.keyS,
isMetaPressed: true,
LogicalKeyboardKey.keyS,
isMetaPressed: true,
isShiftPressed: true,
);
```
**Simulate a text input**
We will then simulate text input.
```dart
// Insert 'Hello World' at the beginning of the first node.
editor.insertText(firstTextNode, 'Hello World', 0);
```
**Get information about the text node**
Once the text has been added, we can get information about the text node.
```dart
// Get plain text.
// Get the text of the first text node as plain text
final textAfterInserted = firstTextNode.toRawString();
print(textAfterInserted);
// Get attributes.
// Get the attributes of the text node
final attributes = firstTextNode.attributes;
print(attributes);
```
## Example
For example, we are going to test `select_all_handler.dart`
## A Complete Code Example
In the example code below we are going to test `select_all_handler.dart` by inserting 100 lines of text that read "Welcome to Appflowy 😁" and then simulating the "selectAll" shortcut key being pressed.
Afterwards, we will `expect` that the current selection of the editor is equal to the selection of all the lines that were generated.
```dart
import 'package:appflowy_editor/appflowy_editor.dart';
@ -124,5 +143,3 @@ void main() async {
});
}
```
For more information about testing, such as simulating a click, please refer to [An introduction to widget testing](https://docs.flutter.dev/cookbook/testing/widget/introduction)