Skip to content

Commit 191b67b

Browse files
committed
feat(spx-gui): fixed the issue of merging line breaks after using a custom self-closing tag
1 parent 0416d33 commit 191b67b

File tree

3 files changed

+117
-2
lines changed

3 files changed

+117
-2
lines changed

spx-gui/src/components/common/markdown-vue/MarkdownView.test.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { defineComponent, h, markRaw } from 'vue'
22
import { describe, expect, it } from 'vitest'
33
import { renderToString } from '@vue/test-utils'
44
import { useSlotText } from '@/utils/vnode'
5-
import MarkdowView, { preprocessCustomRawComponents, preprocessIncompleteTags } from './MarkdownView'
5+
import MarkdowView, {
6+
preprocessCustomRawComponents,
7+
preprocessIncompleteTags,
8+
preprocessInlineComponents
9+
} from './MarkdownView'
610

711
describe('preprocessCustomRawComponents', () => {
812
it('should convert custom raw components to <pre> tags', () => {
@@ -98,6 +102,51 @@ describe('preprocessCustomRawComponents', () => {
98102
})
99103
})
100104

105+
describe('preprocessInlineComponents', () => {
106+
it('should separate self-closing tag from next line text with blank line', () => {
107+
const value = '<x-comp />\nHello world'
108+
const tagNames = ['x-comp']
109+
const result = preprocessInlineComponents(value, tagNames)
110+
expect(result).toBe('<x-comp />\n\nHello world')
111+
})
112+
it('should separate self-closing tag from next line text with blank line with attributes', () => {
113+
const value = '<x-comp some="foo" />\nHello world'
114+
const tagNames = ['x-comp']
115+
const result = preprocessInlineComponents(value, tagNames)
116+
expect(result).toBe('<x-comp some="foo" />\n\nHello world')
117+
})
118+
it('should preserve leading spaces of next line', () => {
119+
const value = '<x-comp />\n Hello'
120+
const tagNames = ['x-comp']
121+
const result = preprocessInlineComponents(value, tagNames)
122+
expect(result).toBe('<x-comp />\n\n Hello')
123+
})
124+
it('should ignore when there is a blank line', () => {
125+
const value = '<x-comp />\n\nHello'
126+
const tagNames = ['x-comp']
127+
const result = preprocessInlineComponents(value, tagNames)
128+
expect(result).toBe('<x-comp />\n\nHello')
129+
})
130+
it('should not change when inline already', () => {
131+
const value = '<x-comp />Hello\nWorld'
132+
const tagNames = ['x-comp']
133+
const result = preprocessInlineComponents(value, tagNames)
134+
expect(result).toBe('<x-comp />Hello\nWorld')
135+
})
136+
it('should match spaces before newline', () => {
137+
const value = '<x-comp /> \nHello'
138+
const tagNames = ['x-comp']
139+
const result = preprocessInlineComponents(value, tagNames)
140+
expect(result).toBe('<x-comp />\n\nHello')
141+
})
142+
it('should not change when not self-closing', () => {
143+
const value = '<x-comp></x-comp>\nHello'
144+
const tagNames = ['x-comp']
145+
const result = preprocessInlineComponents(value, tagNames)
146+
expect(result).toBe('<x-comp></x-comp>\nHello')
147+
})
148+
})
149+
101150
describe('preprocessIncompleteTags', () => {
102151
it('should remove the last incomplete tag', () => {
103152
const value = 'Before<custom-incomplete-component>Content'
@@ -255,4 +304,38 @@ After`,
255304
<!--]--></div>
256305
<p><div class="test-comp-1"></div>aaa<div class="test-comp-2">bbb</div><div class="test-comp-2">ccc</div>ddd</p><div class="test-comp-3"><!--[--><p></p><!--]--></div></div>`)
257306
})
307+
it('should handle a custom self-closing tag with text on the next line', async () => {
308+
const testComp1 = {
309+
template: '<div class="test-comp-1">hello world</div>'
310+
}
311+
const result = await renderToString(MarkdowView, {
312+
props: {
313+
value: `Before<test-comp-1 />
314+
After`,
315+
components: markRaw({
316+
custom: {
317+
'test-comp-1': testComp1
318+
}
319+
})
320+
}
321+
})
322+
expect(result).toBe('<div><p>Before<div class="test-comp-1">hello world</div></p>\n<p>After</p></div>')
323+
})
324+
it('should handle a custom self-closing tag when text follows immediately on the next line', async () => {
325+
const testComp1 = {
326+
template: '<div class="test-comp-1">hello world</div>'
327+
}
328+
const result = await renderToString(MarkdowView, {
329+
props: {
330+
value: `Before<test-comp-1 />middle
331+
After`,
332+
components: markRaw({
333+
custom: {
334+
'test-comp-1': testComp1
335+
}
336+
})
337+
}
338+
})
339+
expect(result).toBe('<div><p>Before<div class="test-comp-1">hello world</div>middle\nAfter</p></div>')
340+
})
258341
})

spx-gui/src/components/common/markdown-vue/MarkdownView.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,37 @@ export function preprocessCustomRawComponents(value: string, tagNames: string[])
129129
return value
130130
}
131131

132+
/**
133+
* Preprocesses text immediately following custom self-closing elements and line breaks within a Markdown string.
134+
* According to the Markdown specification, this behavior—the custom self-closing element,
135+
* the line break, and the subsequent text being merged into a single html_block—is standard,
136+
* but it is not the result we desire.
137+
*
138+
* We have addressed this situation while adhering to the Markdown specification.
139+
* The text following the custom self-closing element and the line break is separated into two distinct paragraphs.
140+
*
141+
* For example:
142+
* ```markdown
143+
* <custom-component/>
144+
* Content1
145+
* ```
146+
* will be preprocessed into:
147+
* ```markdown
148+
* <custom-component/>
149+
*
150+
* Content1
151+
* ```
152+
* Refer to: https://github.com/goplus/builder/issues/2472
153+
*/
154+
export function preprocessInlineComponents(value: string, tagNames: string[]) {
155+
tagNames.forEach((tagName) => {
156+
// This scenario only occurs with self-closing tags, so this processing currently only targets
157+
// self-closing tags of custom elements.
158+
value = value.replace(new RegExp(`(<${tagName}[^>]*/>)[ \t]*\r?\n([^\n]*\\S[^\n]*)`, 'g'), '$1\n\n$2')
159+
})
160+
return value
161+
}
162+
132163
const tagHeaderPattern = /<([a-zA-Z0-9-]+)(\s|<)/
133164

134165
function processSelfClosingForHastRawNode(node: Raw, tagNames: string[]) {
@@ -200,6 +231,7 @@ export function preprocessIncompleteTags(value: string, tagNames: string[]) {
200231
function parseMarkdown({ value, components }: Props): hast.Nodes {
201232
const customComponents = { ...components?.custom, ...components?.customRaw }
202233
const customTagNames = Object.keys(customComponents)
234+
value = preprocessInlineComponents(value, customTagNames)
203235
value = preprocessCustomRawComponents(value, Object.keys(components?.customRaw ?? {}))
204236
value = preprocessIncompleteTags(value, customTagNames)
205237
const mdast = fromMarkdown(value)

spx-gui/src/components/tutorials/tutorial-course-abandon.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const maxAbandonPredictionCount = 3
1111

1212
const constraints = `
1313
**Constraints**:
14-
- MUST be placed at the **last line** of your message.
14+
- MUST be placed at the **first line** of your message.
1515
- Use at most ONCE per message.`
1616

1717
const predictionTagName = 'tutorial-course-abandon-prediction'

0 commit comments

Comments
 (0)