@scaleflex/template-builder 0.1.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 +356 -0
- package/CHANGELOG.md +66 -0
- package/LICENSE +50 -0
- package/README.md +775 -0
- package/dist/define.cjs +2 -0
- package/dist/define.cjs.map +1 -0
- package/dist/define.d.ts +2 -0
- package/dist/define.js +6 -0
- package/dist/define.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +237 -0
- package/dist/react.cjs +2 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.ts +95 -0
- package/dist/react.js +59 -0
- package/dist/react.js.map +1 -0
- package/dist/template-builder-CSyPZni9.cjs +52 -0
- package/dist/template-builder-CSyPZni9.cjs.map +1 -0
- package/dist/template-builder-S33H_d5T.js +354 -0
- package/dist/template-builder-S33H_d5T.js.map +1 -0
- package/dist/template-builder.d.ts +206 -0
- package/package.json +75 -0
- package/src/define.ts +10 -0
- package/src/index.ts +9 -0
- package/src/protocol.ts +298 -0
- package/src/react.ts +223 -0
- package/src/template-builder.ts +599 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { LitElement, nothing, type PropertyValues } from 'lit';
|
|
2
|
+
import { type BuilderContentData, type BuilderDirtyData, type BuilderErrorData, type BuilderSaveData, type BuilderTheme } from './protocol';
|
|
3
|
+
export type TemplateBuilderStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
4
|
+
/**
|
|
5
|
+
* `save` payload. Which variant arrives follows the mode the element was
|
|
6
|
+
* configured in:
|
|
7
|
+
* - DAM-backed (default) — `BuilderSaveData`; the app uploaded the template and
|
|
8
|
+
* reports the resulting `uuid`.
|
|
9
|
+
* - `stateless` — `BuilderContentData`; nothing was stored, and `content` is
|
|
10
|
+
* the edited template for the host to persist.
|
|
11
|
+
*/
|
|
12
|
+
export type TemplateBuilderSaveDetail = BuilderSaveData | BuilderContentData | undefined;
|
|
13
|
+
export interface TemplateBuilderEventMap {
|
|
14
|
+
ready: CustomEvent<void>;
|
|
15
|
+
open: CustomEvent<void>;
|
|
16
|
+
close: CustomEvent<void>;
|
|
17
|
+
save: CustomEvent<TemplateBuilderSaveDetail>;
|
|
18
|
+
error: CustomEvent<BuilderErrorData>;
|
|
19
|
+
dirtychange: CustomEvent<BuilderDirtyData>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `<sfx-template-builder>` — embeds the Filerobot design-templates builder.
|
|
23
|
+
*
|
|
24
|
+
* The element owns an iframe pointed at a design-templates-app deployment,
|
|
25
|
+
* passes auth via URL params (converted to cookies by the app's proxy), and
|
|
26
|
+
* translates the app's postMessage protocol into DOM CustomEvents:
|
|
27
|
+
* `ready`, `open`, `close`, `save`, `error`.
|
|
28
|
+
*
|
|
29
|
+
* Required: `base-url`, `token`, and one of two credentials:
|
|
30
|
+
* - `sass-key` + `session-uuid` — a Hub session. Full features.
|
|
31
|
+
* - `sec-template` — a Filerobot security-template key. No Hub session needed,
|
|
32
|
+
* but it only works with `stateless`, and Hub-project features (metadata
|
|
33
|
+
* fields, regional variants, project branding) come back empty.
|
|
34
|
+
*
|
|
35
|
+
* In `inline` mode the editor loads as soon as config is complete and fills
|
|
36
|
+
* the host element (size it explicitly). In `modal` mode nothing renders
|
|
37
|
+
* until `open()` is called; the editor then covers the viewport.
|
|
38
|
+
*
|
|
39
|
+
* Two ways to supply the template:
|
|
40
|
+
* - **DAM-backed** (default) — set `template-id` to a Filerobot file uuid. The
|
|
41
|
+
* app loads and saves it itself, and `save` reports the new uuid.
|
|
42
|
+
* - **Stateless** — set `stateless` and assign `content`. The element sends the
|
|
43
|
+
* template into the editor over postMessage and `save` returns the edited
|
|
44
|
+
* document; nothing is stored on the Scaleflex side, and `template-id` is
|
|
45
|
+
* just an opaque string echoed back. Rendering, fonts and asset browsing
|
|
46
|
+
* still use the session's Filerobot tenant.
|
|
47
|
+
*
|
|
48
|
+
* `brand-color` and `theme` restyle the editor chrome to match the host page.
|
|
49
|
+
* They do not touch the rendered template — its colours live in the document.
|
|
50
|
+
*/
|
|
51
|
+
export declare class SfxTemplateBuilder extends LitElement {
|
|
52
|
+
static styles: import("lit").CSSResult;
|
|
53
|
+
/** Origin + optional path prefix of the design-templates-app deployment. */
|
|
54
|
+
baseUrl: string;
|
|
55
|
+
/** Filerobot token (`ftoken`). */
|
|
56
|
+
token: string;
|
|
57
|
+
sassKey: string;
|
|
58
|
+
sessionUuid: string;
|
|
59
|
+
/**
|
|
60
|
+
* Filerobot security-template key — the alternative to `sass-key` +
|
|
61
|
+
* `session-uuid` for hosts with no Hub session to hand over. Requires
|
|
62
|
+
* `stateless`, and degrades the features that come from the Hub project
|
|
63
|
+
* model (metadata fields, regional variants, project branding). When set it
|
|
64
|
+
* wins: neither `sass-key` nor `session-uuid` is passed to the app.
|
|
65
|
+
*/
|
|
66
|
+
secTemplate: string;
|
|
67
|
+
companyUuid: string;
|
|
68
|
+
projectUuid: string;
|
|
69
|
+
/**
|
|
70
|
+
* DAM-backed mode: the Filerobot uuid to load; empty opens the new-template
|
|
71
|
+
* flow. Stateless mode: an opaque host id, echoed back on `save`.
|
|
72
|
+
*/
|
|
73
|
+
templateId: string;
|
|
74
|
+
mode: 'inline' | 'modal';
|
|
75
|
+
/**
|
|
76
|
+
* Hand the template in and take it back out instead of letting the app read
|
|
77
|
+
* and write Filerobot. Requires `content`.
|
|
78
|
+
*/
|
|
79
|
+
stateless: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Stateless mode: the template to edit, as `.fdt` XML. Property only — templates
|
|
82
|
+
* routinely exceed practical attribute/URL sizes, so it is never reflected.
|
|
83
|
+
* Assigning a different value while open loads it into the running editor.
|
|
84
|
+
*/
|
|
85
|
+
content: string;
|
|
86
|
+
/** Stateless mode: display name for the editor header. */
|
|
87
|
+
templateName: string;
|
|
88
|
+
/**
|
|
89
|
+
* Stateless mode: the `template_query` to open on — the value handed back in
|
|
90
|
+
* the `save` payload. Pass back what you stored and the editor reopens on the
|
|
91
|
+
* same layout and variable values; leave it empty and the render falls back
|
|
92
|
+
* to the XML's own `default=` attributes.
|
|
93
|
+
*/
|
|
94
|
+
templateQuery: string;
|
|
95
|
+
/**
|
|
96
|
+
* Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. The app derives
|
|
97
|
+
* buttons, focus rings and highlights from it. Empty keeps the Scaleflex
|
|
98
|
+
* default. Themes the editor UI only — never the rendered template, whose
|
|
99
|
+
* colours live in the document.
|
|
100
|
+
*/
|
|
101
|
+
brandColor: string;
|
|
102
|
+
/** Colour scheme for the editor chrome. Empty leaves the app's own default. */
|
|
103
|
+
theme: BuilderTheme | '';
|
|
104
|
+
/** Ms to wait for the app's ready signal before emitting `error`. 0 disables. */
|
|
105
|
+
readyTimeout: number;
|
|
106
|
+
private _status;
|
|
107
|
+
private _open;
|
|
108
|
+
private _src;
|
|
109
|
+
private _handshakeTimer?;
|
|
110
|
+
/**
|
|
111
|
+
* The app asked for content. Tracked because the request and the `content`
|
|
112
|
+
* assignment race: whichever lands second triggers the send.
|
|
113
|
+
*/
|
|
114
|
+
private _contentRequested;
|
|
115
|
+
/**
|
|
116
|
+
* Identity of the template already delivered, so an unrelated re-render does
|
|
117
|
+
* not resend it and discard the user's edits. Covers the id and name too, not
|
|
118
|
+
* just the content: two host records can hold byte-identical templates, and
|
|
119
|
+
* resending only on content change would leave the app echoing a stale id
|
|
120
|
+
* back on save.
|
|
121
|
+
*/
|
|
122
|
+
private _sentKey?;
|
|
123
|
+
/**
|
|
124
|
+
* The `baseUrl` value already reported as unparseable. `_computeSrc()` runs
|
|
125
|
+
* on every update cycle, so without this a bad URL re-emits `error` forever —
|
|
126
|
+
* once per render, since the error status it sets is already in place after
|
|
127
|
+
* the first.
|
|
128
|
+
*/
|
|
129
|
+
private _reportedBadBaseUrl?;
|
|
130
|
+
/**
|
|
131
|
+
* Whether the sec-template-without-stateless mistake has been reported. Same
|
|
132
|
+
* reason as `_reportedBadBaseUrl`: `_computeSrc()` runs every update cycle
|
|
133
|
+
* and the error status it sets is already in place after the first pass.
|
|
134
|
+
*/
|
|
135
|
+
private _reportedStatelessRequired;
|
|
136
|
+
private _isDirty;
|
|
137
|
+
get status(): TemplateBuilderStatus;
|
|
138
|
+
/**
|
|
139
|
+
* Stateless mode: whether the editor holds edits that have not been handed
|
|
140
|
+
* back yet. Check this before calling `load()` — a swap discards them.
|
|
141
|
+
* Always false in DAM-backed mode, where the app owns saving.
|
|
142
|
+
*/
|
|
143
|
+
get isDirty(): boolean;
|
|
144
|
+
/** Open the editor (loads the iframe). Optionally switch template first. */
|
|
145
|
+
open(templateId?: string): void;
|
|
146
|
+
/** Close the editor and unload the iframe. Does not emit `close`. */
|
|
147
|
+
close(): void;
|
|
148
|
+
/**
|
|
149
|
+
* Stateless mode: load a template, opening the editor if needed. Equivalent
|
|
150
|
+
* to assigning `templateId` / `content` / `templateName` and calling `open()`.
|
|
151
|
+
*/
|
|
152
|
+
load({ content, templateId, name, templateQuery, }: {
|
|
153
|
+
content: string;
|
|
154
|
+
templateId?: string;
|
|
155
|
+
name?: string;
|
|
156
|
+
templateQuery?: string;
|
|
157
|
+
}): void;
|
|
158
|
+
/**
|
|
159
|
+
* Stateless mode: report back whether a `save` was persisted on your side.
|
|
160
|
+
*
|
|
161
|
+
* Optional. The editor clears its unsaved-changes flag as soon as it hands
|
|
162
|
+
* the content over, so not calling this leaves the previous behaviour intact.
|
|
163
|
+
* Calling it with `false` is what earns something: the editor restores the
|
|
164
|
+
* dirty flag and tells the user, rather than showing a failed write as saved.
|
|
165
|
+
*
|
|
166
|
+
* No-op outside stateless mode, where the app did the saving and has nothing
|
|
167
|
+
* to hear back about.
|
|
168
|
+
*/
|
|
169
|
+
confirmSave(ok: boolean, message?: string): void;
|
|
170
|
+
/**
|
|
171
|
+
* Whether the inline-implies-open decision has been made. It cannot be made
|
|
172
|
+
* in `connectedCallback`: frameworks insert the element first and assign
|
|
173
|
+
* properties afterwards in the same task (the React wrapper does), so at
|
|
174
|
+
* connect time `mode` may still hold its `'inline'` default — deciding there
|
|
175
|
+
* flashes a modal's full-viewport overlay open on mount. By the first update
|
|
176
|
+
* cycle the real value has settled.
|
|
177
|
+
*/
|
|
178
|
+
private _autoOpenDecided;
|
|
179
|
+
connectedCallback(): void;
|
|
180
|
+
disconnectedCallback(): void;
|
|
181
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
182
|
+
protected updated(changed: PropertyValues): void;
|
|
183
|
+
render(): import("lit-html").TemplateResult<1> | typeof nothing;
|
|
184
|
+
private _computeSrc;
|
|
185
|
+
private get _appOrigin();
|
|
186
|
+
private _onMessage;
|
|
187
|
+
/**
|
|
188
|
+
* Deliver `content` to the app once both sides are ready: it has asked, and
|
|
189
|
+
* we have something new to give it. Skips a re-send of identical content so
|
|
190
|
+
* an unrelated re-render can't discard the user's in-progress edits.
|
|
191
|
+
*/
|
|
192
|
+
private _maybeSendContent;
|
|
193
|
+
/** Identity of a delivered template, as compared against `_sentKey`. */
|
|
194
|
+
private _contentKey;
|
|
195
|
+
/** Post into the iframe, targeted at the app origin. False if not mounted. */
|
|
196
|
+
private _postToApp;
|
|
197
|
+
private _startHandshakeTimer;
|
|
198
|
+
private _clearHandshakeTimer;
|
|
199
|
+
private _fail;
|
|
200
|
+
private _emit;
|
|
201
|
+
}
|
|
202
|
+
declare global {
|
|
203
|
+
interface HTMLElementTagNameMap {
|
|
204
|
+
'sfx-template-builder': SfxTemplateBuilder;
|
|
205
|
+
}
|
|
206
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scaleflex/template-builder",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Embeddable web component (<sfx-template-builder>) for the Filerobot design-templates builder",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./define": {
|
|
17
|
+
"types": "./dist/define.d.ts",
|
|
18
|
+
"import": "./dist/define.js",
|
|
19
|
+
"require": "./dist/define.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./react": {
|
|
22
|
+
"types": "./dist/react.d.ts",
|
|
23
|
+
"import": "./dist/react.js",
|
|
24
|
+
"require": "./dist/react.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./protocol": {
|
|
27
|
+
"types": "./src/protocol.ts",
|
|
28
|
+
"default": "./src/protocol.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"src/*.ts",
|
|
34
|
+
".claude",
|
|
35
|
+
"README.md",
|
|
36
|
+
"CHANGELOG.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"dev": "vite --config vite.demo.config.ts",
|
|
41
|
+
"dev:demo": "vite --config vite.demo.config.ts",
|
|
42
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
43
|
+
"build:cdn": "vite build --config vite.cdn.config.ts",
|
|
44
|
+
"build:demo": "vite build --config vite.demo.config.ts",
|
|
45
|
+
"preview:demo": "vite preview --config vite.demo.config.ts",
|
|
46
|
+
"build:all": "yarn build && yarn build:cdn",
|
|
47
|
+
"release": "node scripts/release-cdn.mjs plugin",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"test:watch": "vitest"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"lit": "^3.2.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": ">=18",
|
|
57
|
+
"react-dom": ">=18"
|
|
58
|
+
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"react": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"react-dom": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@types/react": "^19",
|
|
69
|
+
"jsdom": "^29.0.1",
|
|
70
|
+
"markdown-it": "^14.0.0",
|
|
71
|
+
"typescript": "^5",
|
|
72
|
+
"vite": "^6.3.5",
|
|
73
|
+
"vitest": "^4.1.2"
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/define.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SfxTemplateBuilder } from './template-builder'
|
|
2
|
+
|
|
3
|
+
if (
|
|
4
|
+
typeof customElements !== 'undefined' &&
|
|
5
|
+
!customElements.get('sfx-template-builder')
|
|
6
|
+
) {
|
|
7
|
+
customElements.define('sfx-template-builder', SfxTemplateBuilder)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { SfxTemplateBuilder }
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Core entry — exports the element class without registering the tag.
|
|
2
|
+
// Import '@scaleflex/template-builder/define' to register <sfx-template-builder>.
|
|
3
|
+
export {
|
|
4
|
+
SfxTemplateBuilder,
|
|
5
|
+
type TemplateBuilderStatus,
|
|
6
|
+
type TemplateBuilderEventMap,
|
|
7
|
+
type TemplateBuilderSaveDetail,
|
|
8
|
+
} from './template-builder'
|
|
9
|
+
export * from './protocol'
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// postMessage protocol between the design-templates app (inside the iframe)
|
|
3
|
+
// and its embedder (the <sfx-template-builder> widget or the Hub).
|
|
4
|
+
//
|
|
5
|
+
// This module is the single source of truth for both sides: the app imports
|
|
6
|
+
// it via `@scaleflex/template-builder/protocol` (workspace TS-source export),
|
|
7
|
+
// the widget bundles it. Message *values* are wire format — never change an
|
|
8
|
+
// existing string; add new messages instead. All changes must stay additive
|
|
9
|
+
// so older widgets keep working against newer app deployments and vice versa.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export const PROTOCOL_VERSION = 2
|
|
13
|
+
|
|
14
|
+
// App → embedder ------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/** Editor mounted with valid auth — the embed handshake succeeded. */
|
|
17
|
+
export const BUILDER_READY = 'design-templates:builder:ready'
|
|
18
|
+
/** Editor UI opened (kept for Hub backwards compatibility; implies ready). */
|
|
19
|
+
export const BUILDER_OPEN = 'design-templates:builder:open'
|
|
20
|
+
/** Editor UI closed / unmounted. */
|
|
21
|
+
export const BUILDER_CLOSE = 'design-templates:builder:close'
|
|
22
|
+
/** Template saved. `data` is absent on app deployments older than protocol v1. */
|
|
23
|
+
export const BUILDER_SAVE = 'design-templates:builder:save'
|
|
24
|
+
/** The app cannot start (e.g. auth cookies missing/blocked). */
|
|
25
|
+
export const BUILDER_ERROR = 'design-templates:builder:error'
|
|
26
|
+
/**
|
|
27
|
+
* Stateless mode only (protocol v2). The editor mounted without a template and
|
|
28
|
+
* is waiting for the host to send `HOST_LOAD`. Re-sent on nothing — the host
|
|
29
|
+
* may answer late; the app keeps waiting until content arrives.
|
|
30
|
+
*/
|
|
31
|
+
export const BUILDER_CONTENT_REQUEST = 'design-templates:builder:content-request'
|
|
32
|
+
/**
|
|
33
|
+
* Stateless mode only (protocol v2). The user saved and the app is handing the
|
|
34
|
+
* edited template back instead of uploading it. This is the stateless
|
|
35
|
+
* counterpart to `BUILDER_SAVE` — a separate message so that embedders written
|
|
36
|
+
* against v1 (which read `data.uuid` from a save they assume was persisted)
|
|
37
|
+
* never receive a payload that was not, in fact, stored anywhere.
|
|
38
|
+
*/
|
|
39
|
+
export const BUILDER_CONTENT = 'design-templates:builder:content'
|
|
40
|
+
/**
|
|
41
|
+
* Stateless mode only (protocol v2). The unsaved-changes flag flipped. Sent so
|
|
42
|
+
* a host can ask the user before swapping the template out from under them —
|
|
43
|
+
* `HOST_LOAD` is honoured unconditionally and discards whatever was in
|
|
44
|
+
* progress, and without this the host has no way to know there was anything to
|
|
45
|
+
* lose.
|
|
46
|
+
*/
|
|
47
|
+
export const BUILDER_DIRTY = 'design-templates:builder:dirty'
|
|
48
|
+
|
|
49
|
+
export interface BuilderReadyMessage {
|
|
50
|
+
type: typeof BUILDER_READY
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface BuilderOpenMessage {
|
|
54
|
+
type: typeof BUILDER_OPEN
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BuilderCloseMessage {
|
|
58
|
+
type: typeof BUILDER_CLOSE
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BuilderSaveData {
|
|
62
|
+
uuid: string
|
|
63
|
+
name?: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface BuilderSaveMessage {
|
|
67
|
+
type: typeof BUILDER_SAVE
|
|
68
|
+
data?: BuilderSaveData
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* `auth` and `invalid-content` are the codes the app itself sends. The widget
|
|
73
|
+
* adds `handshake-timeout` (no ready signal — typically blocked third-party
|
|
74
|
+
* cookies or a missing frame-ancestors entry), `invalid-base-url` and
|
|
75
|
+
* `invalid-config`.
|
|
76
|
+
*/
|
|
77
|
+
export type BuilderErrorCode =
|
|
78
|
+
/**
|
|
79
|
+
* The app could not authenticate. In `secTemplate` mode this also covers a
|
|
80
|
+
* security-template key the Filerobot API refused to exchange for a sass key.
|
|
81
|
+
*/
|
|
82
|
+
| 'auth'
|
|
83
|
+
/** Stateless mode: the `HOST_LOAD` content could not be parsed as a template. */
|
|
84
|
+
| 'invalid-content'
|
|
85
|
+
| 'handshake-timeout'
|
|
86
|
+
| 'invalid-base-url'
|
|
87
|
+
/** Attributes that contradict each other, e.g. `sec-template` without `stateless`. */
|
|
88
|
+
| 'invalid-config'
|
|
89
|
+
| 'unknown'
|
|
90
|
+
|
|
91
|
+
export interface BuilderErrorData {
|
|
92
|
+
code: BuilderErrorCode
|
|
93
|
+
message?: string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface BuilderErrorMessage {
|
|
97
|
+
type: typeof BUILDER_ERROR
|
|
98
|
+
data: BuilderErrorData
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface BuilderContentRequestMessage {
|
|
102
|
+
type: typeof BUILDER_CONTENT_REQUEST
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface BuilderContentData {
|
|
106
|
+
/**
|
|
107
|
+
* The id the host supplied in `HOST_LOAD`, echoed back verbatim. Absent when
|
|
108
|
+
* the host sent content without one.
|
|
109
|
+
*/
|
|
110
|
+
templateId?: string
|
|
111
|
+
/** The edited template, serialized as `.fdt` XML. */
|
|
112
|
+
content: string
|
|
113
|
+
/** Display name the host supplied, echoed back. */
|
|
114
|
+
name?: string
|
|
115
|
+
/**
|
|
116
|
+
* `template_query` for the default render — layout, variable values and
|
|
117
|
+
* locale. In DAM-backed mode this is stored as file metadata; a stateless
|
|
118
|
+
* host must persist it alongside `content` or renders will fall back to
|
|
119
|
+
* whatever defaults the XML alone implies.
|
|
120
|
+
*/
|
|
121
|
+
templateQuery?: string
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface BuilderContentMessage {
|
|
125
|
+
type: typeof BUILDER_CONTENT
|
|
126
|
+
data: BuilderContentData
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface BuilderDirtyData {
|
|
130
|
+
/** True when the editor holds edits that have not been handed back. */
|
|
131
|
+
isDirty: boolean
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface BuilderDirtyMessage {
|
|
135
|
+
type: typeof BUILDER_DIRTY
|
|
136
|
+
data: BuilderDirtyData
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type BuilderMessage =
|
|
140
|
+
| BuilderReadyMessage
|
|
141
|
+
| BuilderOpenMessage
|
|
142
|
+
| BuilderCloseMessage
|
|
143
|
+
| BuilderSaveMessage
|
|
144
|
+
| BuilderErrorMessage
|
|
145
|
+
| BuilderContentRequestMessage
|
|
146
|
+
| BuilderContentMessage
|
|
147
|
+
| BuilderDirtyMessage
|
|
148
|
+
|
|
149
|
+
// Embedder → app --------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Stateless mode only (protocol v2). Hands the app a template to edit. Sent in
|
|
153
|
+
* answer to `BUILDER_CONTENT_REQUEST`, and again whenever the host swaps the
|
|
154
|
+
* template without remounting the iframe.
|
|
155
|
+
*
|
|
156
|
+
* Content travels by postMessage rather than a URL param because template XML
|
|
157
|
+
* routinely exceeds practical URL length limits.
|
|
158
|
+
*
|
|
159
|
+
* The app accepts this message only from the origin pinned as `embedOrigin`
|
|
160
|
+
* when the session credentials were handed over, so an unrelated framing page
|
|
161
|
+
* cannot inject a template into someone else's session.
|
|
162
|
+
*/
|
|
163
|
+
export const HOST_LOAD = 'design-templates:host:load'
|
|
164
|
+
|
|
165
|
+
export interface HostLoadData {
|
|
166
|
+
/**
|
|
167
|
+
* Opaque host-side identifier, echoed back on save. It is never used to
|
|
168
|
+
* fetch anything, so it need not be a Filerobot uuid — any string the host
|
|
169
|
+
* can map back to its own record works.
|
|
170
|
+
*/
|
|
171
|
+
templateId?: string
|
|
172
|
+
/** Template to edit, as `.fdt` XML. */
|
|
173
|
+
content: string
|
|
174
|
+
/** Display name for the editor header. */
|
|
175
|
+
name?: string
|
|
176
|
+
/**
|
|
177
|
+
* `template_query` describing the render to open on — layout and variable
|
|
178
|
+
* values, in the same `$key=value&$key2=value2` form the editor hands back
|
|
179
|
+
* in `BuilderContentData.templateQuery`.
|
|
180
|
+
*
|
|
181
|
+
* Round-trips that value: a host that stored it on save and passes it back
|
|
182
|
+
* here reopens the template exactly as it was left. Omitting it falls back
|
|
183
|
+
* to the `default=` attributes in the XML, which is a different render
|
|
184
|
+
* whenever the query overrode any of them.
|
|
185
|
+
*
|
|
186
|
+
* Applied as display state, not as an edit — it selects the layout and fills
|
|
187
|
+
* variable values without marking the document dirty, so opening a template
|
|
188
|
+
* and closing it again does not look like an unsaved change.
|
|
189
|
+
*/
|
|
190
|
+
templateQuery?: string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface HostLoadMessage {
|
|
194
|
+
type: typeof HOST_LOAD
|
|
195
|
+
data: HostLoadData
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Stateless mode only (protocol v2). Reports whether the host managed to
|
|
200
|
+
* persist the content it received in `BUILDER_CONTENT`.
|
|
201
|
+
*
|
|
202
|
+
* Optional by design. The editor clears its unsaved-changes flag optimistically
|
|
203
|
+
* when it posts `BUILDER_CONTENT`, so a host that never acks behaves exactly as
|
|
204
|
+
* before. Sending `ok: false` is what buys something: the editor restores the
|
|
205
|
+
* dirty flag and tells the user, instead of leaving a failed write looking
|
|
206
|
+
* saved.
|
|
207
|
+
*/
|
|
208
|
+
export const HOST_SAVED = 'design-templates:host:saved'
|
|
209
|
+
|
|
210
|
+
export interface HostSavedData {
|
|
211
|
+
/** False when the host could not persist the content. */
|
|
212
|
+
ok: boolean
|
|
213
|
+
/** Shown to the user when `ok` is false. */
|
|
214
|
+
message?: string
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface HostSavedMessage {
|
|
218
|
+
type: typeof HOST_SAVED
|
|
219
|
+
data: HostSavedData
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export type HostMessage = HostLoadMessage | HostSavedMessage
|
|
223
|
+
|
|
224
|
+
// Embed URL contract ----------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Query params the app's proxy middleware (`src/proxy.ts`) converts into auth
|
|
228
|
+
* cookies on first navigation. Names are wire format.
|
|
229
|
+
*/
|
|
230
|
+
export const EMBED_PARAMS = {
|
|
231
|
+
SESSION_UUID: 'suuid',
|
|
232
|
+
COMPANY_UUID: 'cuuid',
|
|
233
|
+
PROJECT_UUID: 'puuid',
|
|
234
|
+
SASS_KEY: 'sassKey',
|
|
235
|
+
FILEROBOT_TOKEN: 'ftoken',
|
|
236
|
+
/**
|
|
237
|
+
* Filerobot security-template key, the alternative to a Hub session. The app
|
|
238
|
+
* exchanges it for a sass key itself and runs in a reduced mode — see
|
|
239
|
+
* `AUTH_MODES`. Sent *instead of* `sassKey` + `suuid`, never alongside them.
|
|
240
|
+
*/
|
|
241
|
+
SEC_TEMPLATE: 'secTemplate',
|
|
242
|
+
IFRAME: 'iframe',
|
|
243
|
+
/** Origin of the embedding page; the app uses it as postMessage targetOrigin. */
|
|
244
|
+
EMBED_ORIGIN: 'embedOrigin',
|
|
245
|
+
/**
|
|
246
|
+
* Accent colour for the editor chrome, as `#rgb` / `#rrggbb`. The app derives
|
|
247
|
+
* its whole accent ramp from it. Rejected server-side if it doesn't match
|
|
248
|
+
* that shape — it ends up inside a stylesheet.
|
|
249
|
+
*/
|
|
250
|
+
BRAND_COLOR: 'brandColor',
|
|
251
|
+
/** Colour scheme for the editor chrome: `light` | `dark` | `auto`. */
|
|
252
|
+
THEME: 'theme',
|
|
253
|
+
} as const
|
|
254
|
+
|
|
255
|
+
/** Values the `theme` param accepts. */
|
|
256
|
+
export type BuilderTheme = 'light' | 'dark' | 'auto'
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* How the embedder authenticated.
|
|
260
|
+
*
|
|
261
|
+
* - `session` — a Hub session (`suuid` + `sassKey` + `ftoken`). Full features.
|
|
262
|
+
* - `secTemplate` — a Filerobot security-template key (`secTemplate` +
|
|
263
|
+
* `ftoken`). A guest credential: no user identity and no Hub project, so the
|
|
264
|
+
* app accepts it on {@link EMBED_ROUTE} only, and everything that reads the
|
|
265
|
+
* Hub project model (metadata fields, regional variants, project branding)
|
|
266
|
+
* comes back empty. Rendering, fonts and asset browsing work, scoped by
|
|
267
|
+
* whatever the security template grants.
|
|
268
|
+
*
|
|
269
|
+
* Derived by the app from the params it received; named here so both sides use
|
|
270
|
+
* the same vocabulary.
|
|
271
|
+
*/
|
|
272
|
+
export const AUTH_MODES = {
|
|
273
|
+
SESSION: 'session',
|
|
274
|
+
SEC_TEMPLATE: 'secTemplate',
|
|
275
|
+
} as const
|
|
276
|
+
|
|
277
|
+
export type AuthMode = (typeof AUTH_MODES)[keyof typeof AUTH_MODES]
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Shape the app requires of `brandColor`. Hex only: the value is interpolated
|
|
281
|
+
* into a `:root { … }` rule, so anything that could carry CSS syntax is
|
|
282
|
+
* refused rather than escaped. Mirrored in the app's proxy — keep in sync.
|
|
283
|
+
*/
|
|
284
|
+
export const BRAND_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
|
285
|
+
|
|
286
|
+
/** Editor route for an existing template, or the new-template route. */
|
|
287
|
+
export function builderRoute(templateId?: string): string {
|
|
288
|
+
return templateId
|
|
289
|
+
? `/templates/${encodeURIComponent(templateId)}/edit`
|
|
290
|
+
: '/templates/new'
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Stateless editor route. Takes no template id — the id is an opaque host
|
|
295
|
+
* value that arrives with the content over `HOST_LOAD`, not something the app
|
|
296
|
+
* resolves against Filerobot, so it has no place in the URL.
|
|
297
|
+
*/
|
|
298
|
+
export const EMBED_ROUTE = '/templates/embed'
|