From 91fd7df42f087bbf98d354806bc158963a45f324 Mon Sep 17 00:00:00 2001 From: Francis Meng Date: Thu, 30 Oct 2025 16:03:44 -0700 Subject: [PATCH 1/3] Fix model to markdown --- .../creators/createMarkdownBlockGroup.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts b/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts index 02f2cc2d8183..c19ea0270225 100644 --- a/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts +++ b/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts @@ -30,8 +30,10 @@ export function createMarkdownBlockGroup( } break; case 'FormatContainer': - markdownString += createMarkdownBlockQuote(blockGroup, newLinePattern, listCounter); - break; + if (blockGroup.tagName == 'blockquote') { + markdownString += createMarkdownBlockQuote(blockGroup, newLinePattern, listCounter); + break; + } default: const { blocks } = blockGroup; for (const block of blocks) { @@ -92,17 +94,14 @@ function createMarkdownBlockQuote( listCounter: ListCounter ): string { let markdownString = ''; - if (blockquote.tagName == 'blockquote') { - const { blocks } = blockquote; - for (const block of blocks) { - markdownString += - '> ' + - createMarkdownBlock(block, newLinePattern, listCounter, undefined /* newLines */, { - ignoreLineBreaks: true, - }) + - newLinePattern.newLine; - } + const { blocks } = blockquote; + for (const block of blocks) { + markdownString += + '> ' + + createMarkdownBlock(block, newLinePattern, listCounter, undefined /* newLines */, { + ignoreLineBreaks: true, + }) + + newLinePattern.newLine; } - return `${markdownString}\n`; } From fc31ed965c95e0c67c0752477315e0b4f548eca4 Mon Sep 17 00:00:00 2001 From: Francis Meng Date: Mon, 3 Nov 2025 12:09:31 -0800 Subject: [PATCH 2/3] change implementation and add test --- .../creators/createMarkdownBlockGroup.ts | 26 +++- .../creators/createMarkdownBlockgroupTest.ts | 138 ++++++++++++++++++ 2 files changed, 159 insertions(+), 5 deletions(-) diff --git a/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts b/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts index c19ea0270225..6e0799446464 100644 --- a/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts +++ b/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts @@ -32,18 +32,34 @@ export function createMarkdownBlockGroup( case 'FormatContainer': if (blockGroup.tagName == 'blockquote') { markdownString += createMarkdownBlockQuote(blockGroup, newLinePattern, listCounter); - break; + } else { + markdownString += createDefaultMarkdownString( + blockGroup, + newLinePattern, + listCounter + ); } + break; default: - const { blocks } = blockGroup; - for (const block of blocks) { - markdownString += createMarkdownBlock(block, newLinePattern, listCounter); - } + markdownString += createDefaultMarkdownString(blockGroup, newLinePattern, listCounter); break; } return markdownString; } +function createDefaultMarkdownString( + blockGroup: ContentModelBlockGroup, + newLinePattern: MarkdownLineBreaks, + listCounter: ListCounter +): string { + let markdownString = ''; + const { blocks } = blockGroup; + for (const block of blocks) { + markdownString += createMarkdownBlock(block, newLinePattern, listCounter); + } + return markdownString; +} + function createMarkdownListItem( listItem: ContentModelListItem, newLinePattern: MarkdownLineBreaks, diff --git a/packages/roosterjs-content-model-markdown/test/modelToMarkdown/creators/createMarkdownBlockgroupTest.ts b/packages/roosterjs-content-model-markdown/test/modelToMarkdown/creators/createMarkdownBlockgroupTest.ts index 80a07a43c4f0..e26fe5081d52 100644 --- a/packages/roosterjs-content-model-markdown/test/modelToMarkdown/creators/createMarkdownBlockgroupTest.ts +++ b/packages/roosterjs-content-model-markdown/test/modelToMarkdown/creators/createMarkdownBlockgroupTest.ts @@ -209,4 +209,142 @@ describe('createMarkdownBlockGroup', () => { subListItemCount: 0, }); }); + + describe('FormatContainer tests', () => { + it('should handle FormatContainer with blockquote tagName', () => { + const blockGroup = createFormatContainer('blockquote'); + const paragraph = createParagraph(); + const text = createText('This is a blockquote'); + paragraph.segments.push(text); + blockGroup.blocks.push(paragraph); + + runTest(blockGroup, `> This is a blockquote\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + + it('should handle FormatContainer with blockquote tagName and multiple paragraphs', () => { + const blockGroup = createFormatContainer('blockquote'); + + const paragraph1 = createParagraph(); + const text1 = createText('First paragraph'); + paragraph1.segments.push(text1); + blockGroup.blocks.push(paragraph1); + + const paragraph2 = createParagraph(); + const text2 = createText('Second paragraph'); + paragraph2.segments.push(text2); + blockGroup.blocks.push(paragraph2); + + runTest(blockGroup, `> First paragraph\n> Second paragraph\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + + it('should handle FormatContainer with div tagName (non-blockquote)', () => { + const blockGroup = createFormatContainer('div'); + const paragraph = createParagraph(); + const text = createText('This is a div content'); + paragraph.segments.push(text); + blockGroup.blocks.push(paragraph); + + runTest(blockGroup, `This is a div content\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + + it('should handle FormatContainer with section tagName (non-blockquote)', () => { + const blockGroup = createFormatContainer('section'); + const paragraph = createParagraph(); + const text = createText('Section content'); + paragraph.segments.push(text); + blockGroup.blocks.push(paragraph); + + runTest(blockGroup, `Section content\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + + it('should handle FormatContainer with article tagName (non-blockquote)', () => { + const blockGroup = createFormatContainer('article'); + const paragraph = createParagraph(); + const text = createText('Article content'); + paragraph.segments.push(text); + blockGroup.blocks.push(paragraph); + + runTest(blockGroup, `Article content\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + + it('should handle FormatContainer with non-blockquote tagName and multiple paragraphs', () => { + const blockGroup = createFormatContainer('div'); + + const paragraph1 = createParagraph(); + const text1 = createText('First div paragraph'); + paragraph1.segments.push(text1); + blockGroup.blocks.push(paragraph1); + + const paragraph2 = createParagraph(); + const text2 = createText('Second div paragraph'); + paragraph2.segments.push(text2); + blockGroup.blocks.push(paragraph2); + + runTest(blockGroup, `First div paragraph\n\nSecond div paragraph\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + + it('should handle FormatContainer with blockquote and formatted text', () => { + const blockGroup = createFormatContainer('blockquote'); + const paragraph = createParagraph(); + + const boldText = createText('Bold '); + boldText.format.fontWeight = 'bold'; + + const italicText = createText('italic '); + italicText.format.italic = true; + + const normalText = createText('and normal text'); + + paragraph.segments.push(boldText); + paragraph.segments.push(italicText); + paragraph.segments.push(normalText); + blockGroup.blocks.push(paragraph); + + runTest(blockGroup, `> **Bold ***italic *and normal text\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + + it('should handle FormatContainer with non-blockquote and formatted text', () => { + const blockGroup = createFormatContainer('div'); + const paragraph = createParagraph(); + + const boldText = createText('Bold '); + boldText.format.fontWeight = 'bold'; + + const italicText = createText('italic '); + italicText.format.italic = true; + + const normalText = createText('and normal text'); + + paragraph.segments.push(boldText); + paragraph.segments.push(italicText); + paragraph.segments.push(normalText); + blockGroup.blocks.push(paragraph); + + runTest(blockGroup, `**Bold ***italic *and normal text\n\n`, { + listItemCount: 0, + subListItemCount: 0, + }); + }); + }); }); From a13a0b59d0fa80053bcee2a0d553aeb34871e68f Mon Sep 17 00:00:00 2001 From: Francis Meng Date: Mon, 3 Nov 2025 15:57:08 -0800 Subject: [PATCH 3/3] update --- .../modelToMarkdown/creators/createMarkdownBlockGroup.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts b/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts index 6e0799446464..9fa7702de040 100644 --- a/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts +++ b/packages/roosterjs-content-model-markdown/lib/modelToMarkdown/creators/createMarkdownBlockGroup.ts @@ -55,7 +55,11 @@ function createDefaultMarkdownString( let markdownString = ''; const { blocks } = blockGroup; for (const block of blocks) { - markdownString += createMarkdownBlock(block, newLinePattern, listCounter); + markdownString += createMarkdownBlock(block, newLinePattern, listCounter, { + table: newLinePattern.newLine, + paragraph: newLinePattern.lineBreak, + divider: newLinePattern.lineBreak, + }); } return markdownString; }