feat: document to markdown

This commit is contained in:
Lucas.Xu 2022-11-08 15:44:10 +08:00
commit 0a5678f934
8 changed files with 123 additions and 2 deletions

View file

@ -33,4 +33,8 @@ export 'src/render/selection_menu/selection_menu_widget.dart';
export 'src/l10n/l10n.dart';
export 'src/render/style/plugin_styles.dart';
export 'src/render/style/editor_style.dart';
export 'src/plugins/markdown/delta_markdown_encoder.dart';
export 'src/plugins/markdown/encoder/delta_markdown_encoder.dart';
export 'src/plugins/markdown/encoder/document_markdown_encoder.dart';
export 'src/plugins/markdown/encoder/parser/node_parser.dart';
export 'src/plugins/markdown/encoder/parser/text_node_parser.dart';
export 'src/plugins/markdown/encoder/parser/image_node_parser.dart';

View file

@ -0,0 +1,88 @@
import 'dart:convert';
import 'package:appflowy_editor/appflowy_editor.dart';
/// A [Delta] encoder that encodes a [Delta] to Markdown.
///
/// Only support inline styles, like bold, italic, underline, strike, code.
class DeltaMarkdownEncoder extends Converter<Delta, String> {
@override
String convert(Delta input) {
final buffer = StringBuffer();
final iterator = input.iterator;
while (iterator.moveNext()) {
final op = iterator.current;
if (op is TextInsert) {
final attributes = op.attributes;
if (attributes != null) {
buffer.write(_prefixSyntax(attributes));
buffer.write(op.text);
buffer.write(_suffixSyntax(attributes));
} else {
buffer.write(op.text);
}
}
}
return buffer.toString();
}
String _prefixSyntax(Attributes attributes) {
var syntax = '';
if (attributes[BuiltInAttributeKey.bold] == true &&
attributes[BuiltInAttributeKey.italic] == true) {
syntax += '***';
} else if (attributes[BuiltInAttributeKey.bold] == true) {
syntax += '**';
} else if (attributes[BuiltInAttributeKey.italic] == true) {
syntax += '_';
}
if (attributes[BuiltInAttributeKey.strikethrough] == true) {
syntax += '~~';
}
if (attributes[BuiltInAttributeKey.underline] == true) {
syntax += '<u>';
}
if (attributes[BuiltInAttributeKey.code] == true) {
syntax += '`';
}
if (attributes[BuiltInAttributeKey.href] != null) {
syntax += '[';
}
return syntax;
}
String _suffixSyntax(Attributes attributes) {
var syntax = '';
if (attributes[BuiltInAttributeKey.href] != null) {
syntax += '](${attributes[BuiltInAttributeKey.href]})';
}
if (attributes[BuiltInAttributeKey.code] == true) {
syntax += '`';
}
if (attributes[BuiltInAttributeKey.underline] == true) {
syntax += '</u>';
}
if (attributes[BuiltInAttributeKey.strikethrough] == true) {
syntax += '~~';
}
if (attributes[BuiltInAttributeKey.bold] == true &&
attributes[BuiltInAttributeKey.italic] == true) {
syntax += '***';
} else if (attributes[BuiltInAttributeKey.bold] == true) {
syntax += '**';
} else if (attributes[BuiltInAttributeKey.italic] == true) {
syntax += '_';
}
return syntax;
}
}

View file

@ -0,0 +1,39 @@
import 'dart:convert';
import 'package:appflowy_editor/src/core/document/document.dart';
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/image_node_parser.dart';
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart';
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/text_node_parser.dart';
class DocumentMarkdownEncoder extends Converter<Document, String> {
DocumentMarkdownEncoder({
this.parsers = const [
TextNodeParser(),
ImageNodeParser(),
],
});
final List<NodeParser> parsers;
@override
String convert(Document input) {
final buffer = StringBuffer();
for (final node in input.root.children) {
NodeParser? parser =
parsers.firstWhereOrNull((element) => element.id == node.type);
if (parser != null) {
buffer.write(parser.transform(node));
}
}
return buffer.toString();
}
}
extension IterableExtension<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T element) test) {
for (var element in this) {
if (test(element)) return element;
}
return null;
}
}

View file

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

View file

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

View file

@ -0,0 +1,57 @@
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart';
import 'package:appflowy_editor/src/plugins/markdown/encoder/delta_markdown_encoder.dart';
import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart';
class TextNodeParser extends NodeParser {
const TextNodeParser();
@override
String get id => 'text';
@override
String transform(Node node) {
assert(node is TextNode);
final textNode = node as TextNode;
final markdown = DeltaMarkdownEncoder().convert(textNode.delta);
final attributes = textNode.attributes;
var result = markdown;
var suffix = '\n';
if (attributes.isNotEmpty &&
attributes.containsKey(BuiltInAttributeKey.subtype)) {
final subtype = attributes[BuiltInAttributeKey.subtype];
if (subtype == 'heading') {
final heading = attributes[BuiltInAttributeKey.heading];
if (heading == 'h1') {
result = '# $markdown';
} else if (heading == 'h2') {
result = '## $markdown';
} else if (heading == 'h3') {
result = '### $markdown';
} else if (heading == 'h4') {
result = '#### $markdown';
} else if (heading == 'h5') {
result = '##### $markdown';
} else if (heading == 'h6') {
result = '###### $markdown';
}
} else if (subtype == 'quote') {
result = '> $markdown';
} else if (subtype == 'code-block') {
result = '```\n$markdown\n```';
} else if (subtype == 'bulleted-list') {
result = '* $markdown';
} else if (subtype == 'number-list') {
final number = attributes['number'];
result = '$number. $markdown';
} else if (subtype == 'checkbox') {
if (attributes[BuiltInAttributeKey.checkbox] == true) {
result = '- [x] $markdown';
} else {
result = '- [ ] $markdown';
}
}
}
return '$result$suffix';
}
}