Browse Source

feat: document to markdown

Lucas.Xu 2 years ago
parent
commit
9eaa79b558

+ 5 - 1
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -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';

+ 0 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/document_markdown_encoder.dart


+ 0 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/delta_markdown_encoder.dart → frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/delta_markdown_encoder.dart


+ 39 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/document_markdown_encoder.dart

@@ -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;
+  }
+}

+ 14 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/image_node_parser.dart

@@ -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']})';
+  }
+}

+ 8 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/node_parser.dart

@@ -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);
+}

+ 57 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/text_node_parser.dart

@@ -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';
+  }
+}

+ 0 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/markdown_encoder.dart

@@ -1 +0,0 @@
-