@sleekcms/sync 2.7.2 → 2.9.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 +19 -3
- package/dist/AGENT.md +108 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,9 +201,11 @@ No manual deploy. No waiting. SleekCMS rebuilds on every sync and the preview re
|
|
|
201
201
|
│ ├── entries/<key>[].json # Collection entry content (array)
|
|
202
202
|
│ └── images.json # Reusable site-level images (handle → shortcut)
|
|
203
203
|
│
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
├── public/
|
|
205
|
+
│ ├── css/<name>.css # Stylesheets
|
|
206
|
+
│ └── js/<name>.js # Scripts
|
|
207
|
+
│
|
|
208
|
+
└── setup.json # Site setup: default page title, Tailwind on/off, sitemap on/off
|
|
207
209
|
```
|
|
208
210
|
|
|
209
211
|
### File naming
|
|
@@ -410,6 +412,20 @@ Any form with `data-sleekcms="<name>"` captures and stores submissions in the CM
|
|
|
410
412
|
|
|
411
413
|
Content lives in `src/content/` as plain JSON. Edit any content file and save — it syncs like any other file.
|
|
412
414
|
|
|
415
|
+
## Site setup (`src/setup.json`)
|
|
416
|
+
|
|
417
|
+
Site-wide decisions made once — the default page title, whether Tailwind is compiled, and whether a sitemap is published — live in `src/setup.json`, mirrored from **Settings → Website** in the CMS. The file is JSON with a comment above each key explaining it:
|
|
418
|
+
|
|
419
|
+
```json5
|
|
420
|
+
{
|
|
421
|
+
title: "Acme Dental", // default <title> for pages without their own; null = the site name
|
|
422
|
+
tw_enabled: true, // compile src/public/css/tailwind.css and inject it into every page
|
|
423
|
+
sitemap_enabled: false, // publish sitemap.xml + robots.txt on every build
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Edit the values and save — it syncs like any other file. Keys you leave out keep their current value. Which pages are excluded from the sitemap is managed in the CMS UI, not in this file.
|
|
428
|
+
|
|
413
429
|
## Images
|
|
414
430
|
|
|
415
431
|
Images are part of content, not a separate asset pipeline. Drop a shortcut string into any `image` field and the sync engine resolves it to a full `{ url, alt }` object on save:
|
package/dist/AGENT.md
CHANGED
|
@@ -9,6 +9,7 @@ SleekCMS is a headless CMS plus static site generator. There are no servers, no
|
|
|
9
9
|
| **Model** (`.model`) | The shape of the content (field names + types) | `src/models/...` |
|
|
10
10
|
| **Content** (`.json` / `.md`) | The actual values for those fields | `src/content/...` |
|
|
11
11
|
| **View** (`.ejs`) | The HTML template that renders the content | `src/pages/`, `src/entries/`, `src/blocks/`, `src/layouts/` |
|
|
12
|
+
| **Assets** (`.css` / `.js`) | Stylesheets and scripts | `src/css/`, `src/js/` |
|
|
12
13
|
|
|
13
14
|
A page's HTML = **layout** wrapping ( **page view** rendering **page content** validated by **page model** ).
|
|
14
15
|
|
|
@@ -75,19 +76,39 @@ blocks/<key>.ejs Block views (required for every block)
|
|
|
75
76
|
layouts/common.ejs The single site layout (see §8)
|
|
76
77
|
|
|
77
78
|
css/<name>.css Stylesheets — must be included via link()
|
|
78
|
-
css/tailwind.css Tailwind (auto-compiled, auto-injected — never link() it)
|
|
79
|
+
css/tailwind.css Tailwind v4 input CSS (auto-compiled, auto-injected — never link() it; @theme design tokens + the /* DESIGN */ header live here)
|
|
79
80
|
js/<name>.js Scripts — must be included via script()
|
|
80
81
|
|
|
81
82
|
content/pages/<key>.json Content for a single page (object)
|
|
82
|
-
content/pages/<key
|
|
83
|
-
content/pages/<key
|
|
83
|
+
content/pages/<key>+/<slug>.json Content for ONE record of a collection page — one file per slug
|
|
84
|
+
content/pages/<key>+/<slug>.md Same record as Markdown + frontmatter, when the model qualifies (§7)
|
|
84
85
|
content/entries/<key>.json Content for a single entry (object)
|
|
85
|
-
content/entries/<key
|
|
86
|
-
|
|
86
|
+
content/entries/<key>+.json Collection entry — ONE file holding a JSON array of all items
|
|
87
87
|
content/images.json Site-level reusable images: handle → shortcut (§7)
|
|
88
|
-
content/videos.json Site-level videos: handle →
|
|
88
|
+
content/videos.json Site-level videos: handle → pexels:/pixabay: ref (user uploads show as embed_url) (§7)
|
|
89
|
+
|
|
90
|
+
setup.json Site setup (JSON, comments allowed): default page title, Tailwind on/off, sitemap on/off — edit values in place, never delete
|
|
89
91
|
```
|
|
90
92
|
|
|
93
|
+
**Collection storage differs by kind — do not confuse them:**
|
|
94
|
+
- **Pages collection** (`blog+`) → a *folder* of per-slug files: `content/pages/blog+/post-1.json`, `post-2.json`, … Each item has its own URL.
|
|
95
|
+
- **Entries collection** (`testimonials+`) → a *single file* containing a JSON array: `content/entries/testimonials+.json` holds `[{...}, {...}]`. Items are shared data with no URLs.
|
|
96
|
+
|
|
97
|
+
**Path spaces** — three different things use `/`-style paths; don't confuse them:
|
|
98
|
+
- **Workspace file path** (what you edit under `src/`): `pages/about.ejs`, `css/styles.css`.
|
|
99
|
+
- **Public URL** (what `link()`/`script()` and HTML `href`/`src` take): `/css/styles.css`, `/js/app.js`.
|
|
100
|
+
- **Page route** (what visitors see): `/about`, `/blog/<slug>`. Computed from the page key.
|
|
101
|
+
|
|
102
|
+
**Site setup (`setup.json`)** — the set-once site decisions, mirrored from Settings > Website in the CMS. Each key is explained by the comment above it in the file:
|
|
103
|
+
|
|
104
|
+
| Key | Value | Effect |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `title` | string or `null` | Default `<title>` for pages that don't set their own (via `title()` or the SEO block). `null` = the site name. |
|
|
107
|
+
| `tw_enabled` | boolean | `true` compiles `css/tailwind.css` and injects it into every page — required for any site styled with Tailwind utilities. |
|
|
108
|
+
| `sitemap_enabled` | boolean | `true` publishes `sitemap.xml` + `robots.txt` on every build. Turn it on for public sites and SEO work. |
|
|
109
|
+
|
|
110
|
+
Edit values in place and keep the keys. Keys you omit keep their stored values; unknown keys are ignored. Which pages are excluded from the sitemap is a CMS UI setting, not a key in this file. Never delete the file.
|
|
111
|
+
|
|
91
112
|
## 5. Common Tasks
|
|
92
113
|
|
|
93
114
|
### Create a single page (e.g. `/about`)
|
|
@@ -103,7 +124,7 @@ content/videos.json Site-level videos: handle → embed_url. READ-ON
|
|
|
103
124
|
2. `pages/blog+.ejs` — renders **one** post; `item` is that post's content.
|
|
104
125
|
3. One content file per post: `content/pages/blog+/<slug>.json` (or `.md` when the model qualifies, §7).
|
|
105
126
|
4. Optionally create a listing page with key `blog` (single page) that loops with `getPages('/blog', { collection: true })`.
|
|
106
|
-
5. For blogs, also create an RSS feed (§
|
|
127
|
+
5. For blogs, also create an RSS feed (§12) and link it for autodiscovery.
|
|
107
128
|
|
|
108
129
|
### Change what a page says
|
|
109
130
|
|
|
@@ -130,7 +151,7 @@ Like JSON but with **no quotes**; each value is a field type name:
|
|
|
130
151
|
features: [
|
|
131
152
|
{
|
|
132
153
|
title: text,
|
|
133
|
-
icon:
|
|
154
|
+
icon: icon
|
|
134
155
|
}
|
|
135
156
|
]
|
|
136
157
|
}
|
|
@@ -155,9 +176,10 @@ Like JSON but with **no quotes**; each value is a field type name:
|
|
|
155
176
|
| `date` | `"YYYY-MM-DD"` string |
|
|
156
177
|
| `datetime` | ISO 8601 string |
|
|
157
178
|
| `time` | `"HH:mm"` string |
|
|
158
|
-
| `image` | Shortcut string (preferred when writing) or resolved `{ "url", "alt" }` object — see §7.
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
179
|
+
| `image` | Shortcut string (preferred when writing) or resolved `{ "url", "alt" }` object — see §7. Shortcuts only resolve in `image`/`icon` fields — in a `text` field the string stays literal text |
|
|
180
|
+
| `icon` | Svg icon. Use for icons the user is expected to edit/swap in the CMS (e.g. features/services). Fill with placeholder `"iconify:<prefix:name or search>"` (or `"cms:<handle>"` pointing at an svg); render with `svg(item.icon)`. Fixed UI icons don't need fields — call `svg('iconify:...')` directly in templates |
|
|
181
|
+
| `video` | Ref string (preferred when writing) or the resolved object you read back. Write `"cms:<handle>"` to reuse a video from `content/videos.json` (or a user upload by its handle), or `"pexels:<search>\|<title>"` / `"pixabay:<search>\|<title>"` for a one-off — resolved server-side (see §7). **Never invent** `video_id`/`embed_url` objects; render with `embed()` only |
|
|
182
|
+
| `file` | Write a plain file name, e.g. `"brochure.pdf"` (pdf, docx, xlsx, pptx, csv, tsv, txt, md — no path, no URL, never an object). On save the CMS attaches the site file with that name, or creates a small placeholder file the user replaces in the CMS UI; reads back as the file name. Render a link with `item.x.url` / `item.x.name` |
|
|
161
183
|
| `json` | Any JSON |
|
|
162
184
|
| `sheet` | Array of arrays |
|
|
163
185
|
| `location` | `{ "markers": [{ "lat", "lng" }], "img": "<google static map url>" }` — `img` supports Google Static Maps params; markers are already part of the URL |
|
|
@@ -168,6 +190,8 @@ Like JSON but with **no quotes**; each value is a field type name:
|
|
|
168
190
|
| Group `{ ... }` | Nested object |
|
|
169
191
|
| Collection `[{ ... }]` | Array of nested objects |
|
|
170
192
|
|
|
193
|
+
> **Images need an `image` field and icons an `icon` field — never `text`.** A shortcut string (`unsplash:`/`pexels:`/`pixabay:`/`iconify:`/`url:`/`cms:`) resolves to a real image **only** when its model field is typed `image` or `icon`, or inside a `markdown` body via ``. In any other field — `text`, `paragraph`, `richtext`, `code`, `link` — the shortcut is stored and rendered **verbatim**. Symptom: a raw `iconify:…`/`pexels:…` string showing up on the page means the field was mis-typed — change it to `image`/`icon` in the `.model` and the existing value resolves on the next save.
|
|
194
|
+
|
|
171
195
|
### Entry references in content
|
|
172
196
|
|
|
173
197
|
The value is the **0-based index** of the target record in `content/entries/<key>+.json`, written as a string:
|
|
@@ -225,7 +249,7 @@ Content files hold the values for model fields. Keys must match the model. Savin
|
|
|
225
249
|
|
|
226
250
|
### Images — shortcut strings
|
|
227
251
|
|
|
228
|
-
When writing an `image` value, prefer the shortcut form `"<source>:<search>"`. Sources: `unsplash`, `pexels`, `pixabay`, `iconify`, `url` (direct URL), `cms` (reusable handle, below).
|
|
252
|
+
When writing an `image` value, prefer the shortcut form `"<source>:<search>"`. Sources — this is the complete set: `unsplash`, `pexels`, `pixabay`, `iconify`, `url` (direct URL), `cms` (reusable handle, below).
|
|
229
253
|
|
|
230
254
|
- `"pexels:doctor"` — stock search
|
|
231
255
|
- `"url:https://example.com/photo.jpg"` — specific asset
|
|
@@ -234,21 +258,33 @@ When writing an `image` value, prefer the shortcut form `"<source>:<search>"`. S
|
|
|
234
258
|
|
|
235
259
|
On save, the sync engine resolves each shortcut to a real `{ "url", "alt" }` object.
|
|
236
260
|
|
|
237
|
-
**
|
|
261
|
+
Give **every** image field a resolvable shortcut from the sources above — including slots the user will eventually fill with their own photos (personal galleries, headshots, family pages, "I'll add my pictures later"): write a stock search whose subject and alt describe the photo that belongs there, e.g. `"pexels:friends laughing golden hour|Ghazia with her girlies"`. The site ships with a real photo in every slot, and the user swaps any image for their own in the CMS UI whenever they're ready.
|
|
238
262
|
|
|
239
|
-
**
|
|
263
|
+
**Shortcuts only resolve in `image` and `icon` fields.** A common mistake: declaring a visual field as `text` (e.g. `icon: text`) and then writing `"iconify:lucide:clock"` as its value. Nothing errors — the string simply stays literal text and never resolves. Any field that will hold a photo or illustration must be typed `image`, and any CMS-editable icon must be typed `icon`. If a shortcut isn't resolving, check the field's type in the `.model` first.
|
|
264
|
+
|
|
265
|
+
**Reusable images and customizable site icons:** for site-wide images or icons the user is expected to change in the CMS (e.g. site logo), declare them once in `content/images.json` as `handle → shortcut` with a placeholder shortcut:
|
|
240
266
|
|
|
241
267
|
```json
|
|
242
|
-
{ "logo": "
|
|
268
|
+
{ "logo": "iconify:lucide:sparkles", "hero": "pexels:mountain sunrise" }
|
|
243
269
|
```
|
|
244
270
|
|
|
245
|
-
then reference with `"cms:logo"` in any image field, or
|
|
271
|
+
then reference with `"cms:logo"` in any `image`/`icon` field, or render directly in templates — `svg('cms:logo', { class: 'h-8 w-auto' })` for SVGs/icons, `img('cms:hero', '1920x600')` for photos. (`getImage('logo')` returns the resolved image object for a bare handle — rarely needed; the helpers take the value or `cms:` ref directly.)
|
|
246
272
|
|
|
247
273
|
**Images inside markdown:** use the same shortcuts in standard syntax: ``. Append `|<alt>` for alt text and an optional `<W>x<H>` token to set rendered size (default `600x400`): ``. Refs are rewritten to CDN URLs on save.
|
|
248
274
|
|
|
249
|
-
### Videos
|
|
275
|
+
### Videos
|
|
276
|
+
|
|
277
|
+
`content/videos.json` maps `handle → ref` for site-level videos. To add a stock video, declare a handle with a `pexels:<search>` or `pixabay:<search>` ref (their free stock **video** catalogs) — optional `_` (landscape) / `/` (portrait) marker, optional `|<title>`:
|
|
278
|
+
|
|
279
|
+
```json
|
|
280
|
+
{ "hero-loop": "pexels:_ocean waves|Hero background loop" }
|
|
281
|
+
```
|
|
250
282
|
|
|
251
|
-
|
|
283
|
+
On save the platform searches the provider, picks a clip, and ingests it into the site's video library; processing is asynchronous (the embed shows a processing screen until transcoding finishes — no action needed). Writes MERGE by handle: handles you omit are preserved. Re-saving the same ref is a no-op; changing a ref replaces the video. Entries whose value is an **embed URL** are videos the user uploaded in the CMS UI — echo or omit them, never modify or re-point them; deletion is CMS-UI-only.
|
|
284
|
+
|
|
285
|
+
Render in templates: `<%- embed(getVideo('hero-loop'), { size: '1280x720' }) %>`. Never hand-roll an `<iframe>`.
|
|
286
|
+
|
|
287
|
+
Content `video` fields accept the same refs: `"cms:<handle>"` reuses a declared (or user-uploaded) video by handle — preferred when the same video appears in more than one place — while a direct `"pexels:<search>|<title>"` / `"pixabay:<search>|<title>"` creates a record-local video. Stock videos read back as refs; user-uploaded videos read back as objects — echo those untouched.
|
|
252
288
|
|
|
253
289
|
### Markdown content files (`.md`)
|
|
254
290
|
|
|
@@ -304,6 +340,7 @@ There is exactly **one** layout: `layouts/common.ejs`. It wraps every page autom
|
|
|
304
340
|
<head>
|
|
305
341
|
<meta charset="UTF-8">
|
|
306
342
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
343
|
+
<% if (item.seo) { %><%- render(item.seo) %><% } %>
|
|
307
344
|
</head>
|
|
308
345
|
<body>
|
|
309
346
|
<header><%- render(getEntry('header')) %></header>
|
|
@@ -321,7 +358,7 @@ There is exactly **one** layout: `layouts/common.ejs`. It wraps every page autom
|
|
|
321
358
|
| `getPages(path, opts?)` | Array | Pages whose path starts with the prefix. **Always a URL path, never a key.** `{ collection: true }` → collection pages only |
|
|
322
359
|
| `getEntry(handle)` | Object \| Array | Entry by key. The `+` is optional — `getEntry('authors')` and `getEntry('authors+')` are equivalent. Single → object, collection → array |
|
|
323
360
|
| `getSlugs(path)` | string[] | Slugs under a collection path |
|
|
324
|
-
| `getImage(handle)` | Object \| undefined | Resolved image from `images.json` |
|
|
361
|
+
| `getImage(handle)` | Object \| undefined | Resolved image from `images.json` (bare handle, no `cms:` prefix) |
|
|
325
362
|
| `getVideo(handle)` | Object \| undefined | Video from `videos.json` |
|
|
326
363
|
| `getContent(query?)` | Any | Full content payload, optionally filtered with JMESPath |
|
|
327
364
|
| `path(page)` | String | URL path of a page object |
|
|
@@ -345,6 +382,12 @@ There is exactly **one** layout: `layouts/common.ejs`. It wraps every page autom
|
|
|
345
382
|
|
|
346
383
|
`attr` is `"WxH"` or `{ w, h, size, fit, type, class, style }`. `location` values can be passed to `src()`/`img()` for static maps, to `embed(loc, attr?)` for a Google Maps iframe, or to `markers(loc)` for raw coordinates (`attr` adds `zoom`, `maptype`, `scale`).
|
|
347
384
|
|
|
385
|
+
Render `icon` fields or direct icon shortcuts with `svg()` — it inlines the svg markup at build time, so the icon scales cleanly and can be styled with CSS (e.g. `svg(item.icon, { class: 'w-6 h-6' })` or directly `svg('iconify:lucide:arrow-right', { class: 'w-4 h-4' })`).
|
|
386
|
+
|
|
387
|
+
> **Direct UI Icons vs. CMS-Editable Icons:**
|
|
388
|
+
> - **Static UI icons** (chevrons, arrows, badges, buttons, decorative bullet checks): render directly in `.ejs` with `svg('iconify:<family>:<name>', { class: '...' })` — no model field or `images.json` entry required.
|
|
389
|
+
> - **User-editable icons** (site logo, service/feature icons): when the user is expected to customize/swap the icon in the CMS, expose it via `content/images.json` (for site handles like `"logo"`) or an `icon: icon` model field, providing a placeholder `"iconify:..."` in content so the site ships visually complete. Render with `svg('cms:logo')` or `svg(item.icon)`.
|
|
390
|
+
|
|
348
391
|
### Helpers — video
|
|
349
392
|
|
|
350
393
|
`embed(video, attr?)` returns a provider-aware `<iframe>`. `attr` is `"WxH"` or `{ w, h, size, autoplay, muted, loop, preload, t, class, style }`. Browsers block autoplay unless `muted: true` is also set.
|
|
@@ -371,7 +414,7 @@ Callable from **any** view (page, block, entry, layout); duplicates are removed
|
|
|
371
414
|
<section data-aos="fade-up" data-aos-delay="150">...</section>
|
|
372
415
|
```
|
|
373
416
|
|
|
374
|
-
**CSS/JS rules:** include assets only via `link()` / `script()` — never raw `<link>`/`<script>` tags in views. Exception: creating `css/tailwind.css` enables Tailwind
|
|
417
|
+
**CSS/JS rules:** include assets only via `link()` / `script()` — never raw `<link>`/`<script>` tags in views. Exception: creating `css/tailwind.css` enables Tailwind v4 — the file is the Tailwind *input CSS*, compiled against the rendered HTML and injected automatically; never `link()` it. Whenever you write it, keep `@import "tailwindcss";` as its first statement (plus the two `@plugin` lines: `@plugin "@tailwindcss/typography";` and `@plugin "@tailwindcss/forms";`) — dropping the import silently removes all Tailwind styling. Define the site's fonts/colors/radii as `@theme` tokens there (giving utilities like `font-display` / `bg-brand-600`) with a `/* DESIGN */` header comment recording the design direction. v4, not v3: custom utilities go in `@utility name { … }`, never `@layer utilities { .name { … } }` (a v3 pattern that yields "Cannot apply unknown utility class"); config is CSS-first — there is no `tailwind.config.js`. Default to a polished Tailwind design with a deliberate design direction.
|
|
375
418
|
|
|
376
419
|
## 9. SEO
|
|
377
420
|
|
|
@@ -393,11 +436,13 @@ Make every site strongly SEO- and sharing-friendly. The standard pattern is a re
|
|
|
393
436
|
<% if (item.image) meta({ property: 'og:image', content: src(item.image, '1200x630') }) %>
|
|
394
437
|
```
|
|
395
438
|
|
|
396
|
-
Add `seo: block(seo)` to page models and
|
|
439
|
+
Add `seo: block(seo)` to page models and render it **once from the layout head**: `<% if (item.seo) { %><%- render(item.seo) %><% } %>` in `layouts/common.ejs` (as in the layout example above) — not per page view.
|
|
440
|
+
|
|
441
|
+
Two site-level SEO switches live in `setup.json` (§4), not in views: `sitemap_enabled: true` publishes `sitemap.xml` + `robots.txt` on every build, and `title` is the site's default `<title>` for pages whose SEO block leaves the title empty (`null` = the site name). Sitemap page exclusions are chosen in the CMS UI — there is no file setting for them.
|
|
397
442
|
|
|
398
443
|
## 10. Forms
|
|
399
444
|
|
|
400
|
-
Any `<form>` with `data-sleekcms="<name>"` works automatically — submissions are captured and shown in the CMS dashboard. No backend, no action URL, no JS.
|
|
445
|
+
Any `<form>` with `data-sleekcms="<hardcoded name>"` works automatically — submissions are captured and shown in the CMS dashboard. No backend, no action URL, no JS.
|
|
401
446
|
|
|
402
447
|
```html
|
|
403
448
|
<form data-sleekcms="contact">
|
|
@@ -470,16 +515,48 @@ Always create an RSS feed for blogs. Use the page key `rss.xml` — because the
|
|
|
470
515
|
|
|
471
516
|
Make it discoverable from the layout: `<% link({ rel: 'alternate', type: 'application/rss+xml', title: 'RSS', href: '/rss.xml' }) %>`
|
|
472
517
|
|
|
473
|
-
## 13.
|
|
518
|
+
## 13. Ecommerce (Snipcart)
|
|
519
|
+
|
|
520
|
+
Snipcart powers stores. Use it when the business **sells products** and the user asks for a store, cart, or buy buttons — or when selling is clearly core to the business. **Never add a store speculatively.** Use Snipcart's default UI (side-modal cart, checkout, customer accounts) as-is; style only the site's own markup (buy buttons, product cards, header controls) with Tailwind.
|
|
521
|
+
|
|
522
|
+
1. **Settings entry with the API key.** `models/entries/settings.model` → `{ snipcart_key: text }`, value in `content/entries/settings.json`. If the site already has a settings-style entry, add the field there instead of creating a second one. An empty value falls back to a shared test-mode key, so checkout works immediately as a test store.
|
|
523
|
+
2. **One line in the layout.** At the end of `<body>` in `layouts/common.ejs`:
|
|
524
|
+
|
|
525
|
+
```ejs
|
|
526
|
+
<%- snipcart(getEntry('settings')?.snipcart_key) %>
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
This is the complete install — the helper loads all of Snipcart's CDN CSS/JS itself and returns the hidden `#snipcart` mount div. Call it exactly once, from the layout body only; everything after this line is pure markup. Optional second argument: `{ currency: 'eur' }`.
|
|
530
|
+
3. **Products collection.** `models/pages/products+.model` with `{ title: text, price: number, image: image, description: paragraph }`, one content file per product, a `/products` listing page, and standard `snipcart-add-item` buy buttons wired to SleekCMS values:
|
|
531
|
+
|
|
532
|
+
```ejs
|
|
533
|
+
<button class="snipcart-add-item"
|
|
534
|
+
data-item-id="<%= item._slug %>"
|
|
535
|
+
data-item-name="<%= item.title %>"
|
|
536
|
+
data-item-price="<%= Number(item.price).toFixed(2) %>"
|
|
537
|
+
data-item-url="<%= path(item) %>"
|
|
538
|
+
data-item-image="<%- src(item.image, '600x600') %>">
|
|
539
|
+
Add to cart
|
|
540
|
+
</button>
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
`data-item-url` is the product's own page path (`path(item)`), and that page must contain the same buy button — Snipcart re-crawls it to validate orders. Repeat the full `data-item-*` set everywhere the product can be added (listing cards too).
|
|
544
|
+
4. **Header controls.** Add both the cart button (`class="snipcart-checkout"` with `snipcart-items-count`) and the customer sign-in link (`class="snipcart-customer-signin"`) to the site header.
|
|
545
|
+
|
|
546
|
+
## 14. Critical Rules Checklist
|
|
474
547
|
|
|
475
548
|
1. **`+` is part of the key.** A collection's `+` appears on the model, the view, and the content file/folder: `models/pages/blog+.model`, `pages/blog+.ejs`, `content/pages/blog+/<slug>.json`. A file without the `+` is a *different*, single-type key.
|
|
476
549
|
2. Models first: a content field that isn't in the `.model` won't sync. Update the model, then the content.
|
|
477
550
|
3. Blocks never have content files. Block values live inline in the page/entry JSON that declares them.
|
|
478
|
-
4.
|
|
479
|
-
5. `
|
|
480
|
-
6.
|
|
481
|
-
7.
|
|
482
|
-
8.
|
|
483
|
-
9.
|
|
484
|
-
10.
|
|
485
|
-
11.
|
|
551
|
+
4. `file` field values are plain file names ending in a supported extension (`"brochure.pdf"`) — the CMS attaches or placeholder-creates the file server-side; never write objects or URLs.
|
|
552
|
+
5. Never invent `video_id`/`embed_url` objects — render videos with `embed()` only. Videos are set via refs: `content/videos.json` handles for shared videos, and `cms:<handle>` / provider refs in `video` fields (§7); values that read back as embed URLs or objects are user uploads — leave them untouched.
|
|
553
|
+
6. `richtext` → `<%- item.x %>`. `markdown` → `<%- marked(item.x) %>`.
|
|
554
|
+
7. CSS/JS via `link()`/`script()` only; `css/tailwind.css` is auto-injected — never `link()` it. When writing tailwind.css, `@import "tailwindcss";` must stay as its first statement — dropping it silently removes all Tailwind styling.
|
|
555
|
+
8. Editable copy belongs in `content/`, not hard-coded in `.ejs`. Fixed micro-labels may stay in views.
|
|
556
|
+
9. In page keys, `-` separates words and `_` means `/`. `_index` is the home page only; a section index page uses the plain section key.
|
|
557
|
+
10. After edits, check `sync-errors.log` in the workspace root. New line = failure for that file; line removed = fixed.
|
|
558
|
+
11. Images are content-first: author each image as an `image`-field value (`"pexels:doctor|alt text"` — resolves on save) or as a `content/images.json` handle for images reused across pages; templates pass the value straight to the helper (`img(item.field)`, `img('cms:handle')`).
|
|
559
|
+
12. Image shortcuts (`"iconify:..."`, `"pexels:..."`, etc.) resolve **only** in `image`/`icon`-typed fields. CMS-editable icons are declared `icon: icon`, never `icon: text` — render with `svg(item.icon)`; fixed UI icons need no field: `svg('iconify:...')` directly in the view.
|
|
560
|
+
13. Use entries (`entry(key+)`) for shared content (authors, testimonials, nav, footer) — not blocks. Block models cannot contain `block(...)` or `stack` fields; use blocks only when a group of fields also benefits from a reusable view.
|
|
561
|
+
14. Always create an RSS feed for blogs (`rss.xml` key) and make every site strongly SEO- and sharing-friendly (reusable `seo` block rendered once from the layout head).
|
|
562
|
+
15. Site-wide setup — default page title, Tailwind on/off, sitemap on/off — lives in `setup.json` (§4). Change values there rather than hard-coding a site title into the layout, and never delete the file. Sitemap page exclusions are UI-only.
|