mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
docs: readme
This commit is contained in:
parent
ad26f9c86d
commit
6a527a6676
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.
|
||||
|
3458
frontend/app_flowy/packages/flowy_editor/coverage/lcov.info
Normal file
3458
frontend/app_flowy/packages/flowy_editor/coverage/lcov.info
Normal file
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user