feat: support custom markdown parsers (#835)

* feat: support custom markdown parsers

* fix: markdown test

* feat: add markdown quote parser

* chore: use markdown parser

* chore: parse unordered list, to_do list and quote

* feat: add a markdown editor example

* feat: add bulleted list parser

* feat: support nested list and img

* feat: support code block

* feat: support table parser

* chore: refacotr code

* fix: flutter analyze

* chore: add more tests and remove unused files

* fix: test
This commit is contained in:
Lucas.Xu 2024-07-04 09:53:25 +08:00 • committed by GitHub
commit 5bf6c7acd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1413 additions and 1099 deletions

View file

@ -12,6 +12,7 @@ import 'package:example/pages/editor.dart';
import 'package:example/pages/editor_list.dart';
import 'package:example/pages/fixed_toolbar_editor.dart';
import 'package:example/pages/focus_example_for_editor.dart';
import 'package:example/pages/markdown_editor.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -165,6 +166,14 @@ class _HomePageState extends State<HomePage> {
// Theme Demo
_buildSeparator(context, 'Showcases'),
_buildListTile(context, 'Markdown Editor', () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const MarkdownEditor(),
),
);
}),
_buildListTile(context, 'Auto complete Editor', () {
Navigator.push(
context,

View file

@ -0,0 +1,51 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:markdown/markdown.dart' as md;
class MarkdownCodeBlockParserV2 extends CustomMarkdownParser {
const MarkdownCodeBlockParserV2();
@override
List<Node> transform(
md.Node element,
List<CustomMarkdownParser> parsers,
MarkdownListType listType,
) {
if (element is! md.Element) {
return [];
}
if (element.tag != 'pre') {
return [];
}
final ec = element.children;
if (ec == null || ec.isEmpty) {
return [];
}
final code = ec.first;
if (code is! md.Element || code.tag != 'code') {
return [];
}
String? language;
if (code.attributes.containsKey('class')) {
final classes = code.attributes['class']!.split(' ');
final languageClass = classes.firstWhere(
(c) => c.startsWith('language-'),
orElse: () => '',
);
language = languageClass.substring('language-'.length);
}
final deltaDecoder = DeltaMarkdownDecoder();
return [
codeBlockNode(
language: language,
delta: deltaDecoder.convertNodes(code.children),
),
];
}
}

View file

@ -0,0 +1,100 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:example/pages/markdown/markdown_code_block_parser.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
class MarkdownEditor extends StatefulWidget {
const MarkdownEditor({super.key});
@override
State<MarkdownEditor> createState() => _MarkdownEditorState();
}
class _MarkdownEditorState extends State<MarkdownEditor> {
EditorState editorState = EditorState.blank();
final controller = TextEditingController();
final editorStyle = EditorStyle.desktop(
padding: EdgeInsets.zero,
textStyleConfiguration: TextStyleConfiguration(
lineHeight: 1.2,
applyHeightToFirstAscent: true,
applyHeightToLastDescent: true,
text: const TextStyle(
fontSize: 16,
color: Colors.black,
),
code: GoogleFonts.architectsDaughter(),
bold: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
),
),
);
@override
void initState() {
super.initState();
controller.addListener(_onTextChanged);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: const Text('Markdown Editor'),
titleTextStyle: const TextStyle(color: Colors.white),
iconTheme: const IconThemeData(
color: Colors.white,
),
),
body: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
child: AppFlowyEditor(
editorState: editorState,
editorStyle: editorStyle,
editable: true,
blockComponentBuilders: {
...standardBlockComponentBuilderMap,
CodeBlockKeys.type: CodeBlockComponentBuilder(),
},
),
),
const VerticalDivider(),
Expanded(
child: TextFormField(
controller: controller,
maxLines: null,
decoration: const InputDecoration(
hintText: 'Type markdown here ...',
border: InputBorder.none,
),
),
),
],
),
);
}
void _onTextChanged() {
final document = markdownToDocument(
controller.text,
markdownParsers: [
const MarkdownCodeBlockParserV2(),
],
);
setState(() {
editorState = EditorState(document: document);
});
}
}