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