Skip to content

Commit 52e3e84

Browse files
committed
refactor(spx-gui): convert self-closing components into explicit tags for simplified handling
1 parent 85d3777 commit 52e3e84

File tree

1 file changed

+13
-97
lines changed

1 file changed

+13
-97
lines changed

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

Lines changed: 13 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { computed, defineComponent, h, type VNode, type Component } from 'vue'
22
import { fromMarkdown } from 'mdast-util-from-markdown'
33
import { html, find } from 'property-information'
44
import type * as hast from 'hast'
5-
import { toHast, type Raw } from 'mdast-util-to-hast'
5+
import { toHast } from 'mdast-util-to-hast'
66
import { raw } from 'hast-util-raw'
77
import { defaultSchema, sanitize, type Schema as SanitizeSchema } from 'hast-util-sanitize'
88

@@ -129,20 +129,11 @@ export function preprocessCustomRawComponents(value: string, tagNames: string[])
129129
return value
130130
}
131131

132-
const tagNamePattern = /<([a-zA-Z0-9-]+)(\s[^>]*)?\/>$/
133-
function getTagName(line: string): string | null {
134-
const m = line.match(tagNamePattern)
135-
return m != null ? m[1] : null
136-
}
137-
138132
/**
139-
* Preprocesses text immediately following custom self-closing elements and line breaks within a Markdown string.
140-
* According to the Markdown specification, this behavior—the custom self-closing element,
141-
* the line break, and the subsequent text being merged into a single html_block—is standard,
142-
* but it is not the result we desire.
143-
*
144-
* We have addressed this situation while adhering to the Markdown specification.
145-
* The text following the custom self-closing element and the line break is separated into two distinct paragraphs.
133+
* Preprocesses custom self-closing elements in a Markdown string.
134+
* According to the Markdown specification, a custom self-closing element followed by a line break
135+
* and subsequent text will be merged into a single html_block, which is standard behavior
136+
* but not the desired result.
146137
*
147138
* For example:
148139
* ```markdown
@@ -151,90 +142,16 @@ function getTagName(line: string): string | null {
151142
* ```
152143
* will be preprocessed into:
153144
* ```markdown
154-
* <custom-component/>
155-
*
145+
* <custom-component></custom-component>
156146
* Content1
157147
* ```
158148
* Refer to: https://github.com/goplus/builder/issues/2472
159149
*/
160-
export function preprocessInlineComponents(value: string, tagNames: string[]) {
161-
const tagSet = new Set(tagNames)
162-
const lines = value.split(/\r?\n/)
163-
const out: string[] = []
164-
165-
for (let i = 0; i < lines.length; i++) {
166-
const line = lines[i]
167-
const trimmed = line.trimEnd()
168-
169-
// Check if the line ends with a self-closing tag
170-
if (trimmed.endsWith('/>')) {
171-
const tag = getTagName(trimmed)
172-
if (
173-
// Tag name must be in the provided list
174-
tag != null &&
175-
tagSet.has(tag) &&
176-
// There must be a following line
177-
i + 1 < lines.length &&
178-
// The next line must NOT be a blank line
179-
lines[i + 1].trim() !== ''
180-
) {
181-
out.push(line)
182-
out.push('')
183-
continue
184-
}
185-
}
186-
out.push(line)
187-
}
188-
189-
return out.join('\n')
190-
}
191-
192-
const tagHeaderPattern = /<([a-zA-Z0-9-]+)(\s|<)/
193-
194-
function processSelfClosingForHastRawNode(node: Raw, tagNames: string[]) {
195-
const value = node.value.trim()
196-
if (!value.endsWith('/>')) return node
197-
const match = value.match(tagHeaderPattern)
198-
if (match == null) return node
199-
const tagName = match[1]
200-
if (!tagNames.includes(tagName)) return node
201-
return {
202-
...node,
203-
value: value.slice(0, -2) + `></${tagName}>`
204-
}
205-
}
206-
207-
function processSelfClosingForHastNode(node: hast.Node, tagNames: string[]): hast.Node {
208-
switch (node.type) {
209-
case 'raw':
210-
return processSelfClosingForHastRawNode(node as Raw, tagNames)
211-
case 'text':
212-
return node
213-
case 'element': {
214-
const element = node as hast.Element
215-
const processedElement = {
216-
...element,
217-
children: element.children.map((child) => processSelfClosingForHastNode(child, tagNames))
218-
}
219-
return processedElement
220-
}
221-
default:
222-
return node
223-
}
224-
}
225-
226-
/**
227-
* Process self-closing tags in the hast nodes.
228-
* This makes sure self-closing is supported for all custom components.
229-
*/
230-
function processSelfClosingForHastNodes(nodes: hast.Nodes, tagNames: string[]): hast.Nodes {
231-
if (nodes.type === 'root') {
232-
return {
233-
...nodes,
234-
children: nodes.children.map((child) => processSelfClosingForHastNode(child, tagNames))
235-
} as hast.Root
236-
}
237-
return processSelfClosingForHastNode(nodes, tagNames) as hast.RootContent
150+
export function preprocessSelfClosingComponents(value: string, tagNames: string[]) {
151+
tagNames.forEach((tagName) => {
152+
value = value.replace(new RegExp(`<${tagName}([^>]*)/>`, 'g'), `<${tagName}$1></${tagName}>`)
153+
})
154+
return value
238155
}
239156

240157
/**
@@ -260,13 +177,12 @@ export function preprocessIncompleteTags(value: string, tagNames: string[]) {
260177
function parseMarkdown({ value, components }: Props): hast.Nodes {
261178
const customComponents = { ...components?.custom, ...components?.customRaw }
262179
const customTagNames = Object.keys(customComponents)
263-
value = preprocessInlineComponents(value, customTagNames)
180+
value = preprocessSelfClosingComponents(value, customTagNames)
264181
value = preprocessCustomRawComponents(value, Object.keys(components?.customRaw ?? {}))
265182
value = preprocessIncompleteTags(value, customTagNames)
266183
const mdast = fromMarkdown(value)
267184
const hast = toHast(mdast, { allowDangerousHtml: true })
268-
const hastWithSelfClosingProcessed = processSelfClosingForHastNodes(hast, customTagNames)
269-
const rawProcessed = raw(hastWithSelfClosingProcessed, { tagfilter: false })
185+
const rawProcessed = raw(hast, { tagfilter: false })
270186
const sanitizeSchema = getSanitizeSchema(customComponents)
271187
return sanitize(rawProcessed, sanitizeSchema)
272188
}

0 commit comments

Comments
 (0)