@protomarkdown/parser 1.0.0 → 1.0.2
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 +225 -6
- package/dist/HtmlGenerator.d.ts +35 -0
- package/dist/HtmlGenerator.d.ts.map +1 -0
- package/dist/ShadcnCodeGenerator.d.ts +2 -0
- package/dist/ShadcnCodeGenerator.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +457 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +457 -4
- package/dist/index.js.map +1 -1
- package/dist/parser/MarkdownParser.d.ts +2 -0
- package/dist/parser/MarkdownParser.d.ts.map +1 -1
- package/dist/parser/types.d.ts +3 -1
- package/dist/parser/types.d.ts.map +1 -1
- package/package.json +4 -1
package/dist/index.esm.js
CHANGED
|
@@ -56,6 +56,13 @@ class MarkdownParser {
|
|
|
56
56
|
});
|
|
57
57
|
continue;
|
|
58
58
|
}
|
|
59
|
+
// Check for workflow start
|
|
60
|
+
if (line === '[workflow' || line.startsWith('[workflow ')) {
|
|
61
|
+
const result = this.parseWorkflow(lines, i);
|
|
62
|
+
nodes.push(result.node);
|
|
63
|
+
i = result.nextIndex;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
59
66
|
// Check for card start
|
|
60
67
|
const cardMatch = line.match(/^\[--\s*(.*)$/);
|
|
61
68
|
if (cardMatch) {
|
|
@@ -91,6 +98,100 @@ class MarkdownParser {
|
|
|
91
98
|
}
|
|
92
99
|
return { nodes, errors: errors.length > 0 ? errors : undefined };
|
|
93
100
|
}
|
|
101
|
+
parseWorkflow(lines, startIndex) {
|
|
102
|
+
const screens = [];
|
|
103
|
+
let i = startIndex + 1;
|
|
104
|
+
let depth = 1;
|
|
105
|
+
let initialScreen;
|
|
106
|
+
// Parse workflow content until we find the matching closing ]
|
|
107
|
+
while (i < lines.length && depth > 0) {
|
|
108
|
+
const workflowLine = this.options.preserveWhitespace ? lines[i] : lines[i].trim();
|
|
109
|
+
// Check for workflow closing
|
|
110
|
+
if (workflowLine === "]") {
|
|
111
|
+
depth--;
|
|
112
|
+
if (depth === 0) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Check for screen opening ([screen id)
|
|
117
|
+
const screenMatch = workflowLine.match(/^\[screen\s+(.+)$/);
|
|
118
|
+
if (screenMatch) {
|
|
119
|
+
const screenId = screenMatch[1].trim();
|
|
120
|
+
const result = this.parseScreen(lines, i, screenId);
|
|
121
|
+
screens.push(result.node);
|
|
122
|
+
// First screen becomes the initial screen
|
|
123
|
+
if (!initialScreen) {
|
|
124
|
+
initialScreen = screenId;
|
|
125
|
+
}
|
|
126
|
+
i = result.nextIndex;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
node: {
|
|
133
|
+
type: "workflow",
|
|
134
|
+
children: screens,
|
|
135
|
+
initialScreen: initialScreen || (screens[0]?.id),
|
|
136
|
+
},
|
|
137
|
+
nextIndex: i + 1,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
parseScreen(lines, startIndex, screenId) {
|
|
141
|
+
const screenChildren = [];
|
|
142
|
+
let i = startIndex + 1;
|
|
143
|
+
let depth = 1;
|
|
144
|
+
// Parse screen content until we find the matching closing ]
|
|
145
|
+
while (i < lines.length && depth > 0) {
|
|
146
|
+
const screenLine = this.options.preserveWhitespace ? lines[i] : lines[i].trim();
|
|
147
|
+
// Check for screen closing
|
|
148
|
+
if (screenLine === "]") {
|
|
149
|
+
depth--;
|
|
150
|
+
if (depth === 0) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Check for nested card opening
|
|
155
|
+
if (screenLine.match(/^\[--\s*(.*)$/)) {
|
|
156
|
+
const nestedTitle = screenLine.match(/^\[--\s*(.*)$/)?.[1] || undefined;
|
|
157
|
+
const result = this.parseCard(lines, i, nestedTitle);
|
|
158
|
+
screenChildren.push(result.node);
|
|
159
|
+
i = result.nextIndex;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Check for nested grid opening
|
|
163
|
+
if (screenLine.match(/^\[grid\s+(.*)$/)) {
|
|
164
|
+
const nestedConfig = screenLine.match(/^\[grid\s+(.*)$/)?.[1] || '';
|
|
165
|
+
const result = this.parseContainer(lines, i, 'grid', nestedConfig);
|
|
166
|
+
screenChildren.push(result.node);
|
|
167
|
+
i = result.nextIndex;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// Check for nested div opening
|
|
171
|
+
if (screenLine.match(/^\[\s*(.*)$/) && !screenLine.includes("]")) {
|
|
172
|
+
const nestedConfig = screenLine.match(/^\[\s*(.*)$/)?.[1] || '';
|
|
173
|
+
const result = this.parseContainer(lines, i, 'div', nestedConfig);
|
|
174
|
+
screenChildren.push(result.node);
|
|
175
|
+
i = result.nextIndex;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (screenLine) {
|
|
179
|
+
const childNode = this.parseLine(screenLine);
|
|
180
|
+
if (childNode) {
|
|
181
|
+
screenChildren.push(childNode);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
node: {
|
|
188
|
+
type: "screen",
|
|
189
|
+
id: screenId,
|
|
190
|
+
children: screenChildren,
|
|
191
|
+
},
|
|
192
|
+
nextIndex: i + 1,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
94
195
|
parseCard(lines, startIndex, title) {
|
|
95
196
|
const cardChildren = [];
|
|
96
197
|
let i = startIndex + 1;
|
|
@@ -355,13 +456,26 @@ class MarkdownParser {
|
|
|
355
456
|
};
|
|
356
457
|
}
|
|
357
458
|
// Parse multiple buttons on one line ([btn1][(btn2)])
|
|
358
|
-
const multiButtonMatch = line.match(/^(\[\(?[^\[\]
|
|
459
|
+
const multiButtonMatch = line.match(/^(\[\(?[^\[\]]+\)?\]\s*)+$/);
|
|
359
460
|
if (multiButtonMatch) {
|
|
360
|
-
const buttons = line.match(/\[(\(?)[^\[\]
|
|
461
|
+
const buttons = line.match(/\[(\(?)[^\[\]]+?(\)?)\]/g);
|
|
361
462
|
if (buttons && buttons.length > 1) {
|
|
362
463
|
return {
|
|
363
464
|
type: "container",
|
|
364
465
|
children: buttons.map((btn) => {
|
|
466
|
+
// Check for navigation syntax: [(text) -> target] or [text -> target]
|
|
467
|
+
const navMatch = btn.match(/\[(\(?)(.+?)(\)?)\s*->\s*([^\]]+)\]/);
|
|
468
|
+
if (navMatch) {
|
|
469
|
+
const isDefault = navMatch[1] === "(" && navMatch[3] === ")";
|
|
470
|
+
const content = navMatch[2].trim();
|
|
471
|
+
const navigateTo = navMatch[4].trim();
|
|
472
|
+
return {
|
|
473
|
+
type: "button",
|
|
474
|
+
content,
|
|
475
|
+
variant: isDefault ? "default" : "outline",
|
|
476
|
+
navigateTo,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
365
479
|
const innerMatch = btn.match(/\[(\(?)(.+?)(\)?)\]/);
|
|
366
480
|
if (innerMatch) {
|
|
367
481
|
const isDefault = innerMatch[1] === "(" && innerMatch[3] === ")";
|
|
@@ -381,6 +495,18 @@ class MarkdownParser {
|
|
|
381
495
|
};
|
|
382
496
|
}
|
|
383
497
|
}
|
|
498
|
+
// Parse default button with navigation [(button text) -> target]
|
|
499
|
+
const defaultButtonNavMatch = line.match(/^\[\((.+?)\)\s*->\s*([^\]]+)\]$/);
|
|
500
|
+
if (defaultButtonNavMatch) {
|
|
501
|
+
const content = defaultButtonNavMatch[1].trim();
|
|
502
|
+
const navigateTo = defaultButtonNavMatch[2].trim();
|
|
503
|
+
return {
|
|
504
|
+
type: "button",
|
|
505
|
+
content,
|
|
506
|
+
variant: "default",
|
|
507
|
+
navigateTo,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
384
510
|
// Parse default button [(button text)] or [(button text) | classes]
|
|
385
511
|
const defaultButtonMatch = line.match(/^\[\((.+?)\)(?:\s*\|\s*(.+))?\]$/);
|
|
386
512
|
if (defaultButtonMatch) {
|
|
@@ -393,6 +519,18 @@ class MarkdownParser {
|
|
|
393
519
|
...(className && { className }),
|
|
394
520
|
};
|
|
395
521
|
}
|
|
522
|
+
// Parse outline button with navigation [button text -> target]
|
|
523
|
+
const buttonNavMatch = line.match(/^\[([^|]+?)\s*->\s*([^\]]+)\]$/);
|
|
524
|
+
if (buttonNavMatch) {
|
|
525
|
+
const content = buttonNavMatch[1].trim();
|
|
526
|
+
const navigateTo = buttonNavMatch[2].trim();
|
|
527
|
+
return {
|
|
528
|
+
type: "button",
|
|
529
|
+
content,
|
|
530
|
+
variant: "outline",
|
|
531
|
+
navigateTo,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
396
534
|
// Parse outline button [button text] or [button text | classes]
|
|
397
535
|
const buttonMatch = line.match(/^\[([^|]+?)(?:\s*\|\s*(.+))?\]$/);
|
|
398
536
|
if (buttonMatch) {
|
|
@@ -501,6 +639,8 @@ class ShadcnCodeGenerator {
|
|
|
501
639
|
generate(nodes) {
|
|
502
640
|
this.indentLevel = 0;
|
|
503
641
|
this.requiredImports.clear();
|
|
642
|
+
// Check if the component contains a workflow
|
|
643
|
+
const hasWorkflow = nodes.some(node => node.type === 'workflow');
|
|
504
644
|
// Generate component body
|
|
505
645
|
const componentBody = this.generateNodes(nodes);
|
|
506
646
|
// Add base indentation (6 spaces = 3 levels for proper JSX nesting)
|
|
@@ -510,7 +650,8 @@ class ShadcnCodeGenerator {
|
|
|
510
650
|
.join('\n');
|
|
511
651
|
// Collect imports
|
|
512
652
|
const imports = this.generateImports();
|
|
513
|
-
|
|
653
|
+
const reactImport = hasWorkflow ? "import { useState } from 'react';\n" : "";
|
|
654
|
+
return `${reactImport}${imports}
|
|
514
655
|
|
|
515
656
|
export function GeneratedComponent() {
|
|
516
657
|
return (
|
|
@@ -598,6 +739,10 @@ ${indentedBody}
|
|
|
598
739
|
return this.generateItalic(node, index);
|
|
599
740
|
case "image":
|
|
600
741
|
return this.generateImage(node, index);
|
|
742
|
+
case "workflow":
|
|
743
|
+
return this.generateWorkflow(node, index);
|
|
744
|
+
case "screen":
|
|
745
|
+
return this.generateScreen(node, index);
|
|
601
746
|
default:
|
|
602
747
|
return "";
|
|
603
748
|
}
|
|
@@ -713,7 +858,9 @@ ${this.indent()}</div>`;
|
|
|
713
858
|
this.requiredImports.add("Button");
|
|
714
859
|
const variant = node.variant || "default";
|
|
715
860
|
const className = node.className ? ` className="${node.className}"` : "";
|
|
716
|
-
|
|
861
|
+
// Add onClick handler if button has navigation
|
|
862
|
+
const onClick = node.navigateTo ? ` onClick={() => setCurrentScreen('${node.navigateTo}')}` : "";
|
|
863
|
+
return `${this.indent()}<Button key={${index}} variant="${variant}"${className}${onClick}>${this.escapeJSX(node.content || "")}</Button>`;
|
|
717
864
|
}
|
|
718
865
|
generateContainer(node, index) {
|
|
719
866
|
this.indentLevel++;
|
|
@@ -814,7 +961,312 @@ ${this.indent()}</div>`;
|
|
|
814
961
|
const alt = node.alt || "";
|
|
815
962
|
return `${this.indent()}<img key={${index}} src="${src}" alt="${this.escapeJSX(alt)}" className="max-w-full h-auto" />`;
|
|
816
963
|
}
|
|
964
|
+
generateWorkflow(node, index) {
|
|
965
|
+
const screens = node.children || [];
|
|
966
|
+
const initialScreen = node.initialScreen || screens[0]?.id || "home";
|
|
967
|
+
// Generate state management
|
|
968
|
+
const stateDeclaration = `${this.indent()}const [currentScreen, setCurrentScreen] = useState('${initialScreen}');`;
|
|
969
|
+
// Generate screen rendering
|
|
970
|
+
this.indentLevel++;
|
|
971
|
+
const screenCases = screens.map((screen, i) => {
|
|
972
|
+
const screenId = screen.id || `screen-${i}`;
|
|
973
|
+
const screenContent = screen.children ? this.generateNodes(screen.children) : "";
|
|
974
|
+
return `${this.indent()}${i === 0 ? '' : 'else '}if (currentScreen === '${screenId}') {
|
|
975
|
+
${this.indent()} return (
|
|
976
|
+
${this.indent()} <div className="space-y-2">
|
|
977
|
+
${screenContent}
|
|
978
|
+
${this.indent()} </div>
|
|
979
|
+
${this.indent()} );
|
|
980
|
+
${this.indent()}}`;
|
|
981
|
+
}).join('\n');
|
|
982
|
+
this.indentLevel--;
|
|
983
|
+
// Return fallback if no screen matches
|
|
984
|
+
const fallback = `${this.indent()}return <div>Screen not found</div>;`;
|
|
985
|
+
return `${this.indent()}<div key={${index}}>
|
|
986
|
+
${this.indent()} {(() => {
|
|
987
|
+
${stateDeclaration}
|
|
988
|
+
|
|
989
|
+
${screenCases}
|
|
990
|
+
${fallback}
|
|
991
|
+
${this.indent()} })()}
|
|
992
|
+
${this.indent()}</div>`;
|
|
993
|
+
}
|
|
994
|
+
generateScreen(node, index) {
|
|
995
|
+
// Screens are handled within workflow generation
|
|
996
|
+
// This method is for standalone screen nodes (if used outside workflow)
|
|
997
|
+
this.indentLevel++;
|
|
998
|
+
const children = node.children ? this.generateNodes(node.children) : "";
|
|
999
|
+
this.indentLevel--;
|
|
1000
|
+
return `${this.indent()}<div key={${index}} data-screen-id="${node.id || index}" className="space-y-2">
|
|
1001
|
+
${children}
|
|
1002
|
+
${this.indent()}</div>`;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Generates HTML from a Proto Markdown AST
|
|
1008
|
+
* Used for VS Code extension preview rendering
|
|
1009
|
+
*/
|
|
1010
|
+
class HtmlGenerator {
|
|
1011
|
+
/**
|
|
1012
|
+
* Generate HTML from markdown AST
|
|
1013
|
+
*/
|
|
1014
|
+
generate(nodes) {
|
|
1015
|
+
return nodes.map((node) => this.renderNode(node)).join("\n");
|
|
1016
|
+
}
|
|
1017
|
+
renderNode(node) {
|
|
1018
|
+
switch (node.type) {
|
|
1019
|
+
case "header":
|
|
1020
|
+
return this.renderHeader(node);
|
|
1021
|
+
case "text":
|
|
1022
|
+
return this.renderText(node);
|
|
1023
|
+
case "bold":
|
|
1024
|
+
return this.renderBold(node);
|
|
1025
|
+
case "italic":
|
|
1026
|
+
return this.renderItalic(node);
|
|
1027
|
+
case "input":
|
|
1028
|
+
return this.renderInput(node);
|
|
1029
|
+
case "textarea":
|
|
1030
|
+
return this.renderTextarea(node);
|
|
1031
|
+
case "checkbox":
|
|
1032
|
+
return this.renderCheckbox(node);
|
|
1033
|
+
case "radiogroup":
|
|
1034
|
+
return this.renderRadioGroup(node);
|
|
1035
|
+
case "dropdown":
|
|
1036
|
+
return this.renderDropdown(node);
|
|
1037
|
+
case "button":
|
|
1038
|
+
return this.renderButton(node);
|
|
1039
|
+
case "card":
|
|
1040
|
+
return this.renderCard(node);
|
|
1041
|
+
case "container":
|
|
1042
|
+
return this.renderContainer(node);
|
|
1043
|
+
case "grid":
|
|
1044
|
+
return this.renderGrid(node);
|
|
1045
|
+
case "div":
|
|
1046
|
+
return this.renderDiv(node);
|
|
1047
|
+
case "table":
|
|
1048
|
+
return this.renderTable(node);
|
|
1049
|
+
case "image":
|
|
1050
|
+
return this.renderImage(node);
|
|
1051
|
+
case "workflow":
|
|
1052
|
+
return this.renderWorkflow(node);
|
|
1053
|
+
case "screen":
|
|
1054
|
+
return this.renderScreen(node);
|
|
1055
|
+
default:
|
|
1056
|
+
return `<div class="proto-unknown">${JSON.stringify(node)}</div>`;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
renderHeader(node) {
|
|
1060
|
+
const level = node.level || 1;
|
|
1061
|
+
let content;
|
|
1062
|
+
if (node.children && node.children.length > 0) {
|
|
1063
|
+
content = this.renderInlineNodes(node.children);
|
|
1064
|
+
}
|
|
1065
|
+
else {
|
|
1066
|
+
content = this.escapeHtml(node.content || "");
|
|
1067
|
+
}
|
|
1068
|
+
return `<h${level} class="proto-header">${content}</h${level}>`;
|
|
1069
|
+
}
|
|
1070
|
+
renderText(node) {
|
|
1071
|
+
if (node.children && node.children.length > 0) {
|
|
1072
|
+
const content = this.renderInlineNodes(node.children);
|
|
1073
|
+
return `<p class="proto-text">${content}</p>`;
|
|
1074
|
+
}
|
|
1075
|
+
return `<p class="proto-text">${this.escapeHtml(node.content || "")}</p>`;
|
|
1076
|
+
}
|
|
1077
|
+
renderBold(node) {
|
|
1078
|
+
if (node.children && node.children.length > 0) {
|
|
1079
|
+
return `<strong>${this.renderInlineNodes(node.children)}</strong>`;
|
|
1080
|
+
}
|
|
1081
|
+
return `<strong>${this.escapeHtml(node.content || "")}</strong>`;
|
|
1082
|
+
}
|
|
1083
|
+
renderItalic(node) {
|
|
1084
|
+
if (node.children && node.children.length > 0) {
|
|
1085
|
+
return `<em>${this.renderInlineNodes(node.children)}</em>`;
|
|
1086
|
+
}
|
|
1087
|
+
return `<em>${this.escapeHtml(node.content || "")}</em>`;
|
|
1088
|
+
}
|
|
1089
|
+
renderInlineNodes(nodes) {
|
|
1090
|
+
return nodes.map((node) => this.renderInlineNode(node)).join("");
|
|
1091
|
+
}
|
|
1092
|
+
renderInlineNode(node) {
|
|
1093
|
+
switch (node.type) {
|
|
1094
|
+
case "bold":
|
|
1095
|
+
if (node.children && node.children.length > 0) {
|
|
1096
|
+
return `<strong>${this.renderInlineNodes(node.children)}</strong>`;
|
|
1097
|
+
}
|
|
1098
|
+
return `<strong>${this.escapeHtml(node.content || "")}</strong>`;
|
|
1099
|
+
case "italic":
|
|
1100
|
+
if (node.children && node.children.length > 0) {
|
|
1101
|
+
return `<em>${this.renderInlineNodes(node.children)}</em>`;
|
|
1102
|
+
}
|
|
1103
|
+
return `<em>${this.escapeHtml(node.content || "")}</em>`;
|
|
1104
|
+
case "text":
|
|
1105
|
+
if (node.children && node.children.length > 0) {
|
|
1106
|
+
return this.renderInlineNodes(node.children);
|
|
1107
|
+
}
|
|
1108
|
+
return this.escapeHtml(node.content || "");
|
|
1109
|
+
default:
|
|
1110
|
+
return this.escapeHtml(node.content || "");
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
renderInput(node) {
|
|
1114
|
+
const placeholder = node.inputType === "password" ? "••••••••" : "";
|
|
1115
|
+
return `
|
|
1116
|
+
<div class="proto-field">
|
|
1117
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1118
|
+
<input type="${node.inputType || "text"}" class="proto-input" placeholder="${placeholder}" disabled />
|
|
1119
|
+
</div>`;
|
|
1120
|
+
}
|
|
1121
|
+
renderTextarea(node) {
|
|
1122
|
+
return `
|
|
1123
|
+
<div class="proto-field">
|
|
1124
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1125
|
+
<textarea class="proto-textarea" disabled></textarea>
|
|
1126
|
+
</div>`;
|
|
1127
|
+
}
|
|
1128
|
+
renderCheckbox(node) {
|
|
1129
|
+
return `
|
|
1130
|
+
<div class="proto-checkbox">
|
|
1131
|
+
<input type="checkbox" class="proto-checkbox-input" disabled />
|
|
1132
|
+
<label class="proto-checkbox-label">${this.escapeHtml(node.label || "")}</label>
|
|
1133
|
+
</div>`;
|
|
1134
|
+
}
|
|
1135
|
+
renderRadioGroup(node) {
|
|
1136
|
+
const options = (node.options || [])
|
|
1137
|
+
.map((opt) => `
|
|
1138
|
+
<div class="proto-radio-option">
|
|
1139
|
+
<input type="radio" class="proto-radio-input" name="${this.escapeHtml(node.label || "")}" disabled />
|
|
1140
|
+
<label class="proto-radio-label">${this.escapeHtml(opt)}</label>
|
|
1141
|
+
</div>`)
|
|
1142
|
+
.join("");
|
|
1143
|
+
return `
|
|
1144
|
+
<div class="proto-radiogroup">
|
|
1145
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1146
|
+
<div class="proto-radio-options">${options}</div>
|
|
1147
|
+
</div>`;
|
|
1148
|
+
}
|
|
1149
|
+
renderDropdown(node) {
|
|
1150
|
+
const options = (node.options || ["Select an option"])
|
|
1151
|
+
.map((opt) => `<option>${this.escapeHtml(opt)}</option>`)
|
|
1152
|
+
.join("");
|
|
1153
|
+
return `
|
|
1154
|
+
<div class="proto-field">
|
|
1155
|
+
<label class="proto-label">${this.escapeHtml(node.label || "")}</label>
|
|
1156
|
+
<select class="proto-select" disabled>${options}</select>
|
|
1157
|
+
</div>`;
|
|
1158
|
+
}
|
|
1159
|
+
renderButton(node) {
|
|
1160
|
+
const btnClass = node.variant === "default"
|
|
1161
|
+
? "proto-button-default"
|
|
1162
|
+
: "proto-button-outline";
|
|
1163
|
+
const navIndicator = node.navigateTo
|
|
1164
|
+
? ` <span class="proto-nav-indicator">→ ${this.escapeHtml(node.navigateTo)}</span>`
|
|
1165
|
+
: "";
|
|
1166
|
+
return `<button class="proto-button ${btnClass}" disabled>${this.escapeHtml(node.content || "")}${navIndicator}</button>`;
|
|
1167
|
+
}
|
|
1168
|
+
renderCard(node) {
|
|
1169
|
+
let cardTitle = "";
|
|
1170
|
+
if (node.titleChildren && node.titleChildren.length > 0) {
|
|
1171
|
+
cardTitle = `<div class="proto-card-header">${this.renderInlineNodes(node.titleChildren)}</div>`;
|
|
1172
|
+
}
|
|
1173
|
+
else if (node.title) {
|
|
1174
|
+
cardTitle = `<div class="proto-card-header">${this.escapeHtml(node.title)}</div>`;
|
|
1175
|
+
}
|
|
1176
|
+
const cardChildren = node.children ? this.generate(node.children) : "";
|
|
1177
|
+
return `
|
|
1178
|
+
<div class="proto-card">
|
|
1179
|
+
${cardTitle}
|
|
1180
|
+
<div class="proto-card-content">${cardChildren}</div>
|
|
1181
|
+
</div>`;
|
|
1182
|
+
}
|
|
1183
|
+
renderContainer(node) {
|
|
1184
|
+
const children = node.children ? this.generate(node.children) : "";
|
|
1185
|
+
return `<div class="proto-container">${children}</div>`;
|
|
1186
|
+
}
|
|
1187
|
+
renderGrid(node) {
|
|
1188
|
+
const children = node.children ? this.generate(node.children) : "";
|
|
1189
|
+
const gridConfig = this.parseGridConfig(node.gridConfig || "");
|
|
1190
|
+
return `<div class="proto-grid" style="${gridConfig}">${children}</div>`;
|
|
1191
|
+
}
|
|
1192
|
+
renderDiv(node) {
|
|
1193
|
+
const children = node.children ? this.generate(node.children) : "";
|
|
1194
|
+
return `<div class="proto-div ${this.escapeHtml(node.className || "")}">${children}</div>`;
|
|
1195
|
+
}
|
|
1196
|
+
renderTable(node) {
|
|
1197
|
+
const headerCells = (node.headers || [])
|
|
1198
|
+
.map((h) => `<th class="proto-table-th">${this.escapeHtml(h)}</th>`)
|
|
1199
|
+
.join("");
|
|
1200
|
+
const bodyRows = (node.rows || [])
|
|
1201
|
+
.map((row) => `<tr>${row
|
|
1202
|
+
.map((cell) => `<td class="proto-table-td">${this.escapeHtml(cell)}</td>`)
|
|
1203
|
+
.join("")}</tr>`)
|
|
1204
|
+
.join("");
|
|
1205
|
+
return `
|
|
1206
|
+
<table class="proto-table">
|
|
1207
|
+
<thead><tr>${headerCells}</tr></thead>
|
|
1208
|
+
<tbody>${bodyRows}</tbody>
|
|
1209
|
+
</table>`;
|
|
1210
|
+
}
|
|
1211
|
+
renderImage(node) {
|
|
1212
|
+
return `<img class="proto-image" src="${this.escapeHtml(node.src || "")}" alt="${this.escapeHtml(node.alt || "")}" />`;
|
|
1213
|
+
}
|
|
1214
|
+
renderWorkflow(node) {
|
|
1215
|
+
const screens = (node.children || [])
|
|
1216
|
+
.map((screen, idx) => {
|
|
1217
|
+
const isInitial = screen.id === node.initialScreen || idx === 0;
|
|
1218
|
+
const screenContent = screen.children
|
|
1219
|
+
? this.generate(screen.children)
|
|
1220
|
+
: "";
|
|
1221
|
+
const screenId = screen.id || "";
|
|
1222
|
+
return `
|
|
1223
|
+
<div class="proto-screen${isInitial ? " proto-screen-active" : ""}" data-screen-id="${this.escapeHtml(screenId)}">
|
|
1224
|
+
<div class="proto-screen-header">
|
|
1225
|
+
<span class="proto-screen-badge">${this.escapeHtml(screenId)}</span>
|
|
1226
|
+
${isInitial
|
|
1227
|
+
? '<span class="proto-screen-initial">Initial</span>'
|
|
1228
|
+
: ""}
|
|
1229
|
+
</div>
|
|
1230
|
+
<div class="proto-screen-content">${screenContent}</div>
|
|
1231
|
+
</div>`;
|
|
1232
|
+
})
|
|
1233
|
+
.join("");
|
|
1234
|
+
return `<div class="proto-workflow">${screens}</div>`;
|
|
1235
|
+
}
|
|
1236
|
+
renderScreen(node) {
|
|
1237
|
+
const screenChildren = node.children ? this.generate(node.children) : "";
|
|
1238
|
+
const screenId = node.id || "";
|
|
1239
|
+
return `
|
|
1240
|
+
<div class="proto-screen" data-screen-id="${this.escapeHtml(screenId)}">
|
|
1241
|
+
<div class="proto-screen-header">
|
|
1242
|
+
<span class="proto-screen-badge">${this.escapeHtml(screenId)}</span>
|
|
1243
|
+
</div>
|
|
1244
|
+
<div class="proto-screen-content">${screenChildren}</div>
|
|
1245
|
+
</div>`;
|
|
1246
|
+
}
|
|
1247
|
+
parseGridConfig(config) {
|
|
1248
|
+
const styles = [];
|
|
1249
|
+
// Parse cols-N
|
|
1250
|
+
const colsMatch = config.match(/cols-(\d+)/);
|
|
1251
|
+
if (colsMatch) {
|
|
1252
|
+
styles.push(`grid-template-columns: repeat(${colsMatch[1]}, 1fr)`);
|
|
1253
|
+
}
|
|
1254
|
+
// Parse gap-N
|
|
1255
|
+
const gapMatch = config.match(/gap-(\d+)/);
|
|
1256
|
+
if (gapMatch) {
|
|
1257
|
+
styles.push(`gap: ${parseInt(gapMatch[1]) * 4}px`);
|
|
1258
|
+
}
|
|
1259
|
+
return styles.join("; ");
|
|
1260
|
+
}
|
|
1261
|
+
escapeHtml(text) {
|
|
1262
|
+
return text
|
|
1263
|
+
.replace(/&/g, "&")
|
|
1264
|
+
.replace(/</g, "<")
|
|
1265
|
+
.replace(/>/g, ">")
|
|
1266
|
+
.replace(/"/g, """)
|
|
1267
|
+
.replace(/'/g, "'");
|
|
1268
|
+
}
|
|
817
1269
|
}
|
|
818
1270
|
|
|
819
|
-
export { MarkdownParser, ShadcnCodeGenerator };
|
|
1271
|
+
export { HtmlGenerator, MarkdownParser, ShadcnCodeGenerator };
|
|
820
1272
|
//# sourceMappingURL=index.esm.js.map
|