diff --git a/README.md b/README.md index 3853fb8d..340ac4b0 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Demo App: https://bulletjournal.us/home/index.html Pub: https://pub.dev/packages/flutter_quill +[简体中文文档](./doc_cn.md) + ## Usage See the `example` directory for a minimal example of how to use FlutterQuill. You typically just need to instantiate a controller: @@ -151,7 +153,7 @@ QuillToolbar.basic( } ), ] -``` +``` ### Custom Size Image for Mobile diff --git a/doc_cn.md b/doc_cn.md new file mode 100644 index 00000000..2bf9de93 --- /dev/null +++ b/doc_cn.md @@ -0,0 +1,353 @@ +

+ +

+

支持 Flutter 平台的富文本编辑器

+ +[![MIT License][license-badge]][license-link] +[![PRs Welcome][prs-badge]][prs-link] +[![Watch on GitHub][github-watch-badge]][github-watch-link] +[![Star on GitHub][github-star-badge]][github-star-link] +[![Watch on GitHub][github-forks-badge]][github-forks-link] + +[license-badge]: https://img.shields.io/github/license/singerdmx/flutter-quill.svg?style=for-the-badge +[license-link]: https://github.com/singerdmx/flutter-quill/blob/master/LICENSE +[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge +[prs-link]: https://github.com/singerdmx/flutter-quill/issues +[github-watch-badge]: https://img.shields.io/github/watchers/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff +[github-watch-link]: https://github.com/singerdmx/flutter-quill/watchers +[github-star-badge]: https://img.shields.io/github/stars/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff +[github-star-link]: https://github.com/singerdmx/flutter-quill/stargazers +[github-forks-badge]: https://img.shields.io/github/forks/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff +[github-forks-link]: https://github.com/singerdmx/flutter-quill/network/members + + +FlutterQuill 是一个富文本编辑器,同样也是 [Quill] 在 [Flutter] 的版本。 + +该库是为移动平台构建的 “ 所见即所得 ” 的富文本编辑器,同时我们还正在对 Web 平台进行兼容。查看我们的 [Youtube 播放列表] 或 [代码介绍] 以了解代码的详细内容。你可以加入我们的 [Slack Group] 来进行讨论。 + +Demo App: https://bulletjournal.us/home/index.html + +Pub: https://pub.dev/packages/flutter_quill + +## 用法 + +查看 `示例` 目录来学习 FlutterQuill 最简单的使用方法,你通常只需要实例化一个控制器: + +``` +QuillController _controller = QuillController.basic(); +``` + +然后在你的 App 中嵌入工具栏和编辑器,例如: + +```dart +Column( + children: [ + QuillToolbar.basic(controller: _controller), + Expanded( + child: Container( + child: QuillEditor.basic( + controller: _controller, + readOnly: false, // true for view only mode + ), + ), + ) + ], +) +``` +查看 [示例页面] 以了解高级用户。 + +## 输入 / 输出 + +该库使用 [Quill] 作为内部数据格式。 + +* 使用 `_controller.document.toDelta()` 获取增量。 +* 使用 `_controller.document.toPlainText()` 获取纯文本。 + +FlutterQuill 提供了一些 JSON 序列化支持,以便您可以保存和打开文档。 要将文档保存为 JSON 类型,请执行以下操作: + +``` +var json = jsonEncode(_controller.document.toDelta().toJson()); +``` + +然后你就可以将其存储。 + +想要 FlutterQuill 编辑器使用你之前存储的 JSON 数据,请执行以下操作: + +``` +var myJSON = jsonDecode(incomingJSONText); +_controller = QuillController( + document: Document.fromJson(myJSON), + selection: TextSelection.collapsed(offset: 0)); +``` + +## Web +对于 web 开发,请执行 `flutter config --enable-web` 来获取对 flutter 的支持或使用 [ReactQuill] 获取对 React 的支持。 + +进行 Web 开发需要提供 `EmbedBuilder`, 参考:[defaultEmbedBuilderWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L29). +进行 Web 开发还需要提供 `webImagePickImpl`, 参考: [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L225). + + +## Desktop + +在桌面端进行工具栏按钮开发,需要提供 `filePickImpl`。参考: [示例页面](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L205). + +## 配置 + +`QuillToolbar` 类允许您自定义可用的格式选项。[示例页面] 提供了高级使用和配置的示例代码。 + +### 字号 +在编辑器工具栏中,提供了具有字号功能的下拉菜单。 这可以通过 `showFontSize` 启用或禁用。 + +启用后,可以通过*可选的* `fontSizeValues` 属性修改默认字号。 `fontSizeValues` 接受一个 `Map`,其中包含一个 `String` 类型的标题和一个 `String` 类型的字号。 例子: +``` +fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'} +``` + +字体大小可以使用 `0` 值清除,例如: +``` +fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '0'} +``` + +### 字体 +想要使用你自己的字体,请更新你的 [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) 并且传入 `fontFamilyValues`。详情内容请查看 [this change](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa), [this article](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) 和 [this](https://www.flutterbeads.com/change-font-family-flutter/)。 + +### 自定义按钮 +您可以通过 `customButtons` 可选参数将自定义按钮添加到工具栏的*末尾*,该参数接收的了行是 `QuillCustomButton` 的 `List`。 + +要添加一个 Icon,我们应该实例化一个新的新的 `QuillCustomButton` +``` + QuillCustomButton( + icon:Icons.ac_unit, + onTap: () { + debugPrint('snowflake'); + } + ), +``` + +每个 `QuillCustomButton` 都是 `customButtons` 可选参数的一部分,如下所示: +``` +QuillToolbar.basic( + (...), + customButtons: [ + QuillCustomButton( + icon:Icons.ac_unit, + onTap: () { + debugPrint('snowflake1'); + } + ), + + QuillCustomButton( + icon:Icons.ac_unit, + onTap: () { + debugPrint('snowflake2'); + } + ), + + QuillCustomButton( + icon:Icons.ac_unit, + onTap: () { + debugPrint('snowflake3'); + } + ), + ] +``` + +### 移动端上自定义图片尺寸 + +定义`mobileWidth`、`mobileHeight`、`mobileMargin`、`mobileAlignment`如下: +``` +{ + "insert": { + "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png" + }, + "attributes":{ + "style":"mobileWidth: 50; mobileHeight: 50; mobileMargin: 10; mobileAlignment: topLeft" + } +} +``` + +### 自定义嵌入块 +有时您想在文本中添加一些自定义内容或者是自定义小部件。 比如向文本添加注释,或者在文本编辑器中添加的任何自定义内容。 + +您唯一需要做的就是添加一个 `CustomBlockEmbed` 并将其映射到 `customElementsEmbedBuilder` 中,以将自定义块内的数据转换为一个 widget! + +例子: + +从 `CustomBlockEmbed` 开始,我们在这里扩展它并添加对 'Note' widget 的方法,这就是 `Document`,`flutter_quill` 使用它来呈现富文本。 + +```dart +class NotesBlockEmbed extends CustomBlockEmbed { + const NotesBlockEmbed(String value) : super(noteType, value); + + static const String noteType = 'notes'; + + static NotesBlockEmbed fromDocument(Document document) => + NotesBlockEmbed(jsonEncode(document.toDelta().toJson())); + + Document get document => Document.fromJson(jsonDecode(data)); +} +``` + +然后,我们需要将这个 “notes” 类型映射到 widget 中。在例子中,我使用 `ListTile` 来显示它,使用 `onTap` 方法俩编辑内容,另外不要忘记将此方法添加到 `QuillEditor` 中。 + +```dart +Widget customElementsEmbedBuilder( + BuildContext context, + QuillController controller, + CustomBlockEmbed block, + bool readOnly, + void Function(GlobalKey videoContainerKey)? onVideoInit, +) { + switch (block.type) { + case 'notes': + final notes = NotesBlockEmbed(block.data).document; + + return Material( + color: Colors.transparent, + child: ListTile( + title: Text( + notes.toPlainText().replaceAll('\n', ' '), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + leading: const Icon(Icons.notes), + onTap: () => _addEditNote(context, document: notes), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: const BorderSide(color: Colors.grey), + ), + ), + ); + default: + return const SizedBox(); + } +} +``` + +然后,编写一个方法来添加/编辑内容,`showDialog` 方法显示 Quill 编辑器以编辑内容,用户编辑完成后,需要检查文档是否有内容,如果有内容,在 `CustomBlockEmbed` 中添加/编辑 `NotesBlockEmbed`(注意,如果没有在 `NotesBlockEmbed` 中传递 `CustomBlockEmbed` ,编辑将不会生效)。 + +```dart +Future _addEditNote(BuildContext context, {Document? document}) async { + final isEditing = document != null; + final quillEditorController = QuillController( + document: document ?? Document(), + selection: const TextSelection.collapsed(offset: 0), + ); + + await showDialog( + context: context, + builder: (context) => AlertDialog( + titlePadding: const EdgeInsets.only(left: 16, top: 8), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('${isEditing ? 'Edit' : 'Add'} note'), + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close), + ) + ], + ), + content: QuillEditor.basic( + controller: quillEditorController, + readOnly: false, + ), + ), + ); + + if (quillEditorController.document.isEmpty()) return; + + final block = BlockEmbed.custom( + NotesBlockEmbed.fromDocument(quillEditorController.document), + ); + final controller = _controller!; + final index = controller.selection.baseOffset; + final length = controller.selection.extentOffset - index; + + if (isEditing) { + final offset = getEmbedNode(controller, controller.selection.start).item1; + controller.replaceText( + offset, 1, block, TextSelection.collapsed(offset: offset)); + } else { + controller.replaceText(index, length, block, null); + } +} +``` + +这样我们就成功的在富文本编辑器中添加了一个自定义小组件。 + +

+ 1 +

+ +> 更多信息和视频示例,请参阅 [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877) + +> 有关更多详细信息,请查看 [this YouTube video](https://youtu.be/pI5p5j7cfHc) + +### 翻译 + +该库为 quill 工具栏和编辑器提供翻译,除非您设置自己的语言环境,否则它将遵循系统语言环境: + +```dart +QuillToolbar(locale: Locale('fr'), ...) +QuillEditor(locale: Locale('fr'), ...) +``` + +目前,可提供以下 22 种语言环境的翻译: + +* `Locale('en')` +* `Locale('ar')` +* `Locale('de')` +* `Locale('da')` +* `Locale('fr')` +* `Locale('zh', 'CN')` +* `Locale('zh', 'HK')` +* `Locale('ko')` +* `Locale('ru')` +* `Locale('es')` +* `Locale('tr')` +* `Locale('uk')` +* `Locale('ur')` +* `Locale('pt')` +* `Locale('pl')` +* `Locale('vi')` +* `Locale('id')` +* `Locale('nl')` +* `Locale('no')` +* `Locale('fa')` +* `Locale('hi')` +* `Locale('sr')` + +#### 贡献翻译 +翻译文件位于 [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart)。 随意贡献您自己的翻译,只需复制英文翻译映射并将值替换为您的翻译。 然后打开一个拉取请求,这样每个人都可以从您的翻译中受益! + +--- + +

+ 1 + 1 +

+ + +

+ 1 + 1 +

+ + + +## 帮助 + + + + +[Quill]: https://quilljs.com/docs/formats +[Flutter]: https://github.com/flutter/flutter +[FlutterQuill]: https://pub.dev/packages/flutter_quill +[ReactQuill]: https://github.com/zenoamaro/react-quill +[Youtube 播放列表]: https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2 +[Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g +[示例页面]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart +[代码介绍]: https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md