feat: add more showcases on desktop/mobile platform (#481)
This commit is contained in:
parent
6699d58631
commit
78dd254d1e
33 changed files with 1187 additions and 434 deletions
109
example/assets/arabic_example.json
Normal file
109
example/assets/arabic_example.json
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
{
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"delta": [{ "insert": "👋 مرحبًا بك في محرر AppFlowy" }],
|
||||
"level": 2,
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert": "محرر AppFlowy هو محرر نصوص غني قابل للتخصيص بشكل كبير لـ Flutter"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert": "قابل للتخصيص",
|
||||
"attributes": { "bg_color": "0x4d2196f3" }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert": "مُغطى بالاختبارات",
|
||||
"attributes": { "bg_color": "0x4d2196f3" }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{ "insert": "المزيد قادم قريبًا!", "attributes": { "code": true } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"delta": [{ "insert": "إليك مثال يمكنك تجربته" }],
|
||||
"level": 4,
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": false,
|
||||
"delta": [
|
||||
{ "insert": "استخدم محرر AppFlowy كمكون لبناء تطبيقك الخاص." }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{ "insert": "استخدم" },
|
||||
{
|
||||
"insert": " / ",
|
||||
"attributes": { "code": true, "bg_color": "0x4d2196f3" }
|
||||
},
|
||||
{ "insert": "لإدراج الكتل." }
|
||||
],
|
||||
"checked": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": false,
|
||||
"delta": [
|
||||
{ "insert": "حدد النص لتشغيل شريط الأدوات وتنسيق ملاحظاتك." }
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert": "إذا كانت لديك أسئلة أو ملاحظات، يرجى تقديم مشكلة على Github أو الانضمام إلى المجتمع مع أكثر من 1000 بنّاء!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,128 +1,85 @@
|
|||
{
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [{
|
||||
"children": [
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"level": 2,
|
||||
"delta": [{
|
||||
"insert": "👋 "
|
||||
},
|
||||
{
|
||||
"insert": "Welcome to",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
"delta": [
|
||||
{ "insert": "👋 " },
|
||||
{ "insert": "Welcome to", "attributes": { "bold": true } },
|
||||
{ "insert": " " },
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": {
|
||||
"href": "appflowy.io",
|
||||
"italic": true,
|
||||
"bold": true
|
||||
}
|
||||
"attributes": { "italic": false, "bold": true }
|
||||
}
|
||||
]
|
||||
],
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [{
|
||||
"insert": "AppFlowy Editor is a"
|
||||
},
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
"delta": [
|
||||
{ "insert": "AppFlowy Editor is a" },
|
||||
{ "insert": " " },
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {
|
||||
"bold": true
|
||||
}
|
||||
"attributes": { "bold": true, "bg_color": "0x7fffeb3b" }
|
||||
},
|
||||
{ "insert": " " },
|
||||
{ "insert": "rich-text editor", "attributes": { "italic": true } },
|
||||
{ "insert": " for " },
|
||||
{ "insert": "Flutter", "attributes": { "underline": true } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert": " "
|
||||
},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {
|
||||
"italic": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " for "
|
||||
},
|
||||
{
|
||||
"insert": "Flutter",
|
||||
"attributes": {
|
||||
"underline": true
|
||||
}
|
||||
"insert": "Customizable",
|
||||
"attributes": { "bg_color": "0x4d00BCF0" }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"checked": true,
|
||||
"delta": [{
|
||||
"insert": "Customizable"
|
||||
}]
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Test-covered",
|
||||
"attributes": { "bg_color": "0x4d00BCF0" }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"checked": true,
|
||||
"delta": [{
|
||||
"insert": "Test-covered"
|
||||
}]
|
||||
"delta": [
|
||||
{ "insert": "More to come!", "attributes": { "code": true } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"delta": [{ "insert": "Here is an example you can give it a try" }],
|
||||
"level": 4,
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": false,
|
||||
"delta": [{
|
||||
"insert": "more to come!"
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "quote",
|
||||
"data": {
|
||||
"delta": [{
|
||||
"insert": "Here is an example you can give a try"
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [{
|
||||
"insert": "You can also use "
|
||||
},
|
||||
"delta": [
|
||||
{ "insert": "Use " },
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": {
|
||||
|
|
@ -132,49 +89,158 @@
|
|||
"bg_color": "0x6000BCF0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"insert": " as a component to build your own app."
|
||||
}
|
||||
{ "insert": " as a component to build your own app./" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"delta": []
|
||||
"delta": [
|
||||
{ "insert": "Use the " },
|
||||
{
|
||||
"insert": " / ",
|
||||
"attributes": { "bg_color": "0x4d00BCF0", "code": true }
|
||||
},
|
||||
{ "insert": " to insert blocks." }
|
||||
],
|
||||
"checked": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"delta": [{
|
||||
"insert": "Use / to insert blocks"
|
||||
}]
|
||||
"checked": false,
|
||||
"delta": [
|
||||
{ "insert": "Select text to " },
|
||||
{
|
||||
"insert": "trigger the toolbar",
|
||||
"attributes": { "bg_color": "0x4de91e63" }
|
||||
},
|
||||
{ "insert": " to " },
|
||||
{
|
||||
"insert": "format",
|
||||
"attributes": {
|
||||
"code": true,
|
||||
"bold": true,
|
||||
"underline": true,
|
||||
"italic": true,
|
||||
"font_color": "0xfff44336",
|
||||
"bg_color": "0x4d4caf50"
|
||||
}
|
||||
},
|
||||
{ "insert": " your notes." }
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"type": "table",
|
||||
"children": [
|
||||
{
|
||||
"type": "table/cell",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{ "insert": "Insert a " },
|
||||
{ "insert": "table", "attributes": { "bold": true } },
|
||||
{ "insert": " via" },
|
||||
{
|
||||
"insert": " / ",
|
||||
"attributes": { "code": true, "bg_color": "0x4d9c27b0" }
|
||||
},
|
||||
{ "insert": "command" }
|
||||
],
|
||||
"align": "center"
|
||||
}
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"colPosition": 0,
|
||||
"rowPosition": 0,
|
||||
"height": 47.0,
|
||||
"width": 553.09375
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "table/cell",
|
||||
"children": [{ "type": "paragraph", "data": { "delta": [] } }],
|
||||
"data": {
|
||||
"colPosition": 0,
|
||||
"rowPosition": 1,
|
||||
"height": 46.0,
|
||||
"width": 553.09375
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "table/cell",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{ "insert": "the " },
|
||||
{
|
||||
"insert": "contents",
|
||||
"attributes": { "underline": true }
|
||||
},
|
||||
{ "insert": " in the table also support " },
|
||||
{
|
||||
"insert": "rich-text format",
|
||||
"attributes": { "bold": true, "italic": true }
|
||||
}
|
||||
],
|
||||
"align": "center"
|
||||
}
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"colPosition": 1,
|
||||
"rowPosition": 0,
|
||||
"height": 47.0,
|
||||
"width": 490.3984375,
|
||||
"colBackgroundColor": "0x4dffeb3b"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "table/cell",
|
||||
"children": [{ "type": "paragraph", "data": { "delta": [] } }],
|
||||
"data": {
|
||||
"colPosition": 1,
|
||||
"rowPosition": 1,
|
||||
"height": 46.0,
|
||||
"width": 490.3984375,
|
||||
"colBackgroundColor": "0x4dffeb3b"
|
||||
}
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"delta": [{
|
||||
"insert": "Select text to trigger to the toolbar to format your notes."
|
||||
}]
|
||||
"colsLen": 2,
|
||||
"rowsLen": 2,
|
||||
"colDefaultWidth": 80.0,
|
||||
"rowDefaultHeight": 40.0,
|
||||
"colMinimumWidth": 40.0,
|
||||
"colsHeight": 99.0
|
||||
}
|
||||
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [{
|
||||
"insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
|
||||
}]
|
||||
"delta": [
|
||||
{ "insert": "If you have questions or feedback, please " },
|
||||
{
|
||||
"insert": "submit an issue",
|
||||
"attributes": { "bg_color": "0x4de91e63" }
|
||||
},
|
||||
{
|
||||
"insert": " on Github or join the community along with 1000+ builders!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
example/assets/images/header.png
Normal file
BIN
example/assets/images/header.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 362 KiB |
140
example/assets/mobile_example.json
Normal file
140
example/assets/mobile_example.json
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"level": 2,
|
||||
"delta": [
|
||||
{ "insert": "👋 " },
|
||||
{ "insert": "Welcome to", "attributes": { "bold": true } },
|
||||
{ "insert": " " },
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": { "italic": false, "bold": true }
|
||||
}
|
||||
],
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{ "insert": "AppFlowy Editor is a" },
|
||||
{ "insert": " " },
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": { "bold": true, "bg_color": "0x7fffeb3b" }
|
||||
},
|
||||
{ "insert": " " },
|
||||
{ "insert": "rich-text editor", "attributes": { "italic": true } },
|
||||
{ "insert": " for " },
|
||||
{ "insert": "Flutter", "attributes": { "underline": true } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Customizable",
|
||||
"attributes": { "bg_color": "0x4d00BCF0" }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert": "Test-covered",
|
||||
"attributes": { "bg_color": "0x4d00BCF0" }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{ "insert": "More to come!", "attributes": { "code": true } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"delta": [{ "insert": "Here is an example you can give it a try" }],
|
||||
"level": 4,
|
||||
"align": "center"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": false,
|
||||
"delta": [
|
||||
{ "insert": "Use " },
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": {
|
||||
"italic": true,
|
||||
"bold": true,
|
||||
"font_color": "0xffD70040",
|
||||
"bg_color": "0x6000BCF0"
|
||||
}
|
||||
},
|
||||
{ "insert": " as a component to build your own app." }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": false,
|
||||
"delta": [
|
||||
{ "insert": "Select text to " },
|
||||
{
|
||||
"insert": "trigger the toolbar",
|
||||
"attributes": { "bg_color": "0x4de91e63" }
|
||||
},
|
||||
{ "insert": " to " },
|
||||
{
|
||||
"insert": "format",
|
||||
"attributes": {
|
||||
"code": true,
|
||||
"bold": true,
|
||||
"underline": true,
|
||||
"italic": true,
|
||||
"font_color": "0xfff44336",
|
||||
"bg_color": "0x4d4caf50"
|
||||
}
|
||||
},
|
||||
{ "insert": " your notes." }
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "type": "paragraph", "data": { "delta": [] } },
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{ "insert": "If you have questions or feedback, please " },
|
||||
{
|
||||
"insert": "submit an issue",
|
||||
"attributes": { "bg_color": "0x4de91e63" }
|
||||
},
|
||||
{
|
||||
"insert": " on Github or join the community along with 1000+ builders!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,9 @@ class _HomePageState extends State<HomePage> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_jsonString = rootBundle.loadString('assets/example.json');
|
||||
_jsonString = PlatformExtension.isDesktopOrWeb
|
||||
? rootBundle.loadString('assets/example.json')
|
||||
: rootBundle.loadString('assets/mobile_example.json');
|
||||
|
||||
_widgetBuilder = (context) => Editor(
|
||||
jsonString: _jsonString,
|
||||
|
|
@ -109,7 +111,9 @@ class _HomePageState extends State<HomePage> {
|
|||
// AppFlowy Editor Demo
|
||||
_buildSeparator(context, 'AppFlowy Editor Demo'),
|
||||
_buildListTile(context, 'With Example.json', () {
|
||||
final jsonString = rootBundle.loadString('assets/example.json');
|
||||
final jsonString = PlatformExtension.isDesktopOrWeb
|
||||
? rootBundle.loadString('assets/example.json')
|
||||
: rootBundle.loadString('assets/mobile_example.json');
|
||||
_loadEditor(context, jsonString);
|
||||
}),
|
||||
_buildListTile(context, 'With Large Document (10000+ lines)', () {
|
||||
|
|
@ -159,6 +163,16 @@ class _HomePageState extends State<HomePage> {
|
|||
),
|
||||
);
|
||||
}),
|
||||
_buildListTile(context, 'RTL', () {
|
||||
final jsonString = rootBundle.loadString(
|
||||
'assets/arabic_example.json',
|
||||
);
|
||||
_loadEditor(
|
||||
context,
|
||||
jsonString,
|
||||
textDirection: TextDirection.rtl,
|
||||
);
|
||||
}),
|
||||
|
||||
// Encoder Demo
|
||||
_buildSeparator(context, 'Export To X Demo'),
|
||||
|
|
@ -227,8 +241,9 @@ class _HomePageState extends State<HomePage> {
|
|||
|
||||
Future<void> _loadEditor(
|
||||
BuildContext context,
|
||||
Future<String> jsonString,
|
||||
) async {
|
||||
Future<String> jsonString, {
|
||||
TextDirection textDirection = TextDirection.ltr,
|
||||
}) async {
|
||||
final completer = Completer<void>();
|
||||
_jsonString = jsonString;
|
||||
setState(
|
||||
|
|
@ -238,6 +253,7 @@ class _HomePageState extends State<HomePage> {
|
|||
onEditorStateChange: (editorState) {
|
||||
_editorState = editorState;
|
||||
},
|
||||
textDirection: textDirection,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ class _CustomizeThemeForEditorState extends State<CustomizeThemeForEditor> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
editorState = rootBundle.loadString('assets/example.json').then((value) {
|
||||
final jsonString = PlatformExtension.isDesktopOrWeb
|
||||
? rootBundle.loadString('assets/example.json')
|
||||
: rootBundle.loadString('assets/mobile_example.json');
|
||||
editorState = jsonString.then((value) {
|
||||
return EditorState(
|
||||
document: Document.fromJson(
|
||||
Map<String, Object>.from(
|
||||
|
|
@ -104,10 +107,15 @@ class _CustomizeThemeForEditorState extends State<CustomizeThemeForEditor> {
|
|||
BulletedListBlockKeys.type: BulletedListBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
iconBuilder: (context, node) {
|
||||
return const Icon(
|
||||
Icons.circle,
|
||||
size: 20,
|
||||
color: Colors.green,
|
||||
return Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(
|
||||
Icons.circle,
|
||||
size: 10,
|
||||
color: Colors.red,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
|||
180
example/lib/pages/desktop_editor.dart
Normal file
180
example/lib/pages/desktop_editor.dart
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class DesktopEditor extends StatefulWidget {
|
||||
const DesktopEditor({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
this.textDirection = TextDirection.ltr,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final TextDirection textDirection;
|
||||
|
||||
@override
|
||||
State<DesktopEditor> createState() => _DesktopEditorState();
|
||||
}
|
||||
|
||||
class _DesktopEditorState extends State<DesktopEditor> {
|
||||
EditorState get editorState => widget.editorState;
|
||||
|
||||
late final EditorScrollController editorScrollController;
|
||||
|
||||
late EditorStyle editorStyle;
|
||||
late Map<String, BlockComponentBuilder> blockComponentBuilders;
|
||||
late List<CommandShortcutEvent> commandShortcuts;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
editorScrollController = EditorScrollController(
|
||||
editorState: editorState,
|
||||
shrinkWrap: false,
|
||||
);
|
||||
|
||||
editorStyle = _buildDesktopEditorStyle();
|
||||
blockComponentBuilders = _buildBlockComponentBuilders();
|
||||
commandShortcuts = _buildCommandShortcuts();
|
||||
}
|
||||
|
||||
@override
|
||||
void reassemble() {
|
||||
super.reassemble();
|
||||
|
||||
editorStyle = _buildDesktopEditorStyle();
|
||||
blockComponentBuilders = _buildBlockComponentBuilders();
|
||||
commandShortcuts = _buildCommandShortcuts();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(PlatformExtension.isDesktopOrWeb);
|
||||
return FloatingToolbar(
|
||||
items: [
|
||||
paragraphItem,
|
||||
...headingItems,
|
||||
...markdownFormatItems,
|
||||
quoteItem,
|
||||
bulletedListItem,
|
||||
numberedListItem,
|
||||
linkItem,
|
||||
buildTextColorItem(),
|
||||
buildHighlightColorItem(),
|
||||
...textDirectionItems,
|
||||
...alignmentItems,
|
||||
],
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
child: Directionality(
|
||||
textDirection: widget.textDirection,
|
||||
child: AppFlowyEditor(
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
blockComponentBuilders: blockComponentBuilders,
|
||||
commandShortcutEvents: commandShortcuts,
|
||||
editorStyle: editorStyle,
|
||||
header: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: Image.asset(
|
||||
'assets/images/header.png',
|
||||
fit: BoxFit.fitWidth,
|
||||
height: 150,
|
||||
),
|
||||
),
|
||||
footer: const SizedBox(
|
||||
height: 100,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// showcase 1: customize the editor style.
|
||||
EditorStyle _buildDesktopEditorStyle() {
|
||||
return EditorStyle.desktop(
|
||||
cursorColor: Colors.blue,
|
||||
selectionColor: Colors.grey.shade300,
|
||||
textStyleConfiguration: TextStyleConfiguration(
|
||||
text: GoogleFonts.poppins(
|
||||
fontSize: 16,
|
||||
color: Colors.black,
|
||||
),
|
||||
code: GoogleFonts.architectsDaughter(),
|
||||
bold: GoogleFonts.poppins(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 200.0),
|
||||
);
|
||||
}
|
||||
|
||||
// showcase 2: customize the block style
|
||||
Map<String, BlockComponentBuilder> _buildBlockComponentBuilders() {
|
||||
final map = {
|
||||
...standardBlockComponentBuilderMap,
|
||||
};
|
||||
// customize the image block component to show a menu
|
||||
map[ImageBlockKeys.type] = ImageBlockComponentBuilder(
|
||||
showMenu: true,
|
||||
menuBuilder: (node, _) {
|
||||
return const Positioned(
|
||||
right: 10,
|
||||
child: Text('⭐️ Here is a menu!'),
|
||||
);
|
||||
},
|
||||
);
|
||||
// customize the heading block component
|
||||
final levelToFontSize = [
|
||||
30.0,
|
||||
26.0,
|
||||
22.0,
|
||||
18.0,
|
||||
16.0,
|
||||
14.0,
|
||||
];
|
||||
map[HeadingBlockKeys.type] = HeadingBlockComponentBuilder(
|
||||
textStyleBuilder: (level) => GoogleFonts.poppins(
|
||||
fontSize: levelToFontSize.elementAtOrNull(level - 1) ?? 14.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
// customize the padding
|
||||
map.forEach((key, value) {
|
||||
value.configuration = BlockComponentConfiguration(
|
||||
padding: (_) => const EdgeInsets.symmetric(vertical: 8.0),
|
||||
);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
// showcase 3: customize the command shortcuts
|
||||
List<CommandShortcutEvent> _buildCommandShortcuts() {
|
||||
return [
|
||||
// customize the highlight color
|
||||
customToggleHighlightCommand(
|
||||
style: ToggleColorsStyle(
|
||||
highlightColor: Colors.orange.shade700,
|
||||
),
|
||||
),
|
||||
...[
|
||||
...standardCommandShortcutEvents
|
||||
..removeWhere(
|
||||
(el) => el == toggleHighlightCommand,
|
||||
),
|
||||
],
|
||||
...findAndReplaceCommands(
|
||||
context: context,
|
||||
localizations: FindReplaceLocalizations(
|
||||
find: 'Find',
|
||||
previousMatch: 'Previous match',
|
||||
nextMatch: 'Next match',
|
||||
close: 'Close',
|
||||
replace: 'Replace',
|
||||
replaceAll: 'Replace all',
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:example/pages/desktop_editor.dart';
|
||||
import 'package:example/pages/mobile_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
@ -10,122 +11,57 @@ class Editor extends StatelessWidget {
|
|||
required this.jsonString,
|
||||
required this.onEditorStateChange,
|
||||
this.editorStyle,
|
||||
this.textDirection = TextDirection.ltr,
|
||||
});
|
||||
|
||||
final Future<String> jsonString;
|
||||
final EditorStyle? editorStyle;
|
||||
final void Function(EditorState editorState) onEditorStateChange;
|
||||
|
||||
final TextDirection textDirection;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: jsonString,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
final editorState = EditorState(
|
||||
document: Document.fromJson(
|
||||
Map<String, Object>.from(
|
||||
json.decode(snapshot.data!),
|
||||
),
|
||||
),
|
||||
);
|
||||
editorState.logConfiguration
|
||||
..handler = debugPrint
|
||||
..level = LogLevel.off;
|
||||
editorState.transactionStream.listen((event) {
|
||||
if (event.$1 == TransactionTime.after) {
|
||||
onEditorStateChange(editorState);
|
||||
}
|
||||
});
|
||||
final editorScrollController = EditorScrollController(
|
||||
editorState: editorState,
|
||||
shrinkWrap: false,
|
||||
);
|
||||
if (PlatformExtension.isDesktopOrWeb) {
|
||||
return FloatingToolbar(
|
||||
items: [
|
||||
paragraphItem,
|
||||
...headingItems,
|
||||
...markdownFormatItems,
|
||||
quoteItem,
|
||||
bulletedListItem,
|
||||
numberedListItem,
|
||||
linkItem,
|
||||
buildTextColorItem(),
|
||||
buildHighlightColorItem(),
|
||||
...textDirectionItems,
|
||||
...alignmentItems,
|
||||
],
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
child: _buildDesktopEditor(
|
||||
context,
|
||||
editorState,
|
||||
editorScrollController,
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: FutureBuilder<String>(
|
||||
future: jsonString,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData &&
|
||||
snapshot.connectionState == ConnectionState.done) {
|
||||
final editorState = EditorState(
|
||||
document: Document.fromJson(
|
||||
Map<String, Object>.from(
|
||||
json.decode(snapshot.data!),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (PlatformExtension.isMobile) {
|
||||
return MobileEditor(
|
||||
editorState: editorState,
|
||||
onEditorStateChange: onEditorStateChange,
|
||||
);
|
||||
}
|
||||
}
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDesktopEditor(
|
||||
BuildContext context,
|
||||
EditorState editorState,
|
||||
EditorScrollController? editorScrollController,
|
||||
) {
|
||||
final customBlockComponentBuilders = {
|
||||
...standardBlockComponentBuilderMap,
|
||||
ImageBlockKeys.type: ImageBlockComponentBuilder(
|
||||
showMenu: true,
|
||||
menuBuilder: (node, _) {
|
||||
return const Positioned(
|
||||
right: 10,
|
||||
child: Text('Sample Menu'),
|
||||
);
|
||||
editorState.logConfiguration
|
||||
..handler = debugPrint
|
||||
..level = LogLevel.off;
|
||||
|
||||
editorState.transactionStream.listen((event) {
|
||||
if (event.$1 == TransactionTime.after) {
|
||||
onEditorStateChange(editorState);
|
||||
}
|
||||
});
|
||||
|
||||
if (PlatformExtension.isDesktopOrWeb) {
|
||||
return DesktopEditor(
|
||||
editorState: editorState,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
} else if (PlatformExtension.isMobile) {
|
||||
return MobileEditor(
|
||||
editorState: editorState,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
};
|
||||
return AppFlowyEditor(
|
||||
editorState: editorState,
|
||||
shrinkWrap: true,
|
||||
editorScrollController: editorScrollController,
|
||||
blockComponentBuilders: customBlockComponentBuilders,
|
||||
commandShortcutEvents: [
|
||||
customToggleHighlightCommand(
|
||||
style: ToggleColorsStyle(
|
||||
highlightColor: Theme.of(context).highlightColor,
|
||||
),
|
||||
),
|
||||
...[
|
||||
...standardCommandShortcutEvents
|
||||
..removeWhere(
|
||||
(el) => el == toggleHighlightCommand,
|
||||
),
|
||||
],
|
||||
...findAndReplaceCommands(
|
||||
context: context,
|
||||
localizations: FindReplaceLocalizations(
|
||||
find: 'Find',
|
||||
previousMatch: 'Previous match',
|
||||
nextMatch: 'Next match',
|
||||
close: 'Close',
|
||||
replace: 'Replace',
|
||||
replaceAll: 'Replace all',
|
||||
),
|
||||
),
|
||||
],
|
||||
characterShortcutEvents: standardCharacterShortcutEvents,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,11 @@ class MobileEditor extends StatefulWidget {
|
|||
const MobileEditor({
|
||||
super.key,
|
||||
required this.editorState,
|
||||
required this.onEditorStateChange,
|
||||
this.editorStyle,
|
||||
});
|
||||
|
||||
final EditorState editorState;
|
||||
final EditorStyle? editorStyle;
|
||||
final void Function(EditorState editorState) onEditorStateChange;
|
||||
|
||||
@override
|
||||
State<MobileEditor> createState() => _MobileEditorState();
|
||||
|
|
@ -30,18 +28,6 @@ class _MobileEditorState extends State<MobileEditor> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// customize the log configuration
|
||||
editorState.logConfiguration
|
||||
..handler = debugPrint
|
||||
..level = LogLevel.off;
|
||||
|
||||
// listen to editor state change
|
||||
editorState.transactionStream.listen((event) {
|
||||
if (event.$1 == TransactionTime.after) {
|
||||
widget.onEditorStateChange(editorState);
|
||||
}
|
||||
});
|
||||
|
||||
editorScrollController = EditorScrollController(
|
||||
editorState: editorState,
|
||||
shrinkWrap: false,
|
||||
|
|
@ -61,23 +47,29 @@ class _MobileEditorState extends State<MobileEditor> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(PlatformExtension.isMobile);
|
||||
return Column(
|
||||
children: [
|
||||
// build appflowy editor
|
||||
Expanded(
|
||||
child: AppFlowyEditor(
|
||||
editorStyle: editorStyle,
|
||||
child: MobileFloatingToolbar(
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
blockComponentBuilders: blockComponentBuilders,
|
||||
// showcase 3: customize the header and footer.
|
||||
header: Image.asset(
|
||||
'assets/images/icon.jpeg',
|
||||
fit: BoxFit.fitWidth,
|
||||
height: 100,
|
||||
),
|
||||
footer: const SizedBox(
|
||||
height: 100,
|
||||
child: AppFlowyEditor(
|
||||
editorStyle: editorStyle,
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
blockComponentBuilders: blockComponentBuilders,
|
||||
// showcase 3: customize the header and footer.
|
||||
header: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10.0),
|
||||
child: Image.asset(
|
||||
'assets/images/header.png',
|
||||
),
|
||||
),
|
||||
footer: const SizedBox(
|
||||
height: 100,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -112,7 +104,7 @@ class _MobileEditorState extends State<MobileEditor> {
|
|||
),
|
||||
code: GoogleFonts.badScript(),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ flutter:
|
|||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
assets:
|
||||
- assets/example.json
|
||||
- assets/example.html
|
||||
- assets/images/icon.jpeg
|
||||
- assets/
|
||||
- assets/
|
||||
- assets/images/
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
|
|
|||
|
|
@ -44,6 +44,11 @@ class BlockSelectionContainer extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
// In RTL mode, if the alignment is topStart,
|
||||
// the selection will be on the opposite side of the block component.
|
||||
alignment: Directionality.of(context) == TextDirection.ltr
|
||||
? AlignmentDirectional.topStart
|
||||
: AlignmentDirectional.topEnd,
|
||||
children: [
|
||||
BlockSelectionArea(
|
||||
node: node,
|
||||
|
|
|
|||
|
|
@ -117,8 +117,15 @@ class SelectionAreaPainter extends CustomPainter {
|
|||
..color = selectionColor
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
for (final rect in rects) {
|
||||
canvas.drawRect(rect, paint);
|
||||
for (var rect in rects) {
|
||||
// if rect.width is 0, we draw a small rect to indicate the selection area
|
||||
if (rect.width <= 0) {
|
||||
rect = Rect.fromLTWH(rect.left, rect.top, 8.0, rect.height);
|
||||
}
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
paint,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,13 +36,10 @@ Node bulletedListNode({
|
|||
|
||||
class BulletedListBlockComponentBuilder extends BlockComponentBuilder {
|
||||
BulletedListBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.iconBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final BlockIconBuilder? iconBuilder;
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -17,14 +17,11 @@ Node dividerNode() {
|
|||
|
||||
class DividerBlockComponentBuilder extends BlockComponentBuilder {
|
||||
DividerBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.lineColor = Colors.grey,
|
||||
this.height = 10,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final Color lineColor;
|
||||
final double height;
|
||||
|
||||
|
|
|
|||
|
|
@ -40,13 +40,10 @@ Node headingNode({
|
|||
|
||||
class HeadingBlockComponentBuilder extends BlockComponentBuilder {
|
||||
HeadingBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.textStyleBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
/// The text style of the heading block.
|
||||
final TextStyle Function(int level)? textStyleBuilder;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,14 +56,11 @@ typedef ImageBlockComponentMenuBuilder = Widget Function(
|
|||
|
||||
class ImageBlockComponentBuilder extends BlockComponentBuilder {
|
||||
ImageBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.showMenu = false,
|
||||
this.menuBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
/// Whether to show the menu of this block component.
|
||||
final bool showMenu;
|
||||
|
||||
|
|
|
|||
|
|
@ -41,13 +41,10 @@ Node numberedListNode({
|
|||
|
||||
class NumberedListBlockComponentBuilder extends BlockComponentBuilder {
|
||||
NumberedListBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.iconBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final BlockIconBuilder? iconBuilder;
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -34,13 +34,10 @@ Node quoteNode({
|
|||
|
||||
class QuoteBlockComponentBuilder extends BlockComponentBuilder {
|
||||
QuoteBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.iconBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final BlockIconBuilder? iconBuilder;
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -58,16 +58,13 @@ typedef TableBlockComponentMenuBuilder = Widget Function(
|
|||
|
||||
class TableBlockComponentBuilder extends BlockComponentBuilder {
|
||||
TableBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.addIcon = TableDefaults.addIcon,
|
||||
this.borderColor = TableDefaults.borderColor,
|
||||
this.borderHoverColor = TableDefaults.borderHoverColor,
|
||||
this.menuBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final Widget addIcon;
|
||||
final TableBlockComponentMenuBuilder? menuBuilder;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,10 @@ class TableCellBlockKeys {
|
|||
|
||||
class TableCellBlockComponentBuilder extends BlockComponentBuilder {
|
||||
TableCellBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
super.configuration,
|
||||
this.menuBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final TableBlockComponentMenuBuilder? menuBuilder;
|
||||
|
||||
@override
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue