feat: support parsing the nested list when exporting the document to markdown format (#540)

* feat: support exporting nested list

* feat: support parsing the nested list

* fix: tests
This commit is contained in:
Lucas.Xu 2023-10-14 23:47:57 +08:00 • committed by GitHub
commit 9ae85ea162
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 149 additions and 90 deletions

View file

@ -1,7 +1,6 @@
import 'dart:convert';
import 'package:appflowy_editor/src/core/document/document.dart';
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.dart';
class DocumentMarkdownEncoder extends Converter<Document, String> {
@ -19,9 +18,23 @@ class DocumentMarkdownEncoder extends Converter<Document, String> {
(element) => element.id == node.type,
);
if (parser != null) {
buffer.write(parser.transform(node));
buffer.write(parser.transform(node, this));
}
}
return buffer.toString();
}
String convertNodes(
List<Node> nodes, {
bool withIndent = false,
}) {
final result = convert(Document(root: pageNode(children: nodes)));
if (result.isNotEmpty && withIndent) {
return result
.split('\n')
.map((e) => e.isNotEmpty ? '\t$e' : e)
.join('\n');
}
return result;
}
}

View file

@ -4,20 +4,17 @@ class BulletedListNodeParser extends NodeParser {
const BulletedListNodeParser();
@override
String get id => 'bulleted_list';
String get id => BulletedListBlockKeys.type;
@override
String transform(Node node) {
assert(node.type == 'bulleted_list');
final delta = node.delta;
if (delta == null) {
throw Exception('Delta is null');
String transform(Node node, DocumentMarkdownEncoder? encoder) {
final delta = node.delta ?? Delta()
..insert('');
final children = encoder?.convertNodes(node.children, withIndent: true);
String markdown = '* ${DeltaMarkdownEncoder().convert(delta)}\n';
if (children != null && children.isNotEmpty) {
markdown += children;
}
final markdown = DeltaMarkdownEncoder().convert(delta);
final result = '* $markdown';
final suffix = node.next == null ? '' : '\n';
return '$result$suffix';
return markdown;
}
}

View file

@ -7,7 +7,7 @@ class CodeBlockNodeParser extends NodeParser {
String get id => 'code';
@override
String transform(Node node) {
String transform(Node node, DocumentMarkdownEncoder? encoder) {
assert(node.type == 'code');
final delta = node.delta;

View file

@ -4,16 +4,12 @@ class HeadingNodeParser extends NodeParser {
const HeadingNodeParser();
@override
String get id => 'heading';
String get id => HeadingBlockKeys.type;
@override
String transform(Node node) {
assert(node.type == 'heading');
final delta = node.delta;
if (delta == null) {
throw Exception('Delta is null');
}
String transform(Node node, DocumentMarkdownEncoder? encoder) {
final delta = node.delta ?? Delta()
..insert('');
final markdown = DeltaMarkdownEncoder().convert(delta);
final attributes = node.attributes;
final level = attributes[HeadingBlockKeys.level] as int? ?? 1;

View file

@ -1,14 +1,13 @@
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
class ImageNodeParser extends NodeParser {
const ImageNodeParser();
@override
String get id => 'image';
String get id => ImageBlockKeys.type;
@override
String transform(Node node) {
String transform(Node node, DocumentMarkdownEncoder? encoder) {
return '![](${node.attributes['image_src']})';
}
}

View file

@ -1,8 +1,8 @@
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
abstract class NodeParser {
const NodeParser();
String get id;
String transform(Node node);
String transform(Node node, DocumentMarkdownEncoder? encoder);
}

View file

@ -4,20 +4,18 @@ class NumberedListNodeParser extends NodeParser {
const NumberedListNodeParser();
@override
String get id => 'numbered_list';
String get id => NumberedListBlockKeys.type;
@override
String transform(Node node) {
assert(node.type == 'numbered_list');
final delta = node.delta;
if (delta == null) {
throw Exception('Delta is null');
String transform(Node node, DocumentMarkdownEncoder? encoder) {
final delta = node.delta ?? Delta()
..insert('');
final number = node.attributes[NumberedListBlockKeys.number] ?? '1';
final children = encoder?.convertNodes(node.children, withIndent: true);
String markdown = '$number. ${DeltaMarkdownEncoder().convert(delta)}\n';
if (children != null && children.isNotEmpty) {
markdown += children;
}
final markdown = DeltaMarkdownEncoder().convert(delta);
final result = '1. $markdown'; // FIXME: support parse the number
final suffix = node.next == null ? '' : '\n';
return '$result$suffix';
return markdown;
}
}

View file

@ -4,20 +4,17 @@ class QuoteNodeParser extends NodeParser {
const QuoteNodeParser();
@override
String get id => 'quote';
String get id => QuoteBlockKeys.type;
@override
String transform(Node node) {
assert(node.type == 'quote');
final delta = node.delta;
if (delta == null) {
throw Exception('Delta is null');
String transform(Node node, DocumentMarkdownEncoder? encoder) {
final delta = node.delta ?? Delta()
..insert('');
final children = encoder?.convertNodes(node.children, withIndent: true);
String markdown = '> ${DeltaMarkdownEncoder().convert(delta)}\n';
if (children != null && children.isNotEmpty) {
markdown += children;
}
final markdown = DeltaMarkdownEncoder().convert(delta);
final result = '> $markdown';
final suffix = node.next == null ? '' : '\n';
return '$result$suffix';
return markdown;
}
}

View file

@ -8,7 +8,7 @@ class TableNodeParser extends NodeParser {
String get id => 'table';
@override
String transform(Node node) {
String transform(Node node, DocumentMarkdownEncoder? encoder) {
final int rowsLen = node.attributes['rowsLen'],
colsLen = node.attributes['colsLen'];
String result = '';

View file

@ -4,17 +4,24 @@ class TextNodeParser extends NodeParser {
const TextNodeParser();
@override
String get id => 'paragraph';
String get id => ParagraphBlockKeys.type;
@override
String transform(Node node) {
final delta = node.delta;
if (delta == null) {
assert(false, 'Delta is null');
String transform(Node node, DocumentMarkdownEncoder? encoder) {
final delta = node.delta ?? Delta()
..insert('');
final children = encoder?.convertNodes(node.children, withIndent: true);
String markdown = DeltaMarkdownEncoder().convert(delta);
if (markdown.isEmpty && children == null) {
return '';
} else if (node
.findParent((element) => element.type == TableBlockKeys.type) ==
null) {
markdown += '\n';
}
final markdown = DeltaMarkdownEncoder().convert(delta);
final suffix = node.next == null ? '' : '\n';
return '$markdown$suffix';
if (children != null && children.isNotEmpty) {
markdown += children;
}
return markdown;
}
}

View file

@ -4,22 +4,19 @@ class TodoListNodeParser extends NodeParser {
const TodoListNodeParser();
@override
String get id => 'todo_list';
String get id => TodoListBlockKeys.type;
@override
String transform(Node node) {
assert(node.type == 'todo_list');
final delta = node.delta;
if (delta == null) {
throw Exception('Delta is null');
String transform(Node node, DocumentMarkdownEncoder? encoder) {
final delta = node.delta ?? Delta()
..insert('');
final checked =
node.attributes[TodoListBlockKeys.checked] == true ? '- [x]' : '- [ ]';
final children = encoder?.convertNodes(node.children, withIndent: true);
String markdown = '$checked ${DeltaMarkdownEncoder().convert(delta)}\n';
if (children != null && children.isNotEmpty) {
markdown += children;
}
final markdown = DeltaMarkdownEncoder().convert(delta);
final attributes = node.attributes;
final checked = attributes[TodoListBlockKeys.checked] == true;
final result = checked ? '- [x] $markdown' : '- [ ] $markdown';
final suffix = node.next == null ? '' : '\n';
return '$result$suffix';
return markdown;
}
}