From 9a01f90aeec342f1df26845aef7a31bc2a3674c3 Mon Sep 17 00:00:00 2001 From: Eric Phillips <55208878+egp415@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:18:57 -0700 Subject: [PATCH] doc: Editorial updates to the ReadMe for AppFlowyEditor (#900) docs: Editorial updates to the ReadMe for AppFlowyEditor --- .../packages/appflowy_editor/README.md | 61 +++++++++------- .../documentation/customizing.md | 67 +++++++++--------- .../appflowy_editor/documentation/testing.md | 69 ++++++++++++------- 3 files changed, 115 insertions(+), 82 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/README.md b/frontend/app_flowy/packages/appflowy_editor/README.md index 718c108d5a..21df5f247a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/README.md +++ b/frontend/app_flowy/packages/appflowy_editor/README.md @@ -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. diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md b/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md index 85fa195c96..ed0f257ad0 100644 --- a/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md +++ b/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md @@ -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(); @@ -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. diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md b/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md index 25d918c4b5..d51583a9c5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md +++ b/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md @@ -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)