multisite-cms-mcp 1.2.4 → 1.3.0
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 +5 -6
- package/dist/tools/get-conversion-guide.d.ts.map +1 -1
- package/dist/tools/get-conversion-guide.js +134 -295
- package/dist/tools/get-example.d.ts.map +1 -1
- package/dist/tools/get-example.js +166 -409
- package/dist/tools/get-schema.d.ts +1 -1
- package/dist/tools/get-schema.d.ts.map +1 -1
- package/dist/tools/get-schema.js +130 -450
- package/dist/tools/get-tenant-schema.d.ts +9 -3
- package/dist/tools/get-tenant-schema.d.ts.map +1 -1
- package/dist/tools/get-tenant-schema.js +16 -4
- package/dist/tools/sync-schema.d.ts +1 -1
- package/dist/tools/sync-schema.d.ts.map +1 -1
- package/dist/tools/sync-schema.js +51 -97
- package/dist/tools/validate-manifest.d.ts.map +1 -1
- package/dist/tools/validate-manifest.js +26 -83
- package/dist/tools/validate-package.d.ts.map +1 -1
- package/dist/tools/validate-package.js +46 -41
- package/dist/tools/validate-template.d.ts +2 -2
- package/dist/tools/validate-template.d.ts.map +1 -1
- package/dist/tools/validate-template.js +67 -201
- package/package.json +1 -1
|
@@ -37,9 +37,9 @@ A minimal manifest with static pages only:
|
|
|
37
37
|
- \`file\` points to the HTML file in pages/ folder
|
|
38
38
|
- \`editable: true\` allows inline editing in the CMS
|
|
39
39
|
- \`defaultHeadHtml\` is injected into all pages`,
|
|
40
|
-
manifest_custom_paths: `# manifest.json with
|
|
40
|
+
manifest_custom_paths: `# manifest.json with CMS Collections
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Configure templates for your CMS collections:
|
|
43
43
|
|
|
44
44
|
\`\`\`json
|
|
45
45
|
{
|
|
@@ -58,26 +58,25 @@ When your site uses different URLs than the defaults:
|
|
|
58
58
|
}
|
|
59
59
|
],
|
|
60
60
|
"cmsTemplates": {
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"authorsPath": "/contributors"
|
|
61
|
+
"postsIndex": "templates/posts_index.html",
|
|
62
|
+
"postsIndexPath": "/blog",
|
|
63
|
+
"postsDetail": "templates/posts_detail.html",
|
|
64
|
+
"postsDetailPath": "/blog",
|
|
65
|
+
"teamIndex": "templates/team.html",
|
|
66
|
+
"teamIndexPath": "/team",
|
|
67
|
+
"servicesIndex": "templates/services.html",
|
|
68
|
+
"servicesIndexPath": "/services",
|
|
69
|
+
"servicesDetail": "templates/service-detail.html",
|
|
70
|
+
"servicesDetailPath": "/services"
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
\`\`\`
|
|
75
74
|
|
|
76
|
-
**Path Configuration:**
|
|
77
|
-
- \`
|
|
78
|
-
- \`
|
|
79
|
-
- \`
|
|
80
|
-
-
|
|
75
|
+
**Path Configuration Pattern:**
|
|
76
|
+
- \`{collectionSlug}Index\` - Template file for listing page
|
|
77
|
+
- \`{collectionSlug}IndexPath\` - URL for listing page
|
|
78
|
+
- \`{collectionSlug}Detail\` - Template file for detail page
|
|
79
|
+
- \`{collectionSlug}DetailPath\` - URL base for detail pages`,
|
|
81
80
|
manifest_minimal_with_ui: `# Minimal manifest.json (Configure Templates via Settings UI)
|
|
82
81
|
|
|
83
82
|
When you want to upload pages first and configure CMS templates later via the Settings UI:
|
|
@@ -115,21 +114,16 @@ When you want to upload pages first and configure CMS templates later via the Se
|
|
|
115
114
|
|
|
116
115
|
**After uploading this package:**
|
|
117
116
|
1. Go to Dashboard → Settings → CMS Templates
|
|
118
|
-
2.
|
|
119
|
-
3.
|
|
120
|
-
4. Set the Blog URL Path (e.g., /blog)
|
|
121
|
-
5. Click Save
|
|
117
|
+
2. Configure template mappings for your collections
|
|
118
|
+
3. Set URL paths
|
|
122
119
|
|
|
123
120
|
**Benefits of this approach:**
|
|
124
121
|
- Faster initial upload - no need to configure cmsTemplates in JSON
|
|
125
122
|
- Visual UI makes it easier to understand the configuration
|
|
126
|
-
- Can change template mappings without re-uploading the package
|
|
127
|
-
|
|
123
|
+
- Can change template mappings without re-uploading the package`,
|
|
124
|
+
blog_index_template: `# Blog/Posts Index Template
|
|
128
125
|
|
|
129
|
-
|
|
130
|
-
blog_index_template: `# Blog Index Template
|
|
131
|
-
|
|
132
|
-
Lists all blog posts with filtering options:
|
|
126
|
+
Lists all posts with featured section (using a "posts" collection):
|
|
133
127
|
|
|
134
128
|
\`\`\`html
|
|
135
129
|
<main class="blog-page">
|
|
@@ -139,14 +133,14 @@ Lists all blog posts with filtering options:
|
|
|
139
133
|
</header>
|
|
140
134
|
|
|
141
135
|
<!-- Featured Post (first featured article) -->
|
|
142
|
-
{{#each
|
|
136
|
+
{{#each posts featured=true limit=1}}
|
|
143
137
|
<article class="featured-post">
|
|
144
|
-
{{#if
|
|
145
|
-
<img src="{{
|
|
138
|
+
{{#if image}}
|
|
139
|
+
<img src="{{image}}" alt="{{name}}" class="featured-image">
|
|
146
140
|
{{/if}}
|
|
147
141
|
<div class="featured-content">
|
|
148
142
|
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
149
|
-
<p class="excerpt">{{
|
|
143
|
+
<p class="excerpt">{{summary}}</p>
|
|
150
144
|
<div class="meta">
|
|
151
145
|
{{#if author}}
|
|
152
146
|
<span class="author">By {{author.name}}</span>
|
|
@@ -159,18 +153,18 @@ Lists all blog posts with filtering options:
|
|
|
159
153
|
|
|
160
154
|
<!-- All Posts Grid -->
|
|
161
155
|
<div class="posts-grid">
|
|
162
|
-
{{#each
|
|
156
|
+
{{#each posts limit=12 sort="publishedAt" order="desc"}}
|
|
163
157
|
<article class="post-card">
|
|
164
|
-
{{#if
|
|
165
|
-
<img src="{{
|
|
158
|
+
{{#if thumbnail}}
|
|
159
|
+
<img src="{{thumbnail}}" alt="{{name}}">
|
|
166
160
|
{{else}}
|
|
167
|
-
{{#if
|
|
168
|
-
<img src="{{
|
|
161
|
+
{{#if image}}
|
|
162
|
+
<img src="{{image}}" alt="{{name}}">
|
|
169
163
|
{{/if}}
|
|
170
164
|
{{/if}}
|
|
171
165
|
<div class="card-content">
|
|
172
166
|
<h3><a href="{{url}}">{{name}}</a></h3>
|
|
173
|
-
<p>{{
|
|
167
|
+
<p>{{summary}}</p>
|
|
174
168
|
{{#if author}}
|
|
175
169
|
<span class="byline">{{author.name}}</span>
|
|
176
170
|
{{/if}}
|
|
@@ -178,22 +172,26 @@ Lists all blog posts with filtering options:
|
|
|
178
172
|
</article>
|
|
179
173
|
{{/each}}
|
|
180
174
|
</div>
|
|
175
|
+
|
|
176
|
+
{{#unless posts}}
|
|
177
|
+
<p>No posts yet. Check back soon!</p>
|
|
178
|
+
{{/unless}}
|
|
181
179
|
</main>
|
|
182
180
|
\`\`\`
|
|
183
181
|
|
|
184
182
|
**Key patterns:**
|
|
185
|
-
- \`{{#each
|
|
186
|
-
- \`{{#each
|
|
187
|
-
- Always wrap images in \`{{#if
|
|
183
|
+
- \`{{#each posts featured=true limit=1}}\` for featured post
|
|
184
|
+
- \`{{#each posts limit=12 sort="publishedAt" order="desc"}}\` for post grid
|
|
185
|
+
- Always wrap images in \`{{#if image}}\`
|
|
188
186
|
- Use \`{{url}}\` for links to post detail page`,
|
|
189
187
|
blog_post_template: `# Blog Post Detail Template
|
|
190
188
|
|
|
191
|
-
Single
|
|
189
|
+
Single post page with author information (using a "posts" collection with author relation):
|
|
192
190
|
|
|
193
191
|
\`\`\`html
|
|
194
192
|
<article class="blog-post">
|
|
195
|
-
{{#if
|
|
196
|
-
<img src="{{
|
|
193
|
+
{{#if image}}
|
|
194
|
+
<img src="{{image}}" alt="{{name}}" class="hero-image">
|
|
197
195
|
{{/if}}
|
|
198
196
|
|
|
199
197
|
<header class="post-header">
|
|
@@ -202,8 +200,8 @@ Single blog post with author information:
|
|
|
202
200
|
<div class="post-meta">
|
|
203
201
|
{{#if author}}
|
|
204
202
|
<div class="author-info">
|
|
205
|
-
{{#if author.
|
|
206
|
-
<img src="{{author.
|
|
203
|
+
{{#if author.photo}}
|
|
204
|
+
<img src="{{author.photo}}" alt="{{author.name}}" class="author-avatar">
|
|
207
205
|
{{/if}}
|
|
208
206
|
<a href="{{author.url}}" class="author-name">{{author.name}}</a>
|
|
209
207
|
</div>
|
|
@@ -212,30 +210,22 @@ Single blog post with author information:
|
|
|
212
210
|
</div>
|
|
213
211
|
</header>
|
|
214
212
|
|
|
215
|
-
{{#if
|
|
216
|
-
<p class="lead">{{
|
|
213
|
+
{{#if summary}}
|
|
214
|
+
<p class="lead">{{summary}}</p>
|
|
217
215
|
{{/if}}
|
|
218
216
|
|
|
219
217
|
<div class="post-content">
|
|
220
|
-
{{{
|
|
218
|
+
{{{body}}}
|
|
221
219
|
</div>
|
|
222
220
|
|
|
223
221
|
{{#if author}}
|
|
224
222
|
<aside class="author-box">
|
|
225
|
-
{{#if author.
|
|
226
|
-
<img src="{{author.
|
|
223
|
+
{{#if author.photo}}
|
|
224
|
+
<img src="{{author.photo}}" alt="{{author.name}}">
|
|
227
225
|
{{/if}}
|
|
228
226
|
<div class="author-details">
|
|
229
227
|
<h3>About <a href="{{author.url}}">{{author.name}}</a></h3>
|
|
230
228
|
{{{author.bio}}}
|
|
231
|
-
<div class="social-links">
|
|
232
|
-
{{#if author.twitterProfileLink}}
|
|
233
|
-
<a href="{{author.twitterProfileLink}}">Twitter</a>
|
|
234
|
-
{{/if}}
|
|
235
|
-
{{#if author.linkedinProfileLink}}
|
|
236
|
-
<a href="{{author.linkedinProfileLink}}">LinkedIn</a>
|
|
237
|
-
{{/if}}
|
|
238
|
-
</div>
|
|
239
229
|
</div>
|
|
240
230
|
</aside>
|
|
241
231
|
{{/if}}
|
|
@@ -243,11 +233,13 @@ Single blog post with author information:
|
|
|
243
233
|
\`\`\`
|
|
244
234
|
|
|
245
235
|
**IMPORTANT:**
|
|
246
|
-
- \`{{{
|
|
236
|
+
- \`{{{body}}}\` uses TRIPLE braces (contains HTML)
|
|
247
237
|
- \`{{{author.bio}}}\` uses TRIPLE braces (contains HTML)
|
|
248
238
|
- Everything else uses double braces`,
|
|
249
239
|
team_template: `# Team Members Template
|
|
250
240
|
|
|
241
|
+
Using a "team" collection with name, role, photo, bio, order fields:
|
|
242
|
+
|
|
251
243
|
\`\`\`html
|
|
252
244
|
<section class="team-section">
|
|
253
245
|
<header class="section-header">
|
|
@@ -279,9 +271,10 @@ Single blog post with author information:
|
|
|
279
271
|
|
|
280
272
|
**Key points:**
|
|
281
273
|
- \`sort="order" order="asc"\` maintains manual ordering
|
|
282
|
-
- \`{{{bio}}}\` uses triple braces for HTML content
|
|
283
|
-
|
|
284
|
-
|
|
274
|
+
- \`{{{bio}}}\` uses triple braces for HTML content`,
|
|
275
|
+
downloads_template: `# Downloads/Resources Template
|
|
276
|
+
|
|
277
|
+
Using a "downloads" collection with name, description, fileUrl, category fields:
|
|
285
278
|
|
|
286
279
|
\`\`\`html
|
|
287
280
|
<section class="downloads-section">
|
|
@@ -294,7 +287,7 @@ Single blog post with author information:
|
|
|
294
287
|
{{#each downloads sort="order" order="asc"}}
|
|
295
288
|
<div class="download-item">
|
|
296
289
|
<div class="download-info">
|
|
297
|
-
<h3>{{
|
|
290
|
+
<h3>{{name}}</h3>
|
|
298
291
|
{{#if description}}
|
|
299
292
|
<p>{{description}}</p>
|
|
300
293
|
{{/if}}
|
|
@@ -309,11 +302,11 @@ Single blog post with author information:
|
|
|
309
302
|
{{/each}}
|
|
310
303
|
</div>
|
|
311
304
|
</section>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
**Note:** Downloads uses \`{{title}}\` not \`{{name}}\``,
|
|
305
|
+
\`\`\``,
|
|
315
306
|
authors_template: `# Authors Index Template
|
|
316
307
|
|
|
308
|
+
Using an "authors" collection:
|
|
309
|
+
|
|
317
310
|
\`\`\`html
|
|
318
311
|
<section class="authors-section">
|
|
319
312
|
<header class="section-header">
|
|
@@ -323,8 +316,8 @@ Single blog post with author information:
|
|
|
323
316
|
<div class="authors-grid">
|
|
324
317
|
{{#each authors}}
|
|
325
318
|
<a href="{{url}}" class="author-card">
|
|
326
|
-
{{#if
|
|
327
|
-
<img src="{{
|
|
319
|
+
{{#if photo}}
|
|
320
|
+
<img src="{{photo}}" alt="{{name}}" class="author-photo">
|
|
328
321
|
{{/if}}
|
|
329
322
|
<h3>{{name}}</h3>
|
|
330
323
|
{{#if bio}}
|
|
@@ -337,11 +330,13 @@ Single blog post with author information:
|
|
|
337
330
|
\`\`\``,
|
|
338
331
|
author_detail_template: `# Author Detail Template
|
|
339
332
|
|
|
333
|
+
Using an "authors" collection with detail pages:
|
|
334
|
+
|
|
340
335
|
\`\`\`html
|
|
341
336
|
<article class="author-detail">
|
|
342
337
|
<header class="author-header">
|
|
343
|
-
{{#if
|
|
344
|
-
<img src="{{
|
|
338
|
+
{{#if photo}}
|
|
339
|
+
<img src="{{photo}}" alt="{{name}}" class="author-photo-large">
|
|
345
340
|
{{/if}}
|
|
346
341
|
<h1>{{name}}</h1>
|
|
347
342
|
|
|
@@ -349,11 +344,11 @@ Single blog post with author information:
|
|
|
349
344
|
{{#if email}}
|
|
350
345
|
<a href="mailto:{{email}}">Email</a>
|
|
351
346
|
{{/if}}
|
|
352
|
-
{{#if
|
|
353
|
-
<a href="{{
|
|
347
|
+
{{#if twitter}}
|
|
348
|
+
<a href="{{twitter}}">Twitter</a>
|
|
354
349
|
{{/if}}
|
|
355
|
-
{{#if
|
|
356
|
-
<a href="{{
|
|
350
|
+
{{#if linkedin}}
|
|
351
|
+
<a href="{{linkedin}}">LinkedIn</a>
|
|
357
352
|
{{/if}}
|
|
358
353
|
</div>
|
|
359
354
|
</header>
|
|
@@ -362,27 +357,30 @@ Single blog post with author information:
|
|
|
362
357
|
<div class="author-bio">{{{bio}}}</div>
|
|
363
358
|
{{/if}}
|
|
364
359
|
|
|
360
|
+
<!-- Show posts by this author (if you have a posts collection with author relation) -->
|
|
365
361
|
<section class="author-articles">
|
|
366
362
|
<h2>Articles by {{name}}</h2>
|
|
367
363
|
<div class="articles-grid">
|
|
368
|
-
{{#each
|
|
369
|
-
|
|
370
|
-
{{
|
|
371
|
-
|
|
364
|
+
{{#each posts}}
|
|
365
|
+
{{#if (eq author.name ../name)}}
|
|
366
|
+
<a href="{{url}}" class="article-card">
|
|
367
|
+
{{#if thumbnail}}
|
|
368
|
+
<img src="{{thumbnail}}" alt="{{name}}">
|
|
369
|
+
{{/if}}
|
|
370
|
+
<h3>{{name}}</h3>
|
|
371
|
+
<p>{{summary}}</p>
|
|
372
|
+
</a>
|
|
372
373
|
{{/if}}
|
|
373
|
-
<h3>{{name}}</h3>
|
|
374
|
-
<p>{{postSummary}}</p>
|
|
375
|
-
</a>
|
|
376
374
|
{{/each}}
|
|
377
375
|
</div>
|
|
378
376
|
</section>
|
|
379
377
|
</article>
|
|
380
378
|
\`\`\`
|
|
381
379
|
|
|
382
|
-
**
|
|
380
|
+
**Pattern:** \`{{#if (eq author.name ../name)}}\` filters posts by this author`,
|
|
383
381
|
custom_collection_template: `# Custom Collection Template
|
|
384
382
|
|
|
385
|
-
For user-defined
|
|
383
|
+
For any user-defined collection. All collections have built-in \`name\`, \`slug\`, \`url\`, and date fields.
|
|
386
384
|
|
|
387
385
|
## Index Template
|
|
388
386
|
|
|
@@ -408,6 +406,10 @@ For user-defined collections. All custom collections have mandatory \`name\` and
|
|
|
408
406
|
</article>
|
|
409
407
|
{{/each}}
|
|
410
408
|
</div>
|
|
409
|
+
|
|
410
|
+
{{#unless products}}
|
|
411
|
+
<p>No products yet.</p>
|
|
412
|
+
{{/unless}}
|
|
411
413
|
</section>
|
|
412
414
|
\`\`\`
|
|
413
415
|
|
|
@@ -433,38 +435,27 @@ For user-defined collections. All custom collections have mandatory \`name\` and
|
|
|
433
435
|
</article>
|
|
434
436
|
\`\`\`
|
|
435
437
|
|
|
436
|
-
##
|
|
438
|
+
## Built-in Tokens (Available on ALL items)
|
|
437
439
|
|
|
438
|
-
|
|
439
|
-
- \`{{
|
|
440
|
-
- \`{{slug}}\` - URL slug for detail pages
|
|
440
|
+
- \`{{name}}\` - Item name/title
|
|
441
|
+
- \`{{slug}}\` - URL slug
|
|
441
442
|
- \`{{url}}\` - Full URL to detail page
|
|
442
|
-
- \`{{publishedAt}}\` - Publish date
|
|
443
|
+
- \`{{publishedAt}}\` - Publish date
|
|
444
|
+
- \`{{createdAt}}\` - Creation date
|
|
445
|
+
- \`{{updatedAt}}\` - Last modified date
|
|
443
446
|
|
|
444
|
-
## In manifest.json
|
|
447
|
+
## In manifest.json
|
|
445
448
|
|
|
446
449
|
\`\`\`json
|
|
447
450
|
{
|
|
448
451
|
"cmsTemplates": {
|
|
449
|
-
"
|
|
450
|
-
"
|
|
451
|
-
"
|
|
452
|
-
"
|
|
452
|
+
"productsIndex": "templates/products_index.html",
|
|
453
|
+
"productsIndexPath": "/shop",
|
|
454
|
+
"productsDetail": "templates/products_detail.html",
|
|
455
|
+
"productsDetailPath": "/products"
|
|
453
456
|
}
|
|
454
457
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
**Path Configuration (flat format):**
|
|
458
|
-
- \`{slug}Index\` - Template file for the list page
|
|
459
|
-
- \`{slug}IndexPath\` - URL for the list page (e.g., /shop → lists all products)
|
|
460
|
-
- \`{slug}Detail\` - Template file for the detail page
|
|
461
|
-
- \`{slug}DetailPath\` - Base URL for detail pages (e.g., /product → /product/my-product-slug)
|
|
462
|
-
|
|
463
|
-
This serves:
|
|
464
|
-
- Collection index at \`/shop\` (lists all products using productIndex template)
|
|
465
|
-
- Detail pages at \`/product/product-slug\` (individual product using productDetail template)
|
|
466
|
-
|
|
467
|
-
**IMPORTANT:** The manifest's paths are the SINGLE SOURCE OF TRUTH. Collection slug is for database only.`,
|
|
458
|
+
\`\`\``,
|
|
468
459
|
form_handling: `# Form Handling
|
|
469
460
|
|
|
470
461
|
Forms are automatically captured by the CMS:
|
|
@@ -488,37 +479,38 @@ Forms are automatically captured by the CMS:
|
|
|
488
479
|
|
|
489
480
|
<button type="submit">Send Message</button>
|
|
490
481
|
</form>
|
|
482
|
+
\`\`\`
|
|
483
|
+
|
|
484
|
+
## Form Handler Script
|
|
491
485
|
|
|
492
|
-
|
|
486
|
+
\`\`\`javascript
|
|
493
487
|
document.querySelectorAll('form[data-form-name]').forEach(form => {
|
|
494
488
|
form.addEventListener('submit', async (e) => {
|
|
495
489
|
e.preventDefault();
|
|
496
490
|
|
|
491
|
+
const formName = form.dataset.formName || 'general';
|
|
497
492
|
const formData = new FormData(form);
|
|
498
493
|
const data = Object.fromEntries(formData);
|
|
499
494
|
|
|
500
|
-
|
|
495
|
+
// IMPORTANT: Endpoint is /_forms/{formName}
|
|
496
|
+
const response = await fetch('/_forms/' + formName, {
|
|
501
497
|
method: 'POST',
|
|
502
498
|
headers: { 'Content-Type': 'application/json' },
|
|
503
|
-
body: JSON.stringify(
|
|
504
|
-
formName: form.dataset.formName,
|
|
505
|
-
data: data
|
|
506
|
-
})
|
|
499
|
+
body: JSON.stringify(data)
|
|
507
500
|
});
|
|
508
501
|
|
|
509
502
|
if (response.ok) {
|
|
510
503
|
form.reset();
|
|
511
|
-
alert('Thank you!
|
|
504
|
+
alert(form.dataset.successMessage || 'Thank you!');
|
|
512
505
|
}
|
|
513
506
|
});
|
|
514
507
|
});
|
|
515
|
-
</script>
|
|
516
508
|
\`\`\`
|
|
517
509
|
|
|
518
510
|
**Key points:**
|
|
519
511
|
- Add \`data-form-name="xxx"\` to identify the form
|
|
520
|
-
-
|
|
521
|
-
-
|
|
512
|
+
- Endpoint is \`/_forms/{formName}\` (NOT \`/api/forms/submit\`)
|
|
513
|
+
- All \`name\` attributes are captured as fields`,
|
|
522
514
|
asset_paths: `# Asset Path Rules
|
|
523
515
|
|
|
524
516
|
**ALL asset paths must use /public/ prefix:**
|
|
@@ -569,78 +561,28 @@ Logos, icons, decorative backgrounds, UI elements - these are bundled with the s
|
|
|
569
561
|
\`\`\`
|
|
570
562
|
|
|
571
563
|
### 2. Content Images (Use CMS Tokens)
|
|
572
|
-
|
|
564
|
+
Post images, team photos, product images - these are managed through the CMS:
|
|
573
565
|
\`\`\`html
|
|
574
566
|
<!-- USE CMS tokens for content -->
|
|
575
|
-
<img src="{{
|
|
576
|
-
<img src="{{
|
|
567
|
+
<img src="{{image}}" alt="{{name}}">
|
|
568
|
+
<img src="{{thumbnail}}" alt="{{name}}">
|
|
577
569
|
<img src="{{photo}}" alt="{{name}}">
|
|
578
570
|
\`\`\`
|
|
579
571
|
|
|
580
572
|
---
|
|
581
573
|
|
|
582
|
-
## How to Decide: Static vs CMS Token
|
|
583
|
-
|
|
584
|
-
| Image Type | Use | Example |
|
|
585
|
-
|------------|-----|---------|
|
|
586
|
-
| Company logo | \`/public/images/logo.png\` | Header/footer logo |
|
|
587
|
-
| UI icons | \`/public/images/icons/...\` | Arrows, social icons |
|
|
588
|
-
| Decorative backgrounds | \`/public/images/...\` | Hero patterns, textures |
|
|
589
|
-
| Favicon | \`/public/images/favicon.ico\` | Browser tab icon |
|
|
590
|
-
| Blog post images | \`{{mainImage}}\` | Article hero image |
|
|
591
|
-
| Team member photos | \`{{photo}}\` | Staff headshots |
|
|
592
|
-
| Product images | \`{{productImage}}\` | E-commerce photos |
|
|
593
|
-
| User-uploaded content | \`{{fieldName}}\` | Any CMS-managed media |
|
|
594
|
-
|
|
595
|
-
**Rule of thumb:** If the image is part of the site's design/branding → static. If it's content that changes per item → CMS token.
|
|
596
|
-
|
|
597
|
-
---
|
|
598
|
-
|
|
599
|
-
## Content Images: Avoid Hardcoded Examples
|
|
600
|
-
|
|
601
|
-
When converting a static site with example blog posts or team members, replace the example content with CMS tokens:
|
|
602
|
-
|
|
603
|
-
**WRONG - Hardcoded example content:**
|
|
604
|
-
\`\`\`html
|
|
605
|
-
<div class="videos-grid">
|
|
606
|
-
<article class="video-card">
|
|
607
|
-
<img src="/images/example-video.jpg" alt="Example">
|
|
608
|
-
<h2>Example Video Title</h2>
|
|
609
|
-
<p>Example description text...</p>
|
|
610
|
-
</article>
|
|
611
|
-
</div>
|
|
612
|
-
\`\`\`
|
|
613
|
-
|
|
614
|
-
**CORRECT - CMS tokens with loop:**
|
|
615
|
-
\`\`\`html
|
|
616
|
-
<div class="videos-grid">
|
|
617
|
-
{{#each video sort="publishedAt" order="desc"}}
|
|
618
|
-
<article class="video-card">
|
|
619
|
-
{{#if videothumbnail}}
|
|
620
|
-
<img src="{{videothumbnail}}" alt="{{name}}">
|
|
621
|
-
{{/if}}
|
|
622
|
-
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
623
|
-
<p>{{shortdescription}}</p>
|
|
624
|
-
</article>
|
|
625
|
-
{{/each}}
|
|
626
|
-
</div>
|
|
627
|
-
\`\`\`
|
|
628
|
-
|
|
629
|
-
---
|
|
630
|
-
|
|
631
574
|
## Always Wrap Content Images in Conditionals
|
|
632
575
|
|
|
633
|
-
Since CMS images may not be uploaded, always check:
|
|
634
576
|
\`\`\`html
|
|
635
|
-
{{#if
|
|
636
|
-
<img src="{{
|
|
577
|
+
{{#if image}}
|
|
578
|
+
<img src="{{image}}" alt="{{name}}" class="hero-image">
|
|
637
579
|
{{/if}}
|
|
638
580
|
\`\`\`
|
|
639
581
|
|
|
640
|
-
Or provide a CSS fallback
|
|
582
|
+
Or provide a CSS fallback:
|
|
641
583
|
\`\`\`html
|
|
642
|
-
{{#if
|
|
643
|
-
<img src="{{
|
|
584
|
+
{{#if image}}
|
|
585
|
+
<img src="{{image}}" alt="{{name}}">
|
|
644
586
|
{{else}}
|
|
645
587
|
<div class="placeholder-image"></div>
|
|
646
588
|
{{/if}}
|
|
@@ -648,61 +590,14 @@ Or provide a CSS fallback (not a hardcoded placeholder image):
|
|
|
648
590
|
|
|
649
591
|
---
|
|
650
592
|
|
|
651
|
-
## Image Field Names by Collection
|
|
652
|
-
|
|
653
|
-
**Blog Posts:**
|
|
654
|
-
- \`{{mainImage}}\` - Hero/cover image
|
|
655
|
-
- \`{{thumbnailImage}}\` - Small preview image
|
|
656
|
-
|
|
657
|
-
**Team Members:**
|
|
658
|
-
- \`{{photo}}\` - Headshot
|
|
659
|
-
|
|
660
|
-
**Authors:**
|
|
661
|
-
- \`{{picture}}\` - Author photo
|
|
662
|
-
|
|
663
|
-
**Custom Collections:**
|
|
664
|
-
- **Token name = field slug** defined when creating the collection
|
|
665
|
-
- e.g., field slug \`videothumbnail\` → \`{{videothumbnail}}\`
|
|
666
|
-
- e.g., field slug \`ogimage\` → \`{{ogimage}}\`
|
|
667
|
-
- e.g., field slug \`productImage\` → \`{{productImage}}\`
|
|
668
|
-
|
|
669
|
-
**Use \`get_tenant_schema\` tool to see exact field slugs for a tenant's custom collections.**
|
|
670
|
-
|
|
671
|
-
---
|
|
672
|
-
|
|
673
|
-
## How CMS Images Work
|
|
674
|
-
|
|
675
|
-
1. User creates a custom collection with image fields (e.g., \`videothumbnail\`)
|
|
676
|
-
2. User uploads images via CMS dashboard
|
|
677
|
-
3. Template uses the field slug as the token: \`{{videothumbnail}}\`
|
|
678
|
-
4. CMS automatically provides the correct URL at runtime
|
|
679
|
-
|
|
680
|
-
---
|
|
681
|
-
|
|
682
593
|
## Common Mistakes
|
|
683
594
|
|
|
684
595
|
1. **Replacing logos with CMS tokens** - Keep static UI images as \`/public/\` paths
|
|
685
|
-
2. **Hardcoding example
|
|
686
|
-
3. **
|
|
687
|
-
4. **Missing conditionals** - Always wrap optional content images in \`{{#if}}\`
|
|
688
|
-
5. **Hardcoded placeholder images** - Use CSS placeholders, not image files`,
|
|
596
|
+
2. **Hardcoding example content images** - Use \`{{#each}}\` loops with CMS tokens
|
|
597
|
+
3. **Missing conditionals** - Always wrap optional content images in \`{{#if}}\``,
|
|
689
598
|
relation_fields: `# Relation Fields - Linking Collections
|
|
690
599
|
|
|
691
|
-
Relation fields let you link items from one collection to another
|
|
692
|
-
|
|
693
|
-
---
|
|
694
|
-
|
|
695
|
-
## Creating a Relation Field
|
|
696
|
-
|
|
697
|
-
In the CMS dashboard, when adding a custom field to a collection:
|
|
698
|
-
1. Choose field type: **Relation**
|
|
699
|
-
2. Select the target collection to link to (built-in or custom)
|
|
700
|
-
3. The field will store a reference to an item from that collection
|
|
701
|
-
|
|
702
|
-
**Example: Adding a "Category" relation field to a Projects collection**
|
|
703
|
-
- Create a "Categories" custom collection with items like "Web Design", "Branding", "Mobile"
|
|
704
|
-
- Add a relation field called "category" to Projects, linked to Categories
|
|
705
|
-
- Each project can now be assigned a category
|
|
600
|
+
Relation fields let you link items from one collection to another.
|
|
706
601
|
|
|
707
602
|
---
|
|
708
603
|
|
|
@@ -730,11 +625,8 @@ When a relation field is set, access the related item's data using dot notation:
|
|
|
730
625
|
|
|
731
626
|
## Available Relation Tokens
|
|
732
627
|
|
|
733
|
-
All relation fields expose these tokens:
|
|
734
|
-
|
|
735
628
|
| Token | Description |
|
|
736
629
|
|-------|-------------|
|
|
737
|
-
| \`{{relationField.id}}\` | Related item's ID |
|
|
738
630
|
| \`{{relationField.name}}\` | Related item's name |
|
|
739
631
|
| \`{{relationField.slug}}\` | Related item's URL slug |
|
|
740
632
|
| \`{{relationField.url}}\` | Full URL to related item's detail page |
|
|
@@ -742,29 +634,11 @@ All relation fields expose these tokens:
|
|
|
742
634
|
|
|
743
635
|
---
|
|
744
636
|
|
|
745
|
-
##
|
|
746
|
-
|
|
747
|
-
### Tags/Categories on Blog Posts
|
|
748
|
-
|
|
749
|
-
\`\`\`html
|
|
750
|
-
{{#each blogs limit=10 sort="publishedAt" order="desc"}}
|
|
751
|
-
<article class="blog-card">
|
|
752
|
-
<h2>{{name}}</h2>
|
|
753
|
-
{{#if tag}}
|
|
754
|
-
<span class="tag">{{tag.name}}</span>
|
|
755
|
-
{{/if}}
|
|
756
|
-
<p>{{postSummary}}</p>
|
|
757
|
-
<a href="{{url}}">Read more</a>
|
|
758
|
-
</article>
|
|
759
|
-
{{/each}}
|
|
760
|
-
\`\`\`
|
|
761
|
-
|
|
762
|
-
### Category/Tag Detail Pages - Filter by Relation
|
|
637
|
+
## Filter by Relation on Detail Pages
|
|
763
638
|
|
|
764
639
|
On a category detail page, show only items with that category:
|
|
765
640
|
|
|
766
641
|
\`\`\`html
|
|
767
|
-
<!-- templates/category-detail.html -->
|
|
768
642
|
<h1>{{name}}</h1>
|
|
769
643
|
<p>Projects in this category:</p>
|
|
770
644
|
|
|
@@ -780,50 +654,13 @@ On a category detail page, show only items with that category:
|
|
|
780
654
|
</div>
|
|
781
655
|
\`\`\`
|
|
782
656
|
|
|
783
|
-
### Related Service on Project
|
|
784
|
-
|
|
785
|
-
\`\`\`html
|
|
786
|
-
<div class="project-detail">
|
|
787
|
-
<h1>{{name}}</h1>
|
|
788
|
-
{{{description}}}
|
|
789
|
-
|
|
790
|
-
{{#if relatedService}}
|
|
791
|
-
<aside class="service-highlight">
|
|
792
|
-
<h3>Service Used</h3>
|
|
793
|
-
<a href="{{relatedService.url}}">
|
|
794
|
-
<strong>{{relatedService.name}}</strong>
|
|
795
|
-
</a>
|
|
796
|
-
{{#if relatedService.shortDescription}}
|
|
797
|
-
<p>{{relatedService.shortDescription}}</p>
|
|
798
|
-
{{/if}}
|
|
799
|
-
</aside>
|
|
800
|
-
{{/if}}
|
|
801
|
-
</div>
|
|
802
|
-
\`\`\`
|
|
803
|
-
|
|
804
|
-
---
|
|
805
|
-
|
|
806
|
-
## Target Collections
|
|
807
|
-
|
|
808
|
-
Relation fields can link to:
|
|
809
|
-
|
|
810
|
-
**Built-in Collections:**
|
|
811
|
-
- \`blogs\` - Blog posts
|
|
812
|
-
- \`authors\` - Content authors
|
|
813
|
-
- \`team\` - Team members
|
|
814
|
-
- \`downloads\` - Downloadable files
|
|
815
|
-
|
|
816
|
-
**Custom Collections:**
|
|
817
|
-
- Any custom collection you create (e.g., \`categories\`, \`tags\`, \`services\`, \`clients\`)
|
|
818
|
-
|
|
819
657
|
---
|
|
820
658
|
|
|
821
659
|
## Best Practices
|
|
822
660
|
|
|
823
661
|
1. **Always wrap in {{#if}}** - The relation may not be set for all items
|
|
824
662
|
2. **Use for categorization** - Tags, categories, content types
|
|
825
|
-
3. **Link related content** - Featured author, related service, parent category
|
|
826
|
-
4. **Filter on detail pages** - Use \`{{#if (eq relation.slug ../slug)}}\` to show related items`,
|
|
663
|
+
3. **Link related content** - Featured author, related service, parent category`,
|
|
827
664
|
data_edit_keys: `# Inline Editing with data-edit-key
|
|
828
665
|
|
|
829
666
|
Make text editable in the CMS visual editor:
|
|
@@ -854,31 +691,31 @@ Make text editable in the CMS visual editor:
|
|
|
854
691
|
|
|
855
692
|
**Basic loop:**
|
|
856
693
|
\`\`\`html
|
|
857
|
-
{{#each
|
|
694
|
+
{{#each posts}}
|
|
858
695
|
<article>
|
|
859
696
|
<h2>{{name}}</h2>
|
|
860
|
-
<p>{{
|
|
697
|
+
<p>{{summary}}</p>
|
|
861
698
|
</article>
|
|
862
699
|
{{/each}}
|
|
863
700
|
\`\`\`
|
|
864
701
|
|
|
865
702
|
**With limit:**
|
|
866
703
|
\`\`\`html
|
|
867
|
-
{{#each
|
|
704
|
+
{{#each posts limit=6}}
|
|
868
705
|
...
|
|
869
706
|
{{/each}}
|
|
870
707
|
\`\`\`
|
|
871
708
|
|
|
872
|
-
**Featured only:**
|
|
709
|
+
**Featured only (if collection has boolean "featured" field):**
|
|
873
710
|
\`\`\`html
|
|
874
|
-
{{#each
|
|
711
|
+
{{#each posts featured=true}}
|
|
875
712
|
...
|
|
876
713
|
{{/each}}
|
|
877
714
|
\`\`\`
|
|
878
715
|
|
|
879
716
|
**Combined:**
|
|
880
717
|
\`\`\`html
|
|
881
|
-
{{#each
|
|
718
|
+
{{#each posts featured=true limit=3}}
|
|
882
719
|
...
|
|
883
720
|
{{/each}}
|
|
884
721
|
\`\`\`
|
|
@@ -892,7 +729,7 @@ Make text editable in the CMS visual editor:
|
|
|
892
729
|
|
|
893
730
|
**Loop variables:**
|
|
894
731
|
\`\`\`html
|
|
895
|
-
{{#each
|
|
732
|
+
{{#each posts limit=5}}
|
|
896
733
|
<article class="{{#if @first}}featured{{/if}} {{#if @last}}last{{/if}}">
|
|
897
734
|
<span class="position">Item {{@index}}</span>
|
|
898
735
|
<h2>{{name}}</h2>
|
|
@@ -907,15 +744,15 @@ Make text editable in the CMS visual editor:
|
|
|
907
744
|
|
|
908
745
|
**{{#if}} - Render if truthy:**
|
|
909
746
|
\`\`\`html
|
|
910
|
-
{{#if
|
|
911
|
-
<img src="{{
|
|
747
|
+
{{#if image}}
|
|
748
|
+
<img src="{{image}}" alt="{{name}}">
|
|
912
749
|
{{/if}}
|
|
913
750
|
\`\`\`
|
|
914
751
|
|
|
915
752
|
**{{#if}} with {{else}}:**
|
|
916
753
|
\`\`\`html
|
|
917
|
-
{{#if
|
|
918
|
-
<img src="{{
|
|
754
|
+
{{#if thumbnail}}
|
|
755
|
+
<img src="{{thumbnail}}" alt="{{name}}">
|
|
919
756
|
{{else}}
|
|
920
757
|
<div class="placeholder">No image</div>
|
|
921
758
|
{{/if}}
|
|
@@ -932,8 +769,8 @@ Make text editable in the CMS visual editor:
|
|
|
932
769
|
\`\`\`html
|
|
933
770
|
{{#if author}}
|
|
934
771
|
<div class="author-info">
|
|
935
|
-
{{#if author.
|
|
936
|
-
<img src="{{author.
|
|
772
|
+
{{#if author.photo}}
|
|
773
|
+
<img src="{{author.photo}}" alt="{{author.name}}">
|
|
937
774
|
{{/if}}
|
|
938
775
|
<span>{{author.name}}</span>
|
|
939
776
|
</div>
|
|
@@ -942,24 +779,25 @@ Make text editable in the CMS visual editor:
|
|
|
942
779
|
|
|
943
780
|
**Multiple conditions (check both):**
|
|
944
781
|
\`\`\`html
|
|
945
|
-
{{#if
|
|
782
|
+
{{#if image}}
|
|
946
783
|
{{#if featured}}
|
|
947
|
-
<img src="{{
|
|
784
|
+
<img src="{{image}}" class="featured-image">
|
|
948
785
|
{{/if}}
|
|
949
786
|
{{/if}}
|
|
950
787
|
\`\`\``,
|
|
951
|
-
nested_fields: `# Nested Field Access
|
|
788
|
+
nested_fields: `# Nested Field Access (Relation Fields)
|
|
789
|
+
|
|
790
|
+
Access related item fields using dot notation:
|
|
952
791
|
|
|
953
|
-
**Author fields in blog templates:**
|
|
954
792
|
\`\`\`html
|
|
955
|
-
{{#each
|
|
793
|
+
{{#each posts}}
|
|
956
794
|
<article>
|
|
957
795
|
<h2>{{name}}</h2>
|
|
958
796
|
|
|
959
797
|
{{#if author}}
|
|
960
798
|
<div class="author">
|
|
961
|
-
{{#if author.
|
|
962
|
-
<img src="{{author.
|
|
799
|
+
{{#if author.photo}}
|
|
800
|
+
<img src="{{author.photo}}" alt="{{author.name}}">
|
|
963
801
|
{{/if}}
|
|
964
802
|
<span>By {{author.name}}</span>
|
|
965
803
|
|
|
@@ -972,17 +810,14 @@ Make text editable in the CMS visual editor:
|
|
|
972
810
|
{{/each}}
|
|
973
811
|
\`\`\`
|
|
974
812
|
|
|
975
|
-
**Available nested author
|
|
813
|
+
**Available nested tokens (for a relation field named "author"):**
|
|
976
814
|
- \`{{author.name}}\`
|
|
977
815
|
- \`{{author.slug}}\`
|
|
978
|
-
- \`{{author.picture}}\`
|
|
979
|
-
- \`{{{author.bio}}}\` (triple braces!)
|
|
980
|
-
- \`{{author.email}}\`
|
|
981
|
-
- \`{{author.twitterProfileLink}}\`
|
|
982
|
-
- \`{{author.linkedinProfileLink}}\`
|
|
983
816
|
- \`{{author.url}}\`
|
|
817
|
+
- \`{{{author.bio}}}\` (triple braces for richText!)
|
|
818
|
+
- Any other field from the related collection
|
|
984
819
|
|
|
985
|
-
**Always wrap in {{#if author}}** to handle
|
|
820
|
+
**Always wrap in {{#if author}}** to handle items without the relation set`,
|
|
986
821
|
featured_posts: `# Featured Posts Section
|
|
987
822
|
|
|
988
823
|
**Homepage featured posts (3 most recent):**
|
|
@@ -990,14 +825,14 @@ Make text editable in the CMS visual editor:
|
|
|
990
825
|
<section class="featured-posts">
|
|
991
826
|
<h2 data-edit-key="home-blog-title">Latest News</h2>
|
|
992
827
|
|
|
993
|
-
{{#each
|
|
828
|
+
{{#each posts featured=true limit=3}}
|
|
994
829
|
<article class="featured-card {{#if @first}}large{{/if}}">
|
|
995
|
-
{{#if
|
|
996
|
-
<img src="{{
|
|
830
|
+
{{#if image}}
|
|
831
|
+
<img src="{{image}}" alt="{{name}}">
|
|
997
832
|
{{/if}}
|
|
998
833
|
<div class="card-content">
|
|
999
834
|
<h3><a href="{{url}}">{{name}}</a></h3>
|
|
1000
|
-
<p>{{
|
|
835
|
+
<p>{{summary}}</p>
|
|
1001
836
|
{{#if author}}
|
|
1002
837
|
<span class="byline">By {{author.name}}</span>
|
|
1003
838
|
{{/if}}
|
|
@@ -1006,28 +841,11 @@ Make text editable in the CMS visual editor:
|
|
|
1006
841
|
</article>
|
|
1007
842
|
{{/each}}
|
|
1008
843
|
|
|
1009
|
-
<a href="/blog" class="view-all">View All Posts
|
|
844
|
+
<a href="/blog" class="view-all">View All Posts</a>
|
|
1010
845
|
</section>
|
|
1011
846
|
\`\`\`
|
|
1012
847
|
|
|
1013
|
-
**
|
|
1014
|
-
\`\`\`html
|
|
1015
|
-
{{#each blogs featured=true limit=1}}
|
|
1016
|
-
<section class="hero-post">
|
|
1017
|
-
{{#if mainImage}}
|
|
1018
|
-
<div class="hero-image" style="background-image: url('{{mainImage}}')">
|
|
1019
|
-
{{/if}}
|
|
1020
|
-
<div class="hero-content">
|
|
1021
|
-
<span class="label">Featured</span>
|
|
1022
|
-
<h1><a href="{{url}}">{{name}}</a></h1>
|
|
1023
|
-
<p>{{postSummary}}</p>
|
|
1024
|
-
</div>
|
|
1025
|
-
{{#if mainImage}}
|
|
1026
|
-
</div>
|
|
1027
|
-
{{/if}}
|
|
1028
|
-
</section>
|
|
1029
|
-
{{/each}}
|
|
1030
|
-
\`\`\``,
|
|
848
|
+
**Note:** Requires a boolean "featured" field on your collection`,
|
|
1031
849
|
parent_context: `# Parent Context References (\`../\`)
|
|
1032
850
|
|
|
1033
851
|
Inside loops, access the **parent scope** (the page's current item) using \`../\`:
|
|
@@ -1041,11 +859,11 @@ Inside loops, access the **parent scope** (the page's current item) using \`../\
|
|
|
1041
859
|
<section class="author-articles">
|
|
1042
860
|
<h2>Posts by {{name}}</h2>
|
|
1043
861
|
|
|
1044
|
-
{{#each
|
|
862
|
+
{{#each posts}}
|
|
1045
863
|
{{#if (eq author.name ../name)}}
|
|
1046
864
|
<article class="post-card">
|
|
1047
865
|
<h3><a href="{{url}}">{{name}}</a></h3>
|
|
1048
|
-
<p>{{
|
|
866
|
+
<p>{{summary}}</p>
|
|
1049
867
|
<time>{{publishedAt}}</time>
|
|
1050
868
|
</article>
|
|
1051
869
|
{{/if}}
|
|
@@ -1055,8 +873,8 @@ Inside loops, access the **parent scope** (the page's current item) using \`../\
|
|
|
1055
873
|
\`\`\`
|
|
1056
874
|
|
|
1057
875
|
**How It Works:**
|
|
1058
|
-
- Inside \`{{#each
|
|
1059
|
-
- \`author.name\` = the
|
|
876
|
+
- Inside \`{{#each posts}}\`, the context is each post
|
|
877
|
+
- \`author.name\` = the post's author
|
|
1060
878
|
- \`../name\` = the parent context (the author being displayed on the page)
|
|
1061
879
|
- Only posts where author.name matches ../name are shown
|
|
1062
880
|
|
|
@@ -1069,23 +887,12 @@ Inside loops, access the **parent scope** (the page's current item) using \`../\
|
|
|
1069
887
|
</a>
|
|
1070
888
|
{{/each}}
|
|
1071
889
|
</nav>
|
|
1072
|
-
|
|
1073
|
-
<div class="category-content">
|
|
1074
|
-
<h1>{{name}}</h1>
|
|
1075
|
-
|
|
1076
|
-
{{#each items}}
|
|
1077
|
-
{{#if (eq category ../slug)}}
|
|
1078
|
-
<div class="item">{{name}}</div>
|
|
1079
|
-
{{/if}}
|
|
1080
|
-
{{/each}}
|
|
1081
|
-
</div>
|
|
1082
890
|
\`\`\`
|
|
1083
891
|
|
|
1084
892
|
**Available Parent Fields:**
|
|
1085
893
|
- \`../name\` - Parent item's name
|
|
1086
894
|
- \`../slug\` - Parent item's slug
|
|
1087
|
-
- \`../fieldName\` - Any field from the parent item
|
|
1088
|
-
- \`../nested.field\` - Nested field access in parent`,
|
|
895
|
+
- \`../fieldName\` - Any field from the parent item`,
|
|
1089
896
|
equality_comparison: `# Equality Comparisons
|
|
1090
897
|
|
|
1091
898
|
Compare two values using \`(eq field1 field2)\` helper:
|
|
@@ -1093,7 +900,7 @@ Compare two values using \`(eq field1 field2)\` helper:
|
|
|
1093
900
|
## Show When Equal ({{#if (eq ...)}})
|
|
1094
901
|
\`\`\`html
|
|
1095
902
|
{{#if (eq author.slug ../slug)}}
|
|
1096
|
-
<span class="current-author-badge"
|
|
903
|
+
<span class="current-author-badge">Your Post</span>
|
|
1097
904
|
{{/if}}
|
|
1098
905
|
\`\`\`
|
|
1099
906
|
|
|
@@ -1101,13 +908,13 @@ Compare two values using \`(eq field1 field2)\` helper:
|
|
|
1101
908
|
The most common pattern for "Related Posts" or "Other Items" sections:
|
|
1102
909
|
|
|
1103
910
|
\`\`\`html
|
|
1104
|
-
<!-- On a
|
|
911
|
+
<!-- On a post page, show other posts EXCEPT the current one -->
|
|
1105
912
|
<h3>Related Posts</h3>
|
|
1106
|
-
{{#each
|
|
913
|
+
{{#each posts limit=3}}
|
|
1107
914
|
{{#unless (eq slug ../slug)}}
|
|
1108
915
|
<article>
|
|
1109
916
|
<a href="{{url}}">{{name}}</a>
|
|
1110
|
-
<p>{{
|
|
917
|
+
<p>{{summary}}</p>
|
|
1111
918
|
</article>
|
|
1112
919
|
{{/unless}}
|
|
1113
920
|
{{/each}}
|
|
@@ -1126,38 +933,20 @@ The most common pattern for "Related Posts" or "Other Items" sections:
|
|
|
1126
933
|
{{/eq}}
|
|
1127
934
|
|
|
1128
935
|
{{#eq category "news"}}
|
|
1129
|
-
<span class="news-icon"
|
|
936
|
+
<span class="news-icon">News</span>
|
|
1130
937
|
{{/eq}}
|
|
1131
938
|
\`\`\`
|
|
1132
939
|
|
|
1133
|
-
## Inside Loops with Parent Context
|
|
1134
|
-
\`\`\`html
|
|
1135
|
-
{{#each blogs}}
|
|
1136
|
-
<article class="{{#if (eq author.name ../name)}}highlight{{/if}}">
|
|
1137
|
-
<h3>{{name}}</h3>
|
|
1138
|
-
{{#if (eq author.name ../name)}}
|
|
1139
|
-
<span class="yours">You wrote this!</span>
|
|
1140
|
-
{{/if}}
|
|
1141
|
-
</article>
|
|
1142
|
-
{{/each}}
|
|
1143
|
-
\`\`\`
|
|
1144
|
-
|
|
1145
940
|
## Summary Table
|
|
1146
941
|
|
|
1147
942
|
| Syntax | Shows content when... |
|
|
1148
943
|
|--------|----------------------|
|
|
1149
944
|
| \`{{#if (eq a b)}}\` | a equals b |
|
|
1150
945
|
| \`{{#unless (eq a b)}}\` | a does NOT equal b |
|
|
1151
|
-
| \`{{#eq field "value"}}\` | field equals "value"
|
|
1152
|
-
|
|
1153
|
-
**Common Use Cases:**
|
|
1154
|
-
- Related posts (exclude current) - use \`{{#unless (eq slug ../slug)}}\`
|
|
1155
|
-
- Filter by current author/category - use \`{{#if (eq author.slug ../slug)}}\`
|
|
1156
|
-
- Highlight active items - use \`{{#if (eq ...)}}\`
|
|
1157
|
-
- Show badges for specific statuses - use \`{{#eq status "value"}}\``,
|
|
946
|
+
| \`{{#eq field "value"}}\` | field equals "value" |`,
|
|
1158
947
|
youtube_embed: `# YouTube Video Embeds
|
|
1159
948
|
|
|
1160
|
-
**IMPORTANT:** YouTube iframes require specific attributes to work correctly. Missing attributes will cause Error 150/153
|
|
949
|
+
**IMPORTANT:** YouTube iframes require specific attributes to work correctly. Missing attributes will cause Error 150/153.
|
|
1161
950
|
|
|
1162
951
|
## Correct YouTube Embed Format
|
|
1163
952
|
|
|
@@ -1182,20 +971,6 @@ The most common pattern for "Related Posts" or "Other Items" sections:
|
|
|
1182
971
|
| \`frameborder="0"\` | Recommended | Removes border |
|
|
1183
972
|
| \`title="..."\` | Recommended | Accessibility for screen readers |
|
|
1184
973
|
|
|
1185
|
-
## Common Mistake (Causes Error 153)
|
|
1186
|
-
|
|
1187
|
-
\`\`\`html
|
|
1188
|
-
<!-- ❌ WRONG - Missing referrerpolicy -->
|
|
1189
|
-
<iframe src="{{videoUrl}}" allowfullscreen></iframe>
|
|
1190
|
-
|
|
1191
|
-
<!-- ✅ CORRECT - All required attributes -->
|
|
1192
|
-
<iframe
|
|
1193
|
-
src="{{videoUrl}}"
|
|
1194
|
-
referrerpolicy="strict-origin-when-cross-origin"
|
|
1195
|
-
allowfullscreen
|
|
1196
|
-
></iframe>
|
|
1197
|
-
\`\`\`
|
|
1198
|
-
|
|
1199
974
|
## Video URL Format
|
|
1200
975
|
|
|
1201
976
|
Store YouTube URLs in embed format:
|
|
@@ -1205,7 +980,7 @@ https://www.youtube.com/embed/VIDEO_ID
|
|
|
1205
980
|
|
|
1206
981
|
**NOT** watch format:
|
|
1207
982
|
\`\`\`
|
|
1208
|
-
https://www.youtube.com/watch?v=VIDEO_ID
|
|
983
|
+
https://www.youtube.com/watch?v=VIDEO_ID
|
|
1209
984
|
\`\`\`
|
|
1210
985
|
|
|
1211
986
|
## Full Example with Conditional
|
|
@@ -1227,24 +1002,6 @@ https://www.youtube.com/watch?v=VIDEO_ID ❌
|
|
|
1227
1002
|
</div>
|
|
1228
1003
|
{{/if}}
|
|
1229
1004
|
</div>
|
|
1230
|
-
\`\`\`
|
|
1231
|
-
|
|
1232
|
-
## Responsive Video CSS
|
|
1233
|
-
|
|
1234
|
-
\`\`\`css
|
|
1235
|
-
.video-container {
|
|
1236
|
-
position: relative;
|
|
1237
|
-
width: 100%;
|
|
1238
|
-
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
.video-container iframe {
|
|
1242
|
-
position: absolute;
|
|
1243
|
-
top: 0;
|
|
1244
|
-
left: 0;
|
|
1245
|
-
width: 100%;
|
|
1246
|
-
height: 100%;
|
|
1247
|
-
}
|
|
1248
1005
|
\`\`\``,
|
|
1249
1006
|
};
|
|
1250
1007
|
/**
|