@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/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, "&amp;")
1329
+ .replace(/</g, "&lt;")
1330
+ .replace(/>/g, "&gt;")
1331
+ .replace(/"/g, "&quot;")
1332
+ .replace(/'/g, "&#039;");
1333
+ }
1334
+ }
1335
+
1336
+ exports.HtmlGenerator = HtmlGenerator;
1008
1337
  exports.MarkdownParser = MarkdownParser;
1009
1338
  exports.ShadcnCodeGenerator = ShadcnCodeGenerator;
1010
1339
  //# sourceMappingURL=index.js.map