@craftware/crafty-sdk 0.2.2 → 0.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.
Files changed (84) hide show
  1. package/README.md +64 -0
  2. package/dist/attrs.d.ts +33 -0
  3. package/dist/attrs.d.ts.map +1 -0
  4. package/dist/attrs.js +52 -0
  5. package/dist/attrs.js.map +1 -0
  6. package/dist/cdn.d.ts +5 -0
  7. package/dist/cdn.d.ts.map +1 -0
  8. package/dist/cdn.js +5 -0
  9. package/dist/cdn.js.map +1 -0
  10. package/dist/client.d.ts +12 -7
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +13 -8
  13. package/dist/client.js.map +1 -1
  14. package/dist/crafty-visual-editing.global.js +4 -0
  15. package/dist/crafty-visual-editing.global.js.map +7 -0
  16. package/dist/http.d.ts +7 -1
  17. package/dist/http.d.ts.map +1 -1
  18. package/dist/http.js +32 -2
  19. package/dist/http.js.map +1 -1
  20. package/dist/index.d.ts +10 -6
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +4 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/preview.d.ts +56 -0
  25. package/dist/preview.d.ts.map +1 -0
  26. package/dist/preview.js +174 -0
  27. package/dist/preview.js.map +1 -0
  28. package/dist/resources/collections.d.ts +2 -2
  29. package/dist/resources/collections.d.ts.map +1 -1
  30. package/dist/resources/entries.d.ts +3 -2
  31. package/dist/resources/entries.d.ts.map +1 -1
  32. package/dist/resources/entries.js +20 -4
  33. package/dist/resources/entries.js.map +1 -1
  34. package/dist/resources/forms.d.ts +2 -2
  35. package/dist/resources/forms.d.ts.map +1 -1
  36. package/dist/resources/forms.js +1 -1
  37. package/dist/resources/forms.js.map +1 -1
  38. package/dist/resources/mediaCollections.d.ts +2 -2
  39. package/dist/resources/mediaCollections.d.ts.map +1 -1
  40. package/dist/resources/mediaFiles.d.ts +2 -2
  41. package/dist/resources/mediaFiles.d.ts.map +1 -1
  42. package/dist/resources/pages.d.ts +2 -2
  43. package/dist/resources/pages.d.ts.map +1 -1
  44. package/dist/resources/pages.js +4 -0
  45. package/dist/resources/pages.js.map +1 -1
  46. package/dist/types/public.d.ts +11 -2
  47. package/dist/types/public.d.ts.map +1 -1
  48. package/dist/version.d.ts +2 -0
  49. package/dist/version.d.ts.map +1 -0
  50. package/dist/version.js +3 -0
  51. package/dist/version.js.map +1 -0
  52. package/dist/visual-editing/bridge.d.ts +35 -0
  53. package/dist/visual-editing/bridge.d.ts.map +1 -0
  54. package/dist/visual-editing/bridge.js +385 -0
  55. package/dist/visual-editing/bridge.js.map +1 -0
  56. package/dist/visual-editing/index.d.ts +10 -0
  57. package/dist/visual-editing/index.d.ts.map +1 -0
  58. package/dist/visual-editing/index.js +10 -0
  59. package/dist/visual-editing/index.js.map +1 -0
  60. package/dist/visual-editing/live-preview.d.ts +15 -0
  61. package/dist/visual-editing/live-preview.d.ts.map +1 -0
  62. package/dist/visual-editing/live-preview.js +63 -0
  63. package/dist/visual-editing/live-preview.js.map +1 -0
  64. package/dist/visual-editing/markers.d.ts +11 -0
  65. package/dist/visual-editing/markers.d.ts.map +1 -0
  66. package/dist/visual-editing/markers.js +43 -0
  67. package/dist/visual-editing/markers.js.map +1 -0
  68. package/dist/visual-editing/origin.d.ts +10 -0
  69. package/dist/visual-editing/origin.d.ts.map +1 -0
  70. package/dist/visual-editing/origin.js +37 -0
  71. package/dist/visual-editing/origin.js.map +1 -0
  72. package/dist/visual-editing/overlay.d.ts +13 -0
  73. package/dist/visual-editing/overlay.d.ts.map +1 -0
  74. package/dist/visual-editing/overlay.js +107 -0
  75. package/dist/visual-editing/overlay.js.map +1 -0
  76. package/dist/visual-editing/protocol.d.ts +79 -0
  77. package/dist/visual-editing/protocol.d.ts.map +1 -0
  78. package/dist/visual-editing/protocol.js +150 -0
  79. package/dist/visual-editing/protocol.js.map +1 -0
  80. package/package.json +13 -4
  81. package/skills/crafty-cms/SKILL.md +14 -1
  82. package/skills/crafty-cms/references/mcp-configuration.md +7 -0
  83. package/skills/crafty-cms/references/sdk-development.md +13 -1
  84. package/skills/crafty-cms/references/visual-editor.md +218 -0
@@ -0,0 +1,218 @@
1
+ # Preparing a site for the Crafty visual editor
2
+
3
+ The visual editor lets a non-technical user open their real site inside the Crafty admin, click (or tap) a text or image, edit it in a side panel, watch the change live, and publish. It only works when the frontend is prepared with `@craftware/crafty-sdk` **0.3.0 or later**. This guide is the complete checklist.
4
+
5
+ ## The mandatory question
6
+
7
+ Whenever you integrate Crafty into a frontend (a new site, new pages or sections, or moving hardcoded content into Crafty), ask the user this exact question once, before you finish:
8
+
9
+ > **¿Quieres dejar el sitio preparado para el editor visual de Crafty?**
10
+
11
+ - **Yes** → run the whole "Yes flow" below.
12
+ - **No** → run the "No flow".
13
+ - If the user later asks "habilita el editor visual de Crafty" (or anything equivalent) for an existing site, do not ask again: run the Yes flow.
14
+
15
+ ## No flow
16
+
17
+ Add nothing: no `attrs()`, no `enableVisualEditing()`, no preview token handling, no headers, no `configure_visual_editor` call. End your final message with this sentence for the user:
18
+
19
+ > El editor visual de Crafty no quedó activado. Cuando quieras activarlo, pide: «habilita el editor visual de Crafty».
20
+
21
+ ## Yes flow
22
+
23
+ Do every step. Keep changes minimal and in the site's existing style.
24
+
25
+ ### 1. Decide the render mode
26
+
27
+ | `render_mode` | How to recognise it | Token strategy (step 4) |
28
+ |---|---|---|
29
+ | `ssr` | Content is fetched on the server for every request: Next.js dynamic routes / Server Components without static caching, Nuxt SSR, Astro `output: "server"`, Remix loaders, Express/Fastify templates. | Read it per request with `previewFromRequest()`. |
30
+ | `hybrid` | HTML is prerendered at build time **and** the browser fetches Crafty again on load (the Vite + prerender + client refresh pattern). | Automatic in the browser. |
31
+ | `static` | Content is baked at build time only; the browser never calls the Crafty API. | Add a browser re-fetch that only runs in preview. |
32
+ | `unknown` | You cannot tell. | Treat it like `hybrid` and tell the user. |
33
+
34
+ ### 2. Upgrade the SDK
35
+
36
+ ```bash
37
+ npm install @craftware/crafty-sdk@^0.3.0
38
+ ```
39
+
40
+ ### 3. Mark every editable element
41
+
42
+ Use `attrs(target, field, { label })` (also available as `crafty.attrs` on the client). `target` is the block or entry the value came from; `field` is the field key or the JSON path of the value.
43
+
44
+ Rules:
45
+
46
+ - Mark only content that comes from Crafty. Never mark hardcoded text.
47
+ - Put the attributes on the innermost element whose **entire text** is the field value (`<h1>`, `<p>`, `<span>`, the `<a>` or `<button>` label, `<li>`). Live preview replaces that element's text, so an element that also contains icons or other fields must not be marked; wrap the value in a `<span>` and mark the span.
48
+ - Images from a media `reference` field: mark the `<img>` with the field key (`image`, `photo`). URL fields (`url`) are edited as text: mark the element that shows or uses them (for a link, mark the `<a>`).
49
+ - Values inside `json` fields: use the exact path of the rendered value, e.g. `sections[2].title`, `hero.title_segments[0].text`, `extra.ctaPrimaryLabel`. Array indexes are positions in the stored JSON, which is the order you render; build them from the loop index (`` `sections[${i}].title` ``).
50
+ - Editable types: `string`, `text`, `longText`, `url`, `number`, media `reference`, and string/number values inside `json`. Do not mark booleans, dates, selects, multi-references, or array items that are objects.
51
+ - `label` is a short Spanish name the client understands ("Título principal", "Texto del botón"). Without it the editor shows the field key.
52
+ - Pass blocks and entries as the API returns them. If an entry has fields named `order`, `data` and `type` at once, pass `{ kind: "entry", id: entry.id }` instead.
53
+
54
+ React / Next.js / Astro (JSX):
55
+
56
+ ```tsx
57
+ import { attrs } from "@craftware/crafty-sdk";
58
+
59
+ <h1 {...attrs(block, "title", { label: "Título principal" })}>{block.data.title}</h1>
60
+ <img {...attrs(block, "image", { label: "Imagen principal" })} src={imageUrl} alt="" />
61
+
62
+ {sections.map((section, i) => (
63
+ <section key={i}>
64
+ <h2 {...attrs(entry, `sections[${i}].title`, { label: "Título de la sección" })}>{section.title}</h2>
65
+ <p {...attrs(entry, `sections[${i}].body`, { label: "Texto de la sección" })}>{section.body}</p>
66
+ </section>
67
+ ))}
68
+ ```
69
+
70
+ Vue:
71
+
72
+ ```vue
73
+ <h1 v-bind="attrs(entry, 'hero.title', { label: 'Título principal' })">{{ entry.hero.title }}</h1>
74
+ ```
75
+
76
+ HTML string templates (vanilla TS):
77
+
78
+ ```ts
79
+ import { attrsHtml } from "@craftware/crafty-sdk";
80
+
81
+ html += `<h1 ${attrsHtml(entry, "extra.title", { label: "Título" })}>${escapeHtml(extra.title)}</h1>`;
82
+ ```
83
+
84
+ ### 4. Deliver the preview token
85
+
86
+ The editor loads the site with `?crafty_preview=<token>`. Requests that carry it as `X-Crafty-Preview` get unpublished drafts and bypass the API cache.
87
+
88
+ - **hybrid / SPA:** nothing to do. A client created in the browser reads the token from the URL, keeps it in `sessionStorage`, and sends it on every request. Make sure the browser re-fetch really runs on every page load.
89
+ - **ssr:** create the client **per request** with the token, never share a module-level client that received one, and never cache a preview response:
90
+
91
+ ```ts
92
+ import { createCraftyClient, previewFromRequest } from "@craftware/crafty-sdk";
93
+
94
+ // Express / Node: previewFromRequest(req) Web Request: previewFromRequest(request)
95
+ // Next.js App Router: previewFromRequest({ query: await searchParams })
96
+ const preview = previewFromRequest(req);
97
+ const crafty = createCraftyClient({ baseUrl: process.env.CRAFTY_URL!, apiKey: process.env.CRAFTY_API_KEY!, preview });
98
+
99
+ if (preview) {
100
+ res.setHeader("Cache-Control", "no-store"); // Next.js: call noStore() / use dynamic rendering for this request
101
+ }
102
+ ```
103
+
104
+ Server-rendered pages should also tell the editor what they show (step 5, `page` / `entries`).
105
+ - **static:** the built HTML has no drafts, so re-fetch in the browser only when previewing and re-render:
106
+
107
+ ```ts
108
+ import { readPreviewToken } from "@craftware/crafty-sdk";
109
+
110
+ if (readPreviewToken()) {
111
+ const fresh = await crafty.entries.list("site-pages");
112
+ renderPage(fresh.data); // the same function the build uses
113
+ }
114
+ ```
115
+
116
+ If the site cannot re-render in the browser, set `render_mode` to `static` anyway and tell the user that the editor will show changes only after saving and reloading, and the public site only after a deploy.
117
+
118
+ ### 5. Turn on the bridge
119
+
120
+ Call it once in the browser entry (it keeps watching the DOM, so it can run before or after the first render):
121
+
122
+ ```ts
123
+ import { enableVisualEditing } from "@craftware/crafty-sdk/visual-editing";
124
+
125
+ enableVisualEditing({
126
+ onRefresh: () => reloadCraftyContent(), // re-fetch + re-render; omit it to let the editor reload the page
127
+ });
128
+ ```
129
+
130
+ - It does nothing for normal visitors (it needs the admin iframe, a token, and an allowed admin origin), so ship it to production.
131
+ - Even then it stays inert until the backend confirms the token — a response with `X-Crafty-Preview-Status: active`, normally the site's own first Crafty request for that page. **`ssr` sites where the browser never calls the Crafty API itself must pass `client`** (a `CraftyClient`, typically the same one already created for that render mode) so the bridge can confirm the token itself with one lightweight request (`pages.list({ per_page: 1 })`): `enableVisualEditing({ client: crafty })`. `hybrid` and `static` sites already make a browser-side request that carries the header, so `client` is optional for them. If the API answers `invalid`, the bridge stays off and tells the admin instead of activating.
132
+ - If the site fetches Crafty through something other than this SDK's client (so neither path above ever runs), report `X-Crafty-Preview-Status` yourself with `notifyPreviewActive()` / `notifyPreviewInvalid()` from `@craftware/crafty-sdk/visual-editing` (also exports `getPreviewStatus()` and the `PREVIEW_ACTIVE_EVENT` / `PREVIEW_INVALID_EVENT` event names).
133
+ - `onRefresh`: register it when the site can re-fetch Crafty and re-render without a reload (SPAs, hybrid sites). Otherwise omit it.
134
+ - SSR: pass what the server rendered, e.g. `enableVisualEditing({ client: crafty, page: { id: page.id, slug: page.slug }, entries: [{ id: post.id, collection: "blog" }] })`.
135
+ - `adminOrigins`: only when the tenant's admin is not on `https://<tenant>.crafty.do` (custom admin domain, or local development such as `http://<tenant>.crafty.test`). It replaces the default list.
136
+ - No bundler:
137
+
138
+ ```html
139
+ <script src="https://cdn.jsdelivr.net/npm/@craftware/crafty-sdk@0.3/dist/crafty-visual-editing.global.js"></script>
140
+ <script>CraftyVisualEditing.enableVisualEditing();</script>
141
+ ```
142
+
143
+ Pin an exact version (`@0.3.0`) with an `integrity="sha384-…" crossorigin="anonymous"` attribute (jsDelivr shows the hash) when the site uses Subresource Integrity.
144
+
145
+ ### 6. Allow the admin to frame the site
146
+
147
+ The site must accept being framed by the admin. Sites deployed to Crafty Hosting get this header from Crafty; for any other host add it (and remove any `X-Frame-Options: DENY` / `SAMEORIGIN`):
148
+
149
+ ```
150
+ Content-Security-Policy: frame-ancestors 'self' https://<tenant>.crafty.do
151
+ ```
152
+
153
+ - Netlify / Cloudflare Pages (`_headers` in the build output):
154
+
155
+ ```
156
+ /*
157
+ Content-Security-Policy: frame-ancestors 'self' https://<tenant>.crafty.do
158
+ ```
159
+
160
+ - Vercel (`vercel.json`):
161
+
162
+ ```json
163
+ { "headers": [{ "source": "/(.*)", "headers": [{ "key": "Content-Security-Policy", "value": "frame-ancestors 'self' https://<tenant>.crafty.do" }] }] }
164
+ ```
165
+
166
+ - Nginx: `add_header Content-Security-Policy "frame-ancestors 'self' https://<tenant>.crafty.do" always;`
167
+
168
+ If the site already sends a `Content-Security-Policy`, add the `frame-ancestors` directive to it instead of sending a second header.
169
+
170
+ ### 7. Make sure images come from public repositories
171
+
172
+ Static and hybrid sites keep image URLs in their HTML; private repositories give expiring URLs. Check with `list_media` (each file has `is_public`) or `crafty.mediaCollections.list()`. If images the site shows live in a private repository, tell the user: «Las imágenes del repositorio X son privadas y sus enlaces caducan en sitios estáticos. Márcalo como público en Crafty → Media.» Uploads made from the visual editor go to the public "Editor visual" repository by default.
173
+
174
+ ### 8. Configure Crafty
175
+
176
+ Call `configure_visual_editor` (scope `crafty:write`) with what you learned:
177
+
178
+ ```json
179
+ {
180
+ "site_id": 3,
181
+ "environment": "production",
182
+ "frontend_url": "https://www.example.com",
183
+ "routes": {
184
+ "page_pattern": "/{slug}",
185
+ "home_slug": "home",
186
+ "collections": { "blog": "/blog/{slug}" },
187
+ "fixed": ["/", "/en", "/blog"]
188
+ },
189
+ "render_mode": "hybrid"
190
+ }
191
+ ```
192
+
193
+ - `frontend_url`: the public URL of that environment (omit it when it is the site's domain in production or the Crafty Hosting URL).
194
+ - `routes.page_pattern` / `home_slug`: only if the site renders Crafty **pages**; patterns start with `/` and contain `{slug}`.
195
+ - `routes.collections`: collections whose entries have their own URL; patterns contain `{slug}` or `{id}`.
196
+ - `routes.fixed`: every other URL that shows Crafty content (home in other languages, listing pages, single-page sites: just `["/"]`).
197
+ - `render_mode`: from step 1.
198
+ - Repeat with `environment: "development"` (or others) when they have their own URL.
199
+ - Calling it with only `site_id` returns the current settings without changing anything.
200
+
201
+ ### 9. Verify
202
+
203
+ 1. Build and type-check the site.
204
+ 2. Run it locally and open a page with `?crafty_preview=test`. In DevTools:
205
+ - `document.querySelectorAll("[data-crafty-field]").length` is greater than 0 on every page type (home, a page, a collection entry).
206
+ - Requests to the Crafty API carry `X-Crafty-Preview: test`, and the responses say `X-Crafty-Preview-Status: invalid` (expected: `test` is not a real token; the content stays public).
207
+ - Nothing is highlighted: outside the admin iframe the bridge stays off.
208
+ 3. After deploying, ask the user to open Crafty → the site → **Editar sitio** (`https://<tenant>.crafty.do/sites/<site_id>/visual-editor?path=/`). If hovering highlights texts and a click opens the field, the editor received `crafty:ready`. If it shows «Este sitio aún no tiene edición con clic activada», go through the troubleshooting list.
209
+
210
+ End with a short summary for the user in Spanish: what was marked, the render mode, and what they must do after publishing (nothing for `ssr`/`hybrid`; a deploy for `static`).
211
+
212
+ ## Troubleshooting
213
+
214
+ - **The editor shows «Este sitio aún no tiene edición con clic activada»** — the iframe is blocked (browser console: `frame-ancestors` or `X-Frame-Options`), `enableVisualEditing()` is not in the bundle of that page, the SDK is older than 0.3.0, the admin origin is not allowed (custom admin domain → `adminOrigins`), a redirect dropped the query string (trailing-slash or locale redirects must keep `?crafty_preview=`), or the site is `ssr` and never confirmed the token because `client` was not passed to `enableVisualEditing()`.
215
+ - **«Esta URL no mostró la página …»** — `routes` do not match the site's URLs; fix them with `configure_visual_editor`.
216
+ - **Saved drafts do not appear in the editor** — SSR: the client was created once at module level or the response was cached; static: the preview re-fetch is missing.
217
+ - **Typing does not update the text live** — the attributes are on a wrapper that contains more than the value, or the field is rich text (`longText` shows after saving the draft).
218
+ - **Clicking an editable card navigates away** — the element inside the link is not marked; mark it (a marked element inside a link is selected, not followed).