multisite-cms-mcp 1.0.5 → 1.0.7

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.
@@ -1 +1 @@
1
- {"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAoe1H;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAkD1E"}
1
+ {"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAugB1H;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAkD1E"}
@@ -287,6 +287,38 @@ Inside {{#each}}:
287
287
  - \`{{@last}}\` - true for last item
288
288
  - \`{{@index}}\` - zero-based index
289
289
 
290
+ ## Parent Context (\`../\`)
291
+ Inside loops, access the parent scope:
292
+ \`\`\`html
293
+ {{#each blogs}}
294
+ {{#if (eq author.name ../name)}}
295
+ <!-- Only show posts by current author -->
296
+ <h3>{{name}}</h3>
297
+ {{/if}}
298
+ {{/each}}
299
+ \`\`\`
300
+
301
+ - \`../name\` - Parent item's name field
302
+ - \`../slug\` - Parent item's slug
303
+ - \`../fieldName\` - Any field from parent scope
304
+
305
+ **Use cases:**
306
+ - Author pages: filter posts by current author
307
+ - Category pages: filter items by current category
308
+ - Related items: match based on current page
309
+
310
+ ## Equality Comparisons
311
+ Compare two values:
312
+ \`\`\`html
313
+ {{#if (eq author.name ../name)}}
314
+ <!-- True when fields match -->
315
+ {{/if}}
316
+
317
+ {{#eq status "published"}}
318
+ <!-- Compare to literal string -->
319
+ {{/eq}}
320
+ \`\`\`
321
+
290
322
  ## Collection Fields
291
323
 
292
324
  **Blog Posts:**
@@ -331,33 +363,34 @@ Forms are automatically captured by the CMS.
331
363
 
332
364
  ## Form Handler Script
333
365
 
334
- Add to your JavaScript:
366
+ Add to your JavaScript (typically /public/js/main.js):
335
367
 
336
368
  \`\`\`javascript
337
369
  document.querySelectorAll('form[data-form-name]').forEach(form => {
338
370
  form.addEventListener('submit', async (e) => {
339
371
  e.preventDefault();
340
372
 
373
+ const formName = form.dataset.formName || 'general';
341
374
  const formData = new FormData(form);
342
375
  const data = Object.fromEntries(formData);
343
376
 
344
- const response = await fetch('/api/forms/submit', {
377
+ // IMPORTANT: Endpoint is /_forms/{formName}
378
+ const response = await fetch('/_forms/' + formName, {
345
379
  method: 'POST',
346
380
  headers: { 'Content-Type': 'application/json' },
347
- body: JSON.stringify({
348
- formName: form.dataset.formName,
349
- data: data
350
- })
381
+ body: JSON.stringify(data)
351
382
  });
352
383
 
353
384
  if (response.ok) {
354
385
  form.reset();
355
- alert('Thank you!');
386
+ alert(form.dataset.successMessage || 'Thank you!');
356
387
  }
357
388
  });
358
389
  });
359
390
  \`\`\`
360
391
 
392
+ **CRITICAL:** The form endpoint is \`/_forms/{formName}\` - NOT \`/api/forms/submit\`
393
+
361
394
  ## Naming Conventions
362
395
 
363
396
  - Contact form → \`contact\`
@@ -444,7 +477,9 @@ Keep external URLs unchanged:
444
477
  - [ ] Templates include header/footer
445
478
  - [ ] {{#each}} loops have {{/each}}
446
479
  - [ ] {{#if}} conditions have {{/if}}
480
+ - [ ] {{#eq}} comparisons have {{/eq}}
447
481
  - [ ] Rich text uses {{{triple braces}}}
482
+ - [ ] Parent refs (../) only inside loops
448
483
  - [ ] Correct field names used
449
484
 
450
485
  ## ✓ Field Names
@@ -1,4 +1,4 @@
1
- type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts';
1
+ type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison';
2
2
  /**
3
3
  * Returns example code for a specific pattern
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,CAAC;AA0qBrB;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
1
+ {"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,CAAC;AAqxB1B;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
@@ -664,6 +664,111 @@ Make text editable in the CMS visual editor:
664
664
  </section>
665
665
  {{/each}}
666
666
  \`\`\``,
667
+ parent_context: `# Parent Context References (\`../\`)
668
+
669
+ Inside loops, access the **parent scope** (the page's current item) using \`../\`:
670
+
671
+ **Use Case: Author Detail Page - Show Only This Author's Posts**
672
+ \`\`\`html
673
+ <article class="author-detail">
674
+ <h1>{{name}}</h1>
675
+ <p class="bio">{{{bio}}}</p>
676
+
677
+ <section class="author-articles">
678
+ <h2>Posts by {{name}}</h2>
679
+
680
+ {{#each blogs}}
681
+ {{#if (eq author.name ../name)}}
682
+ <article class="post-card">
683
+ <h3><a href="{{url}}">{{name}}</a></h3>
684
+ <p>{{postSummary}}</p>
685
+ <time>{{publishedAt}}</time>
686
+ </article>
687
+ {{/if}}
688
+ {{/each}}
689
+ </section>
690
+ </article>
691
+ \`\`\`
692
+
693
+ **How It Works:**
694
+ - Inside \`{{#each blogs}}\`, the context is each blog post
695
+ - \`author.name\` = the blog post's author
696
+ - \`../name\` = the parent context (the author being displayed on the page)
697
+ - Only posts where author.name matches ../name are shown
698
+
699
+ **Use Case: Category Page - Highlight Current Category**
700
+ \`\`\`html
701
+ <nav class="category-nav">
702
+ {{#each categories}}
703
+ <a href="{{url}}" class="{{#if (eq slug ../slug)}}active{{/if}}">
704
+ {{name}}
705
+ </a>
706
+ {{/each}}
707
+ </nav>
708
+
709
+ <div class="category-content">
710
+ <h1>{{name}}</h1>
711
+
712
+ {{#each items}}
713
+ {{#if (eq category ../slug)}}
714
+ <div class="item">{{name}}</div>
715
+ {{/if}}
716
+ {{/each}}
717
+ </div>
718
+ \`\`\`
719
+
720
+ **Available Parent Fields:**
721
+ - \`../name\` - Parent item's name
722
+ - \`../slug\` - Parent item's slug
723
+ - \`../fieldName\` - Any field from the parent item
724
+ - \`../nested.field\` - Nested field access in parent`,
725
+ equality_comparison: `# Equality Comparisons
726
+
727
+ Compare two values using \`(eq field1 field2)\` helper:
728
+
729
+ **Compare Two Fields:**
730
+ \`\`\`html
731
+ {{#if (eq author.slug ../slug)}}
732
+ <span class="current-author-badge">✓ Your Post</span>
733
+ {{/if}}
734
+
735
+ {{#if (eq category selectedCategory)}}
736
+ <div class="active">{{name}}</div>
737
+ {{/if}}
738
+ \`\`\`
739
+
740
+ **Compare Field to Literal String (use {{#eq}}):**
741
+ \`\`\`html
742
+ {{#eq status "published"}}
743
+ <span class="badge badge-success">Published</span>
744
+ {{/eq}}
745
+
746
+ {{#eq type "featured"}}
747
+ <div class="featured-highlight">⭐ {{name}}</div>
748
+ {{/eq}}
749
+
750
+ {{#eq category "news"}}
751
+ <span class="news-icon">📰</span>
752
+ {{/eq}}
753
+ \`\`\`
754
+
755
+ **Inside Loops with Parent Context:**
756
+ \`\`\`html
757
+ {{#each blogs}}
758
+ <article class="{{#if (eq author.name ../name)}}highlight{{/if}}">
759
+ <h3>{{name}}</h3>
760
+ {{#if (eq author.name ../name)}}
761
+ <span class="yours">You wrote this!</span>
762
+ {{/if}}
763
+ </article>
764
+ {{/each}}
765
+ \`\`\`
766
+
767
+ **Common Use Cases:**
768
+ - Filter items by current category/author/tag
769
+ - Highlight active menu items
770
+ - Show badges for specific statuses
771
+ - Conditional styling based on relationships`,
667
772
  };
668
773
  /**
669
774
  * Returns example code for a specific pattern
@@ -1 +1 @@
1
- {"version":3,"file":"get-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CA6IjD"}
1
+ {"version":3,"file":"get-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAmOjD"}
@@ -128,6 +128,40 @@ Inside \`{{#each}}\` blocks:
128
128
  {{/each}}
129
129
  \`\`\`
130
130
 
131
+ ### Parent Context References (\`../\`)
132
+ Inside loops, access the parent scope (page's current item) using \`../\`:
133
+
134
+ \`\`\`html
135
+ <!-- On author detail page, show only posts by THIS author -->
136
+ {{#each blogs}}
137
+ {{#if (eq author.name ../name)}}
138
+ <h3>{{name}}</h3>
139
+ {{/if}}
140
+ {{/each}}
141
+ \`\`\`
142
+
143
+ - \`../name\` - Parent item's name field
144
+ - \`../slug\` - Parent item's slug
145
+ - \`../fieldName\` - Any field from the parent scope
146
+
147
+ **Use cases:**
148
+ - Author pages: filter posts by current author
149
+ - Category pages: filter items by current category
150
+ - Related items: match based on current detail page
151
+
152
+ ### Equality Comparisons
153
+ Compare two values in conditionals:
154
+
155
+ \`\`\`html
156
+ {{#if (eq author.name ../name)}}
157
+ <!-- True when fields match -->
158
+ {{/if}}
159
+
160
+ {{#eq status "published"}}
161
+ <!-- Compare field to literal string -->
162
+ {{/eq}}
163
+ \`\`\`
164
+
131
165
  ### Rich Text (Triple Braces)
132
166
  For HTML content that should NOT be escaped:
133
167
  \`\`\`html
@@ -144,5 +178,57 @@ For HTML content that should NOT be escaped:
144
178
  3. **Always wrap optional fields in {{#if}}** - Check before rendering
145
179
  4. **Use {{url}} for links** - Generates correct path based on manifest
146
180
  5. **Match field names exactly** - \`{{name}}\` not \`{{title}}\`
181
+
182
+ ---
183
+
184
+ ## Form Handling
185
+
186
+ Forms are automatically captured and stored in the CMS.
187
+
188
+ ### Form Setup
189
+ 1. Add \`data-form-name="xxx"\` attribute to the form
190
+ 2. Include the form handler script in your JavaScript
191
+
192
+ \`\`\`html
193
+ <form data-form-name="contact">
194
+ <input type="text" name="firstName" required>
195
+ <input type="email" name="email" required>
196
+ <textarea name="message"></textarea>
197
+ <button type="submit">Send</button>
198
+ </form>
199
+ \`\`\`
200
+
201
+ ### Form Handler Script (add to /public/js/main.js)
202
+ \`\`\`javascript
203
+ document.querySelectorAll('form[data-form-name]').forEach(form => {
204
+ form.addEventListener('submit', async (e) => {
205
+ e.preventDefault();
206
+ const formName = form.dataset.formName || 'general';
207
+ const formData = new FormData(form);
208
+ const data = Object.fromEntries(formData);
209
+
210
+ const response = await fetch('/_forms/' + formName, {
211
+ method: 'POST',
212
+ headers: { 'Content-Type': 'application/json' },
213
+ body: JSON.stringify(data)
214
+ });
215
+
216
+ if (response.ok) {
217
+ form.reset();
218
+ alert(form.dataset.successMessage || 'Thank you!');
219
+ }
220
+ });
221
+ });
222
+ \`\`\`
223
+
224
+ **CRITICAL:** Endpoint is \`/_forms/{formName}\` - NOT \`/api/forms/submit\`
225
+
226
+ ### Common Form Names
227
+ - \`contact\` - Contact/inquiry forms
228
+ - \`newsletter\` - Email signups
229
+ - \`quote-request\` - Quote/consultation requests
230
+ - \`appointment\` - Scheduling forms
231
+
232
+ Tenant context is handled automatically by the system.
147
233
  `;
148
234
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AA0E7J;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CAgNjB"}
1
+ {"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AA0E7J;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CA+OjB"}
@@ -208,6 +208,34 @@ async function validateTemplate(html, templateType, collectionSlug) {
208
208
  if (siteTokens.length > 0) {
209
209
  suggestions.push(`- Found ${siteTokens.length} site token(s) like {{site.site_name}} - these come from manifest.json`);
210
210
  }
211
+ // Validate parent context references (../)
212
+ const parentRefs = html.match(/\{\{\.\.\/([\w.]+)\}\}/g) || [];
213
+ if (parentRefs.length > 0) {
214
+ // Check if they're used inside a loop
215
+ if (eachLoops.length === 0) {
216
+ warnings.push(`- Found ${parentRefs.length} parent reference(s) like {{../name}} but no {{#each}} loop - these only work inside loops`);
217
+ }
218
+ else {
219
+ suggestions.push(`- Found ${parentRefs.length} parent context reference(s) ({{../fieldName}}) - accesses parent scope in loops`);
220
+ }
221
+ }
222
+ // Validate equality helper syntax
223
+ const eqHelpers = html.match(/\{\{#if\s+\(eq\s+[^)]+\)\s*\}\}/g) || [];
224
+ if (eqHelpers.length > 0) {
225
+ suggestions.push(`- Found ${eqHelpers.length} equality comparison(s) like {{#if (eq field1 field2)}} - compares two values`);
226
+ // Check if closing {{/if}} exists for each
227
+ for (const helper of eqHelpers) {
228
+ if (!html.includes('{{/if}}')) {
229
+ errors.push(`- Missing {{/if}} to close: ${helper}`);
230
+ }
231
+ }
232
+ }
233
+ // Validate {{#eq}} blocks
234
+ const eqBlocks = html.match(/\{\{#eq\s+[\w.]+\s+"[^"]+"\s*\}\}/g) || [];
235
+ const eqCloses = (html.match(/\{\{\/eq\}\}/g) || []).length;
236
+ if (eqBlocks.length !== eqCloses) {
237
+ errors.push(`- Unbalanced {{#eq}}: ${eqBlocks.length} opens, ${eqCloses} closes`);
238
+ }
211
239
  // Build result
212
240
  let output = '';
213
241
  if (errors.length === 0 && warnings.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multisite-cms-mcp",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "MCP server for AI-assisted website conversion to CMS format. Provides validation, examples, and schema tools.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {