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.
@@ -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 = options && options._locals && options._locals.nonce
36
- ? { ...options, nonce: options._locals.nonce }
37
- : options
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
- if (callback) return callback(error)
43
- return error.message
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.7.0",
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.8",
53
- "jsdom": "^28.1.0",
52
+ "handlebars": "^4.7.9",
53
+ "jsdom": "^29.1.1",
54
54
  "mustache": "^4.2.0",
55
55
  "underscore": "^1.13.8"
56
56
  },
@@ -1,42 +1,181 @@
1
- const { component, H1, H2, H3, H4, H5, H6, P, Blockquote } = require("../..")
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 (typeof children === "string") {
5
- const lines = children
6
- .trim()
7
- .split("\n")
8
- .map((line) => line.trim())
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)