Skip to content

Commit 5734606

Browse files
committed
revisions on conversion
1 parent dfc96cc commit 5734606

File tree

3 files changed

+101
-74
lines changed

3 files changed

+101
-74
lines changed

plugins/raw-markdown/convert-components.js

Lines changed: 97 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,74 @@ function parseProps(propsString) {
2222
}
2323

2424
/**
25-
* Converts <Screenshot /> components to bold alt text
26-
* Example: <Screenshot alt="test" src="..." /> -> **test**
25+
*
26+
* @param {string} content - The content to search
27+
* @param {string} componentName - Name of the component (e.g., "Screenshot")
28+
* @returns {Array} Array of match objects with { props, children, fullMatch, index }
2729
*/
28-
function convertScreenshot(content) {
29-
const screenshotRegex = /<Screenshot\s+([^>]*?)(?:\/?>|>\s*<\/Screenshot>)/gs
30+
function matchComponent(content, componentName) {
31+
const matches = []
3032

31-
return content.replace(screenshotRegex, (match, propsString) => {
32-
const props = parseProps(propsString)
33-
if (props.alt) {
34-
return `\n\n**${props.alt}**\n\n`
35-
}
36-
return "\n\n**[Screenshot]**\n\n"
33+
// Universal regex that matches:
34+
// <ComponentName (props with any content including >) (self-closing /> OR open-tag > children </ComponentName>)
35+
// Uses [\s\S]*? to match props across newlines (non-greedy to stop at /> or >)
36+
const pattern = `<${componentName}([\\s\\S]*?)(?:\\/>|>([\\s\\S]*?)<\\/${componentName}>)`
37+
const regex = new RegExp(pattern, 'g')
38+
39+
let match
40+
41+
while ((match = regex.exec(content)) !== null) {
42+
matches.push({
43+
fullMatch: match[0],
44+
propsString: match[1] ? match[1].trim() : '',
45+
props: parseProps(match[1] ? match[1].trim() : ''),
46+
children: match[2] || '', // undefined if self-closing
47+
index: match.index,
48+
isSelfClosing: !match[2] && match[2] !== ''
49+
})
50+
}
51+
52+
return matches
53+
}
54+
55+
/**
56+
* Replace component occurrences using the unified matcher
57+
*
58+
* @param {string} content - The content to process
59+
* @param {string} componentName - Name of the component
60+
* @param {function} replacer - Function that receives match object and returns replacement string
61+
* @returns {string} Processed content
62+
*/
63+
function replaceComponent(content, componentName, replacer) {
64+
const matches = matchComponent(content, componentName)
65+
66+
// Replace from end to start to maintain correct indices
67+
let result = content
68+
for (let i = matches.length - 1; i >= 0; i--) {
69+
const match = matches[i]
70+
const replacement = replacer(match)
71+
result = result.substring(0, match.index) + replacement + result.substring(match.index + match.fullMatch.length)
72+
}
73+
74+
return result
75+
}
76+
77+
/**
78+
* Removes <Screenshot /> components
79+
*/
80+
function convertScreenshot(content) {
81+
return replaceComponent(content, 'Screenshot', (_) => {
82+
return ""
3783
})
3884
}
3985

4086
/**
4187
* Converts <CodeBlock /> components to markdown code blocks
4288
*/
4389
function convertCodeBlock(content) {
44-
const codeBlockRegex = /<CodeBlock\s+([^>]*?)>([\s\S]*?)<\/CodeBlock>/g
45-
46-
return content.replace(codeBlockRegex, (match, propsString, children) => {
47-
const props = parseProps(propsString)
48-
const language = props.language || ""
49-
const trimmedCode = children.trim()
90+
return replaceComponent(content, 'CodeBlock', (match) => {
91+
const language = match.props.language || match.props.className?.replace('language-', '') || ""
92+
const trimmedCode = match.children.trim()
5093
return `\n\n\`\`\`${language}\n${trimmedCode}\n\`\`\`\n\n`
5194
})
5295
}
@@ -55,12 +98,14 @@ function convertCodeBlock(content) {
5598
* Converts <DocButton /> components to markdown links
5699
*/
57100
function convertDocButton(content) {
58-
const docButtonRegex = /<DocButton\s+([^>]*?)>([\s\S]*?)<\/DocButton>/g
59-
60-
return content.replace(docButtonRegex, (match, propsString, children) => {
61-
const props = parseProps(propsString)
62-
const href = props.href || props.to || "#"
63-
const text = children.trim()
101+
return replaceComponent(content, 'DocButton', (match) => {
102+
const href = match.props.href || match.props.to || "#"
103+
// Strip JSX tags like <>, </>, and other HTML/JSX elements from children
104+
const text = match.children
105+
.trim()
106+
.replace(/<\/?>/g, '') // Remove React fragments <> and </>
107+
.replace(/<[^>]+>/g, '') // Remove other HTML/JSX tags
108+
.trim()
64109
return `[${text}](${href})`
65110
})
66111
}
@@ -69,13 +114,11 @@ function convertDocButton(content) {
69114
* Converts <Tabs> and <TabItem> to markdown table
70115
*/
71116
function convertTabs(content) {
72-
const tabsRegex = /<Tabs\s+([\s\S]*?)>([\s\S]*?)<\/Tabs>/g
73-
74-
return content.replace(tabsRegex, (match, propsString, children) => {
117+
return replaceComponent(content, 'Tabs', (match) => {
75118
// Extract values array from props (can span multiple lines)
76-
const valuesMatch = propsString.match(/values=\{(\[[\s\S]*?\])\}/s)
119+
const valuesMatch = match.propsString.match(/values=\{(\[[\s\S]*?\])\}/s)
77120
if (!valuesMatch) {
78-
return match // Return original if can't parse
121+
return match.fullMatch // Return original if can't parse
79122
}
80123

81124
let valuesArray
@@ -88,15 +131,15 @@ function convertTabs(content) {
88131
valuesArray = JSON.parse(valuesStr)
89132
} catch (e) {
90133
console.warn("[convert-components] Failed to parse Tabs values:", e.message)
91-
return match
134+
return match.fullMatch
92135
}
93136

94137
// Extract TabItem contents
95138
const tabItems = []
96139
const tabItemRegex = /<TabItem\s+value=["']([^"']+)["'][^>]*?>([\s\S]*?)<\/TabItem>/g
97140
let tabMatch
98141

99-
while ((tabMatch = tabItemRegex.exec(children)) !== null) {
142+
while ((tabMatch = tabItemRegex.exec(match.children)) !== null) {
100143
const value = tabMatch[1]
101144
const content = tabMatch[2].trim()
102145
tabItems.push({ value, content })
@@ -134,16 +177,12 @@ function convertConfigTable(content, docsPath) {
134177
importMap.set(varName, unescapedPath)
135178
}
136179

137-
const configTableRegex = /<ConfigTable\s+([^>]*?)\/>/g
138-
139-
return content.replace(configTableRegex, (match, propsString) => {
140-
const props = parseProps(propsString)
141-
180+
return replaceComponent(content, 'ConfigTable', (match) => {
142181
// ConfigTable typically uses a rows prop pointing to JSON data
143-
if (props.rows) {
182+
if (match.props.rows) {
144183
try {
145184
// Get the import path for this variable
146-
const importPath = importMap.get(props.rows)
185+
const importPath = importMap.get(match.props.rows)
147186
if (importPath) {
148187
// Resolve the path relative to the markdown file's directory
149188
const configPath = path.join(docsPath, importPath)
@@ -222,22 +261,25 @@ async function fetchReleaseVersion() {
222261
}
223262

224263
function convertInterpolateReleaseData(content, releaseVersion) {
225-
// Match <InterpolateReleaseData ... /> (self-closing)
226-
// Extract what renderText returns and replace {release.name} with version
227-
const interpolateRegex = /<InterpolateReleaseData[\s\S]*?\/>/g
228-
229-
return content.replace(interpolateRegex, (match) => {
230-
// Extract everything inside renderText={(...) => ( ... )}
231-
// Match from the opening paren after => to the closing paren before )}
232-
const renderTextMatch = match.match(/renderText=\{[^(]*\([^)]*\)\s*=>\s*\(([\s\S]*?)\)\s*\}/);
264+
return replaceComponent(content, 'InterpolateReleaseData', (match) => {
265+
// Try two patterns:
266+
// 1. Arrow function with implicit return: (release) => (...)
267+
// 2. Arrow function with explicit return: (release) => { return (...) }
268+
let renderTextMatch = match.fullMatch.match(/renderText=\{[^(]*\([^)]*\)\s*=>\s*\(([\s\S]*?)\)\s*\}/);
269+
270+
if (!renderTextMatch) {
271+
// Try pattern with { return (...) }
272+
renderTextMatch = match.fullMatch.match(/renderText=\{[^(]*\([^)]*\)\s*=>\s*\{\s*return\s*\(([\s\S]*?)\)\s*\}\s*\}/);
273+
}
233274

234275
if (renderTextMatch) {
235276
// Extract the JSX content being returned
236277
let extracted = renderTextMatch[1].trim();
237278

238-
// Replace ${release.name} with the actual version (note the $ before the {)
239-
extracted = extracted.replace(/\$\{release\.name\}/g, releaseVersion);
240-
extracted = extracted.replace(/\$\{release\.tag_name\}/g, releaseVersion);
279+
// Replace template literal placeholders with actual version
280+
// Handles both ${release.name} (with $) and {release.name} (without $ inside template literals)
281+
extracted = extracted.replace(/\$?\{release\.name\}/g, releaseVersion);
282+
extracted = extracted.replace(/\$?\{release\.tag_name\}/g, releaseVersion);
241283

242284
// Remove JSX template literal syntax: {`...`} becomes just the content
243285
extracted = extracted.replace(/\{`/g, '');
@@ -307,13 +349,9 @@ function convertRailroadDiagrams(content, docsPath) {
307349
* @param {object} repoExamples - Repository examples data from remote-repo-example plugin
308350
*/
309351
function convertRemoteRepoExample(content, repoExamples = {}) {
310-
// Use [\s\S]*? to match across multiple lines
311-
const remoteRepoRegex = /<RemoteRepoExample\s+([\s\S]*?)\/>/g
312-
313-
return content.replace(remoteRepoRegex, (match, propsString) => {
314-
const props = parseProps(propsString)
315-
const name = props.name || 'unknown'
316-
const lang = props.lang || 'text'
352+
return replaceComponent(content, 'RemoteRepoExample', (match) => {
353+
const name = match.props.name || 'unknown'
354+
const lang = match.props.lang || 'text'
317355
const id = `${name}/${lang}`
318356

319357
// Get the example from plugin data
@@ -328,7 +366,7 @@ function convertRemoteRepoExample(content, repoExamples = {}) {
328366
let output = '\n\n'
329367

330368
// Add header if it exists and header prop is not false
331-
if (props.header !== 'false' && example.header) {
369+
if (match.props.header !== 'false' && example.header) {
332370
output += `${example.header}\n\n`
333371
}
334372

@@ -383,12 +421,8 @@ const clients = [
383421
},
384422
]
385423
function convertILPClientsTable(content) {
386-
// Use [\s\S]*? to match across multiple lines
387-
const ilpClientsRegex = /<ILPClientsTable\s+([\s\S]*?)\/>/g
388-
389-
return content.replace(ilpClientsRegex, (match, propsString) => {
390-
const props = parseProps(propsString)
391-
const language = props.language
424+
return replaceComponent(content, 'ILPClientsTable', (match) => {
425+
const language = match.props.language
392426

393427
// Filter by language if specified
394428
const filteredClients = language
@@ -573,23 +607,16 @@ function prependFrontmatter(content, frontmatter, includeTitle = true) {
573607
* @param {string} content - The processed markdown content
574608
* @returns {string} Cleaned markdown
575609
*/
576-
function cleanForLLM(content) {
610+
function normalizeNewLines(content) {
577611
return content
578612
.replace(/\n{3,}/g, '\n\n') // Normalize multiple newlines
579613
.trim()
580614
}
581615

582616
module.exports = {
583617
convertAllComponents,
584-
convertScreenshot,
585-
convertDocButton,
586-
convertCodeBlock,
587-
convertTabs,
588-
convertConfigTable,
589-
convertInterpolateReleaseData,
590-
fetchReleaseVersion,
591618
bumpHeadings,
592-
cleanForLLM,
619+
normalizeNewLines,
593620
removeImports,
594621
processPartialImports,
595622
prependFrontmatter,

plugins/raw-markdown/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const {
66
removeImports,
77
processPartialImports,
88
prependFrontmatter,
9-
cleanForLLM,
9+
normalizeNewLines,
1010
} = require("./convert-components")
1111

1212
module.exports = () => ({
@@ -73,7 +73,7 @@ module.exports = () => ({
7373
processedContent = removeImports(processedContent)
7474

7575
// Clean and normalize
76-
processedContent = cleanForLLM(processedContent) + "\n"
76+
processedContent = normalizeNewLines(processedContent) + "\n"
7777

7878
let urlPath
7979

scripts/generate-reference-full.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const yaml = require('js-yaml')
44
const {
55
convertAllComponents,
66
bumpHeadings,
7-
cleanForLLM,
7+
normalizeNewLines,
88
removeImports,
99
processPartialImports,
1010
prependFrontmatter,
@@ -88,7 +88,7 @@ async function extractFrontmatterAndContent(raw, filePath) {
8888
processedContent = removeImports(processedContent)
8989

9090
// Clean and normalize
91-
processedContent = cleanForLLM(processedContent)
91+
processedContent = normalizeNewLines(processedContent)
9292

9393
// Bump heading levels for proper hierarchy in combined document
9494
// H1 (title) becomes H2, H2 becomes H3, etc.

0 commit comments

Comments
 (0)