From 2699acb48124f4b22721690c12f0c621ebf6d5c0 Mon Sep 17 00:00:00 2001 From: Saif Addin Date: Fri, 16 Jan 2026 19:01:16 +0100 Subject: [PATCH] fix: do not lose paste data as code blocks --- .../markdown_soft_code_block_parser.dart | 43 +++++++++++++++++++ .../markdown/decoder/parser/parser.dart | 1 + .../plugins/markdown/document_markdown.dart | 1 + 3 files changed, 45 insertions(+) create mode 100644 lib/src/plugins/markdown/decoder/parser/markdown_soft_code_block_parser.dart diff --git a/lib/src/plugins/markdown/decoder/parser/markdown_soft_code_block_parser.dart b/lib/src/plugins/markdown/decoder/parser/markdown_soft_code_block_parser.dart new file mode 100644 index 000000000..57ec0e40e --- /dev/null +++ b/lib/src/plugins/markdown/decoder/parser/markdown_soft_code_block_parser.dart @@ -0,0 +1,43 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:markdown/markdown.dart' as md; + +/// Parses markdown code blocks (indentation or fenced) into paragraphs. +/// This prevents code blocks from being dropped if the editor doesn't support them, +/// and effectively strips indentation from 4-space indented blocks (as per md spec). +class MarkdownSoftCodeBlockParser extends CustomMarkdownParser { + const MarkdownSoftCodeBlockParser(); + + @override + List transform( + md.Node node, + List parsers, { + MarkdownListType listType = MarkdownListType.unknown, + int? startNumber, + }) { + if (node is md.Element && node.tag == 'pre') { + String text = ''; + + if (node.children?.isNotEmpty == true) { + final codeNode = node.children!.first; + if (codeNode is md.Element && codeNode.tag == 'code') { + text = codeNode.textContent; + } else { + text = node.textContent; + } + } else { + text = node.textContent; + } + + // Remove trailing newline which code blocks often have + if (text.endsWith('\n')) { + text = text.substring(0, text.length - 1); + } + + // Split into multiple lines and create a paragraph for each + // This effectively "flattens" the code block into text + final lines = text.split('\n'); + return lines.map((line) => paragraphNode(text: line)).toList(); + } + return []; + } +} diff --git a/lib/src/plugins/markdown/decoder/parser/parser.dart b/lib/src/plugins/markdown/decoder/parser/parser.dart index 800e9250c..a86ddaaba 100644 --- a/lib/src/plugins/markdown/decoder/parser/parser.dart +++ b/lib/src/plugins/markdown/decoder/parser/parser.dart @@ -1,6 +1,7 @@ export 'custom_markdown_node_parser.dart'; export 'markdown_block_quote_parser.dart'; export 'markdown_divider_parser.dart'; +export 'markdown_soft_code_block_parser.dart'; export 'markdown_heading_parser.dart'; export 'markdown_image_parser.dart'; export 'markdown_ordered_list_item_parser.dart'; diff --git a/lib/src/plugins/markdown/document_markdown.dart b/lib/src/plugins/markdown/document_markdown.dart index e4678ca35..e906d7399 100644 --- a/lib/src/plugins/markdown/document_markdown.dart +++ b/lib/src/plugins/markdown/document_markdown.dart @@ -18,6 +18,7 @@ Document markdownToDocument( markdownParsers: [ ...markdownParsers, const MarkdownParagraphParserV2(), + const MarkdownSoftCodeBlockParser(), const MarkdownHeadingParserV2(), const MarkdownTodoListParserV2(), const MarkdownUnorderedListParserV2(),