boxwood 2.16.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +14 -0
- package/package.json +1 -1
- package/ui/markdown/index.js +3 -3
- package/ui/prose/README.md +168 -16
- package/ui/prose/index.js +36 -5
- package/ui/prose/utilities/processConditionals.js +1 -1
- package/ui/prose/utilities/processLoops.js +70 -9
- package/ui/prose/utilities/protectCode.js +111 -0
- package/ui/prose/utilities/stripComments.js +19 -0
- package/ui/prose/utilities/validate.js +304 -0
- package/ui/{prose/utilities → utilities}/parseCustomTag.js +4 -3
- package/ui/utilities/replaceVariables.js +457 -0
- package/ui/markdown/utilities/parseCustomTag.js +0 -218
- package/ui/markdown/utilities/replaceVariables.js +0 -115
- package/ui/prose/utilities/brackets.js +0 -21
- package/ui/prose/utilities/convertNodes.js +0 -193
- package/ui/prose/utilities/format.js +0 -184
- package/ui/prose/utilities/params.js +0 -29
- package/ui/prose/utilities/parseBlock.js +0 -284
- package/ui/prose/utilities/replaceVariables.js +0 -120
- /package/ui/{markdown/utilities → utilities}/brackets.js +0 -0
- /package/ui/{markdown/utilities → utilities}/convertNodes.js +0 -0
- /package/ui/{markdown/utilities → utilities}/format.js +0 -0
- /package/ui/{markdown/utilities → utilities}/params.js +0 -0
- /package/ui/{markdown/utilities → utilities}/parseBlock.js +0 -0
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve a path like "images[0].src" or "user.name" from a data object
|
|
3
|
-
* @param {Object} data - The data object to resolve the path from
|
|
4
|
-
* @param {string} path - The path to resolve (e.g., "images[0].src", "user.name")
|
|
5
|
-
* @returns {*} - The resolved value or undefined
|
|
6
|
-
*/
|
|
7
|
-
function resolvePath(data, path) {
|
|
8
|
-
// Handle simple variable names (backwards compatibility)
|
|
9
|
-
if (!/[.\[]/.test(path)) {
|
|
10
|
-
return data[path]
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Split the path into parts, handling both dot notation and bracket notation
|
|
14
|
-
// e.g., "images[0].src" -> ["images", "0", "src"]
|
|
15
|
-
const parts = path
|
|
16
|
-
.replace(/\[(\d+)\]/g, ".$1") // Convert [0] to .0
|
|
17
|
-
.split(".")
|
|
18
|
-
.filter(Boolean)
|
|
19
|
-
|
|
20
|
-
let current = data
|
|
21
|
-
for (const part of parts) {
|
|
22
|
-
if (current === null || current === undefined) {
|
|
23
|
-
return undefined
|
|
24
|
-
}
|
|
25
|
-
current = current[part]
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return current
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Replace {variableName} placeholders in text with actual values from data
|
|
33
|
-
* Supports:
|
|
34
|
-
* - Simple variables: {name}
|
|
35
|
-
* - Array indexing: {images[0]}
|
|
36
|
-
* - Property access: {user.name}
|
|
37
|
-
* - Combined: {images[0].src}
|
|
38
|
-
* @param {string} text - Text containing variable placeholders
|
|
39
|
-
* @param {Object} data - Data object with variable values
|
|
40
|
-
* @returns {string|Array} - Text with variables replaced, or array if mixed content
|
|
41
|
-
*/
|
|
42
|
-
function replaceVariables(text, data) {
|
|
43
|
-
if (!text || typeof text !== "string") {
|
|
44
|
-
return text
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!data || typeof data !== "object") {
|
|
48
|
-
return text
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Check if text contains any variables
|
|
52
|
-
if (!text.includes("{") || !text.includes("}")) {
|
|
53
|
-
return text
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const result = []
|
|
57
|
-
let i = 0
|
|
58
|
-
let lastIndex = 0
|
|
59
|
-
|
|
60
|
-
while (i < text.length) {
|
|
61
|
-
if (text[i] === "\\" && text[i + 1] === "{") {
|
|
62
|
-
// Escaped opening brace
|
|
63
|
-
result.push(text.substring(lastIndex, i))
|
|
64
|
-
result.push("{")
|
|
65
|
-
i += 2
|
|
66
|
-
lastIndex = i
|
|
67
|
-
continue
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (text[i] === "{") {
|
|
71
|
-
const closeIndex = text.indexOf("}", i + 1)
|
|
72
|
-
|
|
73
|
-
if (closeIndex !== -1) {
|
|
74
|
-
// Found a variable placeholder
|
|
75
|
-
const variablePath = text.substring(i + 1, closeIndex).trim()
|
|
76
|
-
|
|
77
|
-
if (variablePath) {
|
|
78
|
-
// Add text before the variable
|
|
79
|
-
if (i > lastIndex) {
|
|
80
|
-
result.push(text.substring(lastIndex, i))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Resolve the variable value (supports paths like "images[0].src")
|
|
84
|
-
const value = resolvePath(data, variablePath)
|
|
85
|
-
if (value !== undefined && value !== null) {
|
|
86
|
-
result.push(String(value))
|
|
87
|
-
} else {
|
|
88
|
-
// Variable not found, keep the placeholder
|
|
89
|
-
result.push(text.substring(i, closeIndex + 1))
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
i = closeIndex + 1
|
|
93
|
-
lastIndex = i
|
|
94
|
-
continue
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
i++
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Add remaining text
|
|
103
|
-
if (lastIndex < text.length) {
|
|
104
|
-
result.push(text.substring(lastIndex))
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// If no substitutions were made, return original text
|
|
108
|
-
if (result.length === 0) {
|
|
109
|
-
return text
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return result.join("")
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = { replaceVariables }
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// Find the matching closing bracket, accounting for nested brackets
|
|
2
|
-
function findMatchingBracket(text, startPos) {
|
|
3
|
-
let depth = 1
|
|
4
|
-
let i = startPos + 1
|
|
5
|
-
|
|
6
|
-
while (i < text.length && depth > 0) {
|
|
7
|
-
if (text[i] === "[" && text[i - 1] !== "\\") {
|
|
8
|
-
depth++
|
|
9
|
-
} else if (text[i] === "]" && text[i - 1] !== "\\") {
|
|
10
|
-
depth--
|
|
11
|
-
if (depth === 0) {
|
|
12
|
-
return i
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
i++
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return -1
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
module.exports = { findMatchingBracket }
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
const { resolveAttributes } = require("./parseCustomTag")
|
|
2
|
-
const { replaceVariables } = require("./replaceVariables")
|
|
3
|
-
const { format } = require("./format")
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Recursively parses nested lists with proper indentation handling
|
|
7
|
-
*/
|
|
8
|
-
function parseList(
|
|
9
|
-
items,
|
|
10
|
-
startIndex,
|
|
11
|
-
parentIndent,
|
|
12
|
-
htmlParams,
|
|
13
|
-
data,
|
|
14
|
-
allComponents,
|
|
15
|
-
) {
|
|
16
|
-
const list = []
|
|
17
|
-
let currentIndex = startIndex
|
|
18
|
-
const parentListType = items[startIndex].list
|
|
19
|
-
|
|
20
|
-
while (currentIndex < items.length) {
|
|
21
|
-
const item = items[currentIndex]
|
|
22
|
-
|
|
23
|
-
// Stop conditions
|
|
24
|
-
if (item.type !== "li" || item.indent < parentIndent) {
|
|
25
|
-
break
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (item.indent === parentIndent && item.list !== parentListType) {
|
|
29
|
-
break
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (item.indent > parentIndent) {
|
|
33
|
-
break
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Process current list item
|
|
37
|
-
const processedContent = replaceVariables(item.content, data)
|
|
38
|
-
const content = [format(processedContent, allComponents)]
|
|
39
|
-
currentIndex++
|
|
40
|
-
|
|
41
|
-
// Check for nested lists
|
|
42
|
-
const nextItem = items[currentIndex]
|
|
43
|
-
if (nextItem?.type === "li" && nextItem.indent > item.indent) {
|
|
44
|
-
const nestedResult = parseList(
|
|
45
|
-
items,
|
|
46
|
-
currentIndex,
|
|
47
|
-
nextItem.indent,
|
|
48
|
-
htmlParams,
|
|
49
|
-
data,
|
|
50
|
-
allComponents,
|
|
51
|
-
)
|
|
52
|
-
content.push(nestedResult.list)
|
|
53
|
-
currentIndex = nestedResult.nextIndex
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
list.push(allComponents.li(htmlParams, content))
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const listElement =
|
|
60
|
-
parentListType === "ul"
|
|
61
|
-
? allComponents.ul(htmlParams, list)
|
|
62
|
-
: allComponents.ol(htmlParams, list)
|
|
63
|
-
|
|
64
|
-
return { list: listElement, nextIndex: currentIndex }
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Processes a custom component item into a node
|
|
69
|
-
*/
|
|
70
|
-
function processCustomComponentItem(item, params, data, markdownRenderer) {
|
|
71
|
-
const resolvedAttrs = resolveAttributes(item.attributes, data)
|
|
72
|
-
|
|
73
|
-
if (item.selfClosing) {
|
|
74
|
-
return item.component(resolvedAttrs)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (item.singleLine) {
|
|
78
|
-
return item.component(resolvedAttrs, item.content)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Multi-line component with children - recursively process markdown
|
|
82
|
-
const childContent = item.content
|
|
83
|
-
? markdownRenderer(params, item.content)
|
|
84
|
-
: []
|
|
85
|
-
return item.component(resolvedAttrs, childContent)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Processes blockquote items into a single blockquote node
|
|
90
|
-
*/
|
|
91
|
-
function processBlockquotes(
|
|
92
|
-
items,
|
|
93
|
-
startIndex,
|
|
94
|
-
htmlParams,
|
|
95
|
-
data,
|
|
96
|
-
allComponents,
|
|
97
|
-
) {
|
|
98
|
-
const lines = []
|
|
99
|
-
let i = startIndex
|
|
100
|
-
|
|
101
|
-
while (i < items.length && items[i].type === "blockquote") {
|
|
102
|
-
lines.push(items[i].content)
|
|
103
|
-
i++
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const content = replaceVariables(lines.join("\n"), data)
|
|
107
|
-
const blockquote = allComponents.blockquote(
|
|
108
|
-
htmlParams,
|
|
109
|
-
allComponents.p(htmlParams, format(content, allComponents)),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
return { node: blockquote, nextIndex: i }
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Converts parsed items into final node tree
|
|
117
|
-
*/
|
|
118
|
-
function convertItemsToNodes(
|
|
119
|
-
items,
|
|
120
|
-
params,
|
|
121
|
-
htmlParams,
|
|
122
|
-
data,
|
|
123
|
-
allComponents,
|
|
124
|
-
markdownRenderer,
|
|
125
|
-
) {
|
|
126
|
-
const result = []
|
|
127
|
-
let i = 0
|
|
128
|
-
|
|
129
|
-
while (i < items.length) {
|
|
130
|
-
const item = items[i]
|
|
131
|
-
|
|
132
|
-
if (item.type === "custom-component") {
|
|
133
|
-
result.push(
|
|
134
|
-
processCustomComponentItem(item, params, data, markdownRenderer),
|
|
135
|
-
)
|
|
136
|
-
i++
|
|
137
|
-
} else if (item.type === "li") {
|
|
138
|
-
const listResult = parseList(
|
|
139
|
-
items,
|
|
140
|
-
i,
|
|
141
|
-
item.indent,
|
|
142
|
-
htmlParams,
|
|
143
|
-
data,
|
|
144
|
-
allComponents,
|
|
145
|
-
)
|
|
146
|
-
result.push(listResult.list)
|
|
147
|
-
i = listResult.nextIndex
|
|
148
|
-
} else if (item.type === "blockquote") {
|
|
149
|
-
const { node, nextIndex } = processBlockquotes(
|
|
150
|
-
items,
|
|
151
|
-
i,
|
|
152
|
-
htmlParams,
|
|
153
|
-
data,
|
|
154
|
-
allComponents,
|
|
155
|
-
)
|
|
156
|
-
result.push(node)
|
|
157
|
-
i = nextIndex
|
|
158
|
-
} else if (item.type === "hr") {
|
|
159
|
-
result.push(allComponents.hr(htmlParams))
|
|
160
|
-
i++
|
|
161
|
-
} else if (item.type === "pre") {
|
|
162
|
-
// Code blocks - wrap in <pre><code>
|
|
163
|
-
const codeParams = item.language
|
|
164
|
-
? { class: `language-${item.language}` }
|
|
165
|
-
: {}
|
|
166
|
-
result.push(
|
|
167
|
-
allComponents.pre(
|
|
168
|
-
htmlParams,
|
|
169
|
-
allComponents.code(codeParams, item.content),
|
|
170
|
-
),
|
|
171
|
-
)
|
|
172
|
-
i++
|
|
173
|
-
} else {
|
|
174
|
-
// Regular block elements (h1-h6, p, etc.)
|
|
175
|
-
const { type, content } = item
|
|
176
|
-
const Component = allComponents[type] || allComponents.p
|
|
177
|
-
const processedContent = replaceVariables(content, data)
|
|
178
|
-
result.push(
|
|
179
|
-
Component(htmlParams, format(processedContent, allComponents)),
|
|
180
|
-
)
|
|
181
|
-
i++
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return result
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
module.exports = {
|
|
189
|
-
parseList,
|
|
190
|
-
processCustomComponentItem,
|
|
191
|
-
processBlockquotes,
|
|
192
|
-
convertItemsToNodes,
|
|
193
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
const { findMatchingBracket } = require("./brackets")
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Format inline markdown elements within text
|
|
5
|
-
* Handles: images, links, code, bold, italic
|
|
6
|
-
* @param {string} text - The text to format
|
|
7
|
-
* @param {Object} allComponents - HTML component functions (contains a, img, code, strong, em, etc.)
|
|
8
|
-
* @returns {Array|string} - Formatted content as array of components and strings
|
|
9
|
-
*/
|
|
10
|
-
function format(text, allComponents) {
|
|
11
|
-
const { a: A, img: Img, code: Code, strong: Strong, em: Em } = allComponents
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
!text.includes("*") &&
|
|
15
|
-
!text.includes("`") &&
|
|
16
|
-
!text.includes("[") &&
|
|
17
|
-
!text.includes("!") &&
|
|
18
|
-
!text.includes("<") &&
|
|
19
|
-
!text.includes("\\")
|
|
20
|
-
) {
|
|
21
|
-
return text
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const result = []
|
|
25
|
-
let i = 0
|
|
26
|
-
|
|
27
|
-
while (i < text.length) {
|
|
28
|
-
// Handle escape characters
|
|
29
|
-
if (text[i] === "\\") {
|
|
30
|
-
if (i + 1 < text.length) {
|
|
31
|
-
const nextChar = text[i + 1]
|
|
32
|
-
// Check if next char is a special markdown character
|
|
33
|
-
if (
|
|
34
|
-
nextChar === "*" ||
|
|
35
|
-
nextChar === "`" ||
|
|
36
|
-
nextChar === "[" ||
|
|
37
|
-
nextChar === "]" ||
|
|
38
|
-
nextChar === "(" ||
|
|
39
|
-
nextChar === ")" ||
|
|
40
|
-
nextChar === "!" ||
|
|
41
|
-
nextChar === "<" ||
|
|
42
|
-
nextChar === ">" ||
|
|
43
|
-
nextChar === "\\"
|
|
44
|
-
) {
|
|
45
|
-
// Escape the next character - just add it as literal text
|
|
46
|
-
result.push(nextChar)
|
|
47
|
-
i += 2 // Skip both \ and the escaped character
|
|
48
|
-
continue
|
|
49
|
-
}
|
|
50
|
-
// If not a special char, keep both the backslash and the next character
|
|
51
|
-
result.push(text[i])
|
|
52
|
-
result.push(nextChar)
|
|
53
|
-
i += 2
|
|
54
|
-
continue
|
|
55
|
-
} else {
|
|
56
|
-
// Backslash at end of string
|
|
57
|
-
result.push(text[i])
|
|
58
|
-
i++
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (text[i] === "!" && text[i + 1] === "[") {
|
|
64
|
-
// Try to parse markdown image 
|
|
65
|
-
const altEnd = text.indexOf("]", i + 2)
|
|
66
|
-
|
|
67
|
-
if (altEnd !== -1 && text[altEnd + 1] === "(") {
|
|
68
|
-
const urlEnd = text.indexOf(")", altEnd + 2)
|
|
69
|
-
|
|
70
|
-
if (urlEnd !== -1) {
|
|
71
|
-
const alt = text.substring(i + 2, altEnd)
|
|
72
|
-
const src = text.substring(altEnd + 2, urlEnd)
|
|
73
|
-
result.push(Img({ src, alt }))
|
|
74
|
-
i = urlEnd + 1
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Not a valid image, treat as regular text (skip both ! and [)
|
|
80
|
-
result.push(text[i])
|
|
81
|
-
i++
|
|
82
|
-
} else if (text[i] === "[") {
|
|
83
|
-
// Try to parse markdown link [text](url)
|
|
84
|
-
const textEnd = findMatchingBracket(text, i)
|
|
85
|
-
|
|
86
|
-
if (textEnd !== -1 && text[textEnd + 1] === "(") {
|
|
87
|
-
const urlEnd = text.indexOf(")", textEnd + 2)
|
|
88
|
-
|
|
89
|
-
if (urlEnd !== -1) {
|
|
90
|
-
const linkText = text.substring(i + 1, textEnd)
|
|
91
|
-
const url = text.substring(textEnd + 2, urlEnd)
|
|
92
|
-
// Recursively format the link text to support images, bold, italic inside links
|
|
93
|
-
result.push(A({ href: url }, format(linkText, allComponents)))
|
|
94
|
-
i = urlEnd + 1
|
|
95
|
-
continue
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Not a valid link, treat as regular text
|
|
100
|
-
result.push(text[i])
|
|
101
|
-
i++
|
|
102
|
-
} else if (text[i] === "<") {
|
|
103
|
-
// Try to parse autolink <url> or <email>
|
|
104
|
-
const end = text.indexOf(">", i + 1)
|
|
105
|
-
|
|
106
|
-
if (end !== -1) {
|
|
107
|
-
const content = text.substring(i + 1, end)
|
|
108
|
-
|
|
109
|
-
// Check if it's a URL (starts with http:// or https://)
|
|
110
|
-
if (content.startsWith("http://") || content.startsWith("https://")) {
|
|
111
|
-
result.push(A({ href: content }, content))
|
|
112
|
-
i = end + 1
|
|
113
|
-
continue
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Check if it's an email (contains @ and looks like email)
|
|
117
|
-
if (
|
|
118
|
-
content.includes("@") &&
|
|
119
|
-
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(content)
|
|
120
|
-
) {
|
|
121
|
-
result.push(A({ href: `mailto:${content}` }, content))
|
|
122
|
-
i = end + 1
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Not a valid autolink, treat as regular text
|
|
128
|
-
result.push(text[i])
|
|
129
|
-
i++
|
|
130
|
-
} else if (text[i] === "`") {
|
|
131
|
-
const end = text.indexOf("`", i + 1)
|
|
132
|
-
if (end === -1) {
|
|
133
|
-
result.push(text[i])
|
|
134
|
-
i++
|
|
135
|
-
} else {
|
|
136
|
-
result.push(Code({}, text.substring(i + 1, end)))
|
|
137
|
-
i = end + 1
|
|
138
|
-
}
|
|
139
|
-
} else if (text[i] === "*" && text[i + 1] === "*") {
|
|
140
|
-
const end = text.indexOf("**", i + 2)
|
|
141
|
-
if (end === -1) {
|
|
142
|
-
result.push(text[i])
|
|
143
|
-
i++
|
|
144
|
-
} else {
|
|
145
|
-
result.push(Strong(format(text.substring(i + 2, end), allComponents)))
|
|
146
|
-
i = end + 2
|
|
147
|
-
}
|
|
148
|
-
} else if (text[i] === "*") {
|
|
149
|
-
const end = text.indexOf("*", i + 1)
|
|
150
|
-
if (end === -1) {
|
|
151
|
-
result.push(text[i])
|
|
152
|
-
i++
|
|
153
|
-
} else {
|
|
154
|
-
result.push(Em(format(text.substring(i + 1, end), allComponents)))
|
|
155
|
-
i = end + 1
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
// Find next special character
|
|
159
|
-
const positions = [
|
|
160
|
-
text.indexOf("`", i),
|
|
161
|
-
text.indexOf("*", i),
|
|
162
|
-
text.indexOf("[", i),
|
|
163
|
-
text.indexOf("<", i),
|
|
164
|
-
text.indexOf("\\", i),
|
|
165
|
-
].filter((pos) => pos !== -1)
|
|
166
|
-
|
|
167
|
-
// Look for image pattern ![
|
|
168
|
-
const exclamPos = text.indexOf("!", i)
|
|
169
|
-
if (exclamPos !== -1 && text[exclamPos + 1] === "[") {
|
|
170
|
-
positions.push(exclamPos)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const next = positions.length > 0 ? Math.min(...positions) : text.length
|
|
174
|
-
|
|
175
|
-
result.push(text.substring(i, next))
|
|
176
|
-
if (next === text.length) break
|
|
177
|
-
i = next
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return result.length > 0 ? result : text
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
module.exports = { format }
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extracts HTML parameters from params object by filtering out special keys
|
|
3
|
-
*/
|
|
4
|
-
function extractHtmlParams(params) {
|
|
5
|
-
if (!params) return {}
|
|
6
|
-
|
|
7
|
-
return Object.keys(params).reduce((acc, key) => {
|
|
8
|
-
if (key !== "components" && key !== "data") {
|
|
9
|
-
acc[key] = params[key]
|
|
10
|
-
}
|
|
11
|
-
return acc
|
|
12
|
-
}, {})
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Merges builtin HTML tags with custom components
|
|
17
|
-
* Custom components can override builtin tags if needed
|
|
18
|
-
*/
|
|
19
|
-
function mergeComponents(builtinHtmlTags, customComponents) {
|
|
20
|
-
return {
|
|
21
|
-
...builtinHtmlTags,
|
|
22
|
-
...customComponents,
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
module.exports = {
|
|
27
|
-
extractHtmlParams,
|
|
28
|
-
mergeComponents,
|
|
29
|
-
}
|