docs: readme

This commit is contained in:
Lucas.Xu 2022-08-16 15:08:51 +08:00
parent ad26f9c86d
commit 6a527a6676
7 changed files with 3678 additions and 38 deletions
frontend/app_flowy/packages/flowy_editor

@ -11,15 +11,30 @@ and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
一个可扩展,测试覆盖的 flutter 富文本编辑组件
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
* 可扩展的
* 支持扩展不同样式的视图
* 支持定制快捷键解析
* 支持扩展toolbar/popup list样式(WIP)
* ...
* 协同结构 ready
*
* 质量保证的
* 由于可扩展的结构以及随着功能的增多我们鼓励每个提交的文件或者代码段都可以在test下增加对应的测试用例代码尽可能得保证提交者不需要担心自己的代码影响了已有的逻辑。
## Getting started
```shell
flutter pub add flowy_editor
flutter pub get
```
TODO: List prerequisites and provide or point to information on how to
start using the package.
@ -28,12 +43,47 @@ start using the package.
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
Empty document
```dart
const like = 'sample';
final editorState = EditorState.empty();
final editor = FlowyEditor(
editorState: editorState,
keyEventHandlers: const [],
customBuilders: const {},
);
```
## Additional information
从JSON文件中读取
```dart
final json = ...;
final editorState = EditorState(StateTree.fromJson(data));
final editor = FlowyEditor(
editorState: editorState,
keyEventHandlers: const [],
customBuilders: const {},
);
```
For more. Run the example.
```shell
git clone https://github.com/AppFlowy-IO/AppFlowy.git
cd frontend/app_flowy/packages/flowy_editor/example
flutter run
```
## Examples
## Documentation
* 术语表
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
目前正在完善更多的文档信息
* Selection
*
我们还有很多工作需要继续完成,
Project checker link.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,14 @@
# Contributing
## Reporting Bugs
补充截图,在 Appflowy 仓库增加一个 editor bug 的截图
## 技术讨论与支持
补充discord截图editor那个群
## 提交PR
我们很欢迎和appreciate大家提交的PR。我们也有很多first-contributor-welcome or help-wanted 的 issue 欢迎大家一起来实现。
BTW: 正如ReadMe所说我们想保证大家提交的代码不会影响到现有的代码逻辑和功能所以每次提交PR请附上对应的test建议在test加上对测试用例范围的简单描述。更多细节请看test.md
最后重复一句由于我们是社区驱动的开源编辑器所以我们会认真对待每一个PR以及每一次的PR非常感觉大家的贡献。

@ -0,0 +1,129 @@
# Testing
目前测试文件的目录结构与代码文件的目录结构是保持一致的,这样方便我们查找新增文件的测试情况,以及方便检索对应文件的测试代码路径。
## 提供的测试方法
构造测试的文档数据
```dart
const text = 'Welcome to Appflowy 😁';
// 获取编辑器
final editor = tester.editor;
// 插入空的文本节点
editor.insertEmptyTextNode();
// 插入带信息的文本节点
editor.insertTextNode(text);
// 插入样式heading的文本节点
editor.insertTextNode(text, attributes: {
StyleKey.subtype: StyleKey.heading,
StyleKey.heading: StyleKey.h1,
});
// 插入样式bulleted list的加粗的文本节点
editor.insertTextNode(
'',
attributes: {
StyleKey.subtype: StyleKey.bulletedList,
},
delta: Delta([
TextInsert(text, {StyleKey.bold: true}),
]),
);
```
在测试前必须调用
```dart
await editor.startTesting();
```
获取当前渲染的节点数量
```dart
final length = editor.documentLength;
print(length);
```
获取节点
```dart
// 获取上述文档结构中的第一个文本节点
final firstTextNode = editor.nodeAtPath([0]) as TextNode;
```
更新选区信息
```dart
await editor.updateSelection(
Selection.single(path: firstTextNode.path, startOffset: 0),
);
```
获取选区信息
```dart
final selection = editor.documentSelection;
print(selection);
```
模拟快捷键输入
```dart
// 输入 command + A
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isMetaPressed: true);
// 输入 command + shift + S
await editor.pressLogicKey(
LogicalKeyboardKey.keyS,
isMetaPressed: true,
isShiftPressed: true,
);
```
模拟文字输入
```dart
// 在第一个节点的最起始位置插入'Hello World'
editor.insertText(firstTextNode, 'Hello World', 0);
```
获取文本节点的信息
```dart
// 获取纯文字
final textAfterInserted = firstTextNode.toRawString();
print(textAfterInserted);
// 获取文字的描述信息
final attributes = firstTextNode.attributes;
print(attributes);
```
## Example
例如,目前需要测试 select_all_handler.dart 的文件
完整的例子
```dart
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../infra/test_editor.dart';
void main() async {
setUpAll(() {
TestWidgetsFlutterBinding.ensureInitialized();
});
group('select_all_handler_test.dart', () {
testWidgets('Presses Command + A in the document', (tester) async {
const lines = 100;
const text = 'Welcome to Appflowy 😁';
final editor = tester.editor;
for (var i = 0; i < lines; i++) {
editor.insertTextNode(text);
}
await editor.startTesting();
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isMetaPressed: true);
expect(
editor.documentSelection,
Selection(
start: Position(path: [0], offset: 0),
end: Position(path: [lines - 1], offset: text.length),
),
);
});
}
```
其余关于测试的,例如模拟点击等信息请参考 [An introduction to widget testing](https://docs.flutter.dev/cookbook/testing/widget/introduction)

@ -1,4 +1,3 @@
import 'dart:collection';
import 'dart:convert';
import 'package:flutter/material.dart';
@ -77,8 +76,6 @@ class _MyHomePageState extends State<MyHomePage> {
} else if (page == 1) {
return _buildFlowyEditorWithEmptyDocument();
} else if (page == 2) {
return _buildTextField();
} else if (page == 3) {
return _buildFlowyEditorWithBigDocument();
}
return Container();
@ -115,37 +112,18 @@ class _MyHomePageState extends State<MyHomePage> {
},
icon: const Icon(Icons.text_fields),
),
ActionButton(
onPressed: () {
if (page == 3) return;
setState(() {
page = 3;
});
},
icon: const Icon(Icons.email),
),
],
);
}
Widget _buildFlowyEditorWithEmptyDocument() {
return _buildFlowyEditor(
EditorState(
document: StateTree(
root: Node(
type: 'editor',
children: LinkedList()
..add(
TextNode.empty()
..delta = Delta(
[TextInsert('')],
),
),
attributes: {},
),
),
),
final editorState = EditorState.empty();
final editor = FlowyEditor(
editorState: editorState,
keyEventHandlers: const [],
customBuilders: const {},
);
return editor;
}
Widget _buildFlowyEditorWithExample() {
@ -198,10 +176,4 @@ class _MyHomePageState extends State<MyHomePage> {
),
);
}
Widget _buildTextField() {
return const Center(
child: TextField(),
);
}
}

@ -12,6 +12,19 @@ class StateTree {
required this.root,
});
factory StateTree.empty() {
return StateTree(
root: Node.fromJson({
'type': 'editor',
'children': [
{
'type': 'text',
}
]
}),
);
}
factory StateTree.fromJson(Attributes json) {
assert(json['document'] is Map);

@ -75,6 +75,10 @@ class EditorState {
undoManager.state = this;
}
factory EditorState.empty() {
return EditorState(document: StateTree.empty());
}
/// Apply the transaction to the state.
///
/// The options can be used to determine whether the editor