@protomarkdown/parser 1.0.1 → 1.0.3
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/README.md +9 -0
- package/dist/HtmlGenerator.d.ts +35 -0
- package/dist/HtmlGenerator.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +329 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +329 -0
- package/dist/index.js.map +1 -1
- package/dist/parser/MarkdownParser.d.ts +1 -0
- package/dist/parser/MarkdownParser.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -153,6 +153,13 @@ class MarkdownParser {
|
|
|
153
153
|
break;
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
// Check for table (line with pipes starting with |)
|
|
157
|
+
if (screenLine.includes("|") && screenLine.trim().startsWith("|")) {
|
|
158
|
+
const result = this.parseTable(lines, i, "]");
|
|
159
|
+
screenChildren.push(result.node);
|
|
160
|
+
i = result.nextIndex;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
156
163
|
// Check for nested card opening
|
|
157
164
|
if (screenLine.match(/^\[--\s*(.*)$/)) {
|
|
158
165
|
const nestedTitle = screenLine.match(/^\[--\s*(.*)$/)?.[1] || undefined;
|
|
@@ -194,6 +201,48 @@ class MarkdownParser {
|
|
|
194
201
|
nextIndex: i + 1,
|
|
195
202
|
};
|
|
196
203
|
}
|
|
204
|
+
parseTable(lines, startIndex, closingDelimiter) {
|
|
205
|
+
const line = this.options.preserveWhitespace ? lines[startIndex] : lines[startIndex].trim();
|
|
206
|
+
const headers = line
|
|
207
|
+
.split("|")
|
|
208
|
+
.map((h) => h.trim())
|
|
209
|
+
.filter((h) => h.length > 0);
|
|
210
|
+
let i = startIndex + 1;
|
|
211
|
+
// Skip separator line (|---|---|)
|
|
212
|
+
if (i < lines.length) {
|
|
213
|
+
const separatorLine = this.options.preserveWhitespace
|
|
214
|
+
? lines[i]
|
|
215
|
+
: lines[i].trim();
|
|
216
|
+
if (separatorLine.includes("-") && separatorLine.includes("|")) {
|
|
217
|
+
i++;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Parse table rows
|
|
221
|
+
const rows = [];
|
|
222
|
+
while (i < lines.length) {
|
|
223
|
+
const rowLine = this.options.preserveWhitespace
|
|
224
|
+
? lines[i]
|
|
225
|
+
: lines[i].trim();
|
|
226
|
+
// Stop at closing delimiter or empty line or non-table line
|
|
227
|
+
if (!rowLine || !rowLine.includes("|") || (closingDelimiter && rowLine === closingDelimiter)) {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
const cells = rowLine
|
|
231
|
+
.split("|")
|
|
232
|
+
.map((c) => c.trim())
|
|
233
|
+
.filter((c) => c.length > 0);
|
|
234
|
+
rows.push(cells);
|
|
235
|
+
i++;
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
node: {
|
|
239
|
+
type: "table",
|
|
240
|
+
headers,
|
|
241
|
+
rows,
|
|
242
|
+
},
|
|
243
|
+
nextIndex: i,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
197
246
|
parseCard(lines, startIndex, title) {
|
|
198
247
|
const cardChildren = [];
|
|
199
248
|
let i = startIndex + 1;
|
|
@@ -208,6 +257,13 @@ class MarkdownParser {
|
|
|
208
257
|
break;
|
|
209
258
|
}
|
|
210
259
|
}
|
|
260
|
+
// Check for table (line with pipes starting with |)
|
|
261
|
+
if (cardLine.includes("|") && cardLine.trim().startsWith("|")) {
|
|
262
|
+
const result = this.parseTable(lines, i, "--]");
|
|
263
|
+
cardChildren.push(result.node);
|
|
264
|
+
i = result.nextIndex;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
211
267
|
// Check for nested card opening (must be before div check)
|
|
212
268
|
if (cardLine.match(/^\[--\s*(.*)$/)) {
|
|
213
269
|
const nestedTitle = cardLine.match(/^\[--\s*(.*)$/)?.[1] || undefined;
|
|
@@ -263,6 +319,13 @@ class MarkdownParser {
|
|
|
263
319
|
break;
|
|
264
320
|
}
|
|
265
321
|
}
|
|
322
|
+
// Check for table (line with pipes starting with |)
|
|
323
|
+
if (containerLine.includes("|") && containerLine.trim().startsWith("|")) {
|
|
324
|
+
const result = this.parseTable(lines, i, "]");
|
|
325
|
+
containerChildren.push(result.node);
|
|
326
|
+
i = result.nextIndex;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
266
329
|
// Check for nested card opening (must be before div check)
|
|
267
330
|
if (containerLine.match(/^\[--\s*(.*)$/)) {
|
|
268
331
|
const nestedTitle = containerLine.match(/^\[--\s*(.*)$/)?.[1] || undefined;
|
|
@@ -1005,6 +1068,272 @@ ${this.indent()}</div>`;
|
|
|
1005
1068
|
}
|
|
1006
1069
|
}
|
|
1007
1070
|
|
|
1071
|
+
/**
|
|
1072
|
+
* Generates HTML from a Proto Markdown AST
|
|
1073
|
+
* Used for VS Code extension preview rendering
|
|
1074
|
+
*/
|
|
1075
|
+
class HtmlGenerator {
|
|
1076
|
+
/**
|
|
1077
|
+
* Generate HTML from markdown AST
|
|
1078
|
+
*/
|
|
1079
|
+
generate(nodes) {
|
|
1080
|
+
return nodes.map((node) => this.renderNode(node)).join("\n");
|
|
1081
|
+
}
|
|
1082
|
+
renderNode(node) {
|
|
1083
|
+
switch (node.type) {
|
|
1084
|
+
case "header":
|
|
1085
|
+
return this.renderHeader(node);
|
|
1086
|
+
case "text":
|
|
1087
|
+
return this.renderText(node);
|
|
1088
|
+
case "bold":
|
|
1089
|
+
return this.renderBold(node);
|
|
1090
|
+
case "italic":
|
|
1091
|
+
return this.renderItalic(node);
|
|
1092
|
+
case "input":
|
|
1093
|
+
return this.renderInput(node);
|
|
1094
|
+
case "textarea":
|
|
1095
|
+
return this.renderTextarea(node);
|
|
1096
|
+
case "checkbox":
|
|
1097
|
+
return this.renderCheckbox(node);
|
|
1098
|
+
case "radiogroup":
|
|
1099
|
+
return this.renderRadioGroup(node);
|
|
1100
|
+
case "dropdown":
|
|
1101
|
+
return this.renderDropdown(node);
|
|
1102
|
+
case "button":
|
|
1103
|
+
return this.renderButton(node);
|
|
1104
|
+
case "card":
|
|
1105
|
+
return this.renderCard(node);
|
|
1106
|
+
case "container":
|
|
1107
|
+
return this.renderContainer(node);
|
|
1108
|
+
case "grid":
|
|
1109
|
+
return this.renderGrid(node);
|
|
1110
|
+
case "div":
|
|
1111
|
+
return this.renderDiv(node);
|
|
1112
|
+
case "table":
|
|
1113
|
+
return this.renderTable(node);
|
|
1114
|
+
case "image":
|
|
1115
|
+
return this.renderImage(node);
|
|
1116
|
+
case "workflow":
|
|
1117
|
+
return this.renderWorkflow(node);
|
|
1118
|
+
case "screen":
|
|
1119
|
+
return this.renderScreen(node);
|
|
1120
|
+
default:
|
|
1121
|
+
return `<div class="proto-unknown">${JSON.stringify(node)}</div>`;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
renderHeader(node) {
|
|
1125
|
+
const level = node.level || 1;
|
|
1126
|
+
let content;
|
|
1127
|
+
if (node.children && node.children.length > 0) {
|
|
1128
|
+
content = this.renderInlineNodes(node.children);
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
content = this.escapeHtml(node.content || "");
|
|
1132
|
+
}
|
|
1133
|
+
return `<h${level} class="proto-header">${content}</h${level}>`;
|
|
1134
|
+
}
|
|
1135
|
+
renderText(node) {
|
|
1136
|
+
if (node.children && node.children.length > 0) {
|
|
1137
|
+
const content = this.renderInlineNodes(node.children);
|
|
1138
|
+
return `<p class="proto-text">${content}</p>`;
|
|
1139
|
+
}
|
|
1140
|
+
return `<p class="proto-text">${this.escapeHtml(node.content || "")}</p>`;
|
|
1141
|
+
}
|
|
1142
|
+
renderBold(node) {
|
|
1143
|
+
if (node.children && node.children.length > 0) {
|
|
1144
|
+
return `<strong>${this.renderInlineNodes(node.children)}</strong>`;
|
|
1145
|
+
}
|
|
1146
|
+
return `<strong>${this.escapeHtml(node.content || "")}</strong>`;
|
|
1147
|
+
}
|
|
1148
|
+
renderItalic(node) {
|
|
1149
|
+
if (node.children && node.children.length > 0) {
|
|
1150
|
+
return `<em>${this.renderInlineNodes(node.children)}</em>`;
|
|
1151
|
+
}
|
|
1152
|
+
return `<em>${this.escapeHtml(node.content || "")}</em>`;
|
|
1153
|
+
}
|
|
1154
|
+
renderInlineNodes(nodes) {
|
|
1155
|
+
return nodes.map((node) => this.renderInlineNode(node)).join("");
|
|
1156
|
+
}
|
|
1157
|
+
renderInlineNode(node) {
|
|
1158
|
+
switch (node.type) {
|
|
1159
|
+
case "bold":
|
|
1160
|
+
if (node.children && node.children.length > 0) {
|
|
1161
|
+
return `<strong>${this.renderInlineNodes(node.children)}</strong>`;
|
|
1162
|
+
}
|
|
1163
|
+
return `<strong>${this.escapeHtml(node.content || "")}</strong>`;
|
|
1164
|
+
case "italic":
|
|
1165
|
+
if (node.children && node.children.length > 0) {
|
|
1166
|
+
return `<em>${this.renderInlineNodes(node.children)}</em>`;
|
|
1167
|
+
}
|
|
1168
|
+
return `<em>${this.escapeHtml(node.content || "")}</em>`;
|
|
1169
|
+
case "text":
|
|
1170
|
+
if (node.children && node.children.length > 0) {
|
|
1171
|
+
return this.renderInlineNodes(node.children);
|
|
1172
|
+
}
|
|
1173
|
+
return this.escapeHtml(node.content || "");
|
|
1174
|
+
default:
|
|
1175
|
+
return this.escapeHtml(node.content || "");
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
renderInput(node) {
|
|
1179
|
+
const placeholder = node.inputType === "password" ? "••••••••" : "";
|
|
1180
|
+
return `
|
|
1181
|
+
<div class="proto-field">
|
|
1182
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1183
|
+
<input type="${node.inputType || "text"}" class="proto-input" placeholder="${placeholder}" disabled />
|
|
1184
|
+
</div>`;
|
|
1185
|
+
}
|
|
1186
|
+
renderTextarea(node) {
|
|
1187
|
+
return `
|
|
1188
|
+
<div class="proto-field">
|
|
1189
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1190
|
+
<textarea class="proto-textarea" disabled></textarea>
|
|
1191
|
+
</div>`;
|
|
1192
|
+
}
|
|
1193
|
+
renderCheckbox(node) {
|
|
1194
|
+
return `
|
|
1195
|
+
<div class="proto-checkbox">
|
|
1196
|
+
<input type="checkbox" class="proto-checkbox-input" disabled />
|
|
1197
|
+
<label class="proto-checkbox-label">${this.escapeHtml(node.label || "")}</label>
|
|
1198
|
+
</div>`;
|
|
1199
|
+
}
|
|
1200
|
+
renderRadioGroup(node) {
|
|
1201
|
+
const options = (node.options || [])
|
|
1202
|
+
.map((opt) => `
|
|
1203
|
+
<div class="proto-radio-option">
|
|
1204
|
+
<input type="radio" class="proto-radio-input" name="${this.escapeHtml(node.label || "")}" disabled />
|
|
1205
|
+
<label class="proto-radio-label">${this.escapeHtml(opt)}</label>
|
|
1206
|
+
</div>`)
|
|
1207
|
+
.join("");
|
|
1208
|
+
return `
|
|
1209
|
+
<div class="proto-radiogroup">
|
|
1210
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1211
|
+
<div class="proto-radio-options">${options}</div>
|
|
1212
|
+
</div>`;
|
|
1213
|
+
}
|
|
1214
|
+
renderDropdown(node) {
|
|
1215
|
+
const options = (node.options || ["Select an option"])
|
|
1216
|
+
.map((opt) => `<option>${this.escapeHtml(opt)}</option>`)
|
|
1217
|
+
.join("");
|
|
1218
|
+
return `
|
|
1219
|
+
<div class="proto-field">
|
|
1220
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1221
|
+
<select class="proto-select" disabled>${options}</select>
|
|
1222
|
+
</div>`;
|
|
1223
|
+
}
|
|
1224
|
+
renderButton(node) {
|
|
1225
|
+
const btnClass = node.variant === "default"
|
|
1226
|
+
? "proto-button-default"
|
|
1227
|
+
: "proto-button-outline";
|
|
1228
|
+
const navIndicator = node.navigateTo
|
|
1229
|
+
? ` <span class="proto-nav-indicator">→ ${this.escapeHtml(node.navigateTo)}</span>`
|
|
1230
|
+
: "";
|
|
1231
|
+
return `<button class="proto-button ${btnClass}" disabled>${this.escapeHtml(node.content || "")}${navIndicator}</button>`;
|
|
1232
|
+
}
|
|
1233
|
+
renderCard(node) {
|
|
1234
|
+
let cardTitle = "";
|
|
1235
|
+
if (node.titleChildren && node.titleChildren.length > 0) {
|
|
1236
|
+
cardTitle = `<div class="proto-card-header">${this.renderInlineNodes(node.titleChildren)}</div>`;
|
|
1237
|
+
}
|
|
1238
|
+
else if (node.title) {
|
|
1239
|
+
cardTitle = `<div class="proto-card-header">${this.escapeHtml(node.title)}</div>`;
|
|
1240
|
+
}
|
|
1241
|
+
const cardChildren = node.children ? this.generate(node.children) : "";
|
|
1242
|
+
return `
|
|
1243
|
+
<div class="proto-card">
|
|
1244
|
+
${cardTitle}
|
|
1245
|
+
<div class="proto-card-content">${cardChildren}</div>
|
|
1246
|
+
</div>`;
|
|
1247
|
+
}
|
|
1248
|
+
renderContainer(node) {
|
|
1249
|
+
const children = node.children ? this.generate(node.children) : "";
|
|
1250
|
+
return `<div class="proto-container">${children}</div>`;
|
|
1251
|
+
}
|
|
1252
|
+
renderGrid(node) {
|
|
1253
|
+
const children = node.children ? this.generate(node.children) : "";
|
|
1254
|
+
const gridConfig = this.parseGridConfig(node.gridConfig || "");
|
|
1255
|
+
return `<div class="proto-grid" style="${gridConfig}">${children}</div>`;
|
|
1256
|
+
}
|
|
1257
|
+
renderDiv(node) {
|
|
1258
|
+
const children = node.children ? this.generate(node.children) : "";
|
|
1259
|
+
return `<div class="proto-div ${this.escapeHtml(node.className || "")}">${children}</div>`;
|
|
1260
|
+
}
|
|
1261
|
+
renderTable(node) {
|
|
1262
|
+
const headerCells = (node.headers || [])
|
|
1263
|
+
.map((h) => `<th class="proto-table-th">${this.escapeHtml(h)}</th>`)
|
|
1264
|
+
.join("");
|
|
1265
|
+
const bodyRows = (node.rows || [])
|
|
1266
|
+
.map((row) => `<tr>${row
|
|
1267
|
+
.map((cell) => `<td class="proto-table-td">${this.escapeHtml(cell)}</td>`)
|
|
1268
|
+
.join("")}</tr>`)
|
|
1269
|
+
.join("");
|
|
1270
|
+
return `
|
|
1271
|
+
<table class="proto-table">
|
|
1272
|
+
<thead><tr>${headerCells}</tr></thead>
|
|
1273
|
+
<tbody>${bodyRows}</tbody>
|
|
1274
|
+
</table>`;
|
|
1275
|
+
}
|
|
1276
|
+
renderImage(node) {
|
|
1277
|
+
return `<img class="proto-image" src="${this.escapeHtml(node.src || "")}" alt="${this.escapeHtml(node.alt || "")}" />`;
|
|
1278
|
+
}
|
|
1279
|
+
renderWorkflow(node) {
|
|
1280
|
+
const screens = (node.children || [])
|
|
1281
|
+
.map((screen, idx) => {
|
|
1282
|
+
const isInitial = screen.id === node.initialScreen || idx === 0;
|
|
1283
|
+
const screenContent = screen.children
|
|
1284
|
+
? this.generate(screen.children)
|
|
1285
|
+
: "";
|
|
1286
|
+
const screenId = screen.id || "";
|
|
1287
|
+
return `
|
|
1288
|
+
<div class="proto-screen${isInitial ? " proto-screen-active" : ""}" data-screen-id="${this.escapeHtml(screenId)}">
|
|
1289
|
+
<div class="proto-screen-header">
|
|
1290
|
+
<span class="proto-screen-badge">${this.escapeHtml(screenId)}</span>
|
|
1291
|
+
${isInitial
|
|
1292
|
+
? '<span class="proto-screen-initial">Initial</span>'
|
|
1293
|
+
: ""}
|
|
1294
|
+
</div>
|
|
1295
|
+
<div class="proto-screen-content">${screenContent}</div>
|
|
1296
|
+
</div>`;
|
|
1297
|
+
})
|
|
1298
|
+
.join("");
|
|
1299
|
+
return `<div class="proto-workflow">${screens}</div>`;
|
|
1300
|
+
}
|
|
1301
|
+
renderScreen(node) {
|
|
1302
|
+
const screenChildren = node.children ? this.generate(node.children) : "";
|
|
1303
|
+
const screenId = node.id || "";
|
|
1304
|
+
return `
|
|
1305
|
+
<div class="proto-screen" data-screen-id="${this.escapeHtml(screenId)}">
|
|
1306
|
+
<div class="proto-screen-header">
|
|
1307
|
+
<span class="proto-screen-badge">${this.escapeHtml(screenId)}</span>
|
|
1308
|
+
</div>
|
|
1309
|
+
<div class="proto-screen-content">${screenChildren}</div>
|
|
1310
|
+
</div>`;
|
|
1311
|
+
}
|
|
1312
|
+
parseGridConfig(config) {
|
|
1313
|
+
const styles = [];
|
|
1314
|
+
// Parse cols-N
|
|
1315
|
+
const colsMatch = config.match(/cols-(\d+)/);
|
|
1316
|
+
if (colsMatch) {
|
|
1317
|
+
styles.push(`grid-template-columns: repeat(${colsMatch[1]}, 1fr)`);
|
|
1318
|
+
}
|
|
1319
|
+
// Parse gap-N
|
|
1320
|
+
const gapMatch = config.match(/gap-(\d+)/);
|
|
1321
|
+
if (gapMatch) {
|
|
1322
|
+
styles.push(`gap: ${parseInt(gapMatch[1]) * 4}px`);
|
|
1323
|
+
}
|
|
1324
|
+
return styles.join("; ");
|
|
1325
|
+
}
|
|
1326
|
+
escapeHtml(text) {
|
|
1327
|
+
return text
|
|
1328
|
+
.replace(/&/g, "&")
|
|
1329
|
+
.replace(/</g, "<")
|
|
1330
|
+
.replace(/>/g, ">")
|
|
1331
|
+
.replace(/"/g, """)
|
|
1332
|
+
.replace(/'/g, "'");
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
exports.HtmlGenerator = HtmlGenerator;
|
|
1008
1337
|
exports.MarkdownParser = MarkdownParser;
|
|
1009
1338
|
exports.ShadcnCodeGenerator = ShadcnCodeGenerator;
|
|
1010
1339
|
//# sourceMappingURL=index.js.map
|