@protomarkdown/parser 1.0.0 → 1.0.1

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 CHANGED
@@ -1,14 +1,29 @@
1
- # proto-markdown-parser
1
+ # @protomarkdown/parser
2
2
 
3
- Parser and rendeder for proto markdown syntax
3
+ Parser and renderer for Proto Markdown syntax - A UI prototyping markdown language for rapid React component generation.
4
4
 
5
5
  [Proto Markdown Syntax Documentation](https://www.protomarkdown.org/documentation)
6
6
 
7
- ## Usage
7
+ ## Features
8
+
9
+ - 🎨 Parse Proto Markdown syntax into an Abstract Syntax Tree (AST)
10
+ - ⚛️ Generate React components with Shadcn UI
11
+ - 🔄 Multi-screen workflow navigation system
12
+ - 📋 Form elements (inputs, dropdowns, checkboxes, textareas)
13
+ - 🎯 Cards, grids, and flexible layouts
14
+ - 📊 Tables and data display
15
+ - ✨ Text formatting (bold, italic)
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install @protomarkdown/parser
21
+ ```
22
+
23
+ ## Quick Start
8
24
 
9
25
  ```ts
10
- import { MarkdownParser } from "@protomarkdown/parser";
11
- import { ShadcnCodeGenerator } from "@protomarkdown/parser";
26
+ import { MarkdownParser, ShadcnCodeGenerator } from "@protomarkdown/parser";
12
27
 
13
28
  const parser = new MarkdownParser();
14
29
  const codeGenerator = new ShadcnCodeGenerator();
@@ -22,4 +37,199 @@ Password __*
22
37
 
23
38
  const ast = parser.parse(markdown);
24
39
  const code = codeGenerator.generate(ast.nodes);
25
- ```
40
+ ```
41
+
42
+ ## Workflows - Multi-Screen Navigation
43
+
44
+ Create complete multi-screen workflows with clickable navigation between screens:
45
+
46
+ ```ts
47
+ const workflowMarkdown = `
48
+ [workflow
49
+ [screen welcome
50
+ # Welcome to My App
51
+ Get started with your journey!
52
+ [(Get Started) -> login]
53
+ [Skip to Dashboard -> dashboard]
54
+ ]
55
+
56
+ [screen login
57
+ # Login
58
+ Email ___
59
+ Password __*
60
+ Remember me __[]
61
+ [(Login) -> dashboard]
62
+ [Back -> welcome]
63
+ ]
64
+
65
+ [screen dashboard
66
+ # Dashboard
67
+ Welcome to your dashboard!
68
+
69
+ [grid cols-2 gap-4
70
+ [-- Stats
71
+ Total Users: 150
72
+ --]
73
+ [-- Activity
74
+ Recent activity here
75
+ --]
76
+ ]
77
+
78
+ [Logout -> welcome]
79
+ ]
80
+ ]`;
81
+
82
+ const ast = parser.parse(workflowMarkdown);
83
+ const reactCode = codeGenerator.generate(ast.nodes);
84
+ ```
85
+
86
+ ### Button Navigation Syntax
87
+
88
+ - **Default button with navigation:** `[(Button Text) -> targetScreen]`
89
+ - **Outline button with navigation:** `[Button Text -> targetScreen]`
90
+ - **Multiple navigation buttons:** `[(Next) -> step2][Back -> step1]`
91
+
92
+ The generated component includes:
93
+ - `useState` hook for screen state management
94
+ - Conditional rendering of screens
95
+ - `onClick` handlers for seamless navigation
96
+
97
+ ## Supported Elements
98
+
99
+ ### Form Fields
100
+
101
+ ```markdown
102
+ Email ___ # Text input
103
+ Password __* # Password input
104
+ Description |___| # Textarea
105
+ Country __> [USA, Canada, Mexico] # Dropdown with options
106
+ Remember me __[] # Checkbox
107
+ Gender __() [Male, Female, Other] # Radio group
108
+ ```
109
+
110
+ ### Layouts
111
+
112
+ ```markdown
113
+ [-- Card Title # Card
114
+ Content here
115
+ --]
116
+
117
+ [grid cols-2 gap-4 # Grid layout
118
+ [-- Card 1 --]
119
+ [-- Card 2 --]
120
+ ]
121
+
122
+ [ flex gap-2 # Custom div with classes
123
+ Content
124
+ ]
125
+ ```
126
+
127
+ ### Buttons
128
+
129
+ ```markdown
130
+ [(Submit)] # Default button
131
+ [Cancel] # Outline button
132
+ [(Save)][Reset] # Multiple buttons
133
+ [(Next) -> step2] # Navigation button (workflows)
134
+ ```
135
+
136
+ ### Tables
137
+
138
+ ```markdown
139
+ | Name | Age | City |
140
+ |------|-----|------|
141
+ | John | 30 | NYC |
142
+ | Jane | 25 | LA |
143
+ ```
144
+
145
+ ### Text Formatting
146
+
147
+ ```markdown
148
+ This is *bold* text
149
+ This is _italic_ text
150
+ This is _*bold and italic*_ text
151
+ ```
152
+
153
+ ## API Reference
154
+
155
+ ### MarkdownParser
156
+
157
+ ```ts
158
+ const parser = new MarkdownParser(options?: ParserOptions);
159
+ const result = parser.parse(markdown: string);
160
+ ```
161
+
162
+ **Options:**
163
+ - `strict?: boolean` - Enable strict parsing mode
164
+ - `preserveWhitespace?: boolean` - Preserve leading/trailing whitespace
165
+
166
+ **Returns:** `ParseResult` containing `nodes` (AST) and optional `errors`
167
+
168
+ ### ShadcnCodeGenerator
169
+
170
+ ```ts
171
+ const generator = new ShadcnCodeGenerator();
172
+ const code = generator.generate(nodes: MarkdownNode[]);
173
+ ```
174
+
175
+ **Returns:** Complete React component code as a string with necessary Shadcn UI imports
176
+
177
+ ## Examples
178
+
179
+ ### Login Form
180
+
181
+ ```markdown
182
+ [-- Login
183
+ # Welcome Back
184
+ Email ___
185
+ Password __*
186
+ Remember me __[]
187
+ [(Login)][Forgot Password]
188
+ --]
189
+ ```
190
+
191
+ ### Multi-Step Form Workflow
192
+
193
+ ```markdown
194
+ [workflow
195
+ [screen step1
196
+ # Step 1: Personal Info
197
+ First Name ___ Last Name ___
198
+ Email ___
199
+ [(Next) -> step2]
200
+ ]
201
+ [screen step2
202
+ # Step 2: Address
203
+ Street ___
204
+ City ___ State ___
205
+ [(Back) -> step1][(Next) -> step3]
206
+ ]
207
+ [screen step3
208
+ # Step 3: Review
209
+ Please review your information
210
+ [(Submit) -> confirmation][(Back) -> step2]
211
+ ]
212
+ [screen confirmation
213
+ # Success!
214
+ Your form has been submitted.
215
+ [Start Over -> step1]
216
+ ]
217
+ ]
218
+ ```
219
+
220
+ ## Testing
221
+
222
+ ```bash
223
+ npm test # Run all tests
224
+ npm run build # Build the library
225
+ ```
226
+
227
+ ## License
228
+
229
+ Apache-2.0
230
+
231
+ ## Links
232
+
233
+ - [Proto Markdown Documentation](https://www.protomarkdown.org/documentation)
234
+ - [GitHub Repository](https://github.com/georgii-sh/proto-markdown-parser)
235
+ - [Shadcn UI](https://ui.shadcn.com/)
@@ -41,5 +41,7 @@ export declare class ShadcnCodeGenerator {
41
41
  private generateBold;
42
42
  private generateItalic;
43
43
  private generateImage;
44
+ private generateWorkflow;
45
+ private generateScreen;
44
46
  }
45
47
  //# sourceMappingURL=ShadcnCodeGenerator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ShadcnCodeGenerator.d.ts","sourceRoot":"","sources":["../src/ShadcnCodeGenerator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,eAAe,CAAqB;IAE5C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM;IA4BvC;;OAEG;IACH,OAAO,CAAC,eAAe;IAkCvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAuCpB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,YAAY;IAiCpB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;CAMtB"}
1
+ {"version":3,"file":"ShadcnCodeGenerator.d.ts","sourceRoot":"","sources":["../src/ShadcnCodeGenerator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,eAAe,CAAqB;IAE5C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM;IAgCvC;;OAEG;IACH,OAAO,CAAC,eAAe;IAkCvB;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAetB,OAAO,CAAC,kBAAkB;IAkB1B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,gBAAgB;IAmBxB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,YAAY;IAiCpB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,gBAAgB;IAoCxB,OAAO,CAAC,cAAc;CAWvB"}
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(/^(\[\(?[^\[\]|]+\)?\]\s*)+$/);
459
+ const multiButtonMatch = line.match(/^(\[\(?[^\[\]]+\)?\]\s*)+$/);
359
460
  if (multiButtonMatch) {
360
- const buttons = line.match(/\[(\(?)[^\[\]|]+?(\)?)\]/g);
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
- return `${imports}
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
- return `${this.indent()}<Button key={${index}} variant="${variant}"${className}>${this.escapeJSX(node.content || "")}</Button>`;
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,6 +961,46 @@ ${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
+ }
817
1004
  }
818
1005
 
819
1006
  export { MarkdownParser, ShadcnCodeGenerator };