boxwood 2.7.0 → 2.9.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/adapters/express/index.js +13 -5
- package/package.json +3 -3
- package/ui/markdown/index.js +174 -35
|
@@ -32,15 +32,23 @@ function engine(options = {}) {
|
|
|
32
32
|
try {
|
|
33
33
|
const template = await compileFile(path)
|
|
34
34
|
// Automatically inject nonce from res.locals if available
|
|
35
|
-
const templateOptions =
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const templateOptions =
|
|
36
|
+
options && options._locals && options._locals.nonce
|
|
37
|
+
? { ...options, nonce: options._locals.nonce }
|
|
38
|
+
: options
|
|
38
39
|
const html = template(templateOptions)
|
|
39
40
|
if (callback) return callback(null, html)
|
|
40
41
|
return html
|
|
41
42
|
} catch (error) {
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
// Enhance error message with file path for better debugging
|
|
44
|
+
const exception = new Error(
|
|
45
|
+
`Error rendering view "${path}": ${error.message}`,
|
|
46
|
+
)
|
|
47
|
+
exception.stack = error.stack
|
|
48
|
+
exception.originalError = error
|
|
49
|
+
exception.viewPath = path
|
|
50
|
+
if (callback) return callback(exception)
|
|
51
|
+
return exception.message
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
return render
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "boxwood",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Compile HTML templates into JS",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"benchmark": "2.1.4",
|
|
50
50
|
"c8": "^11.0.0",
|
|
51
51
|
"express": "^5.2.1",
|
|
52
|
-
"handlebars": "^4.7.
|
|
53
|
-
"jsdom": "^
|
|
52
|
+
"handlebars": "^4.7.9",
|
|
53
|
+
"jsdom": "^29.1.1",
|
|
54
54
|
"mustache": "^4.2.0",
|
|
55
55
|
"underscore": "^1.13.8"
|
|
56
56
|
},
|
package/ui/markdown/index.js
CHANGED
|
@@ -1,42 +1,181 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
component,
|
|
3
|
+
H1,
|
|
4
|
+
H2,
|
|
5
|
+
H3,
|
|
6
|
+
H4,
|
|
7
|
+
H5,
|
|
8
|
+
H6,
|
|
9
|
+
P,
|
|
10
|
+
Blockquote,
|
|
11
|
+
Ul,
|
|
12
|
+
Ol,
|
|
13
|
+
Li,
|
|
14
|
+
Strong,
|
|
15
|
+
Em,
|
|
16
|
+
Code,
|
|
17
|
+
} = require("../..")
|
|
18
|
+
|
|
19
|
+
const ORDERED_LIST_REGEXP = /^\d+\.\s/
|
|
20
|
+
const UNORDERED_MARKERS = ["- ", "— ", "– ", "• "]
|
|
21
|
+
const HEADINGS = [
|
|
22
|
+
{ prefix: "###### ", type: "h6" },
|
|
23
|
+
{ prefix: "##### ", type: "h5" },
|
|
24
|
+
{ prefix: "#### ", type: "h4" },
|
|
25
|
+
{ prefix: "### ", type: "h3" },
|
|
26
|
+
{ prefix: "## ", type: "h2" },
|
|
27
|
+
{ prefix: "# ", type: "h1" },
|
|
28
|
+
]
|
|
29
|
+
const COMPONENTS = {
|
|
30
|
+
h1: H1,
|
|
31
|
+
h2: H2,
|
|
32
|
+
h3: H3,
|
|
33
|
+
h4: H4,
|
|
34
|
+
h5: H5,
|
|
35
|
+
h6: H6,
|
|
36
|
+
blockquote: Blockquote,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function format(text) {
|
|
40
|
+
if (!text.includes("*") && !text.includes("`")) {
|
|
41
|
+
return text
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const result = []
|
|
45
|
+
let i = 0
|
|
46
|
+
while (i < text.length) {
|
|
47
|
+
if (text[i] === "`") {
|
|
48
|
+
const start = i + 1
|
|
49
|
+
const end = text.indexOf("`", start)
|
|
50
|
+
if (end === -1) {
|
|
51
|
+
result.push(text[i])
|
|
52
|
+
i++
|
|
53
|
+
} else {
|
|
54
|
+
result.push(Code({}, text.substring(start, end)))
|
|
55
|
+
i = end + 1
|
|
56
|
+
}
|
|
57
|
+
} else if (text[i] === "*" && text[i + 1] === "*") {
|
|
58
|
+
const start = i + 2
|
|
59
|
+
const end = text.indexOf("**", start)
|
|
60
|
+
if (end === -1) {
|
|
61
|
+
result.push(text[i])
|
|
62
|
+
i++
|
|
63
|
+
} else {
|
|
64
|
+
result.push(Strong(text.substring(start, end)))
|
|
65
|
+
i = end + 2
|
|
66
|
+
}
|
|
67
|
+
} else if (text[i] === "*") {
|
|
68
|
+
const start = i + 1
|
|
69
|
+
const end = text.indexOf("*", start)
|
|
70
|
+
if (end === -1) {
|
|
71
|
+
result.push(text[i])
|
|
72
|
+
i++
|
|
73
|
+
} else {
|
|
74
|
+
result.push(Em(text.substring(start, end)))
|
|
75
|
+
i = end + 1
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
let next = text.length
|
|
79
|
+
const nextCode = text.indexOf("`", i)
|
|
80
|
+
const nextStar = text.indexOf("*", i)
|
|
81
|
+
if (nextCode !== -1 && nextCode < next) next = nextCode
|
|
82
|
+
if (nextStar !== -1 && nextStar < next) next = nextStar
|
|
83
|
+
if (next === text.length) {
|
|
84
|
+
result.push(text.substring(i))
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
result.push(text.substring(i, next))
|
|
88
|
+
i = next
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return result.length > 0 ? result : text
|
|
92
|
+
}
|
|
2
93
|
|
|
3
94
|
function Markdown(params, children) {
|
|
4
|
-
if (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
.filter(Boolean)
|
|
10
|
-
|
|
11
|
-
return lines.map((line) => {
|
|
12
|
-
if (line.startsWith("# ")) {
|
|
13
|
-
const text = line.substring(2)
|
|
14
|
-
return H1(params, text)
|
|
15
|
-
} else if (line.startsWith("## ")) {
|
|
16
|
-
const text = line.substring(3)
|
|
17
|
-
return H2(params, text)
|
|
18
|
-
} else if (line.startsWith("### ")) {
|
|
19
|
-
const text = line.substring(4)
|
|
20
|
-
return H3(params, text)
|
|
21
|
-
} else if (line.startsWith("#### ")) {
|
|
22
|
-
const text = line.substring(5)
|
|
23
|
-
return H4(params, text)
|
|
24
|
-
} else if (line.startsWith("##### ")) {
|
|
25
|
-
const text = line.substring(6)
|
|
26
|
-
return H5(params, text)
|
|
27
|
-
} else if (line.startsWith("###### ")) {
|
|
28
|
-
const text = line.substring(7)
|
|
29
|
-
return H6(params, text)
|
|
30
|
-
} else if (line.startsWith("> ")) {
|
|
31
|
-
const text = line.substring(2)
|
|
32
|
-
return Blockquote(params, text)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return P(params, line)
|
|
36
|
-
})
|
|
37
|
-
} else {
|
|
95
|
+
if (Array.isArray(children)) {
|
|
96
|
+
return children.map((child) => Markdown(params, child))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof children !== "string") {
|
|
38
100
|
return null
|
|
39
101
|
}
|
|
102
|
+
|
|
103
|
+
const lines = children
|
|
104
|
+
.trim()
|
|
105
|
+
.split("\n")
|
|
106
|
+
.map((line) => line.trim())
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
|
|
109
|
+
const items = lines
|
|
110
|
+
.map((line) => {
|
|
111
|
+
if (UNORDERED_MARKERS.some((marker) => line.startsWith(marker))) {
|
|
112
|
+
const content = line.substring(2)
|
|
113
|
+
if (!content) return null
|
|
114
|
+
return { type: "li", list: "ul", content }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (ORDERED_LIST_REGEXP.test(line)) {
|
|
118
|
+
const content = line.replace(ORDERED_LIST_REGEXP, "")
|
|
119
|
+
if (!content) return null
|
|
120
|
+
return { type: "li", list: "ol", content }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const { prefix, type } of HEADINGS) {
|
|
124
|
+
if (line.startsWith(prefix)) {
|
|
125
|
+
return { type, content: line.substring(prefix.length) }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (line.startsWith("> ")) {
|
|
130
|
+
return { type: "blockquote", content: line.substring(2) }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { type: "p", content: line }
|
|
134
|
+
})
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
|
|
137
|
+
const nodes = []
|
|
138
|
+
let i = 0
|
|
139
|
+
|
|
140
|
+
while (i < items.length) {
|
|
141
|
+
const item = items[i]
|
|
142
|
+
|
|
143
|
+
if (item.type === "li") {
|
|
144
|
+
const list = []
|
|
145
|
+
const parent = item.list
|
|
146
|
+
|
|
147
|
+
while (
|
|
148
|
+
i < items.length &&
|
|
149
|
+
items[i].type === "li" &&
|
|
150
|
+
items[i].list === parent
|
|
151
|
+
) {
|
|
152
|
+
list.push(Li(params, format(items[i].content)))
|
|
153
|
+
i++
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (parent === "ul") {
|
|
157
|
+
nodes.push(Ul(params, list))
|
|
158
|
+
} else if (parent === "ol") {
|
|
159
|
+
nodes.push(Ol(params, list))
|
|
160
|
+
}
|
|
161
|
+
} else if (item.type === "blockquote") {
|
|
162
|
+
const lines = []
|
|
163
|
+
|
|
164
|
+
while (i < items.length && items[i].type === "blockquote") {
|
|
165
|
+
lines.push(items[i].content)
|
|
166
|
+
i++
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
nodes.push(Blockquote(params, P(params, format(lines.join("\n")))))
|
|
170
|
+
} else {
|
|
171
|
+
const { type, content } = item
|
|
172
|
+
const Component = COMPONENTS[type] || P
|
|
173
|
+
nodes.push(Component(params, format(content)))
|
|
174
|
+
i++
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return nodes
|
|
40
179
|
}
|
|
41
180
|
|
|
42
181
|
module.exports = component(Markdown)
|