fastmode-mcp 1.0.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/README.md +561 -0
- package/bin/run.js +50 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +802 -0
- package/dist/lib/api-client.d.ts +81 -0
- package/dist/lib/api-client.d.ts.map +1 -0
- package/dist/lib/api-client.js +237 -0
- package/dist/lib/auth-state.d.ts +13 -0
- package/dist/lib/auth-state.d.ts.map +1 -0
- package/dist/lib/auth-state.js +24 -0
- package/dist/lib/context-fetcher.d.ts +67 -0
- package/dist/lib/context-fetcher.d.ts.map +1 -0
- package/dist/lib/context-fetcher.js +190 -0
- package/dist/lib/credentials.d.ts +52 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/credentials.js +196 -0
- package/dist/lib/device-flow.d.ts +14 -0
- package/dist/lib/device-flow.d.ts.map +1 -0
- package/dist/lib/device-flow.js +244 -0
- package/dist/tools/cms-items.d.ts +56 -0
- package/dist/tools/cms-items.d.ts.map +1 -0
- package/dist/tools/cms-items.js +376 -0
- package/dist/tools/create-site.d.ts +9 -0
- package/dist/tools/create-site.d.ts.map +1 -0
- package/dist/tools/create-site.js +202 -0
- package/dist/tools/deploy-package.d.ts +9 -0
- package/dist/tools/deploy-package.d.ts.map +1 -0
- package/dist/tools/deploy-package.js +434 -0
- package/dist/tools/generate-samples.d.ts +19 -0
- package/dist/tools/generate-samples.d.ts.map +1 -0
- package/dist/tools/generate-samples.js +272 -0
- package/dist/tools/get-conversion-guide.d.ts +7 -0
- package/dist/tools/get-conversion-guide.d.ts.map +1 -0
- package/dist/tools/get-conversion-guide.js +1323 -0
- package/dist/tools/get-example.d.ts +7 -0
- package/dist/tools/get-example.d.ts.map +1 -0
- package/dist/tools/get-example.js +1568 -0
- package/dist/tools/get-field-types.d.ts +30 -0
- package/dist/tools/get-field-types.d.ts.map +1 -0
- package/dist/tools/get-field-types.js +154 -0
- package/dist/tools/get-schema.d.ts +5 -0
- package/dist/tools/get-schema.d.ts.map +1 -0
- package/dist/tools/get-schema.js +320 -0
- package/dist/tools/get-started.d.ts +21 -0
- package/dist/tools/get-started.d.ts.map +1 -0
- package/dist/tools/get-started.js +624 -0
- package/dist/tools/get-tenant-schema.d.ts +18 -0
- package/dist/tools/get-tenant-schema.d.ts.map +1 -0
- package/dist/tools/get-tenant-schema.js +158 -0
- package/dist/tools/list-projects.d.ts +5 -0
- package/dist/tools/list-projects.d.ts.map +1 -0
- package/dist/tools/list-projects.js +101 -0
- package/dist/tools/sync-schema.d.ts +41 -0
- package/dist/tools/sync-schema.d.ts.map +1 -0
- package/dist/tools/sync-schema.js +483 -0
- package/dist/tools/validate-manifest.d.ts +5 -0
- package/dist/tools/validate-manifest.d.ts.map +1 -0
- package/dist/tools/validate-manifest.js +311 -0
- package/dist/tools/validate-package.d.ts +5 -0
- package/dist/tools/validate-package.d.ts.map +1 -0
- package/dist/tools/validate-package.js +337 -0
- package/dist/tools/validate-template.d.ts +12 -0
- package/dist/tools/validate-template.d.ts.map +1 -0
- package/dist/tools/validate-template.js +790 -0
- package/package.json +54 -0
- package/scripts/postinstall.js +129 -0
|
@@ -0,0 +1,1323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getConversionGuide = getConversionGuide;
|
|
4
|
+
const SECTIONS = {
|
|
5
|
+
first_steps: `# REQUIRED FIRST STEPS
|
|
6
|
+
|
|
7
|
+
## STOP. DO NOT BUILD ANYTHING YET.
|
|
8
|
+
|
|
9
|
+
Before writing ANY HTML, templates, or manifest.json, you MUST:
|
|
10
|
+
|
|
11
|
+
1. **Determine which project this is for** - Call \`list_projects\`
|
|
12
|
+
2. **Ask the user** - "Which project?" or "What should I name your new project?"
|
|
13
|
+
3. **Get a projectId** - Either from existing project or from \`create_site\`
|
|
14
|
+
4. **Get the schema** - Call \`get_tenant_schema\` to know what fields exist
|
|
15
|
+
|
|
16
|
+
**Skipping these steps WILL cause deployment failures.**
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Step 1: Check for Existing Projects
|
|
21
|
+
|
|
22
|
+
Call \`list_projects\` to see the user's existing Fast Mode projects.
|
|
23
|
+
|
|
24
|
+
## Step 2: Ask the User
|
|
25
|
+
|
|
26
|
+
Based on what \`list_projects\` returns:
|
|
27
|
+
|
|
28
|
+
**If NO projects exist:**
|
|
29
|
+
- This is a new user - ask: "What would you like to name your new project?"
|
|
30
|
+
- Proceed to Step 3b (New Project)
|
|
31
|
+
|
|
32
|
+
**If projects exist:**
|
|
33
|
+
- Present the list and ask: "Is this website for one of your existing projects, or should I create a new one?"
|
|
34
|
+
- Let the user choose which project, or confirm they want a new one
|
|
35
|
+
|
|
36
|
+
## Step 3a: For EXISTING Projects
|
|
37
|
+
|
|
38
|
+
1. User selects the project from the list
|
|
39
|
+
2. Call \`get_tenant_schema(projectId)\` to get the current collections and fields
|
|
40
|
+
3. **Use this schema to build templates with the correct field names**
|
|
41
|
+
4. When deploying, this will UPDATE the existing project
|
|
42
|
+
5. Proceed to Phase 1 (Website Analysis)
|
|
43
|
+
|
|
44
|
+
## Step 3b: For NEW Projects
|
|
45
|
+
|
|
46
|
+
1. Ask for the project name if you don't have it
|
|
47
|
+
2. Call \`create_site(name)\` to create the project
|
|
48
|
+
3. You'll receive a projectId to use for deployment
|
|
49
|
+
4. After building the package, call \`sync_schema\` to create collections/fields
|
|
50
|
+
5. **Ask the user:** "Would you like me to generate sample items so you can preview how the collections will look?"
|
|
51
|
+
6. If yes, call \`generate_sample_items(projectId)\` - it handles dependency ordering automatically
|
|
52
|
+
7. Proceed to Phase 1 (Website Analysis)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
**WHY THIS MATTERS:**
|
|
57
|
+
- For existing projects: The schema determines which fields to use in templates
|
|
58
|
+
- For new projects: You need the projectId before you can deploy
|
|
59
|
+
- Always: The user must confirm which project to use - never assume
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## CHECKPOINT: Before You Continue
|
|
64
|
+
|
|
65
|
+
**STOP.** Confirm you have completed these steps:
|
|
66
|
+
|
|
67
|
+
| Requirement | How to Get It |
|
|
68
|
+
|-------------|---------------|
|
|
69
|
+
| projectId | From \`list_projects\` (existing) or \`create_site\` (new) |
|
|
70
|
+
| Project name confirmed | Asked the user |
|
|
71
|
+
| Schema loaded | Called \`get_tenant_schema(projectId)\` |
|
|
72
|
+
|
|
73
|
+
**If you don't have a projectId, DO NOT PROCEED.** Go back to Step 1.
|
|
74
|
+
|
|
75
|
+
The projectId is REQUIRED for:
|
|
76
|
+
- \`get_tenant_schema\` - to get existing field names
|
|
77
|
+
- \`deploy_package\` - to upload the site
|
|
78
|
+
- \`sync_schema\` - to create new collections
|
|
79
|
+
|
|
80
|
+
`,
|
|
81
|
+
analysis: `# Phase 1: Website Analysis (MANDATORY)
|
|
82
|
+
|
|
83
|
+
Before writing ANY code, complete this analysis:
|
|
84
|
+
|
|
85
|
+
## 1.1 Map ALL URLs
|
|
86
|
+
Visit every page and document the exact URL structure:
|
|
87
|
+
\`\`\`
|
|
88
|
+
/ (Homepage)
|
|
89
|
+
/about or /about-us or /company
|
|
90
|
+
/services or /what-we-do
|
|
91
|
+
/contact or /get-in-touch
|
|
92
|
+
/blog or /news or /resources or /insights
|
|
93
|
+
/blog/post-slug
|
|
94
|
+
/team or /our-team
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
## 1.2 Categorize Each Page
|
|
98
|
+
|
|
99
|
+
**Page Types:**
|
|
100
|
+
- **Static** - Fixed content (/, /about, /contact)
|
|
101
|
+
- **List** - Shows multiple items (/blog, /team)
|
|
102
|
+
- **Detail** - Single item from a list (/blog/my-post)
|
|
103
|
+
|
|
104
|
+
## 1.3 Identify Content Types
|
|
105
|
+
|
|
106
|
+
Identify repeating content that should be CMS collections:
|
|
107
|
+
|
|
108
|
+
- Time-stamped articles → **Blog/Posts collection**
|
|
109
|
+
- Staff/employee profiles → **Team collection**
|
|
110
|
+
- Downloadable files → **Downloads collection**
|
|
111
|
+
- Product listings → **Products collection**
|
|
112
|
+
- Testimonials → **Testimonials collection**
|
|
113
|
+
|
|
114
|
+
**Note:** Create custom collections in the CMS dashboard. You can use collection templates (Blog, Team, Downloads, etc.) to quickly set up common patterns.
|
|
115
|
+
|
|
116
|
+
## 1.4 Document Assets
|
|
117
|
+
|
|
118
|
+
List all asset locations:
|
|
119
|
+
\`\`\`
|
|
120
|
+
CSS: /css/*.css, /styles/*.css
|
|
121
|
+
JS: /js/*.js, /scripts/*.js
|
|
122
|
+
Images: /images/*, /assets/img/*
|
|
123
|
+
Fonts: Google Fonts links, /fonts/*
|
|
124
|
+
\`\`\`
|
|
125
|
+
|
|
126
|
+
## 1.5 Critical Rule
|
|
127
|
+
|
|
128
|
+
**PRESERVE ORIGINAL URLs**
|
|
129
|
+
|
|
130
|
+
If the site uses \`/resources\` for articles, keep \`/resources\`.
|
|
131
|
+
Do NOT change it to \`/blog\`.
|
|
132
|
+
|
|
133
|
+
Use the manifest's path configuration to maintain original URLs.`,
|
|
134
|
+
structure: `# Package Structure
|
|
135
|
+
|
|
136
|
+
Create a package with this structure (can be at repo root OR in a subfolder like cms_package/):
|
|
137
|
+
|
|
138
|
+
\`\`\`
|
|
139
|
+
website-package/ # Can be at root or in a subfolder
|
|
140
|
+
├── manifest.json # Required: Defines pages and CMS templates
|
|
141
|
+
├── public/ # Static assets (CSS, JS, images, fonts)
|
|
142
|
+
│ ├── css/
|
|
143
|
+
│ │ └── style.css
|
|
144
|
+
│ ├── js/
|
|
145
|
+
│ │ └── main.js
|
|
146
|
+
│ └── images/
|
|
147
|
+
│ └── logo.png
|
|
148
|
+
├── pages/ # HTML pages (static content)
|
|
149
|
+
│ ├── index.html
|
|
150
|
+
│ ├── about.html
|
|
151
|
+
│ └── contact.html
|
|
152
|
+
└── templates/ # CMS templates (dynamic content)
|
|
153
|
+
├── posts_index.html
|
|
154
|
+
├── posts_detail.html
|
|
155
|
+
├── team_index.html
|
|
156
|
+
└── services_index.html
|
|
157
|
+
\`\`\`
|
|
158
|
+
|
|
159
|
+
**SUPPORTED REPO STRUCTURES:**
|
|
160
|
+
\`\`\`
|
|
161
|
+
Option 1: CMS files at root
|
|
162
|
+
repo/
|
|
163
|
+
├── manifest.json
|
|
164
|
+
├── pages/
|
|
165
|
+
├── public/
|
|
166
|
+
└── templates/
|
|
167
|
+
|
|
168
|
+
Option 2: CMS files in subfolder (e.g. for Next.js projects)
|
|
169
|
+
repo/
|
|
170
|
+
├── app/ # Next.js app (ignored)
|
|
171
|
+
├── cms_package/ # CMS files here
|
|
172
|
+
│ ├── manifest.json
|
|
173
|
+
│ ├── pages/
|
|
174
|
+
│ ├── public/
|
|
175
|
+
│ └── templates/
|
|
176
|
+
└── package.json
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
## Folder Rules
|
|
180
|
+
|
|
181
|
+
### public/
|
|
182
|
+
- ALL assets go here (CSS, JS, images, fonts)
|
|
183
|
+
- Maintains subfolder structure
|
|
184
|
+
- Referenced as \`/public/...\` in HTML
|
|
185
|
+
|
|
186
|
+
### pages/
|
|
187
|
+
- Static HTML pages
|
|
188
|
+
- One file per page
|
|
189
|
+
- Use data-edit-key for editable text
|
|
190
|
+
|
|
191
|
+
### templates/
|
|
192
|
+
- CMS-powered pages
|
|
193
|
+
- Use {{tokens}} for dynamic content
|
|
194
|
+
- Named by collection slug (e.g., posts_index.html, posts_detail.html)`,
|
|
195
|
+
seo: `# SEO Tags (CRITICAL - Do NOT Include)
|
|
196
|
+
|
|
197
|
+
Fast Mode automatically manages all SEO meta tags. Do NOT include these in your HTML templates:
|
|
198
|
+
|
|
199
|
+
## Tags to EXCLUDE
|
|
200
|
+
|
|
201
|
+
| Tag | Reason |
|
|
202
|
+
|-----|--------|
|
|
203
|
+
| \`<title>...\` | Managed via Settings → SEO & Design |
|
|
204
|
+
| \`<meta name="description">\` | Managed via Settings → SEO & Design |
|
|
205
|
+
| \`<meta name="keywords">\` | Managed via Settings → SEO & Design |
|
|
206
|
+
| \`<meta property="og:*">\` | Open Graph tags auto-generated |
|
|
207
|
+
| \`<meta name="twitter:*">\` | Twitter cards auto-generated |
|
|
208
|
+
| \`<link rel="icon">\` | Favicon managed in settings |
|
|
209
|
+
| \`<link rel="shortcut icon">\` | Favicon managed in settings |
|
|
210
|
+
| \`<link rel="apple-touch-icon">\` | Managed by Fast Mode |
|
|
211
|
+
| \`<meta name="google-site-verification">\` | Managed in settings |
|
|
212
|
+
|
|
213
|
+
## Correct \`<head>\` Structure
|
|
214
|
+
|
|
215
|
+
\`\`\`html
|
|
216
|
+
<head>
|
|
217
|
+
<meta charset="UTF-8">
|
|
218
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
219
|
+
<!-- SEO managed by Fast Mode -->
|
|
220
|
+
<link rel="stylesheet" href="/public/css/style.css">
|
|
221
|
+
<!-- Font imports, external scripts, etc. are OK -->
|
|
222
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
|
223
|
+
</head>
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
## How Fast Mode SEO Works
|
|
227
|
+
|
|
228
|
+
1. **Global SEO** (Settings → SEO & Design)
|
|
229
|
+
- Site-wide defaults: title, description, OG image, favicon
|
|
230
|
+
- Applied to all pages
|
|
231
|
+
|
|
232
|
+
2. **Page-Level SEO** (Editor → click page → SEO tab)
|
|
233
|
+
- Override specific pages with custom title/description
|
|
234
|
+
- Page-level settings take priority over global
|
|
235
|
+
|
|
236
|
+
3. **Automatic Injection**
|
|
237
|
+
- Fast Mode strips any hardcoded SEO tags from templates
|
|
238
|
+
- Injects correct SEO from settings at render time
|
|
239
|
+
- Guarantees single source of truth
|
|
240
|
+
|
|
241
|
+
## Why This Matters
|
|
242
|
+
|
|
243
|
+
- No duplicate meta tags (bad for SEO ranking)
|
|
244
|
+
- Content managers can update SEO without touching code
|
|
245
|
+
- Consistent SEO across all pages
|
|
246
|
+
- Works retroactively on existing templates`,
|
|
247
|
+
manifest: `# manifest.json Configuration
|
|
248
|
+
|
|
249
|
+
## Basic Structure
|
|
250
|
+
|
|
251
|
+
\`\`\`json
|
|
252
|
+
{
|
|
253
|
+
"pages": [
|
|
254
|
+
{
|
|
255
|
+
"path": "/",
|
|
256
|
+
"file": "pages/index.html",
|
|
257
|
+
"title": "Home",
|
|
258
|
+
"editable": true
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
"cmsTemplates": {
|
|
262
|
+
"postsIndex": "templates/posts_index.html",
|
|
263
|
+
"postsIndexPath": "/blog",
|
|
264
|
+
"postsDetail": "templates/posts_detail.html",
|
|
265
|
+
"postsDetailPath": "/blog",
|
|
266
|
+
"teamIndex": "templates/team_index.html",
|
|
267
|
+
"teamIndexPath": "/team"
|
|
268
|
+
},
|
|
269
|
+
"defaultHeadHtml": "<!-- fonts, meta tags -->"
|
|
270
|
+
}
|
|
271
|
+
\`\`\`
|
|
272
|
+
|
|
273
|
+
## CMS Template Configuration
|
|
274
|
+
|
|
275
|
+
All CMS collections use the **flat format** with four properties per collection:
|
|
276
|
+
|
|
277
|
+
\`\`\`json
|
|
278
|
+
{
|
|
279
|
+
"cmsTemplates": {
|
|
280
|
+
// Collection: posts
|
|
281
|
+
"postsIndex": "templates/posts_index.html",
|
|
282
|
+
"postsIndexPath": "/blog",
|
|
283
|
+
"postsDetail": "templates/posts_detail.html",
|
|
284
|
+
"postsDetailPath": "/blog",
|
|
285
|
+
|
|
286
|
+
// Collection: team (no detail pages)
|
|
287
|
+
"teamIndex": "templates/team_index.html",
|
|
288
|
+
"teamIndexPath": "/team",
|
|
289
|
+
|
|
290
|
+
// Collection: services
|
|
291
|
+
"servicesIndex": "pages/services.html",
|
|
292
|
+
"servicesIndexPath": "/services",
|
|
293
|
+
"servicesDetail": "templates/service-detail.html",
|
|
294
|
+
"servicesDetailPath": "/services"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
\`\`\`
|
|
298
|
+
|
|
299
|
+
**Path Configuration Pattern:**
|
|
300
|
+
- \`{collectionSlug}Index\` - Template file for the index/listing page
|
|
301
|
+
- \`{collectionSlug}IndexPath\` - URL for the collection list page
|
|
302
|
+
- \`{collectionSlug}Detail\` - Template file for the detail page
|
|
303
|
+
- \`{collectionSlug}DetailPath\` - Base URL for detail pages (item slug appended)
|
|
304
|
+
|
|
305
|
+
**IMPORTANT:** The collection slug in the CMS is for the database. URL paths come EXCLUSIVELY from the manifest.
|
|
306
|
+
|
|
307
|
+
## Page Configuration
|
|
308
|
+
|
|
309
|
+
Each page needs:
|
|
310
|
+
- \`path\` - The URL (must start with /)
|
|
311
|
+
- \`file\` - Path to HTML file in pages/
|
|
312
|
+
- \`title\` - Page title for CMS
|
|
313
|
+
- \`editable\` - Enable inline editing (optional)
|
|
314
|
+
|
|
315
|
+
## Two Ways to Configure CMS Templates
|
|
316
|
+
|
|
317
|
+
### Option 1: In manifest.json (during package creation)
|
|
318
|
+
Configure \`cmsTemplates\` section as shown above when building the package.
|
|
319
|
+
|
|
320
|
+
### Option 2: Via Settings UI (after upload)
|
|
321
|
+
After uploading a package, go to **Dashboard → Settings → CMS Templates** to configure template mappings.`,
|
|
322
|
+
templates: `# Template Creation Guide
|
|
323
|
+
|
|
324
|
+
## Template Types
|
|
325
|
+
|
|
326
|
+
1. **Index Templates** - List pages (posts_index.html)
|
|
327
|
+
2. **Detail Templates** - Single item pages (posts_detail.html)
|
|
328
|
+
3. **Static Pages** - Fixed content with data-edit-key
|
|
329
|
+
|
|
330
|
+
## Static UI vs Dynamic Content
|
|
331
|
+
|
|
332
|
+
When converting templates, distinguish between:
|
|
333
|
+
|
|
334
|
+
### 1. Static UI Elements (Keep as-is)
|
|
335
|
+
Logos, icons, decorative backgrounds - these stay as \`/public/\` paths:
|
|
336
|
+
\`\`\`html
|
|
337
|
+
<!-- KEEP these static -->
|
|
338
|
+
<img src="/public/images/logo.png" alt="Company Logo">
|
|
339
|
+
<img src="/public/images/icons/arrow.svg" alt="">
|
|
340
|
+
\`\`\`
|
|
341
|
+
|
|
342
|
+
### 2. Dynamic Content (Replace with CMS tokens)
|
|
343
|
+
Blog posts, team members, products - replace example content with \`{{#each}}\` loops:
|
|
344
|
+
|
|
345
|
+
\`\`\`html
|
|
346
|
+
<!-- WRONG: Hardcoded example content -->
|
|
347
|
+
<article class="post-card">
|
|
348
|
+
<img src="/images/example-post.jpg" alt="Example Post">
|
|
349
|
+
<h2>My Example Post Title</h2>
|
|
350
|
+
<p>This is a sample description...</p>
|
|
351
|
+
</article>
|
|
352
|
+
|
|
353
|
+
<!-- CORRECT: CMS tokens with loop -->
|
|
354
|
+
{{#each posts sort="publishedAt" order="desc"}}
|
|
355
|
+
<article class="post-card">
|
|
356
|
+
{{#if image}}
|
|
357
|
+
<img src="{{image}}" alt="{{name}}">
|
|
358
|
+
{{/if}}
|
|
359
|
+
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
360
|
+
<p>{{summary}}</p>
|
|
361
|
+
</article>
|
|
362
|
+
{{/each}}
|
|
363
|
+
\`\`\`
|
|
364
|
+
|
|
365
|
+
**Rule of thumb:** If it's site branding/design → keep static. If it's content that changes per item → use CMS tokens.
|
|
366
|
+
|
|
367
|
+
## Index Template Pattern
|
|
368
|
+
|
|
369
|
+
\`\`\`html
|
|
370
|
+
<main class="posts-page">
|
|
371
|
+
<h1 data-edit-key="posts-title">Blog</h1>
|
|
372
|
+
|
|
373
|
+
<div class="posts-grid">
|
|
374
|
+
{{#each posts limit=12 sort="publishedAt" order="desc"}}
|
|
375
|
+
<article class="post-card">
|
|
376
|
+
{{#if image}}
|
|
377
|
+
<img src="{{image}}" alt="{{name}}">
|
|
378
|
+
{{/if}}
|
|
379
|
+
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
380
|
+
<p>{{summary}}</p>
|
|
381
|
+
{{#if author}}
|
|
382
|
+
<span>By {{author.name}}</span>
|
|
383
|
+
{{/if}}
|
|
384
|
+
</article>
|
|
385
|
+
{{/each}}
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
{{#unless posts}}
|
|
389
|
+
<p>No posts yet. Check back soon!</p>
|
|
390
|
+
{{/unless}}
|
|
391
|
+
</main>
|
|
392
|
+
\`\`\`
|
|
393
|
+
|
|
394
|
+
## Detail Template Pattern
|
|
395
|
+
|
|
396
|
+
\`\`\`html
|
|
397
|
+
<article class="post-detail">
|
|
398
|
+
{{#if image}}
|
|
399
|
+
<img src="{{image}}" alt="{{name}}">
|
|
400
|
+
{{/if}}
|
|
401
|
+
|
|
402
|
+
<h1>{{name}}</h1>
|
|
403
|
+
|
|
404
|
+
{{#if author}}
|
|
405
|
+
<div class="author">
|
|
406
|
+
By <a href="{{author.url}}">{{author.name}}</a>
|
|
407
|
+
</div>
|
|
408
|
+
{{/if}}
|
|
409
|
+
|
|
410
|
+
{{#if publishedAt}}
|
|
411
|
+
<time>{{publishedAt}}</time>
|
|
412
|
+
{{/if}}
|
|
413
|
+
|
|
414
|
+
<div class="content">
|
|
415
|
+
{{{body}}}
|
|
416
|
+
</div>
|
|
417
|
+
</article>
|
|
418
|
+
\`\`\`
|
|
419
|
+
|
|
420
|
+
## Team Template Pattern
|
|
421
|
+
|
|
422
|
+
\`\`\`html
|
|
423
|
+
<div class="team-grid">
|
|
424
|
+
{{#each team sort="order" order="asc"}}
|
|
425
|
+
<div class="team-member">
|
|
426
|
+
{{#if photo}}
|
|
427
|
+
<img src="{{photo}}" alt="{{name}}">
|
|
428
|
+
{{/if}}
|
|
429
|
+
<h3>{{name}}</h3>
|
|
430
|
+
{{#if role}}
|
|
431
|
+
<p class="role">{{role}}</p>
|
|
432
|
+
{{/if}}
|
|
433
|
+
{{#if bio}}
|
|
434
|
+
<div class="bio">{{{bio}}}</div>
|
|
435
|
+
{{/if}}
|
|
436
|
+
</div>
|
|
437
|
+
{{/each}}
|
|
438
|
+
</div>
|
|
439
|
+
\`\`\`
|
|
440
|
+
|
|
441
|
+
## Static Page Pattern
|
|
442
|
+
|
|
443
|
+
\`\`\`html
|
|
444
|
+
<main class="about-page">
|
|
445
|
+
<h1 data-edit-key="about-title">About Us</h1>
|
|
446
|
+
<p data-edit-key="about-intro">Introduction text...</p>
|
|
447
|
+
|
|
448
|
+
<section>
|
|
449
|
+
<h2 data-edit-key="about-mission-title">Our Mission</h2>
|
|
450
|
+
<p data-edit-key="about-mission-text">Mission statement...</p>
|
|
451
|
+
</section>
|
|
452
|
+
</main>
|
|
453
|
+
\`\`\`
|
|
454
|
+
|
|
455
|
+
## Template Must-Haves
|
|
456
|
+
|
|
457
|
+
- Include complete header (copy from source)
|
|
458
|
+
- Include complete footer (copy from source)
|
|
459
|
+
- All asset paths use /public/
|
|
460
|
+
- Rich text fields use {{{triple braces}}}`,
|
|
461
|
+
tokens: `# Token Reference
|
|
462
|
+
|
|
463
|
+
## Double Braces {{...}} - Escaped Output
|
|
464
|
+
Use for text, URLs, images:
|
|
465
|
+
\`\`\`html
|
|
466
|
+
{{name}}
|
|
467
|
+
{{image}}
|
|
468
|
+
{{url}}
|
|
469
|
+
{{author.name}}
|
|
470
|
+
\`\`\`
|
|
471
|
+
|
|
472
|
+
## Triple Braces {{{...}}} - Raw HTML
|
|
473
|
+
Use for rich text content:
|
|
474
|
+
\`\`\`html
|
|
475
|
+
{{{body}}}
|
|
476
|
+
{{{bio}}}
|
|
477
|
+
{{{description}}}
|
|
478
|
+
\`\`\`
|
|
479
|
+
|
|
480
|
+
**CRITICAL:** If a field contains HTML (richText type), you MUST use triple braces!
|
|
481
|
+
|
|
482
|
+
## Loop Syntax
|
|
483
|
+
\`\`\`html
|
|
484
|
+
{{#each collectionSlug}}
|
|
485
|
+
...content for each item...
|
|
486
|
+
{{/each}}
|
|
487
|
+
\`\`\`
|
|
488
|
+
|
|
489
|
+
**Modifiers:**
|
|
490
|
+
- \`limit=N\` - Maximum items
|
|
491
|
+
- \`featured=true\` - Only featured (if collection has boolean "featured" field)
|
|
492
|
+
- \`sort="field"\` - Sort by field
|
|
493
|
+
- \`order="asc|desc"\` - Sort direction
|
|
494
|
+
- \`where="field:value"\` - Filter by field value
|
|
495
|
+
|
|
496
|
+
## Nested Loops (Hierarchical Content)
|
|
497
|
+
|
|
498
|
+
For content hierarchies like categories with items, docs sections with pages, or any parent-child relationships.
|
|
499
|
+
|
|
500
|
+
**Works on ALL page types:** static pages, index pages, AND detail pages.
|
|
501
|
+
|
|
502
|
+
\`\`\`html
|
|
503
|
+
{{#each doc_categories}}
|
|
504
|
+
<div class="category-section">
|
|
505
|
+
<h3>{{name}}</h3>
|
|
506
|
+
<ul>
|
|
507
|
+
{{#each doc_pages where="category.slug:{{slug}}"}}
|
|
508
|
+
<li><a href="{{url}}">{{name}}</a></li>
|
|
509
|
+
{{/each}}
|
|
510
|
+
</ul>
|
|
511
|
+
</div>
|
|
512
|
+
{{/each}}
|
|
513
|
+
\`\`\`
|
|
514
|
+
|
|
515
|
+
**How it works:**
|
|
516
|
+
- The outer loop iterates through categories
|
|
517
|
+
- \`{{slug}}\` in the where clause references the current category's slug
|
|
518
|
+
- The inner loop filters doc_pages to only those matching that category
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
### Using @root. Prefix
|
|
523
|
+
|
|
524
|
+
Use \`@root.\` to access collections from the root context when you need to be explicit:
|
|
525
|
+
|
|
526
|
+
\`\`\`html
|
|
527
|
+
{{#each doc_categories}}
|
|
528
|
+
<div class="category-section">
|
|
529
|
+
<h3>{{name}}</h3>
|
|
530
|
+
<ul>
|
|
531
|
+
{{#each @root.doc_pages where="category.slug:{{slug}}"}}
|
|
532
|
+
<li><a href="{{url}}">{{name}}</a></li>
|
|
533
|
+
{{/each}}
|
|
534
|
+
</ul>
|
|
535
|
+
</div>
|
|
536
|
+
{{/each}}
|
|
537
|
+
\`\`\`
|
|
538
|
+
|
|
539
|
+
**When to use @root.:**
|
|
540
|
+
- When the inner collection name might be ambiguous
|
|
541
|
+
- When you want to be explicit about accessing root-level data
|
|
542
|
+
- Both \`{{#each collection}}\` and \`{{#each @root.collection}}\` work the same
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
### Common Patterns
|
|
547
|
+
|
|
548
|
+
\`\`\`html
|
|
549
|
+
<!-- Categories with posts -->
|
|
550
|
+
{{#each categories}}
|
|
551
|
+
<section>
|
|
552
|
+
<h2>{{name}}</h2>
|
|
553
|
+
{{#each posts where="category.slug:{{slug}}"}}
|
|
554
|
+
<article>{{name}}</article>
|
|
555
|
+
{{/each}}
|
|
556
|
+
</section>
|
|
557
|
+
{{/each}}
|
|
558
|
+
|
|
559
|
+
<!-- Authors with their articles -->
|
|
560
|
+
{{#each authors}}
|
|
561
|
+
<div class="author-section">
|
|
562
|
+
<h3>{{name}}</h3>
|
|
563
|
+
{{#each posts where="author.id:{{id}}"}}
|
|
564
|
+
<a href="{{url}}">{{name}}</a>
|
|
565
|
+
{{/each}}
|
|
566
|
+
</div>
|
|
567
|
+
{{/each}}
|
|
568
|
+
|
|
569
|
+
<!-- Documentation sidebar (on any page type) -->
|
|
570
|
+
{{#each doc_categories sort="order" order="asc"}}
|
|
571
|
+
<div class="sidebar-section">
|
|
572
|
+
<h4>{{name}}</h4>
|
|
573
|
+
{{#each @root.doc_pages where="category.slug:{{slug}}" sort="order" order="asc"}}
|
|
574
|
+
<a href="{{url}}">{{name}}</a>
|
|
575
|
+
{{/each}}
|
|
576
|
+
</div>
|
|
577
|
+
{{/each}}
|
|
578
|
+
\`\`\`
|
|
579
|
+
|
|
580
|
+
**Important:** The \`{{field}}\` in the where clause is resolved from the outer loop's current item.
|
|
581
|
+
|
|
582
|
+
## Conditionals
|
|
583
|
+
\`\`\`html
|
|
584
|
+
{{#if fieldName}}
|
|
585
|
+
...show if truthy...
|
|
586
|
+
{{else}}
|
|
587
|
+
...show if falsy...
|
|
588
|
+
{{/if}}
|
|
589
|
+
|
|
590
|
+
{{#unless fieldName}}
|
|
591
|
+
...show if falsy...
|
|
592
|
+
{{/unless}}
|
|
593
|
+
\`\`\`
|
|
594
|
+
|
|
595
|
+
## Equality Comparisons
|
|
596
|
+
\`\`\`html
|
|
597
|
+
<!-- Show when equal -->
|
|
598
|
+
{{#if (eq fieldA fieldB)}}...{{/if}}
|
|
599
|
+
|
|
600
|
+
<!-- Show when NOT equal (common for Related Posts) -->
|
|
601
|
+
{{#unless (eq slug ../slug)}}
|
|
602
|
+
<a href="{{url}}">{{name}}</a>
|
|
603
|
+
{{/unless}}
|
|
604
|
+
|
|
605
|
+
<!-- Compare to literal string -->
|
|
606
|
+
{{#eq status "published"}}...{{/eq}}
|
|
607
|
+
\`\`\`
|
|
608
|
+
|
|
609
|
+
## Comparison Helpers (Numeric Comparisons)
|
|
610
|
+
\`\`\`html
|
|
611
|
+
<!-- Less than: show first 4 items differently -->
|
|
612
|
+
{{#if (lt @index 4)}}
|
|
613
|
+
<div class="featured">{{name}}</div>
|
|
614
|
+
{{else}}
|
|
615
|
+
<div class="standard">{{name}}</div>
|
|
616
|
+
{{/if}}
|
|
617
|
+
|
|
618
|
+
<!-- Greater than: skip first item -->
|
|
619
|
+
{{#if (gt @index 0)}}
|
|
620
|
+
<article>{{name}}</article>
|
|
621
|
+
{{/if}}
|
|
622
|
+
|
|
623
|
+
<!-- Less than or equal -->
|
|
624
|
+
{{#if (lte @index 4)}}...{{/if}}
|
|
625
|
+
|
|
626
|
+
<!-- Greater than or equal -->
|
|
627
|
+
{{#if (gte @index 2)}}...{{/if}}
|
|
628
|
+
|
|
629
|
+
<!-- Not equal (for strings/numbers) -->
|
|
630
|
+
{{#if (ne status "draft")}}
|
|
631
|
+
<span>Published</span>
|
|
632
|
+
{{/if}}
|
|
633
|
+
\`\`\`
|
|
634
|
+
|
|
635
|
+
**Available comparison helpers:**
|
|
636
|
+
| Helper | Meaning | Example |
|
|
637
|
+
|--------|---------|---------|
|
|
638
|
+
| \`lt\` | Less than | \`{{#if (lt @index 4)}}\` |
|
|
639
|
+
| \`gt\` | Greater than | \`{{#if (gt @index 0)}}\` |
|
|
640
|
+
| \`lte\` | Less than or equal | \`{{#if (lte price 100)}}\` |
|
|
641
|
+
| \`gte\` | Greater than or equal | \`{{#if (gte stock 5)}}\` |
|
|
642
|
+
| \`ne\` | Not equal | \`{{#if (ne status "draft")}}\` |
|
|
643
|
+
| \`eq\` | Equal | \`{{#if (eq category.slug ../slug)}}\` |
|
|
644
|
+
|
|
645
|
+
**Works with:**
|
|
646
|
+
- Loop variables: \`@index\`, \`@length\`
|
|
647
|
+
- Numeric fields: \`price\`, \`stock\`, \`order\`
|
|
648
|
+
- String fields: \`status\`, \`type\`
|
|
649
|
+
- Literal values: \`4\`, \`"published"\`
|
|
650
|
+
|
|
651
|
+
## Loop Variables
|
|
652
|
+
Inside {{#each}} loops, these special variables are available:
|
|
653
|
+
|
|
654
|
+
**Standalone tokens:**
|
|
655
|
+
- \`{{@first}}\` - true for first item
|
|
656
|
+
- \`{{@last}}\` - true for last item
|
|
657
|
+
- \`{{@index}}\` - zero-based index (0, 1, 2...)
|
|
658
|
+
- \`{{@length}}\` - total number of items
|
|
659
|
+
|
|
660
|
+
**Conditional usage:**
|
|
661
|
+
\`\`\`html
|
|
662
|
+
{{#each posts}}
|
|
663
|
+
{{#if @first}}<div class="featured">{{/if}}
|
|
664
|
+
{{#unless @first}}<hr class="separator">{{/unless}}
|
|
665
|
+
<article>{{name}}</article>
|
|
666
|
+
{{#unless @last}},{{/unless}}
|
|
667
|
+
{{/each}}
|
|
668
|
+
\`\`\`
|
|
669
|
+
|
|
670
|
+
| Pattern | Use Case |
|
|
671
|
+
|---------|----------|
|
|
672
|
+
| \`{{#if @first}}\` | Style first item differently |
|
|
673
|
+
| \`{{#unless @first}}\` | Add separator before all but first |
|
|
674
|
+
| \`{{#if @last}}\` | Style last item differently |
|
|
675
|
+
| \`{{#unless @last}}\` | Add comma/separator after all but last |
|
|
676
|
+
|
|
677
|
+
**Important:** Loop variables only work inside \`{{#each}}\` blocks. Using them outside a loop will not work.
|
|
678
|
+
|
|
679
|
+
## Parent Context (\`../\`)
|
|
680
|
+
Inside loops, access the parent scope:
|
|
681
|
+
\`\`\`html
|
|
682
|
+
<!-- Filter posts by current author -->
|
|
683
|
+
{{#each posts}}
|
|
684
|
+
{{#if (eq author.name ../name)}}
|
|
685
|
+
<h3>{{name}}</h3>
|
|
686
|
+
{{/if}}
|
|
687
|
+
{{/each}}
|
|
688
|
+
|
|
689
|
+
<!-- Related Posts (exclude current) -->
|
|
690
|
+
{{#each posts limit=3}}
|
|
691
|
+
{{#unless (eq slug ../slug)}}
|
|
692
|
+
<a href="{{url}}">{{name}}</a>
|
|
693
|
+
{{/unless}}
|
|
694
|
+
{{/each}}
|
|
695
|
+
\`\`\`
|
|
696
|
+
|
|
697
|
+
- \`../name\` - Parent item's name field
|
|
698
|
+
- \`../slug\` - Parent item's slug
|
|
699
|
+
- \`../fieldName\` - Any field from parent scope
|
|
700
|
+
|
|
701
|
+
**Use cases:**
|
|
702
|
+
- Author pages: filter posts by current author
|
|
703
|
+
- Category pages: filter items by current category
|
|
704
|
+
- Related items: match based on current page
|
|
705
|
+
|
|
706
|
+
## Built-in Fields (Available on ALL items)
|
|
707
|
+
|
|
708
|
+
Every collection item automatically has:
|
|
709
|
+
- \`{{name}}\` - Item name/title (REQUIRED)
|
|
710
|
+
- \`{{slug}}\` - Item URL slug
|
|
711
|
+
- \`{{url}}\` - Full URL to detail page
|
|
712
|
+
- \`{{publishedAt}}\` - Publish date
|
|
713
|
+
- \`{{createdAt}}\` - Creation date
|
|
714
|
+
- \`{{updatedAt}}\` - Last modified date
|
|
715
|
+
|
|
716
|
+
## Custom Fields
|
|
717
|
+
|
|
718
|
+
Custom fields are defined when creating collections. The token matches the field slug:
|
|
719
|
+
\`\`\`html
|
|
720
|
+
{{#each services sort="publishedAt" order="desc"}}
|
|
721
|
+
<h2><a href="{{url}}">{{name}}</a></h2>
|
|
722
|
+
{{{description}}}
|
|
723
|
+
<img src="{{image}}" alt="{{name}}">
|
|
724
|
+
<span class="price">\${{price}}</span>
|
|
725
|
+
{{/each}}
|
|
726
|
+
\`\`\`
|
|
727
|
+
|
|
728
|
+
## Relation Fields
|
|
729
|
+
|
|
730
|
+
Access related item data with dot notation:
|
|
731
|
+
\`\`\`html
|
|
732
|
+
{{#each posts}}
|
|
733
|
+
{{#if category}}
|
|
734
|
+
<span class="category">{{category.name}}</span>
|
|
735
|
+
{{/if}}
|
|
736
|
+
{{/each}}
|
|
737
|
+
\`\`\`
|
|
738
|
+
|
|
739
|
+
**Use \`get_tenant_schema\` tool to see exact fields for a specific project.**`,
|
|
740
|
+
forms: `# Form Handling
|
|
741
|
+
|
|
742
|
+
Forms are automatically captured by the CMS.
|
|
743
|
+
|
|
744
|
+
## Basic Form Setup
|
|
745
|
+
|
|
746
|
+
\`\`\`html
|
|
747
|
+
<form data-form-name="contact" action="/_forms/contact" method="POST">
|
|
748
|
+
<input type="text" name="name" required>
|
|
749
|
+
<input type="email" name="email" required>
|
|
750
|
+
<textarea name="message" required></textarea>
|
|
751
|
+
<button type="submit">Send</button>
|
|
752
|
+
</form>
|
|
753
|
+
\`\`\`
|
|
754
|
+
|
|
755
|
+
## Required Attributes
|
|
756
|
+
|
|
757
|
+
- \`data-form-name="xxx"\` on the form element
|
|
758
|
+
- \`action="/_forms/xxx"\` pointing to the form endpoint (MUST match data-form-name)
|
|
759
|
+
- \`method="POST"\` for form submission
|
|
760
|
+
- \`name="fieldName"\` on each input
|
|
761
|
+
|
|
762
|
+
## Form Handler Script
|
|
763
|
+
|
|
764
|
+
Add to your JavaScript (typically /public/js/main.js):
|
|
765
|
+
|
|
766
|
+
\`\`\`javascript
|
|
767
|
+
document.querySelectorAll('form[data-form-name]').forEach(form => {
|
|
768
|
+
form.addEventListener('submit', async (e) => {
|
|
769
|
+
e.preventDefault();
|
|
770
|
+
|
|
771
|
+
const formName = form.dataset.formName || 'general';
|
|
772
|
+
const formData = new FormData(form);
|
|
773
|
+
const data = Object.fromEntries(formData);
|
|
774
|
+
|
|
775
|
+
// IMPORTANT: Endpoint is /_forms/{formName}
|
|
776
|
+
const response = await fetch('/_forms/' + formName, {
|
|
777
|
+
method: 'POST',
|
|
778
|
+
headers: { 'Content-Type': 'application/json' },
|
|
779
|
+
body: JSON.stringify(data)
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
if (response.ok) {
|
|
783
|
+
form.reset();
|
|
784
|
+
alert(form.dataset.successMessage || 'Thank you!');
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
\`\`\`
|
|
789
|
+
|
|
790
|
+
**CRITICAL:** The form endpoint is \`/_forms/{formName}\` - NOT \`/api/forms/submit\`
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
## CRITICAL: Remove Original Form Handlers
|
|
795
|
+
|
|
796
|
+
**If the source site has JavaScript that handles form submissions, you MUST modify or remove it!**
|
|
797
|
+
|
|
798
|
+
The original site's JavaScript often does:
|
|
799
|
+
- \`e.preventDefault()\` - blocks the actual form submission
|
|
800
|
+
- Shows a "fake" success toast/message - but data goes nowhere
|
|
801
|
+
- Never actually submits to a server
|
|
802
|
+
|
|
803
|
+
### What to Look For in Original JS
|
|
804
|
+
|
|
805
|
+
\`\`\`javascript
|
|
806
|
+
// PROBLEM: This blocks real submissions!
|
|
807
|
+
form.addEventListener('submit', (e) => {
|
|
808
|
+
e.preventDefault();
|
|
809
|
+
// ... validation ...
|
|
810
|
+
showToast('Message sent!'); // FAKE! Data not saved!
|
|
811
|
+
});
|
|
812
|
+
\`\`\`
|
|
813
|
+
|
|
814
|
+
### What You Must Do
|
|
815
|
+
|
|
816
|
+
**Option A: Use Native Form Submission (Simplest)**
|
|
817
|
+
\`\`\`html
|
|
818
|
+
<form data-form-name="contact" action="/_forms/contact" method="POST">
|
|
819
|
+
<input type="text" name="name" required>
|
|
820
|
+
<input type="email" name="email" required>
|
|
821
|
+
<button type="submit">Send</button>
|
|
822
|
+
</form>
|
|
823
|
+
\`\`\`
|
|
824
|
+
Then **remove** the original JavaScript form handler entirely.
|
|
825
|
+
|
|
826
|
+
**Option B: Keep JS but POST to Fast Mode**
|
|
827
|
+
Replace the original handler with one that actually submits:
|
|
828
|
+
\`\`\`javascript
|
|
829
|
+
form.addEventListener('submit', async (e) => {
|
|
830
|
+
e.preventDefault();
|
|
831
|
+
const formName = form.dataset.formName;
|
|
832
|
+
const response = await fetch('/_forms/' + formName, {
|
|
833
|
+
method: 'POST',
|
|
834
|
+
headers: { 'Content-Type': 'application/json' },
|
|
835
|
+
body: JSON.stringify(Object.fromEntries(new FormData(form)))
|
|
836
|
+
});
|
|
837
|
+
if (response.ok) {
|
|
838
|
+
showToast('Message sent!'); // NOW it's real!
|
|
839
|
+
form.reset();
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
\`\`\`
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
846
|
+
## Naming Conventions
|
|
847
|
+
|
|
848
|
+
- Contact form → \`contact\`
|
|
849
|
+
- Newsletter → \`newsletter\`
|
|
850
|
+
- Quote request → \`quote-request\`
|
|
851
|
+
- Application → \`job-application\`
|
|
852
|
+
|
|
853
|
+
## Important
|
|
854
|
+
|
|
855
|
+
**Tenant context is automatic** - the system handles associating forms with the correct site.`,
|
|
856
|
+
assets: `# Asset Path Rules
|
|
857
|
+
|
|
858
|
+
## Static Assets (CSS, JS, Fonts, Site Images)
|
|
859
|
+
|
|
860
|
+
**ALL static asset paths must use /public/ prefix**
|
|
861
|
+
|
|
862
|
+
### HTML Examples
|
|
863
|
+
|
|
864
|
+
\`\`\`html
|
|
865
|
+
<!-- CSS -->
|
|
866
|
+
<link rel="stylesheet" href="/public/css/style.css">
|
|
867
|
+
|
|
868
|
+
<!-- JavaScript -->
|
|
869
|
+
<script src="/public/js/main.js"></script>
|
|
870
|
+
|
|
871
|
+
<!-- Static Images (logos, icons, decorative) -->
|
|
872
|
+
<img src="/public/images/logo.png" alt="Logo">
|
|
873
|
+
|
|
874
|
+
<!-- Favicon -->
|
|
875
|
+
<link rel="icon" href="/public/images/favicon.ico">
|
|
876
|
+
\`\`\`
|
|
877
|
+
|
|
878
|
+
### CSS Examples
|
|
879
|
+
|
|
880
|
+
\`\`\`css
|
|
881
|
+
/* Correct */
|
|
882
|
+
background-image: url('/public/images/hero.jpg');
|
|
883
|
+
src: url('/public/fonts/custom.woff2');
|
|
884
|
+
|
|
885
|
+
/* Wrong */
|
|
886
|
+
background-image: url('../images/hero.jpg');
|
|
887
|
+
background-image: url('images/hero.jpg');
|
|
888
|
+
\`\`\`
|
|
889
|
+
|
|
890
|
+
### Conversion Table
|
|
891
|
+
|
|
892
|
+
| Original | Converted |
|
|
893
|
+
|----------|-----------|
|
|
894
|
+
| css/style.css | /public/css/style.css |
|
|
895
|
+
| ../css/style.css | /public/css/style.css |
|
|
896
|
+
| ./images/logo.png | /public/images/logo.png |
|
|
897
|
+
| /images/logo.png | /public/images/logo.png |
|
|
898
|
+
|
|
899
|
+
---
|
|
900
|
+
|
|
901
|
+
## CMS Content Images (CRITICAL)
|
|
902
|
+
|
|
903
|
+
**CMS-managed images are different from static assets. They are uploaded by users through the CMS dashboard and served from a CDN.**
|
|
904
|
+
|
|
905
|
+
### Two Types of Images
|
|
906
|
+
|
|
907
|
+
1. **Static assets** (\`/public/images/\`) - Site logos, icons, decorative elements bundled in the package
|
|
908
|
+
2. **CMS images** (\`{{fieldName}}\`) - User-uploaded content images managed through the dashboard
|
|
909
|
+
|
|
910
|
+
### CMS Image Handling
|
|
911
|
+
|
|
912
|
+
**ALWAYS use CMS tokens for content images - NEVER hardcode URLs**
|
|
913
|
+
|
|
914
|
+
\`\`\`html
|
|
915
|
+
<!-- CORRECT: CMS token for dynamic content -->
|
|
916
|
+
{{#if image}}
|
|
917
|
+
<img src="{{image}}" alt="{{name}}">
|
|
918
|
+
{{/if}}
|
|
919
|
+
|
|
920
|
+
<!-- WRONG: Hardcoded example content -->
|
|
921
|
+
<img src="/images/example-post.jpg" alt="Example Post">
|
|
922
|
+
\`\`\`
|
|
923
|
+
|
|
924
|
+
### Image Field Examples
|
|
925
|
+
|
|
926
|
+
\`\`\`html
|
|
927
|
+
<!-- Post images -->
|
|
928
|
+
<img src="{{image}}" alt="{{name}}">
|
|
929
|
+
<img src="{{thumbnail}}" alt="{{name}}">
|
|
930
|
+
|
|
931
|
+
<!-- Team member photos -->
|
|
932
|
+
<img src="{{photo}}" alt="{{name}}">
|
|
933
|
+
|
|
934
|
+
<!-- Custom collection images (field slug becomes token) -->
|
|
935
|
+
<img src="{{heroImage}}" alt="{{name}}">
|
|
936
|
+
<img src="{{productImage}}" alt="{{name}}">
|
|
937
|
+
\`\`\`
|
|
938
|
+
|
|
939
|
+
### Common Mistake: Mixing Static and CMS Images
|
|
940
|
+
|
|
941
|
+
\`\`\`html
|
|
942
|
+
<!-- WRONG: Don't mix hardcoded images with CMS content -->
|
|
943
|
+
{{#each products}}
|
|
944
|
+
<img src="/images/product-placeholder.jpg" alt="Product"> <!-- BAD -->
|
|
945
|
+
<h2>{{name}}</h2>
|
|
946
|
+
{{/each}}
|
|
947
|
+
|
|
948
|
+
<!-- CORRECT: All content comes from CMS -->
|
|
949
|
+
{{#each products}}
|
|
950
|
+
{{#if image}}
|
|
951
|
+
<img src="{{image}}" alt="{{name}}">
|
|
952
|
+
{{/if}}
|
|
953
|
+
<h2>{{name}}</h2>
|
|
954
|
+
{{/each}}
|
|
955
|
+
\`\`\`
|
|
956
|
+
|
|
957
|
+
---
|
|
958
|
+
|
|
959
|
+
## External Assets
|
|
960
|
+
|
|
961
|
+
Keep external URLs unchanged:
|
|
962
|
+
\`\`\`html
|
|
963
|
+
<!-- These stay the same -->
|
|
964
|
+
<link href="https://fonts.googleapis.com/..." rel="stylesheet">
|
|
965
|
+
<script src="https://cdn.example.com/lib.js"></script>
|
|
966
|
+
\`\`\``,
|
|
967
|
+
checklist: `# Pre-Submission Checklist
|
|
968
|
+
|
|
969
|
+
## Structure
|
|
970
|
+
- [ ] manifest.json at package root
|
|
971
|
+
- [ ] Static pages in pages/ folder
|
|
972
|
+
- [ ] Assets in public/ folder
|
|
973
|
+
- [ ] Templates in templates/ (or pages/)
|
|
974
|
+
|
|
975
|
+
## SEO (CRITICAL)
|
|
976
|
+
- [ ] NO \`<title>\` tags in HTML
|
|
977
|
+
- [ ] NO \`<meta name="description">\` tags
|
|
978
|
+
- [ ] NO \`<meta property="og:*">\` tags
|
|
979
|
+
- [ ] NO \`<meta name="twitter:*">\` tags
|
|
980
|
+
- [ ] NO \`<link rel="icon">\` tags
|
|
981
|
+
- [ ] Include \`<!-- SEO managed by Fast Mode -->\` comment in head
|
|
982
|
+
|
|
983
|
+
## manifest.json
|
|
984
|
+
- [ ] All static pages listed in pages array
|
|
985
|
+
- [ ] Each page has path, file, title
|
|
986
|
+
- [ ] CMS templates configured with {slug}Index, {slug}IndexPath, {slug}Detail, {slug}DetailPath
|
|
987
|
+
- [ ] Paths match original site URLs
|
|
988
|
+
|
|
989
|
+
## Assets
|
|
990
|
+
- [ ] ALL CSS uses /public/ paths
|
|
991
|
+
- [ ] ALL JS uses /public/ paths
|
|
992
|
+
- [ ] ALL images use /public/ paths
|
|
993
|
+
- [ ] CSS background-image urls updated
|
|
994
|
+
- [ ] Font paths in CSS updated
|
|
995
|
+
|
|
996
|
+
## Templates
|
|
997
|
+
- [ ] Templates include header/footer
|
|
998
|
+
- [ ] {{#each}} loops have {{/each}}
|
|
999
|
+
- [ ] {{#if}} conditions have {{/if}}
|
|
1000
|
+
- [ ] {{#eq}} comparisons have {{/eq}}
|
|
1001
|
+
- [ ] Rich text uses {{{triple braces}}}
|
|
1002
|
+
- [ ] Parent refs (../) only inside loops
|
|
1003
|
+
- [ ] Correct field names used
|
|
1004
|
+
- [ ] **Static UI images** (logos, icons) use \`/public/\` paths
|
|
1005
|
+
- [ ] **Content images** use CMS tokens
|
|
1006
|
+
- [ ] **No hardcoded example content** - dynamic items use \`{{#each}}\` loops
|
|
1007
|
+
- [ ] Index pages iterate over collections, not static example cards
|
|
1008
|
+
|
|
1009
|
+
## Static Pages
|
|
1010
|
+
- [ ] data-edit-key on editable text
|
|
1011
|
+
- [ ] Keys are unique and descriptive
|
|
1012
|
+
- [ ] Forms have data-form-name
|
|
1013
|
+
|
|
1014
|
+
## Final Validation
|
|
1015
|
+
- [ ] Run validate_manifest with manifest.json
|
|
1016
|
+
- [ ] Run validate_template on each template
|
|
1017
|
+
- [ ] Run validate_package for full check`,
|
|
1018
|
+
common_mistakes: `# COMMON MISTAKES TO AVOID
|
|
1019
|
+
|
|
1020
|
+
**IMPORTANT:** AI agents frequently make these mistakes during website conversion. Read this section carefully before building a package.
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## 1. WRONG Manifest Format
|
|
1025
|
+
|
|
1026
|
+
AIs often use a nested object format that Fast Mode does NOT support.
|
|
1027
|
+
|
|
1028
|
+
**WRONG (will NOT work):**
|
|
1029
|
+
\`\`\`json
|
|
1030
|
+
{
|
|
1031
|
+
"collections": {
|
|
1032
|
+
"posts": {
|
|
1033
|
+
"indexPath": "/blog",
|
|
1034
|
+
"indexFile": "collections/posts/index.html",
|
|
1035
|
+
"detailPath": "/blog/:slug",
|
|
1036
|
+
"detailFile": "collections/posts/detail.html"
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
\`\`\`
|
|
1041
|
+
|
|
1042
|
+
**CORRECT (use this):**
|
|
1043
|
+
\`\`\`json
|
|
1044
|
+
{
|
|
1045
|
+
"cmsTemplates": {
|
|
1046
|
+
"postsIndex": "templates/posts_index.html",
|
|
1047
|
+
"postsDetail": "templates/posts_detail.html",
|
|
1048
|
+
"postsIndexPath": "/blog",
|
|
1049
|
+
"postsDetailPath": "/blog"
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
\`\`\`
|
|
1053
|
+
|
|
1054
|
+
Key differences:
|
|
1055
|
+
- Use \`cmsTemplates\`, NOT \`collections\`
|
|
1056
|
+
- Use FLAT keys: \`{slug}Index\`, \`{slug}Detail\`, \`{slug}IndexPath\`, \`{slug}DetailPath\`
|
|
1057
|
+
- DO NOT nest objects inside collection names
|
|
1058
|
+
|
|
1059
|
+
---
|
|
1060
|
+
|
|
1061
|
+
## 2. WRONG Asset Folder
|
|
1062
|
+
|
|
1063
|
+
Fast Mode serves static assets from \`/public/\`, NOT \`/assets/\`.
|
|
1064
|
+
|
|
1065
|
+
**WRONG:**
|
|
1066
|
+
\`\`\`
|
|
1067
|
+
assets/
|
|
1068
|
+
├── css/style.css ← Will return 404!
|
|
1069
|
+
└── js/main.js ← Will return 404!
|
|
1070
|
+
\`\`\`
|
|
1071
|
+
|
|
1072
|
+
**CORRECT:**
|
|
1073
|
+
\`\`\`
|
|
1074
|
+
public/
|
|
1075
|
+
├── css/style.css ← Reference as /public/css/style.css in HTML
|
|
1076
|
+
└── js/main.js ← Reference as /public/js/main.js in HTML
|
|
1077
|
+
\`\`\`
|
|
1078
|
+
|
|
1079
|
+
If CSS/JS isn't loading, check that:
|
|
1080
|
+
- Files are in \`public/\` folder (NOT \`assets/\`)
|
|
1081
|
+
- HTML references use \`/public/css/style.css\` (WITH the /public/ prefix)
|
|
1082
|
+
|
|
1083
|
+
---
|
|
1084
|
+
|
|
1085
|
+
## 3. WRONG Template Folder
|
|
1086
|
+
|
|
1087
|
+
CMS templates go in \`templates/\`, NOT \`collections/\`.
|
|
1088
|
+
|
|
1089
|
+
**WRONG:**
|
|
1090
|
+
\`\`\`
|
|
1091
|
+
collections/
|
|
1092
|
+
├── posts/
|
|
1093
|
+
│ ├── index.html
|
|
1094
|
+
│ └── detail.html
|
|
1095
|
+
\`\`\`
|
|
1096
|
+
|
|
1097
|
+
**CORRECT:**
|
|
1098
|
+
\`\`\`
|
|
1099
|
+
templates/
|
|
1100
|
+
├── posts_index.html
|
|
1101
|
+
└── posts_detail.html
|
|
1102
|
+
\`\`\`
|
|
1103
|
+
|
|
1104
|
+
Template naming: \`{collection}_index.html\` and \`{collection}_detail.html\`
|
|
1105
|
+
|
|
1106
|
+
---
|
|
1107
|
+
|
|
1108
|
+
## 4. MISSING Inline Editing
|
|
1109
|
+
|
|
1110
|
+
Without \`data-edit-key\`, users cannot edit text in the visual editor!
|
|
1111
|
+
|
|
1112
|
+
**WRONG (no inline editing):**
|
|
1113
|
+
\`\`\`html
|
|
1114
|
+
<h1>{{page_title}}</h1>
|
|
1115
|
+
<p>{{intro_text}}</p>
|
|
1116
|
+
\`\`\`
|
|
1117
|
+
|
|
1118
|
+
**CORRECT (enables inline editing):**
|
|
1119
|
+
\`\`\`html
|
|
1120
|
+
<h1 data-edit-key="page_title">{{page_title}}</h1>
|
|
1121
|
+
<p data-edit-key="intro_text">{{intro_text}}</p>
|
|
1122
|
+
\`\`\`
|
|
1123
|
+
|
|
1124
|
+
Every editable text element in static pages should have a unique \`data-edit-key\`.
|
|
1125
|
+
|
|
1126
|
+
---
|
|
1127
|
+
|
|
1128
|
+
## 5. Template URL Mismatches
|
|
1129
|
+
|
|
1130
|
+
Make sure links in templates match the manifest's path configuration.
|
|
1131
|
+
|
|
1132
|
+
**WRONG:**
|
|
1133
|
+
\`\`\`json
|
|
1134
|
+
// manifest.json
|
|
1135
|
+
"postsDetailPath": "/blog"
|
|
1136
|
+
\`\`\`
|
|
1137
|
+
\`\`\`html
|
|
1138
|
+
<!-- template -->
|
|
1139
|
+
<a href="/posts/{{slug}}">Read more</a> ← Wrong path!
|
|
1140
|
+
\`\`\`
|
|
1141
|
+
|
|
1142
|
+
**CORRECT:**
|
|
1143
|
+
\`\`\`json
|
|
1144
|
+
// manifest.json
|
|
1145
|
+
"postsDetailPath": "/blog"
|
|
1146
|
+
\`\`\`
|
|
1147
|
+
\`\`\`html
|
|
1148
|
+
<!-- template -->
|
|
1149
|
+
<a href="{{url}}">Read more</a> ← Uses built-in {{url}} token
|
|
1150
|
+
\`\`\`
|
|
1151
|
+
|
|
1152
|
+
**TIP:** Use \`{{url}}\` instead of hardcoding paths - it automatically uses the manifest config.
|
|
1153
|
+
|
|
1154
|
+
---
|
|
1155
|
+
|
|
1156
|
+
## 6. Field Naming Conventions
|
|
1157
|
+
|
|
1158
|
+
Use \`snake_case\` for field slugs, not camelCase.
|
|
1159
|
+
|
|
1160
|
+
**WRONG:**
|
|
1161
|
+
\`\`\`
|
|
1162
|
+
heroImage
|
|
1163
|
+
authorBio
|
|
1164
|
+
publishedDate
|
|
1165
|
+
\`\`\`
|
|
1166
|
+
|
|
1167
|
+
**CORRECT:**
|
|
1168
|
+
\`\`\`
|
|
1169
|
+
hero_image
|
|
1170
|
+
author_bio
|
|
1171
|
+
published_date
|
|
1172
|
+
\`\`\`
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
## 7. Inlining CSS/JS (Last Resort)
|
|
1177
|
+
|
|
1178
|
+
If external CSS/JS absolutely won't load, you can inline them in manifest.json:
|
|
1179
|
+
|
|
1180
|
+
\`\`\`json
|
|
1181
|
+
{
|
|
1182
|
+
"defaultHeadHtml": "<style>/* all CSS here */</style>",
|
|
1183
|
+
"defaultBodyEndHtml": "<script>/* all JS here */</script>"
|
|
1184
|
+
}
|
|
1185
|
+
\`\`\`
|
|
1186
|
+
|
|
1187
|
+
**But try fixing the /public/ folder first** - inlining makes updates harder.
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
## 8. Keeping Original Form Handlers
|
|
1192
|
+
|
|
1193
|
+
The source site often has JavaScript that "handles" form submissions with fake success messages.
|
|
1194
|
+
|
|
1195
|
+
**WRONG (original site's JS):**
|
|
1196
|
+
\`\`\`javascript
|
|
1197
|
+
form.addEventListener('submit', (e) => {
|
|
1198
|
+
e.preventDefault();
|
|
1199
|
+
showToast('Message sent!'); // FAKE! Data goes nowhere!
|
|
1200
|
+
});
|
|
1201
|
+
\`\`\`
|
|
1202
|
+
|
|
1203
|
+
**CORRECT (submit to Fast Mode):**
|
|
1204
|
+
\`\`\`html
|
|
1205
|
+
<form data-form-name="contact" action="/_forms/contact" method="POST">
|
|
1206
|
+
<!-- inputs -->
|
|
1207
|
+
</form>
|
|
1208
|
+
\`\`\`
|
|
1209
|
+
Then REMOVE the original JavaScript handler, or replace it with one that actually POSTs to \`/_forms/{formName}\`.
|
|
1210
|
+
|
|
1211
|
+
**See \`get_conversion_guide(section: "forms")\` for detailed form handling instructions.**
|
|
1212
|
+
|
|
1213
|
+
---
|
|
1214
|
+
|
|
1215
|
+
## Validation Workflow
|
|
1216
|
+
|
|
1217
|
+
Before deploying, ALWAYS run:
|
|
1218
|
+
|
|
1219
|
+
1. \`validate_manifest\` - Catches format errors
|
|
1220
|
+
2. \`validate_template\` - Catches token errors
|
|
1221
|
+
3. \`validate_package\` - Full structure check
|
|
1222
|
+
|
|
1223
|
+
These tools will catch most of the above mistakes before deployment.`,
|
|
1224
|
+
full: '', // Will be assembled from all sections
|
|
1225
|
+
};
|
|
1226
|
+
/**
|
|
1227
|
+
* Returns the conversion guide, either full or a specific section
|
|
1228
|
+
*/
|
|
1229
|
+
async function getConversionGuide(section) {
|
|
1230
|
+
if (section === 'full') {
|
|
1231
|
+
return `# Complete Website Conversion Guide
|
|
1232
|
+
|
|
1233
|
+
---
|
|
1234
|
+
|
|
1235
|
+
## WORKFLOW OVERVIEW (Follow This Order)
|
|
1236
|
+
|
|
1237
|
+
| Step | Action | Tools to Use |
|
|
1238
|
+
|------|--------|--------------|
|
|
1239
|
+
| 1. SETUP | Call list_projects → Ask user for project → Get projectId | \`list_projects\`, \`create_site\` |
|
|
1240
|
+
| 2. SCHEMA | Get existing schema OR plan new collections | \`get_tenant_schema\`, \`sync_schema\` |
|
|
1241
|
+
| 3. ANALYZE | Map URLs, identify collections, document assets | (manual) |
|
|
1242
|
+
| 4. BUILD | Create manifest.json, pages/, templates/, public/ | \`get_example\` |
|
|
1243
|
+
| 5. VALIDATE | Check all files before deploying | \`validate_manifest\`, \`validate_template\`, \`validate_package\` |
|
|
1244
|
+
| 6. DEPLOY | Upload package to project | \`deploy_package\` |
|
|
1245
|
+
| 7. CONTENT | Optionally manage CMS content | \`create_cms_item\`, \`list_cms_items\`, \`update_cms_item\` |
|
|
1246
|
+
|
|
1247
|
+
**DO NOT skip steps. You MUST have a projectId before you can deploy.**
|
|
1248
|
+
|
|
1249
|
+
## CONTENT MANAGEMENT TOOLS
|
|
1250
|
+
|
|
1251
|
+
After deployment, you can manage CMS content directly:
|
|
1252
|
+
|
|
1253
|
+
| Tool | Description |
|
|
1254
|
+
|------|-------------|
|
|
1255
|
+
| \`create_cms_item\` | Create a new item (blog post, team member, etc.) |
|
|
1256
|
+
| \`list_cms_items\` | List items in a collection with optional filters |
|
|
1257
|
+
| \`get_cms_item\` | Get a single item by slug |
|
|
1258
|
+
| \`update_cms_item\` | Update an existing item |
|
|
1259
|
+
| \`delete_cms_item\` | Delete an item (**requires user confirmation**) |
|
|
1260
|
+
|
|
1261
|
+
**⚠️ DELETE SAFETY:** Never delete without asking the user first. The \`delete_cms_item\` tool requires \`confirmDelete: true\` which you should only set after explicit user permission.
|
|
1262
|
+
|
|
1263
|
+
---
|
|
1264
|
+
|
|
1265
|
+
${SECTIONS.first_steps}
|
|
1266
|
+
---
|
|
1267
|
+
|
|
1268
|
+
${SECTIONS.common_mistakes}
|
|
1269
|
+
|
|
1270
|
+
---
|
|
1271
|
+
|
|
1272
|
+
${SECTIONS.analysis}
|
|
1273
|
+
|
|
1274
|
+
---
|
|
1275
|
+
|
|
1276
|
+
${SECTIONS.structure}
|
|
1277
|
+
|
|
1278
|
+
---
|
|
1279
|
+
|
|
1280
|
+
${SECTIONS.seo}
|
|
1281
|
+
|
|
1282
|
+
---
|
|
1283
|
+
|
|
1284
|
+
${SECTIONS.manifest}
|
|
1285
|
+
|
|
1286
|
+
---
|
|
1287
|
+
|
|
1288
|
+
${SECTIONS.templates}
|
|
1289
|
+
|
|
1290
|
+
---
|
|
1291
|
+
|
|
1292
|
+
${SECTIONS.tokens}
|
|
1293
|
+
|
|
1294
|
+
---
|
|
1295
|
+
|
|
1296
|
+
${SECTIONS.forms}
|
|
1297
|
+
|
|
1298
|
+
---
|
|
1299
|
+
|
|
1300
|
+
${SECTIONS.assets}
|
|
1301
|
+
|
|
1302
|
+
---
|
|
1303
|
+
|
|
1304
|
+
${SECTIONS.checklist}
|
|
1305
|
+
|
|
1306
|
+
---
|
|
1307
|
+
|
|
1308
|
+
## Need More Help?
|
|
1309
|
+
|
|
1310
|
+
Use these MCP tools during conversion:
|
|
1311
|
+
|
|
1312
|
+
- \`get_field_types\` - Get available field types for sync_schema
|
|
1313
|
+
- \`get_tenant_schema\` - Get exact collections and fields for a specific project
|
|
1314
|
+
- \`get_example\` - Get code examples for specific patterns
|
|
1315
|
+
- \`validate_manifest\` - Check your manifest.json
|
|
1316
|
+
- \`validate_template\` - Check template token usage
|
|
1317
|
+
- \`validate_package\` - Full package validation
|
|
1318
|
+
- \`generate_sample_items\` - Create placeholder items after creating new collections (handles relation dependencies automatically)
|
|
1319
|
+
|
|
1320
|
+
**Validate as you work** - don't wait until the end!`;
|
|
1321
|
+
}
|
|
1322
|
+
return SECTIONS[section] || `Section not found: ${section}`;
|
|
1323
|
+
}
|