@scaleflex/template-builder 0.2.0 → 0.4.1
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/.claude/skills/integrate-template-builder/SKILL.md +27 -21
- package/CHANGELOG.md +178 -4
- package/README.md +207 -52
- package/dist/dam-store.d.ts +92 -0
- package/dist/define.cjs +1 -1
- package/dist/define.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -8
- package/dist/protocol.d.ts +60 -2
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.ts +34 -1
- package/dist/react.js +43 -28
- package/dist/react.js.map +1 -1
- package/dist/template-builder-B9Cwo_Q-.js +651 -0
- package/dist/template-builder-B9Cwo_Q-.js.map +1 -0
- package/dist/template-builder-Byqg1q93.cjs +53 -0
- package/dist/template-builder-Byqg1q93.cjs.map +1 -0
- package/dist/template-builder.d.ts +164 -4
- package/package.json +1 -1
- package/src/dam-store.ts +388 -0
- package/src/index.ts +2 -0
- package/src/protocol.ts +64 -2
- package/src/react.ts +111 -27
- package/src/template-builder.ts +405 -7
- package/dist/template-builder-CK2Zlo7E.cjs +0 -53
- package/dist/template-builder-CK2Zlo7E.cjs.map +0 -1
- package/dist/template-builder-De0hRO4s.js +0 -380
- package/dist/template-builder-De0hRO4s.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<h1 align="center">@scaleflex/template-builder</h1>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
Framework-agnostic Design Template Builder Web Component for <a href="https://www.scaleflex.com/page/digital-asset-management">
|
|
10
|
+
Framework-agnostic Design Template Builder Web Component for <a href="https://www.scaleflex.com/page/digital-asset-management">Scaleflex</a>.<br>
|
|
11
11
|
Drop a full multi-layout template editor into any page, in any frontend stack.
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -34,11 +34,13 @@
|
|
|
34
34
|
- [Modes](#modes)
|
|
35
35
|
- [DAM-backed](#dam-backed-default)
|
|
36
36
|
- [Stateless](#stateless)
|
|
37
|
+
- [Storing a rendering copy (dam-store)](#storing-a-rendering-copy-dam-store)
|
|
37
38
|
- [Starting a template from scratch](#starting-a-template-from-scratch)
|
|
38
39
|
- [Reporting a failed save](#reporting-a-failed-save)
|
|
39
40
|
- [Configuration](#configuration)
|
|
40
41
|
- [Attributes & properties](#attributes--properties)
|
|
41
42
|
- [Authentication](#authentication)
|
|
43
|
+
- [Custom metadata fields](#custom-metadata-fields)
|
|
42
44
|
- [Origin registration](#origin-registration)
|
|
43
45
|
- [Cookies](#cookies)
|
|
44
46
|
- [Public Methods](#public-methods)
|
|
@@ -58,11 +60,11 @@
|
|
|
58
60
|
|
|
59
61
|
## Overview
|
|
60
62
|
|
|
61
|
-
`<sfx-template-builder>` embeds the
|
|
62
|
-
on any origin. The element
|
|
63
|
-
deployment, hands over the credentials, and
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
`<sfx-template-builder>` embeds the Scaleflex design-templates builder in a page
|
|
64
|
+
on any origin. The element loads the editor from a `design-templates-app`
|
|
65
|
+
deployment, hands over the credentials, and surfaces everything the editor does
|
|
66
|
+
as ordinary DOM `CustomEvent`s — so from your side it behaves like any other
|
|
67
|
+
component.
|
|
66
68
|
|
|
67
69
|
All the heavy lifting — server-side text rendering, font resolution, asset
|
|
68
70
|
browsing, template XML — stays inside the app deployment. The widget is a
|
|
@@ -72,10 +74,10 @@ protocol adapter.
|
|
|
72
74
|
|
|
73
75
|
- **Framework-agnostic** — a custom element; works in vanilla JS, React, Vue,
|
|
74
76
|
Angular, Svelte. A thin React wrapper ships in the box.
|
|
75
|
-
- **Two storage modes** — let
|
|
77
|
+
- **Two storage modes** — let Scaleflex own the template, or keep the document
|
|
76
78
|
entirely on your side ([Stateless](#stateless)) and never map your users onto
|
|
77
79
|
Scaleflex identities.
|
|
78
|
-
- **Two ways in** — a Hub session, or a
|
|
80
|
+
- **Two ways in** — a Hub session, or a Scaleflex [security
|
|
79
81
|
template](#security-template-guest-auth) when you have no Hub account to hand
|
|
80
82
|
over per user.
|
|
81
83
|
- **Inline or modal** — fill a box in your layout, or cover the viewport.
|
|
@@ -88,17 +90,17 @@ protocol adapter.
|
|
|
88
90
|
origin; the app only accepts a template from the origin pinned when the
|
|
89
91
|
session was handed over.
|
|
90
92
|
- **Diagnosable failures** — auth, blocked cookies, bad content and handshake
|
|
91
|
-
timeouts all surface as an `error` event instead of a stuck
|
|
93
|
+
timeouts all surface as an `error` event instead of a stuck editor.
|
|
92
94
|
|
|
93
95
|
## Requirements
|
|
94
96
|
|
|
95
|
-
- A [
|
|
97
|
+
- A [Scaleflex](https://www.scaleflex.com/page/digital-asset-management) account
|
|
96
98
|
and a running `design-templates-app` deployment to point `base-url` at
|
|
97
99
|
- Credentials minted server-side — either a Hub session (`session-uuid` +
|
|
98
|
-
**SASS key** + **
|
|
100
|
+
**SASS key** + **Scaleflex token**) or a **security template** + token for
|
|
99
101
|
guest access; see [Authentication](#authentication)
|
|
100
|
-
- Your page's origin registered in the deployment's
|
|
101
|
-
|
|
102
|
+
- Your page's origin registered in the deployment's embedding allowlist — see
|
|
103
|
+
[Origin registration](#origin-registration)
|
|
102
104
|
- Modern browser with Custom Elements v1 support (see
|
|
103
105
|
[Browser Support](#browser-support))
|
|
104
106
|
|
|
@@ -113,7 +115,7 @@ npm i @scaleflex/template-builder
|
|
|
113
115
|
### CDN
|
|
114
116
|
|
|
115
117
|
```html
|
|
116
|
-
<script type="module" src="https://cdn.
|
|
118
|
+
<script type="module" src="https://cdn.cloudimage.io/template-builder/0.4.1/template-builder.min.js"></script>
|
|
117
119
|
```
|
|
118
120
|
|
|
119
121
|
The CDN bundle is self-registering — it defines `<sfx-template-builder>` on
|
|
@@ -139,7 +141,7 @@ load, with Lit bundled in. Pin the major version.
|
|
|
139
141
|
|
|
140
142
|
## Quick Start
|
|
141
143
|
|
|
142
|
-
You need two things from your
|
|
144
|
+
You need two things from your Scaleflex project: its **token**, and a
|
|
143
145
|
**security template** key — a named, permission-scoped credential you define
|
|
144
146
|
once, the same guest-auth mechanism the other Scaleflex widgets use. No Hub
|
|
145
147
|
account, and no user of yours ever needs a Scaleflex identity. See
|
|
@@ -211,7 +213,7 @@ Mint the session server-side; never put a long-lived credential in client code.
|
|
|
211
213
|
```html
|
|
212
214
|
<sfx-template-builder
|
|
213
215
|
base-url="https://<your-design-templates-deployment>"
|
|
214
|
-
token="
|
|
216
|
+
token="PROJECT_TOKEN"
|
|
215
217
|
sass-key="SASS_KEY"
|
|
216
218
|
session-uuid="SESSION_UUID"
|
|
217
219
|
template-id="TEMPLATE_UUID"
|
|
@@ -238,7 +240,7 @@ Mint the session server-side; never put a long-lived credential in client code.
|
|
|
238
240
|
|
|
239
241
|
### DAM-backed (default)
|
|
240
242
|
|
|
241
|
-
`template-id` is a
|
|
243
|
+
`template-id` is a DAM file uuid. The app loads the template itself, and
|
|
242
244
|
Save uploads a new version and reports the resulting uuid on the `save` event.
|
|
243
245
|
Leave `template-id` empty to open the new-template flow.
|
|
244
246
|
|
|
@@ -246,7 +248,9 @@ Leave `template-id` empty to open the new-template flow.
|
|
|
246
248
|
|
|
247
249
|
Set `stateless` to keep the document entirely on your side: you pass the
|
|
248
250
|
template in, the user edits it, and you get the edited template back. Nothing is
|
|
249
|
-
stored on the Scaleflex side
|
|
251
|
+
stored on the Scaleflex side (unless you opt into
|
|
252
|
+
[`dam-store`](#storing-a-rendering-copy-dam-store) for a rendering copy), so
|
|
253
|
+
your app keeps its own storage, versioning,
|
|
250
254
|
tenancy and access control, and `template-id` becomes an opaque string that is
|
|
251
255
|
echoed back untouched.
|
|
252
256
|
|
|
@@ -274,8 +278,8 @@ point.
|
|
|
274
278
|
const { content, name, templateQuery } = await res.json()
|
|
275
279
|
|
|
276
280
|
// 2 — pass it in. `content` is a property, never an attribute: templates
|
|
277
|
-
// routinely exceed what fits in markup or a URL,
|
|
278
|
-
//
|
|
281
|
+
// routinely exceed what fits in markup or a URL, so the widget hands the
|
|
282
|
+
// document to the editor directly rather than through either.
|
|
279
283
|
// `templateQuery` is what you stored on the last save; it reopens the
|
|
280
284
|
// template on the same layout and variable values.
|
|
281
285
|
builder.load({ templateId: id, name, content, templateQuery })
|
|
@@ -301,7 +305,7 @@ point.
|
|
|
301
305
|
</script>
|
|
302
306
|
```
|
|
303
307
|
|
|
304
|
-
`templateId` is **your** identifier, not a
|
|
308
|
+
`templateId` is **your** identifier, not a DAM uuid. The app never
|
|
305
309
|
resolves it against anything — it is carried alongside the content and handed
|
|
306
310
|
straight back on save, so use whatever key maps to your own record.
|
|
307
311
|
|
|
@@ -410,7 +414,7 @@ you navigated away from.
|
|
|
410
414
|
#### What stateless does *not* remove
|
|
411
415
|
|
|
412
416
|
Statelessness applies to the **document**, not to the infrastructure. The editor
|
|
413
|
-
still needs an authenticated
|
|
417
|
+
still needs an authenticated Scaleflex tenant for:
|
|
414
418
|
|
|
415
419
|
- **text rendering** — text and shape layers are rasterized server-side (see
|
|
416
420
|
[the render round-trip](#why-the-editor-still-calls-a-server) below),
|
|
@@ -432,7 +436,7 @@ This is the reason a stateless embed still needs a credential, so it is worth
|
|
|
432
436
|
being concrete about.
|
|
433
437
|
|
|
434
438
|
A `.fdt` template is not an image. The image only exists once someone requests
|
|
435
|
-
the template's CDN URL, and it is
|
|
439
|
+
the template's CDN URL, and it is Scaleflex that renders it there — server-side,
|
|
436
440
|
with ImageMagick:
|
|
437
441
|
|
|
438
442
|
```
|
|
@@ -456,8 +460,8 @@ editor drives a WebAssembly build of it), and each layer comes back as a
|
|
|
456
460
|
transparent PNG that the canvas positions with CSS:
|
|
457
461
|
|
|
458
462
|
```
|
|
459
|
-
browser — widget
|
|
460
|
-
|
|
463
|
+
browser — the widget app deployment (base-url)
|
|
464
|
+
──────────────────── ─────────────────────────
|
|
461
465
|
edit a text layer
|
|
462
466
|
│ batched across layers, debounced
|
|
463
467
|
├────── POST /api/render-layers ─────▶ ImageMagick (WASM)
|
|
@@ -497,7 +501,64 @@ session to hand over — and that is exactly the gap a
|
|
|
497
501
|
[security template](#security-template-guest-auth) fills: a permission-scoped,
|
|
498
502
|
project-level guest credential that authorizes rendering, fonts and asset
|
|
499
503
|
browsing without authenticating any particular user, and without your users
|
|
500
|
-
existing in
|
|
504
|
+
existing in Scaleflex at all.
|
|
505
|
+
|
|
506
|
+
### Storing a rendering copy (dam-store)
|
|
507
|
+
|
|
508
|
+
The raw XML in the `save` event is yours to keep — but the CDN renders only
|
|
509
|
+
files it stores, so previews and production banners need a copy in Filerobot
|
|
510
|
+
too. Set `dam-store` and the element makes that copy itself on every stateless
|
|
511
|
+
save, with the same multipart upload the DAM-backed editor uses, before the
|
|
512
|
+
`save` event fires. The detail then carries the links next to the raw data:
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
builder.damStore = true
|
|
516
|
+
builder.addEventListener('save', (e) => {
|
|
517
|
+
const { content, templateQuery, stored, storeError } = e.detail
|
|
518
|
+
// stored = { uuid, url } — the DAM copy. `url` is the .fdt's CDN URL with
|
|
519
|
+
// its current ?vh= cache key; append templateQuery and it IS a render:
|
|
520
|
+
// <img src=`${stored.url}&${templateQuery}`>
|
|
521
|
+
// (`url` can rarely come back empty — the record read-back after the
|
|
522
|
+
// upload failed; the file is stored regardless, under stored.uuid. And
|
|
523
|
+
// join with '?' instead of '&' if your URL happens to carry no query.)
|
|
524
|
+
})
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Added after 0.3.0 — if your pinned CDN bundle predates it, the attribute is
|
|
528
|
+
silently ignored; check the [changelog](./CHANGELOG.md).
|
|
529
|
+
|
|
530
|
+
What to know:
|
|
531
|
+
|
|
532
|
+
- **The raw `content` arrives either way.** A failed upload replaces `stored`
|
|
533
|
+
with `storeError` (a message) — whether a save without a rendering copy
|
|
534
|
+
counts as saved is your call, made where it always is: the save ack
|
|
535
|
+
(`confirmSave(false)` / returning `false` from React's `onSave`).
|
|
536
|
+
- **The raw content never dies with the element.** Closing the editor changes
|
|
537
|
+
nothing: the element outlives a close, so a save still inside its upload
|
|
538
|
+
window simply emits moments later with its real outcome — possibly after
|
|
539
|
+
the `close` event. Only *removing* the element mid-upload flushes the
|
|
540
|
+
pending `save` immediately, raw content with a `storeError` note (the copy,
|
|
541
|
+
if it lands, goes unreported); the React wrapper does the same from its
|
|
542
|
+
unmount cleanup. And rapid saves are serialized, so `save` events always
|
|
543
|
+
arrive in the order the editor posted them.
|
|
544
|
+
- **Same name + folder versions the file in place.** When `template-id` names
|
|
545
|
+
an existing DAM file, its own folder is reused; otherwise new templates land
|
|
546
|
+
in `store-folder` (default `/`). Under a VERSION conflict policy each save is
|
|
547
|
+
its own file row — `stored.uuid` is always the current one, so persist it
|
|
548
|
+
(don't echo it into `template-id`: an id change deliberately reloads the
|
|
549
|
+
editor). The element remembers the copy it last made, so follow-up saves —
|
|
550
|
+
including unchanged re-saves, which resolve to that copy instead of
|
|
551
|
+
erroring — keep working against it even while your own id stays opaque.
|
|
552
|
+
That memory lives in the element instance: to carry it across a page
|
|
553
|
+
reload or remount, pass the persisted uuid back in as `stored-uuid` /
|
|
554
|
+
`storedUuid` (or the `storedUuid` field of `load()`) alongside the content.
|
|
555
|
+
- **The stored `template_query` is record-agnostic.** Values of
|
|
556
|
+
[custom-metadata-bound](#custom-metadata-fields) variables are stripped
|
|
557
|
+
before the query is attached as file metadata, so one record's data never
|
|
558
|
+
becomes the template's own default.
|
|
559
|
+
- **The credential needs upload rights.** A
|
|
560
|
+
[security template](#security-template-guest-auth) must have a scope that
|
|
561
|
+
allows uploads, or every save reports `storeError`.
|
|
501
562
|
|
|
502
563
|
### Starting a template from scratch
|
|
503
564
|
|
|
@@ -557,10 +618,10 @@ template is just a template, and `BLANK_TEMPLATE_XML` is exported from
|
|
|
557
618
|
|
|
558
619
|
### Reporting a failed save
|
|
559
620
|
|
|
560
|
-
The editor clears its unsaved-changes state as soon as it
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
621
|
+
The editor clears its unsaved-changes state as soon as it emits `save` —
|
|
622
|
+
receiving the event says nothing about whether you stored anything. Tell it when
|
|
623
|
+
you didn't, and it restores the dirty flag and warns the user instead of showing
|
|
624
|
+
a failed write as saved:
|
|
564
625
|
|
|
565
626
|
```js
|
|
566
627
|
builder.addEventListener('save', async (e) => {
|
|
@@ -589,18 +650,23 @@ without it a failed write is invisible to the user.
|
|
|
589
650
|
| Attribute / property | Required | Description |
|
|
590
651
|
| --- | --- | --- |
|
|
591
652
|
| `base-url` / `baseUrl` | yes | Origin of the design-templates-app deployment |
|
|
592
|
-
| `token` | yes |
|
|
653
|
+
| `token` | yes | Scaleflex token (`ftoken`) |
|
|
593
654
|
| `sass-key` / `sassKey` | session auth | Project sass key |
|
|
594
655
|
| `session-uuid` / `sessionUuid` | session auth | Hub session uuid |
|
|
595
|
-
| `sec-template` / `secTemplate` | guest auth |
|
|
656
|
+
| `sec-template` / `secTemplate` | guest auth | Scaleflex security-template key, *instead of* `sass-key` + `session-uuid`. Stateless only — see [Authentication](#authentication) |
|
|
596
657
|
| `company-uuid`, `project-uuid` | no | Company / project scoping (session auth only) |
|
|
597
|
-
| `template-id` / `templateId` | no | DAM-backed:
|
|
658
|
+
| `template-id` / `templateId` | no | DAM-backed: the file uuid to edit, empty opens the new-template flow. Stateless: opaque id echoed back on `save` |
|
|
598
659
|
| `mode` | no | `inline` (default; size the element) or `modal` (fullscreen overlay, starts closed — call `open()`) |
|
|
599
660
|
| `stateless` | no | Pass the template in and take it back out instead of using the DAM (see [Stateless](#stateless)). Requires `content`, or `new-template` |
|
|
600
661
|
| `content` (property only) | stateless | The template to edit, as `.fdt` XML. Assigning a new value loads it into a running editor |
|
|
601
662
|
| `new-template` / `newTemplate` | no | Stateless: open on a new, empty template instead of supplying `content` — the widget provides the blank document. Ignored when `content` is set. See [Starting a template from scratch](#starting-a-template-from-scratch) |
|
|
602
663
|
| `template-name` / `templateName` | no | Stateless: header title |
|
|
603
664
|
| `template-query` / `templateQuery` | no | Stateless: the render to open on — the `templateQuery` from the last save. Empty uses the XML's `default=` values. |
|
|
665
|
+
| `custom-metadata` / `customMetadata` | no | Metadata model offered as the **Custom metadata** value source: `[{ key, title?, group? }]`, as an array (property) or JSON (attribute). See [Custom metadata fields](#custom-metadata-fields) |
|
|
666
|
+
| `custom-metadata-label` / `customMetadataLabel` | no | Renames the **Custom metadata** value source in the editor's UI (e.g. `External metadata`). Wording only — the stored template is unaffected. Empty uses the default |
|
|
667
|
+
| `dam-store` / `damStore` | no | Stateless only: the element stores each save in Filerobot too, and `save`'s detail carries `stored: { uuid, url }` (or `storeError`) next to the raw `content`. See [Storing a rendering copy](#storing-a-rendering-copy-dam-store) |
|
|
668
|
+
| `store-folder` / `storeFolder` | no | `dam-store`: folder for templates whose id names no existing DAM file (default `/`); an existing file's own folder always wins |
|
|
669
|
+
| `stored-uuid` / `storedUuid` | no | `dam-store`: the `stored.uuid` you persisted for this document, passed back in so re-saves after a reload resolve to (and version) the existing copy. Per-document — `load()` clears it when omitted |
|
|
604
670
|
| `brand-color` / `brandColor` | no | Accent colour for the editor chrome, `#rgb` / `#rrggbb` |
|
|
605
671
|
| `theme` | no | `light`, `dark` or `auto` |
|
|
606
672
|
| `ready-timeout` / `readyTimeout` | no | Ms to wait for the app handshake before `error` (default 20000, 0 disables) |
|
|
@@ -628,9 +694,9 @@ long-lived master credential in client-side code.
|
|
|
628
694
|
|
|
629
695
|
#### Security template (guest auth)
|
|
630
696
|
|
|
631
|
-
A
|
|
632
|
-
|
|
633
|
-
|
|
697
|
+
A **security template** is a named, permission-scoped credential you define once
|
|
698
|
+
in your Scaleflex project — the same guest-auth mechanism the other Scaleflex
|
|
699
|
+
widgets use. Hand one to the widget and no Hub session is involved at
|
|
634
700
|
all.
|
|
635
701
|
|
|
636
702
|
It is what makes a stateless embed work without Hub accounts. Even when the
|
|
@@ -680,17 +746,85 @@ before you pick it:
|
|
|
680
746
|
|
|
681
747
|
- **Stateless only.** Setting `sec-template` without `stateless` is a config
|
|
682
748
|
error — the widget reports `error` with code `invalid-config` and never mounts
|
|
683
|
-
the
|
|
749
|
+
the editor. The dashboard and the DAM-backed editor keep requiring a session.
|
|
684
750
|
- **Hub-project features come back empty** — metadata fields, regional variants
|
|
685
751
|
and dynamic fields have no model to read, and project branding does not apply
|
|
686
752
|
(theme the chrome with `brand-color` / `theme` instead).
|
|
687
753
|
<!-- internal:end -->
|
|
688
754
|
|
|
755
|
+
### Custom metadata fields
|
|
756
|
+
|
|
757
|
+
A text variable normally takes its value from the render query (**Free text**),
|
|
758
|
+
or from the source asset's DAM metadata (**File metadata**, which needs a
|
|
759
|
+
Hub session — it is unavailable under `sec-template`). `custom-metadata` adds a
|
|
760
|
+
third source: your own field names, so an author binds a variable to `sku`
|
|
761
|
+
instead of having to remember which slug happens to mean the SKU.
|
|
762
|
+
|
|
763
|
+
```html
|
|
764
|
+
<sfx-template-builder
|
|
765
|
+
base-url="https://templates.example.com"
|
|
766
|
+
token="…"
|
|
767
|
+
sec-template="…"
|
|
768
|
+
stateless
|
|
769
|
+
custom-metadata='[
|
|
770
|
+
{ "key": "sku", "title": "SKU", "group": "Product" },
|
|
771
|
+
{ "key": "price", "title": "Price", "group": "Product" },
|
|
772
|
+
{ "key": "campaign", "title": "Campaign name" }
|
|
773
|
+
]'
|
|
774
|
+
></sfx-template-builder>
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
```ts
|
|
778
|
+
// Or as a property, which is nicer for anything built at runtime:
|
|
779
|
+
builder.customMetadata = fields.map((f) => ({ key: f.id, title: f.label }))
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Fields appear in the editor's picker in the order you declare them, grouped
|
|
783
|
+
under `group` where present, with `title` (or the bare `key`) as the label. Set
|
|
784
|
+
no model and the source is not offered at all.
|
|
785
|
+
|
|
786
|
+
The source is called **Custom metadata** in the editor by default;
|
|
787
|
+
`custom-metadata-label` renames it to fit your domain — `External metadata`,
|
|
788
|
+
`Product attributes`, whatever your authors know it as. Wording only: the saved
|
|
789
|
+
template carries the same `custom_ckey` either way. (The label is another
|
|
790
|
+
post-0.3.0 addition — an older pinned bundle ignores it.)
|
|
791
|
+
|
|
792
|
+
**Names only — no values travel with the model, and the editor resolves nothing
|
|
793
|
+
against it.** The binding is stored in the saved `.fdt` on the variable as
|
|
794
|
+
`custom_ckey`, and the variable renders exactly like a free-text one: your
|
|
795
|
+
pipeline substitutes the value by putting `$slug=value` in the render query.
|
|
796
|
+
|
|
797
|
+
```xml
|
|
798
|
+
<variable annotation_id="text_1" display="Product code" name="code"
|
|
799
|
+
type="text_placeholder" source="URL" custom_ckey="sku"
|
|
800
|
+
default="AB-1234" />
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
So the round trip is: read `custom_ckey` back from the template you stored, look
|
|
804
|
+
up that field in your own data, and render with `$code=<that value>`. Until you
|
|
805
|
+
do, the editor and any render show the variable's default value.
|
|
806
|
+
|
|
807
|
+
A few consequences worth knowing:
|
|
808
|
+
|
|
809
|
+
- **The model can change between sessions.** A variable bound to a key your
|
|
810
|
+
current model does not list keeps its binding — the editor shows it read-only
|
|
811
|
+
and says so — so opening a template with a narrower model never silently
|
|
812
|
+
rewrites it.
|
|
813
|
+
- **Nothing is validated against the model at render time.** A key you stop
|
|
814
|
+
sending simply stops being substituted, and the default shows instead.
|
|
815
|
+
- **Send it whenever you like.** The widget delivers the model as soon as the
|
|
816
|
+
editor is ready and re-sends it whenever you change it; assigning an equal
|
|
817
|
+
model is a no-op. It applies to DAM-backed embeds as well as stateless ones.
|
|
818
|
+
- **Malformed fields are dropped, not fatal.** An entry with no `key`, a
|
|
819
|
+
duplicate `key` (first one wins), or a `key` containing a character XML cannot
|
|
820
|
+
carry — a C0 control, an unpaired surrogate — is skipped; the rest of the model
|
|
821
|
+
still works. Ordinary text, punctuation, accents and emoji are all fine.
|
|
822
|
+
|
|
689
823
|
### Origin registration
|
|
690
824
|
|
|
691
|
-
Your page's origin must be in the deployment's
|
|
692
|
-
(`NEXT_PUBLIC_TRUSTED_HUB_ORIGINS`), otherwise the browser refuses to
|
|
693
|
-
|
|
825
|
+
Your page's origin must be in the deployment's embedding allowlist
|
|
826
|
+
(`NEXT_PUBLIC_TRUSTED_HUB_ORIGINS`), otherwise the browser refuses to load the
|
|
827
|
+
editor on your page — Chrome shows "refused to connect" in its place, and the
|
|
694
828
|
widget reports `handshake-timeout`.
|
|
695
829
|
|
|
696
830
|
A deployment allows `'self'`, `https://*.scaleflex.com`,
|
|
@@ -711,9 +845,10 @@ without CHIPS support that block third-party cookies will fail with `auth` or
|
|
|
711
845
|
|
|
712
846
|
| Method | Description |
|
|
713
847
|
| --- | --- |
|
|
714
|
-
| `open(templateId?)` | Open the editor, loading
|
|
715
|
-
| `close()` | Close the editor and unload
|
|
716
|
-
| `load({ content, templateId?, name?, templateQuery? })` | Stateless: load a template, opening the editor if needed. `templateQuery` picks the render to open on — see [About `templateQuery`](#about-templatequery). |
|
|
848
|
+
| `open(templateId?)` | Open the editor, loading it if it isn't loaded yet. Optionally switch template first. |
|
|
849
|
+
| `close()` | Close the editor and unload it. Does not emit `close`. |
|
|
850
|
+
| `load({ content, templateId?, name?, templateQuery?, storedUuid? })` | Stateless: load a template, opening the editor if needed. `templateQuery` picks the render to open on — see [About `templateQuery`](#about-templatequery). `storedUuid` seeds the [dam-store](#storing-a-rendering-copy-dam-store) memory; omitting it clears the seed. |
|
|
851
|
+
| `flushPendingSaves(reason?)` | Emit any `dam-store` saves still waiting on their upload, raw content with `storeError` in place of the links. Only for framework wrappers that unsubscribe listeners before removing the element — the React wrapper calls it for you. |
|
|
717
852
|
| `createNew({ templateId?, name? })` | Stateless: open on a new, empty template — no XML needed. See [Starting a template from scratch](#starting-a-template-from-scratch). |
|
|
718
853
|
| `confirmSave(ok, message?)` | Stateless: report whether you persisted the content. See [Reporting a failed save](#reporting-a-failed-save). |
|
|
719
854
|
|
|
@@ -730,7 +865,7 @@ All events are `CustomEvent`s; the payload is in `detail`.
|
|
|
730
865
|
| --- | --- | --- |
|
|
731
866
|
| `ready` | — | The editor mounted and auth validated. Clears the handshake timeout. |
|
|
732
867
|
| `open` | — | The editor UI opened. |
|
|
733
|
-
| `save` | `{ uuid, name }` (DAM-backed) or `{ templateId, content, name, templateQuery }` (stateless) | The user saved. |
|
|
868
|
+
| `save` | `{ uuid, name }` (DAM-backed) or `{ templateId, content, name, templateQuery }` (stateless; under [`dam-store`](#storing-a-rendering-copy-dam-store) also `stored: { uuid, url }` or `storeError`) | The user saved. |
|
|
734
869
|
| `dirtychange` | `{ isDirty }` | Stateless: the unsaved-changes flag flipped. |
|
|
735
870
|
| `close` | — | The user left the editor, or it unmounted. |
|
|
736
871
|
| `error` | `{ code, message? }` | See below. |
|
|
@@ -739,10 +874,10 @@ All events are `CustomEvent`s; the payload is in `detail`.
|
|
|
739
874
|
|
|
740
875
|
| Code | Means |
|
|
741
876
|
| --- | --- |
|
|
742
|
-
| `auth` | The app could not authenticate — blocked cookies, an expired session, or a security template the
|
|
877
|
+
| `auth` | The app could not authenticate — blocked cookies, an expired session, or a security template the Scaleflex API refused. |
|
|
743
878
|
| `invalid-content` | Stateless: the `content` you sent is not a parseable `.fdt` document. |
|
|
744
|
-
| `invalid-config` | Attributes that contradict each other, e.g. `sec-template` without `stateless`. The
|
|
745
|
-
| `handshake-timeout` | No ready signal in time — usually a missing
|
|
879
|
+
| `invalid-config` | Attributes that contradict each other, e.g. `sec-template` without `stateless`. The editor never mounts. |
|
|
880
|
+
| `handshake-timeout` | No ready signal in time — usually a missing entry in the deployment's embedding allowlist, or blocked third-party cookies. |
|
|
746
881
|
| `invalid-base-url` | `base-url` is not a URL. |
|
|
747
882
|
| `unknown` | Anything the app reported that this version does not name. |
|
|
748
883
|
|
|
@@ -750,6 +885,13 @@ All events are `CustomEvent`s; the payload is in `detail`.
|
|
|
750
885
|
`modal` mode the element tears its overlay down; in `inline` mode it is yours to
|
|
751
886
|
act on — the editor never navigates itself anywhere.
|
|
752
887
|
|
|
888
|
+
Under [`dam-store`](#storing-a-rendering-copy-dam-store), a save whose upload
|
|
889
|
+
is still in flight emits **after** `close`, with its real outcome. Keep your
|
|
890
|
+
`save` listener attached until it arrives — or, if your close handler tears the
|
|
891
|
+
element down anyway, removing it from the DOM flushes the pending save to your
|
|
892
|
+
still-attached listeners; call `flushPendingSaves()` yourself only if you
|
|
893
|
+
detach listeners without removing the element.
|
|
894
|
+
|
|
753
895
|
## React API
|
|
754
896
|
|
|
755
897
|
Props mirror the attributes in camelCase, plus `className` and `style`.
|
|
@@ -763,6 +905,10 @@ The credential props are a discriminated union, so the two
|
|
|
763
905
|
`onSave` may return `false` or a promise; see
|
|
764
906
|
[Reporting a failed save](#reporting-a-failed-save).
|
|
765
907
|
|
|
908
|
+
`customMetadata` is an array prop, compared by identity like the rest — hoist it
|
|
909
|
+
to module scope or memoise it, or every render counts as a change (harmless; the
|
|
910
|
+
element de-dupes by value before it says anything to the editor).
|
|
911
|
+
|
|
766
912
|
The component forwards a ref to the underlying element, which is how you reach
|
|
767
913
|
the [imperative API](#public-methods) — required for `mode="modal"`, which
|
|
768
914
|
renders nothing until `open()` is called:
|
|
@@ -877,15 +1023,15 @@ only thing it needs at runtime is a reachable `design-templates-app` for the
|
|
|
877
1023
|
demo page's Base URL field to point at; it defaults to
|
|
878
1024
|
`https://design-templates.scaleflex.com`.
|
|
879
1025
|
|
|
880
|
-
That default
|
|
1026
|
+
That default loads successfully only from an origin that deployment allows —
|
|
881
1027
|
`'self'`, `*.scaleflex.com`, `*.filerobot.com` and `http://localhost:5173`,
|
|
882
1028
|
which covers both the demo as published to the CDN and `yarn dev:demo`. Serving
|
|
883
1029
|
it anywhere else — another port (`yarn preview:demo` uses 4173), or a copy on
|
|
884
1030
|
your own domain — gets "refused to connect" until that origin joins the
|
|
885
1031
|
deployment's allowlist, which is baked in at build time and so needs a rebuild
|
|
886
1032
|
(see [Origin registration](#origin-registration)). Driving a *locally* running
|
|
887
|
-
app has the same requirement in reverse: `'self'` does not cover
|
|
888
|
-
`localhost:5173`
|
|
1033
|
+
app has the same requirement in reverse: `'self'` does not cover a page on
|
|
1034
|
+
`localhost:5173` embedding `localhost:3000`, but the default list now does.
|
|
889
1035
|
|
|
890
1036
|
The demo page takes its XML from a URL, from a paste, or from a picker listing
|
|
891
1037
|
the `.fdt` files in the project its credentials point at, which fills the id,
|
|
@@ -895,6 +1041,15 @@ whatever that template's scope allows. The picker is demo scaffolding standing
|
|
|
895
1041
|
in for a host's own template store — the widget itself only ever sees the XML
|
|
896
1042
|
the page hands it.
|
|
897
1043
|
|
|
1044
|
+
It also plays the host half of
|
|
1045
|
+
[custom metadata](#custom-metadata-fields). The panel carries a switch and an
|
|
1046
|
+
editable JSON model — sample fields to start from, and add whatever of your own
|
|
1047
|
+
you like. Each field takes an optional `value`, which never reaches the editor:
|
|
1048
|
+
the page substitutes it into the render query for any variable bound to that
|
|
1049
|
+
field, on the way in and again on the way out, and logs each substitution. That
|
|
1050
|
+
is exactly the work a real integration does, so binding a variable to `sku` and
|
|
1051
|
+
reloading shows the host's SKU on the canvas rather than the variable's default.
|
|
1052
|
+
|
|
898
1053
|
End-to-end cover for the embed boundary lives in the app repo at
|
|
899
1054
|
`e2e/embed-widget.spec.ts` — a cross-origin host page loads the built CDN
|
|
900
1055
|
bundle, pulls template XML from its own API and gets the edit back. It needs the
|
|
@@ -907,7 +1062,7 @@ yarn build && yarn start
|
|
|
907
1062
|
yarn test:e2e:embed
|
|
908
1063
|
```
|
|
909
1064
|
|
|
910
|
-
The
|
|
1065
|
+
The host↔app protocol lives in `src/protocol.ts` and is shared with the app
|
|
911
1066
|
via the `./protocol` export, so the two sides cannot drift. Message *values* are
|
|
912
1067
|
wire format: never change an existing string, only add new messages, so an older
|
|
913
1068
|
widget keeps working against a newer app deployment and vice versa.
|
|
@@ -926,7 +1081,7 @@ npm publish, git commit + tag + push. It needs a `.env.local` in this package:
|
|
|
926
1081
|
```env
|
|
927
1082
|
FILEROBOT_CDN_TOKEN=scaleflex
|
|
928
1083
|
FILEROBOT_CDN_SECU=<secu key>
|
|
929
|
-
FILEROBOT_CDN_FOLDER=/plugins/
|
|
1084
|
+
FILEROBOT_CDN_FOLDER=/plugins/cloudimage/template-builder/{version}/
|
|
930
1085
|
```
|
|
931
1086
|
|
|
932
1087
|
Update [CHANGELOG.md](./CHANGELOG.md) before releasing.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `dam-store` save path: upload an edited template to Filerobot so the
|
|
3
|
+
* CDN can render it.
|
|
4
|
+
*
|
|
5
|
+
* A stateless save hands the host raw XML — but the CDN renders only stored
|
|
6
|
+
* files, so a host that wants render URLs (previews, production banners) needs
|
|
7
|
+
* a copy in the DAM too. With `dam-store` the element makes that copy itself,
|
|
8
|
+
* with the same multipart upload the DAM-backed editor uses, and the `save`
|
|
9
|
+
* event carries the stored file's links next to the raw data.
|
|
10
|
+
*
|
|
11
|
+
* The raw `content` remains the host's copy of record: nothing here changes
|
|
12
|
+
* what the save event has always carried.
|
|
13
|
+
*/
|
|
14
|
+
import type { BuilderContentData } from './protocol';
|
|
15
|
+
export declare const FILEROBOT_API = "https://api.filerobot.com";
|
|
16
|
+
/** Credentials the element already holds; one of sassKey / secTemplate. */
|
|
17
|
+
export interface DamStoreAuth {
|
|
18
|
+
token: string;
|
|
19
|
+
sassKey?: string;
|
|
20
|
+
secTemplate?: string;
|
|
21
|
+
sessionUuid?: string;
|
|
22
|
+
companyUuid?: string;
|
|
23
|
+
projectUuid?: string;
|
|
24
|
+
}
|
|
25
|
+
/** The stored copy's links, carried on the `save` event as `detail.stored`. */
|
|
26
|
+
export interface StoredTemplate {
|
|
27
|
+
/** DAM file uuid of the stored `.fdt`. */
|
|
28
|
+
uuid: string;
|
|
29
|
+
/**
|
|
30
|
+
* CDN URL of the stored file, with its current `?vh=` cache key — append a
|
|
31
|
+
* template query to render it. Empty when the file record could not be read
|
|
32
|
+
* back after the upload (the file is stored regardless).
|
|
33
|
+
*/
|
|
34
|
+
url: string;
|
|
35
|
+
}
|
|
36
|
+
export interface FileRecord {
|
|
37
|
+
uuid?: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
folder?: {
|
|
40
|
+
name?: string;
|
|
41
|
+
};
|
|
42
|
+
url?: {
|
|
43
|
+
cdn?: string;
|
|
44
|
+
public?: string;
|
|
45
|
+
path?: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A security template is not a key — it is exchanged for a short-lived sass
|
|
50
|
+
* key first, the template authenticating its own exchange. Same call the app
|
|
51
|
+
* and the asset picker make.
|
|
52
|
+
*
|
|
53
|
+
* Exported (with `apiHeaders` / `getFileRecord`) for the demo page, which
|
|
54
|
+
* plays the host half of the same API conversation — one implementation of
|
|
55
|
+
* the auth rules, not two drifting copies.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveKey(auth: DamStoreAuth): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Session scope only: a minted key carries its own, and pairing it with a
|
|
60
|
+
* session's uuids would mix one mode's key with the other mode's scope.
|
|
61
|
+
*/
|
|
62
|
+
export declare function apiHeaders(auth: DamStoreAuth, key: string): Record<string, string>;
|
|
63
|
+
/**
|
|
64
|
+
* Whether a template id plausibly names a DAM file (hex-and-dashes uuid).
|
|
65
|
+
* Opaque host ids ('demo-1', 'sample-spring-banner') never do — looking them
|
|
66
|
+
* up would waste a round trip per save and couple every save to whichever
|
|
67
|
+
* status the API happens to answer a malformed id with.
|
|
68
|
+
*/
|
|
69
|
+
export declare function looksLikeDamFileUuid(id: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* One file's record. `null` means the identifier names nothing — a 404/gone,
|
|
72
|
+
* a 4xx rejecting the id itself, or the API's not-found envelope — all normal
|
|
73
|
+
* answers here. What THROWS is a failure to answer (auth, rate limit, 5xx,
|
|
74
|
+
* network): collapsing those into null would make a transient blip read as
|
|
75
|
+
* "file gone", and the callers act on that — re-homing an existing template
|
|
76
|
+
* into the fallback folder as a duplicate, or failing an unchanged re-save.
|
|
77
|
+
*/
|
|
78
|
+
export declare function getFileRecord(auth: DamStoreAuth, key: string, uuid: string): Promise<FileRecord | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Store one save in the DAM and return the stored copy's links.
|
|
81
|
+
*
|
|
82
|
+
* Uploads into the folder the file this template is already stored as lives
|
|
83
|
+
* in — the host's `templateId` when it names a DAM file, else `knownUuid`
|
|
84
|
+
* (the copy a previous save in this session made; hosts persist `stored.uuid`
|
|
85
|
+
* rather than echoing it into `template-id`, which would reload the editor) —
|
|
86
|
+
* so same name + folder makes the backend version the template in place. New
|
|
87
|
+
* templates land in `fallbackFolder`. Unchanged content resolves to the
|
|
88
|
+
* already-stored file rather than failing. Throws with a human-readable
|
|
89
|
+
* message when the copy could not be made; the caller decides what a save
|
|
90
|
+
* without a stored copy means.
|
|
91
|
+
*/
|
|
92
|
+
export declare function storeTemplateInDam(data: BuilderContentData, auth: DamStoreAuth, fallbackFolder: string, knownUuid?: string): Promise<StoredTemplate>;
|
package/dist/define.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./template-builder-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./template-builder-Byqg1q93.cjs");typeof customElements<"u"&&!customElements.get("sfx-template-builder")&&customElements.define("sfx-template-builder",e.SfxTemplateBuilder);exports.SfxTemplateBuilder=e.SfxTemplateBuilder;
|
|
2
2
|
//# sourceMappingURL=define.cjs.map
|
package/dist/define.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as e } from "./template-builder-
|
|
1
|
+
import { S as e } from "./template-builder-B9Cwo_Q-.js";
|
|
2
2
|
typeof customElements < "u" && !customElements.get("sfx-template-builder") && customElements.define("sfx-template-builder", e);
|
|
3
3
|
export {
|
|
4
4
|
e as SfxTemplateBuilder
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("./template-builder-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("./template-builder-Byqg1q93.cjs");exports.AUTH_MODES=E.AUTH_MODES;exports.BLANK_TEMPLATE_XML=E.BLANK_TEMPLATE_XML;exports.BRAND_COLOR_PATTERN=E.BRAND_COLOR_PATTERN;exports.BUILDER_CLOSE=E.BUILDER_CLOSE;exports.BUILDER_CONTENT=E.BUILDER_CONTENT;exports.BUILDER_CONTENT_REQUEST=E.BUILDER_CONTENT_REQUEST;exports.BUILDER_DIRTY=E.BUILDER_DIRTY;exports.BUILDER_ERROR=E.BUILDER_ERROR;exports.BUILDER_OPEN=E.BUILDER_OPEN;exports.BUILDER_READY=E.BUILDER_READY;exports.BUILDER_SAVE=E.BUILDER_SAVE;exports.EMBED_PARAMS=E.EMBED_PARAMS;exports.EMBED_ROUTE=E.EMBED_ROUTE;exports.HOST_CONFIG=E.HOST_CONFIG;exports.HOST_LOAD=E.HOST_LOAD;exports.HOST_SAVED=E.HOST_SAVED;exports.PROTOCOL_VERSION=E.PROTOCOL_VERSION;exports.SfxTemplateBuilder=E.SfxTemplateBuilder;exports.builderRoute=E.builderRoute;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { SfxTemplateBuilder, type TemplateBuilderStatus, type TemplateBuilderEventMap, type TemplateBuilderSaveDetail, } from './template-builder';
|
|
1
|
+
export { SfxTemplateBuilder, type TemplateBuilderStatus, type TemplateBuilderEventMap, type TemplateBuilderSaveDetail, type StoredTemplate, type DamStoreAuth, } from './template-builder';
|
|
2
2
|
export * from './protocol';
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
import { A as a, B as _, a as s, b as
|
|
1
|
+
import { A as a, B as _, a as s, b as O, c as T, d as D, e as L, f as B, g as A, h as S, i as I, E as U, j as N, H as e, k as C, l as M, P, S as H, m as l } from "./template-builder-B9Cwo_Q-.js";
|
|
2
2
|
export {
|
|
3
3
|
a as AUTH_MODES,
|
|
4
4
|
_ as BLANK_TEMPLATE_XML,
|
|
5
5
|
s as BRAND_COLOR_PATTERN,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
O as BUILDER_CLOSE,
|
|
7
|
+
T as BUILDER_CONTENT,
|
|
8
|
+
D as BUILDER_CONTENT_REQUEST,
|
|
9
9
|
L as BUILDER_DIRTY,
|
|
10
10
|
B as BUILDER_ERROR,
|
|
11
11
|
A as BUILDER_OPEN,
|
|
12
12
|
S as BUILDER_READY,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
I as BUILDER_SAVE,
|
|
14
|
+
U as EMBED_PARAMS,
|
|
15
15
|
N as EMBED_ROUTE,
|
|
16
|
-
e as
|
|
16
|
+
e as HOST_CONFIG,
|
|
17
|
+
C as HOST_LOAD,
|
|
17
18
|
M as HOST_SAVED,
|
|
18
19
|
P as PROTOCOL_VERSION,
|
|
19
|
-
|
|
20
|
+
H as SfxTemplateBuilder,
|
|
20
21
|
l as builderRoute
|
|
21
22
|
};
|
|
22
23
|
//# sourceMappingURL=index.js.map
|