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:
parent
e8ee051719
commit
5bf6c7acd0
3 changed files with 1413 additions and 1099 deletions
|
|
@ -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,
|
||||
|
|
|
|||
51
example/lib/pages/markdown/markdown_code_block_parser.dart
Normal file
51
example/lib/pages/markdown/markdown_code_block_parser.dart
Normal 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),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
100
example/lib/pages/markdown_editor.dart
Normal file
100
example/lib/pages/markdown_editor.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue