Skip to content

Commit 65f677b

Browse files
authored
Merge pull request #1429 from LucasXu0/refactor_appflowy_editor_example
Refactor appflowy editor example
2 parents e482ac7 + b6ad0ba commit 65f677b

File tree

14 files changed

+422
-42217
lines changed

14 files changed

+422
-42217
lines changed

frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## 0.0.7
22
* Refactor theme customizer, and support dark mode.
3+
* Support export and import markdown.
4+
* Refactor example project.
35
* Fix some bugs.
46

57
## 0.0.6

frontend/app_flowy/packages/appflowy_editor/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,19 @@ flutter pub get
5454
Start by creating a new empty AppFlowyEditor object.
5555

5656
```dart
57-
final editorStyle = EditorStyle.defaultStyle();
5857
final editorState = EditorState.empty(); // an empty state
5958
final editor = AppFlowyEditor(
6059
editorState: editorState,
61-
editorStyle: editorStyle,
6260
);
6361
```
6462

6563
You can also create an editor from a JSON object in order to configure your initial state.
6664

6765
```dart
6866
final json = ...;
69-
final editorStyle = EditorStyle.defaultStyle();
7067
final editorState = EditorState(Document.fromJson(data));
7168
final editor = AppFlowyEditor(
7269
editorState: editorState,
73-
editorStyle: editorStyle,
7470
);
7571
```
7672

frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,6 @@ final editorState = EditorState(
293293
);
294294
return AppFlowyEditor(
295295
editorState: editorState,
296-
editorStyle: EditorStyle.defaultStyle(),
297296
shortcutEvents: const [],
298297
customBuilders: {
299298
'network_image': NetworkImageNodeWidgetBuilder(),

frontend/app_flowy/packages/appflowy_editor/example/assets/big_document.json

Lines changed: 0 additions & 41960 deletions
This file was deleted.

frontend/app_flowy/packages/appflowy_editor/example/assets/example.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
},
1111
"delta": [
1212
{ "insert": "👋 " },
13-
{ "insert": "Welcome to ", "attributes": { "bold": true } },
13+
{ "insert": "Welcome to", "attributes": { "bold": true } },
14+
{ "insert": " " },
1415
{
1516
"insert": "AppFlowy Editor",
1617
"attributes": {
@@ -25,7 +26,8 @@
2526
{
2627
"type": "text",
2728
"delta": [
28-
{ "insert": "AppFlowy Editor is a " },
29+
{ "insert": "AppFlowy Editor is a" },
30+
{ "insert": " " },
2931
{ "insert": "highly customizable", "attributes": { "bold": true } },
3032
{ "insert": " " },
3133
{ "insert": "rich-text editor", "attributes": { "italic": true } },
245 KB
Loading
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:appflowy_editor/appflowy_editor.dart';
5+
import 'package:example/pages/simple_editor.dart';
6+
import 'package:file_picker/file_picker.dart';
7+
import 'package:flutter/foundation.dart';
8+
import 'package:flutter/material.dart';
9+
import 'package:flutter/services.dart';
10+
import 'package:google_fonts/google_fonts.dart';
11+
import 'package:universal_html/html.dart' as html;
12+
13+
enum ExportFileType {
14+
json,
15+
markdown,
16+
html,
17+
}
18+
19+
extension on ExportFileType {
20+
String get extension {
21+
switch (this) {
22+
case ExportFileType.json:
23+
return 'json';
24+
case ExportFileType.markdown:
25+
return 'md';
26+
case ExportFileType.html:
27+
return 'html';
28+
}
29+
}
30+
}
31+
32+
class HomePage extends StatefulWidget {
33+
const HomePage({Key? key}) : super(key: key);
34+
35+
@override
36+
State<HomePage> createState() => _HomePageState();
37+
}
38+
39+
class _HomePageState extends State<HomePage> {
40+
final _scaffoldKey = GlobalKey<ScaffoldState>();
41+
late WidgetBuilder _widgetBuilder;
42+
late EditorState _editorState;
43+
late Future<String> _jsonString;
44+
ThemeData _themeData = ThemeData.light().copyWith(
45+
extensions: [
46+
...lightEditorStyleExtension,
47+
...lightPlguinStyleExtension,
48+
],
49+
);
50+
51+
@override
52+
void initState() {
53+
super.initState();
54+
55+
_jsonString = rootBundle.loadString('assets/example.json');
56+
_widgetBuilder = (context) => SimpleEditor(
57+
jsonString: _jsonString,
58+
themeData: _themeData,
59+
onEditorStateChange: (editorState) {
60+
_editorState = editorState;
61+
},
62+
);
63+
}
64+
65+
@override
66+
Widget build(BuildContext context) {
67+
return Scaffold(
68+
key: _scaffoldKey,
69+
extendBodyBehindAppBar: true,
70+
drawer: _buildDrawer(context),
71+
body: _buildBody(context),
72+
floatingActionButton: _buildFloatingActionButton(context),
73+
);
74+
}
75+
76+
Widget _buildDrawer(BuildContext context) {
77+
return Drawer(
78+
child: ListView(
79+
padding: EdgeInsets.zero,
80+
children: [
81+
DrawerHeader(
82+
padding: EdgeInsets.zero,
83+
margin: EdgeInsets.zero,
84+
child: Image.asset(
85+
'assets/images/icon.png',
86+
fit: BoxFit.fill,
87+
),
88+
),
89+
90+
// AppFlowy Editor Demo
91+
_buildSeparator(context, 'AppFlowy Editor Demo'),
92+
_buildListTile(context, 'With Example.json', () {
93+
final jsonString = rootBundle.loadString('assets/example.json');
94+
_loadEditor(context, jsonString);
95+
}),
96+
_buildListTile(context, 'With Empty Document', () {
97+
final jsonString = Future<String>.value(
98+
jsonEncode(EditorState.empty().document.toJson()).toString(),
99+
);
100+
_loadEditor(context, jsonString);
101+
}),
102+
103+
// Encoder Demo
104+
_buildSeparator(context, 'Encoder Demo'),
105+
_buildListTile(context, 'Export To JSON', () {
106+
_exportFile(_editorState, ExportFileType.json);
107+
}),
108+
_buildListTile(context, 'Export to Markdown', () {
109+
_exportFile(_editorState, ExportFileType.markdown);
110+
}),
111+
112+
// Decoder Demo
113+
_buildSeparator(context, 'Decoder Demo'),
114+
_buildListTile(context, 'Import From JSON', () {
115+
_importFile(ExportFileType.json);
116+
}),
117+
_buildListTile(context, 'Import From Markdown', () {
118+
_importFile(ExportFileType.markdown);
119+
}),
120+
121+
// Theme Demo
122+
_buildSeparator(context, 'Theme Demo'),
123+
_buildListTile(context, 'Bulit In Dark Mode', () {
124+
_jsonString = Future<String>.value(
125+
jsonEncode(_editorState.document.toJson()).toString(),
126+
);
127+
setState(() {
128+
_themeData = ThemeData.dark().copyWith(
129+
extensions: [
130+
...darkEditorStyleExtension,
131+
...darkPlguinStyleExtension,
132+
],
133+
);
134+
});
135+
}),
136+
_buildListTile(context, 'Custom Theme', () {
137+
_jsonString = Future<String>.value(
138+
jsonEncode(_editorState.document.toJson()).toString(),
139+
);
140+
setState(() {
141+
_themeData = _customizeEditorTheme(context);
142+
});
143+
}),
144+
],
145+
),
146+
);
147+
}
148+
149+
Widget _buildBody(BuildContext context) {
150+
return _widgetBuilder(context);
151+
}
152+
153+
Widget _buildListTile(
154+
BuildContext context,
155+
String text,
156+
VoidCallback? onTap,
157+
) {
158+
return ListTile(
159+
dense: true,
160+
contentPadding: const EdgeInsets.only(left: 16),
161+
title: Text(
162+
text,
163+
style: const TextStyle(
164+
color: Colors.blue,
165+
fontSize: 14,
166+
),
167+
),
168+
onTap: () {
169+
Navigator.pop(context);
170+
onTap?.call();
171+
},
172+
);
173+
}
174+
175+
Widget _buildSeparator(BuildContext context, String text) {
176+
return Padding(
177+
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 4),
178+
child: Text(
179+
text,
180+
style: const TextStyle(
181+
color: Colors.grey,
182+
fontSize: 12,
183+
fontWeight: FontWeight.bold,
184+
),
185+
),
186+
);
187+
}
188+
189+
Widget _buildFloatingActionButton(BuildContext context) {
190+
return FloatingActionButton(
191+
onPressed: () {
192+
_scaffoldKey.currentState?.openDrawer();
193+
},
194+
child: const Icon(Icons.menu),
195+
);
196+
}
197+
198+
void _loadEditor(BuildContext context, Future<String> jsonString) {
199+
_jsonString = jsonString;
200+
setState(
201+
() {
202+
_widgetBuilder = (context) => SimpleEditor(
203+
jsonString: _jsonString,
204+
themeData: _themeData,
205+
onEditorStateChange: (editorState) {
206+
_editorState = editorState;
207+
},
208+
);
209+
},
210+
);
211+
}
212+
213+
void _exportFile(
214+
EditorState editorState,
215+
ExportFileType fileType,
216+
) async {
217+
var result = '';
218+
219+
switch (fileType) {
220+
case ExportFileType.json:
221+
result = jsonEncode(editorState.document.toJson());
222+
break;
223+
case ExportFileType.markdown:
224+
result = documentToMarkdown(editorState.document);
225+
break;
226+
case ExportFileType.html:
227+
throw UnimplementedError();
228+
}
229+
230+
if (!kIsWeb) {
231+
final path = await FilePicker.platform.saveFile(
232+
fileName: 'document.${fileType.extension}',
233+
);
234+
if (path != null) {
235+
await File(path).writeAsString(result);
236+
if (mounted) {
237+
ScaffoldMessenger.of(context).showSnackBar(
238+
SnackBar(
239+
content: Text('This document is saved to the $path'),
240+
),
241+
);
242+
}
243+
}
244+
} else {
245+
final blob = html.Blob([result], 'text/plain', 'native');
246+
html.AnchorElement(
247+
href: html.Url.createObjectUrlFromBlob(blob).toString(),
248+
)
249+
..setAttribute('download', 'document.${fileType.extension}')
250+
..click();
251+
}
252+
}
253+
254+
void _importFile(ExportFileType fileType) async {
255+
final result = await FilePicker.platform.pickFiles(
256+
allowMultiple: false,
257+
allowedExtensions: [fileType.extension],
258+
type: FileType.custom,
259+
);
260+
var plainText = '';
261+
if (!kIsWeb) {
262+
final path = result?.files.single.path;
263+
if (path == null) {
264+
return;
265+
}
266+
plainText = await File(path).readAsString();
267+
} else {
268+
final bytes = result?.files.first.bytes;
269+
if (bytes == null) {
270+
return;
271+
}
272+
plainText = const Utf8Decoder().convert(bytes);
273+
}
274+
275+
var jsonString = '';
276+
switch (fileType) {
277+
case ExportFileType.json:
278+
jsonString = jsonEncode(plainText);
279+
break;
280+
case ExportFileType.markdown:
281+
jsonString = jsonEncode(markdownToDocument(plainText).toJson());
282+
break;
283+
case ExportFileType.html:
284+
throw UnimplementedError();
285+
}
286+
287+
if (mounted) {
288+
_loadEditor(context, Future<String>.value(jsonString));
289+
}
290+
}
291+
292+
ThemeData _customizeEditorTheme(BuildContext context) {
293+
final dark = EditorStyle.dark;
294+
final editorStyle = dark.copyWith(
295+
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 150),
296+
cursorColor: Colors.blue.shade600,
297+
selectionColor: Colors.yellow.shade600.withOpacity(0.5),
298+
textStyle: GoogleFonts.poppins().copyWith(
299+
fontSize: 14,
300+
color: Colors.grey,
301+
),
302+
placeholderTextStyle: GoogleFonts.poppins().copyWith(
303+
fontSize: 14,
304+
color: Colors.grey.shade500,
305+
),
306+
code: dark.code?.copyWith(
307+
backgroundColor: Colors.lightBlue.shade200,
308+
fontStyle: FontStyle.italic,
309+
),
310+
highlightColorHex: '0x60FF0000', // red
311+
);
312+
313+
final quote = QuotedTextPluginStyle.dark.copyWith(
314+
textStyle: (_, __) => GoogleFonts.poppins().copyWith(
315+
fontSize: 14,
316+
color: Colors.blue.shade400,
317+
fontStyle: FontStyle.italic,
318+
fontWeight: FontWeight.w700,
319+
),
320+
);
321+
322+
return Theme.of(context).copyWith(extensions: [
323+
editorStyle,
324+
...darkPlguinStyleExtension,
325+
quote,
326+
]);
327+
}
328+
}

0 commit comments

Comments
 (0)