diff --git a/lib/src/plugins/markdown/decoder/document_markdown_decoder.dart b/lib/src/plugins/markdown/decoder/document_markdown_decoder.dart index 3ea2719fe..6529bbd60 100644 --- a/lib/src/plugins/markdown/decoder/document_markdown_decoder.dart +++ b/lib/src/plugins/markdown/decoder/document_markdown_decoder.dart @@ -1,8 +1,13 @@ import 'dart:convert'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:path/path.dart' as p; class DocumentMarkdownDecoder extends Converter { + final imageRegex = RegExp(r'^!\[[^\]]*\]\((.*?)\)'); + final assetRegex = RegExp(r'^\[[^\]]*\]\((.*?)\)'); + final htmlRegex = RegExp('^(http|https)://'); + @override Document convert(String input) { final lines = input.split('\n'); @@ -52,7 +57,6 @@ class DocumentMarkdownDecoder extends Converter { Node _convertLineToNode(String line) { final decoder = DeltaMarkdownDecoder(); - // Heading Style if (line.startsWith('### ')) { return headingNode( @@ -91,6 +95,20 @@ class DocumentMarkdownDecoder extends Converter { return Node(type: 'divider'); } else if (line.startsWith('```') && line.endsWith('```')) { return _codeBlockNodeFromMarkdown(line, decoder); + } else if (imageRegex.hasMatch(line.trim())) { + final filePath = extractImagePath(line.trim()); + // checking if filepath is present or if the filepath is an image or not + if (filePath == null || + !['.png', '.jpg', 'jpeg'].contains(p.extension(filePath))) { + return paragraphNode(text: line.trim()); + } + return imageNode(url: filePath); + } else if (assetRegex.hasMatch(line.trim())) { + // this might be a url or a file like pdf, videos, etc + final filepath = extractFilePath(line.trim()); + if (filepath != null && !htmlRegex.hasMatch(filepath)) { + return paragraphNode(text: line); + } } if (line.isNotEmpty) { @@ -104,6 +122,16 @@ class DocumentMarkdownDecoder extends Converter { ); } + String? extractImagePath(String text) { + final match = imageRegex.firstMatch(text); + return match?.group(1); + } + + String? extractFilePath(String text) { + final match = assetRegex.firstMatch(text); + return match?.group(1); + } + Node _codeBlockNodeFromMarkdown( String markdown, DeltaMarkdownDecoder decoder, diff --git a/pubspec.yaml b/pubspec.yaml index 97e3dc072..a449362de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: nanoid: ^1.0.0 visibility_detector: ^0.4.0+2 file_picker: ^5.3.1 + path: ^1.8.3 + dev_dependencies: flutter_test: sdk: flutter diff --git a/test/plugins/markdown/decoder/document_markdown_decoder_test.dart b/test/plugins/markdown/decoder/document_markdown_decoder_test.dart index c0707a8ad..3040a5537 100644 --- a/test/plugins/markdown/decoder/document_markdown_decoder_test.dart +++ b/test/plugins/markdown/decoder/document_markdown_decoder_test.dart @@ -186,6 +186,35 @@ void main() async { ] } }, + { + "type": "paragraph", + "data": { + "delta": [] + } + }, + { + "type": "paragraph", + "data": { + "delta": [ + { + "insert": "[Example file.pdf](path/to/file.pdf)" + } + ] + } + }, + { + "type": "paragraph", + "data": { + "delta": [] + } + }, + { + "type": "image", + "data": { + "url": "path/to/image.png", + "align": "center" + } + }, { "type": "paragraph", "data": { @@ -334,6 +363,10 @@ You can also use ***AppFlowy Editor*** as a component to build your own app. * Select text to trigger to the toolbar to format your notes. If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders! + +[Example file.pdf](path/to/file.pdf) + +![Example image](path/to/image.png) '''; final result = DocumentMarkdownDecoder().convert(markdown); final data = jsonDecode(example);