@singleton-sd/post-kit-editor 0.5.0 → 0.7.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 +81 -10
- package/dist/EditorErrorBoundary.d.ts +15 -0
- package/dist/EditorErrorBoundary.js +32 -0
- package/dist/EditorErrorBoundary.js.map +1 -0
- package/dist/canvas/EmailBuilderCanvas.js +2 -2
- package/dist/canvas/EmailBuilderCanvas.js.map +1 -1
- package/dist/editor-shell-states.d.ts +8 -0
- package/dist/editor-shell-states.js +15 -0
- package/dist/editor-shell-states.js.map +1 -0
- package/dist/email-template-editor.d.ts +6 -2
- package/dist/email-template-editor.js +78 -35
- package/dist/email-template-editor.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata/MetadataPanel.d.ts +3 -1
- package/dist/metadata/MetadataPanel.js +15 -3
- package/dist/metadata/MetadataPanel.js.map +1 -1
- package/dist/preview/PreviewDataEditor.d.ts +3 -1
- package/dist/preview/PreviewDataEditor.js +9 -2
- package/dist/preview/PreviewDataEditor.js.map +1 -1
- package/dist/preview/PreviewPane.d.ts +3 -1
- package/dist/preview/PreviewPane.js +5 -1
- package/dist/preview/PreviewPane.js.map +1 -1
- package/dist/save-send/SaveSendBar.d.ts +3 -1
- package/dist/save-send/SaveSendBar.js +8 -3
- package/dist/save-send/SaveSendBar.js.map +1 -1
- package/dist/save-send/actions.d.ts +70 -0
- package/dist/save-send/actions.js +75 -0
- package/dist/save-send/actions.js.map +1 -0
- package/dist/validation/ValidationSummary.d.ts +5 -0
- package/dist/validation/ValidationSummary.js +29 -0
- package/dist/validation/ValidationSummary.js.map +1 -0
- package/dist/validation/extract-placeholders.d.ts +2 -0
- package/dist/validation/extract-placeholders.js +49 -0
- package/dist/validation/extract-placeholders.js.map +1 -0
- package/dist/validation/validate.d.ts +17 -0
- package/dist/validation/validate.js +154 -0
- package/dist/validation/validate.js.map +1 -0
- package/dist/variables/VariableCatalogue.js +1 -1
- package/dist/variables/VariableCatalogue.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -25,14 +25,25 @@ import {
|
|
|
25
25
|
EmailTemplateEditor,
|
|
26
26
|
type TemplateSourceFiles,
|
|
27
27
|
type SerializedTemplateSource,
|
|
28
|
+
type ValidationIssue,
|
|
28
29
|
} from '@singleton-sd/post-kit-editor';
|
|
29
30
|
|
|
30
|
-
export function TemplateAdminPage({
|
|
31
|
+
export function TemplateAdminPage({
|
|
32
|
+
template,
|
|
33
|
+
loading,
|
|
34
|
+
loadError,
|
|
35
|
+
}: {
|
|
36
|
+
template: TemplateSourceFiles;
|
|
37
|
+
loading?: boolean;
|
|
38
|
+
loadError?: string;
|
|
39
|
+
}) {
|
|
31
40
|
return (
|
|
32
41
|
<EmailTemplateEditor
|
|
33
42
|
template={template}
|
|
34
43
|
availableVariables={[{ name: 'name', description: 'Recipient display name' }]}
|
|
35
|
-
|
|
44
|
+
loading={loading}
|
|
45
|
+
loadError={loadError}
|
|
46
|
+
onSave={async (serialized: SerializedTemplateSource, files: TemplateSourceFiles) => {
|
|
36
47
|
// Commit serialized.templateJson / metadataJson / previewJson to the
|
|
37
48
|
// consumer repository (e.g. via the app's own server endpoint).
|
|
38
49
|
const res = await fetch('/api/templates', {
|
|
@@ -47,10 +58,9 @@ export function TemplateAdminPage({ template }: { template: TemplateSourceFiles
|
|
|
47
58
|
}}
|
|
48
59
|
onSendTest={async (serialized, _files, recipient) => {
|
|
49
60
|
// Browser → your trusted server only. The server uses
|
|
50
|
-
// @singleton-sd/post-kit-client with
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// browser code.
|
|
61
|
+
// @singleton-sd/post-kit-client with secrets from Azure Key Vault
|
|
62
|
+
// (production) or local `.env` (development). Never embed a
|
|
63
|
+
// long-lived PostKit API key in browser code.
|
|
54
64
|
const res = await fetch('/api/templates/send-test', {
|
|
55
65
|
method: 'POST',
|
|
56
66
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -63,6 +73,9 @@ export function TemplateAdminPage({ template }: { template: TemplateSourceFiles
|
|
|
63
73
|
onDirtyChange={(dirty) => {
|
|
64
74
|
// Optional: guard in-app navigation while dirty.
|
|
65
75
|
}}
|
|
76
|
+
onValidationChange={(issues: ValidationIssue[]) => {
|
|
77
|
+
// Optional: mirror validation in the host chrome.
|
|
78
|
+
}}
|
|
66
79
|
onPreviewRendered={(html) => {
|
|
67
80
|
console.log('preview bytes', html.length);
|
|
68
81
|
}}
|
|
@@ -72,6 +85,39 @@ export function TemplateAdminPage({ template }: { template: TemplateSourceFiles
|
|
|
72
85
|
}
|
|
73
86
|
```
|
|
74
87
|
|
|
88
|
+
A copy-pasteable single-file integration (plus synthetic sample JSON) lives in
|
|
89
|
+
[`examples/minimal/`](./examples/minimal/).
|
|
90
|
+
|
|
91
|
+
## Props
|
|
92
|
+
|
|
93
|
+
| Prop | Type | Required | Description |
|
|
94
|
+
| --- | --- | --- | --- |
|
|
95
|
+
| `template` | `TemplateSourceFiles` | yes | Seeded working triple (`templateJson`, `metadata`, `previewData`). |
|
|
96
|
+
| `onSave` | `(serialized, files) => SaveResult \| void \| Promise<…>` | yes | Persist working files. Receives serialized Git strings **and** structured files. |
|
|
97
|
+
| `onSendTest` | `(serialized, files, recipient) => SendTestResult \| void \| Promise<…>` | no | When set, shows Send-test chrome. Must route through the consumer’s trusted server. |
|
|
98
|
+
| `availableVariables` | `TemplateVariable[]` | no | Catalogue entries offered to the editing user. |
|
|
99
|
+
| `onDirtyChange` | `(dirty: boolean) => void` | no | Fires when working state diverges from the seeded `template` (resets after successful save). |
|
|
100
|
+
| `onValidationChange` | `(issues: ValidationIssue[]) => void` | no | Current validation issues whenever they change. |
|
|
101
|
+
| `onPreviewRendered` | `(html: string) => void` | no | Successful preview HTML (e.g. open-in-new-tab without recompiling). |
|
|
102
|
+
| `loading` | `boolean` | no | Non-interactive loading shell while the host fetches files. |
|
|
103
|
+
| `loadError` | `string` | no | Non-interactive error shell with the host’s message. |
|
|
104
|
+
| `className` | `string` | no | Extra class on the root element. |
|
|
105
|
+
|
|
106
|
+
Also exported: `loadTemplateSource`, `serializeTemplateSource`,
|
|
107
|
+
`validateTemplate`, `hasValidationErrors`, `EDITOR_CLASS_PREFIX`, and related
|
|
108
|
+
types.
|
|
109
|
+
|
|
110
|
+
## What this package does not do
|
|
111
|
+
|
|
112
|
+
- **No persistence** — it never writes to disk, Git, or object storage. `onSave`
|
|
113
|
+
is the only way contents leave the editor.
|
|
114
|
+
- **No sending** — it never calls PostKit or any mail transport. `onSendTest`
|
|
115
|
+
(when provided) is a consumer callback only.
|
|
116
|
+
- **No credentials** — there are no API-key / token props. Secrets stay on the
|
|
117
|
+
consumer’s trusted server (Key Vault / env), never in browser bundles.
|
|
118
|
+
- **No publish pipeline** — compilation and content hashing belong to
|
|
119
|
+
`@singleton-sd/post-kit-compiler` in CI or server tooling.
|
|
120
|
+
|
|
75
121
|
## Persistence is consumer-supplied
|
|
76
122
|
|
|
77
123
|
Save serializes the working state with `serializeTemplateSource()` and passes
|
|
@@ -82,12 +128,29 @@ failure messages (no unhandled rejections).
|
|
|
82
128
|
|
|
83
129
|
Send-test chrome appears **only** when `onSendTest` is provided. The editor
|
|
84
130
|
validates a non-empty, plausible recipient address, then invokes the callback
|
|
85
|
-
with the same serialized payload. Test delivery must go
|
|
86
|
-
trusted server (typically `@singleton-sd/post-kit-client`
|
|
131
|
+
with the same serialized payload plus the recipient. Test delivery must go
|
|
132
|
+
through the consumer's trusted server (typically `@singleton-sd/post-kit-client`
|
|
133
|
+
server-side).
|
|
87
134
|
|
|
88
135
|
Optional `onDirtyChange` fires when working state diverges from the seeded
|
|
89
136
|
`template` prop and resets after a successful save.
|
|
90
137
|
|
|
138
|
+
## Validation and accessibility
|
|
139
|
+
|
|
140
|
+
`validateTemplate(files)` (also exported) checks required metadata, key charset,
|
|
141
|
+
undeclared / unused variables, preview coverage, and optional `render-failed`
|
|
142
|
+
from the preview pane (passed through so the compiler is not invoked twice).
|
|
143
|
+
Error-severity issues disable Save and Send-test; warnings never block.
|
|
144
|
+
|
|
145
|
+
Optional props:
|
|
146
|
+
|
|
147
|
+
- `onValidationChange` — current `ValidationIssue[]` whenever it changes
|
|
148
|
+
- `loading` — non-interactive loading shell while the host fetches files
|
|
149
|
+
- `loadError` — non-interactive error shell with the host’s message
|
|
150
|
+
|
|
151
|
+
Inline field errors use `aria-describedby`. The validation summary is an
|
|
152
|
+
`aria-live` region; each entry focuses the responsible control.
|
|
153
|
+
|
|
91
154
|
## Preview data (synthetic only)
|
|
92
155
|
|
|
93
156
|
`preview.json` is edited in the preview-data panel and used to render the
|
|
@@ -127,12 +190,16 @@ against the resulting HTML string:
|
|
|
127
190
|
```tsx
|
|
128
191
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
129
192
|
|
|
130
|
-
const html = renderToStaticMarkup(
|
|
193
|
+
const html = renderToStaticMarkup(
|
|
194
|
+
<EmailTemplateEditor template={template} onSave={() => {}} />,
|
|
195
|
+
);
|
|
131
196
|
```
|
|
132
197
|
|
|
133
198
|
Specs live next to the code as `src/**/*.spec.tsx`. Behaviour that needs
|
|
134
199
|
interaction is factored into pure functions (preview rows, save/send helpers)
|
|
135
|
-
that can be tested without a DOM.
|
|
200
|
+
that can be tested without a DOM. The end-to-end suite (`src/e2e.spec.tsx`)
|
|
201
|
+
drives load → edit → validate → save / send-test through those helpers plus SSR
|
|
202
|
+
markup.
|
|
136
203
|
|
|
137
204
|
## Development
|
|
138
205
|
|
|
@@ -141,3 +208,7 @@ pnpm test # type-check + run tests
|
|
|
141
208
|
pnpm build # emit CommonJS to dist/
|
|
142
209
|
pnpm lint # covered by root eslint
|
|
143
210
|
```
|
|
211
|
+
|
|
212
|
+
`examples/` is documentation-only: it is not listed in the package `files`
|
|
213
|
+
array and is outside `src/`, so it is neither published to npm nor emitted into
|
|
214
|
+
`dist/`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
|
+
export interface EditorErrorBoundaryProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
}
|
|
5
|
+
interface EditorErrorBoundaryState {
|
|
6
|
+
error: Error | null;
|
|
7
|
+
}
|
|
8
|
+
export declare class EditorErrorBoundary extends Component<EditorErrorBoundaryProps, EditorErrorBoundaryState> {
|
|
9
|
+
state: EditorErrorBoundaryState;
|
|
10
|
+
static getDerivedStateFromError(error: Error): EditorErrorBoundaryState;
|
|
11
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
12
|
+
private handleRetry;
|
|
13
|
+
render(): ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EditorErrorBoundary = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const email_template_editor_1 = require("./email-template-editor");
|
|
7
|
+
class EditorErrorBoundary extends react_1.Component {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.state = { error: null };
|
|
11
|
+
this.handleRetry = () => {
|
|
12
|
+
this.setState({ error: null });
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
static getDerivedStateFromError(error) {
|
|
16
|
+
return { error };
|
|
17
|
+
}
|
|
18
|
+
componentDidCatch(error, info) {
|
|
19
|
+
if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {
|
|
20
|
+
console.error('post-kit-editor render failure', error, info.componentStack);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
render() {
|
|
24
|
+
const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
|
|
25
|
+
if (this.state.error) {
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `${p}fatal-error`, "data-testid": `${p}fatal-error`, role: "alert", children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}fatal-error-heading`, children: "Editor failed to render" }), (0, jsx_runtime_1.jsx)("p", { className: `${p}fatal-error-message`, children: this.state.error.message }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `${p}fatal-error-retry`, "data-testid": `${p}fatal-error-retry`, onClick: this.handleRetry, children: "Retry" })] }));
|
|
27
|
+
}
|
|
28
|
+
return this.props.children;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.EditorErrorBoundary = EditorErrorBoundary;
|
|
32
|
+
//# sourceMappingURL=EditorErrorBoundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EditorErrorBoundary.js","sourceRoot":"","sources":["../src/EditorErrorBoundary.tsx"],"names":[],"mappings":";;;;AAAA,iCAAyE;AAEzE,mEAA8D;AAc9D,MAAa,mBAAoB,SAAQ,iBAGxC;IAHD;;QAIE,UAAK,GAA6B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAa1C,gBAAW,GAAG,GAAS,EAAE;YAC/B,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjC,CAAC,CAAC;IAsBJ,CAAC;IAnCC,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,IAAe;QAE7C,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YACtE,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAMD,MAAM;QACJ,MAAM,CAAC,GAAG,2CAAmB,CAAC;QAC9B,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,CACL,iCAAK,SAAS,EAAE,GAAG,CAAC,aAAa,iBAAe,GAAG,CAAC,aAAa,EAAE,IAAI,EAAC,OAAO,aAC7E,+BAAI,SAAS,EAAE,GAAG,CAAC,qBAAqB,wCAA8B,EACtE,8BAAG,SAAS,EAAE,GAAG,CAAC,qBAAqB,YAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAK,EACvE,mCACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,GAAG,CAAC,mBAAmB,iBACrB,GAAG,CAAC,mBAAmB,EACpC,OAAO,EAAE,IAAI,CAAC,WAAW,sBAGlB,IACL,CACP,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF;AAzCD,kDAyCC"}
|
|
@@ -8,8 +8,8 @@ const EditorBlock_1 = require("./EditorBlock");
|
|
|
8
8
|
const email_builder_1 = require("@usewaypoint/email-builder");
|
|
9
9
|
function EmailBuilderCanvas({ document, onChange, readOnly = false, }) {
|
|
10
10
|
if (readOnly) {
|
|
11
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, children: (0, jsx_runtime_1.jsx)(email_builder_1.Reader, { document: document, rootBlockId: "root" }) }));
|
|
11
|
+
return ((0, jsx_runtime_1.jsx)("div", { id: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, role: "region", tabIndex: -1, "aria-label": "Email document canvas", children: (0, jsx_runtime_1.jsx)(email_builder_1.Reader, { document: document, rootBlockId: "root" }) }));
|
|
12
12
|
}
|
|
13
|
-
return ((0, jsx_runtime_1.jsx)(editor_context_1.CanvasEditorProvider, { document: document, onChange: onChange, children: (0, jsx_runtime_1.jsx)("div", { className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, children: (0, jsx_runtime_1.jsx)(EditorBlock_1.EditorBlock, { id: "root" }) }) }));
|
|
13
|
+
return ((0, jsx_runtime_1.jsx)(editor_context_1.CanvasEditorProvider, { document: document, onChange: onChange, children: (0, jsx_runtime_1.jsx)("div", { id: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, role: "region", tabIndex: -1, "aria-label": "Email document canvas", children: (0, jsx_runtime_1.jsx)(EditorBlock_1.EditorBlock, { id: "root" }) }) }));
|
|
14
14
|
}
|
|
15
15
|
//# sourceMappingURL=EmailBuilderCanvas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmailBuilderCanvas.js","sourceRoot":"","sources":["../../src/canvas/EmailBuilderCanvas.tsx"],"names":[],"mappings":";;AAcA,
|
|
1
|
+
{"version":3,"file":"EmailBuilderCanvas.js","sourceRoot":"","sources":["../../src/canvas/EmailBuilderCanvas.tsx"],"names":[],"mappings":";;AAcA,gDAkCC;;AA7CD,oEAA+D;AAC/D,qDAAwD;AACxD,+CAA4C;AAC5C,8DAAoD;AAQpD,SAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,QAAQ,GAAG,KAAK,GACQ;IACxB,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CACL,gCACE,EAAE,EAAE,GAAG,2CAAmB,QAAQ,EAClC,SAAS,EAAE,GAAG,2CAAmB,QAAQ,iBAC5B,GAAG,2CAAmB,QAAQ,EAC3C,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,CAAC,gBACD,uBAAuB,YAElC,uBAAC,sBAAM,IAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAC,MAAM,GAAG,GAC7C,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,uBAAC,qCAAoB,IAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,YAC1D,gCACE,EAAE,EAAE,GAAG,2CAAmB,QAAQ,EAClC,SAAS,EAAE,GAAG,2CAAmB,QAAQ,iBAC5B,GAAG,2CAAmB,QAAQ,EAC3C,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,CAAC,gBACD,uBAAuB,YAElC,uBAAC,yBAAW,IAAC,EAAE,EAAC,MAAM,GAAG,GACrB,GACe,CACxB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface EditorLoadingStateProps {
|
|
2
|
+
message?: string;
|
|
3
|
+
}
|
|
4
|
+
export declare function EditorLoadingState({ message, }: EditorLoadingStateProps): JSX.Element;
|
|
5
|
+
export interface EditorLoadErrorStateProps {
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function EditorLoadErrorState({ message }: EditorLoadErrorStateProps): JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EditorLoadingState = EditorLoadingState;
|
|
4
|
+
exports.EditorLoadErrorState = EditorLoadErrorState;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const email_template_editor_1 = require("./email-template-editor");
|
|
7
|
+
function EditorLoadingState({ message = 'Loading template…', }) {
|
|
8
|
+
const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
|
|
9
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: `${p}loading`, "data-testid": `${p}loading`, role: "status", "aria-busy": "true", "aria-live": "polite", children: (0, jsx_runtime_1.jsx)("p", { className: `${p}loading-message`, children: message }) }));
|
|
10
|
+
}
|
|
11
|
+
function EditorLoadErrorState({ message }) {
|
|
12
|
+
const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
|
|
13
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `${p}load-error`, "data-testid": `${p}load-error`, role: "alert", children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}load-error-heading`, children: "Could not load template" }), (0, jsx_runtime_1.jsx)("p", { className: `${p}load-error-message`, children: message })] }));
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=editor-shell-states.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor-shell-states.js","sourceRoot":"","sources":["../src/editor-shell-states.tsx"],"names":[],"mappings":";;AAUA,gDAeC;AAOD,oDAQC;;AAtCD,mEAA8D;AAQ9D,SAAgB,kBAAkB,CAAC,EACjC,OAAO,GAAG,mBAAmB,GACL;IACxB,MAAM,CAAC,GAAG,2CAAmB,CAAC;IAC9B,OAAO,CACL,gCACE,SAAS,EAAE,GAAG,CAAC,SAAS,iBACX,GAAG,CAAC,SAAS,EAC1B,IAAI,EAAC,QAAQ,eACH,MAAM,eACN,QAAQ,YAElB,8BAAG,SAAS,EAAE,GAAG,CAAC,iBAAiB,YAAG,OAAO,GAAK,GAC9C,CACP,CAAC;AACJ,CAAC;AAOD,SAAgB,oBAAoB,CAAC,EAAE,OAAO,EAA6B;IACzE,MAAM,CAAC,GAAG,2CAAmB,CAAC;IAC9B,OAAO,CACL,iCAAK,SAAS,EAAE,GAAG,CAAC,YAAY,iBAAe,GAAG,CAAC,YAAY,EAAE,IAAI,EAAC,OAAO,aAC3E,+BAAI,SAAS,EAAE,GAAG,CAAC,oBAAoB,wCAA8B,EACrE,8BAAG,SAAS,EAAE,GAAG,CAAC,oBAAoB,YAAG,OAAO,GAAK,IACjD,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SaveResult, SendTestResult, SerializedTemplateSource } from './save-send/types';
|
|
2
2
|
import type { TemplateSourceFiles, TemplateVariable } from './types';
|
|
3
|
+
import { type ValidationIssue } from './validation/validate';
|
|
3
4
|
export declare const EDITOR_CLASS_PREFIX = "pk-editor-";
|
|
4
5
|
export interface EmailTemplateEditorProps {
|
|
5
6
|
template: TemplateSourceFiles;
|
|
@@ -7,7 +8,10 @@ export interface EmailTemplateEditorProps {
|
|
|
7
8
|
onSave: (serialized: SerializedTemplateSource, files: TemplateSourceFiles) => Promise<SaveResult | void> | SaveResult | void;
|
|
8
9
|
onSendTest?: (serialized: SerializedTemplateSource, files: TemplateSourceFiles, recipient: string) => Promise<SendTestResult | void> | SendTestResult | void;
|
|
9
10
|
onDirtyChange?: (dirty: boolean) => void;
|
|
11
|
+
onValidationChange?: (issues: ValidationIssue[]) => void;
|
|
10
12
|
onPreviewRendered?: (html: string) => void;
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
loadError?: string;
|
|
11
15
|
className?: string;
|
|
12
16
|
}
|
|
13
|
-
export declare function EmailTemplateEditor(
|
|
17
|
+
export declare function EmailTemplateEditor(props: EmailTemplateEditorProps): JSX.Element;
|
|
@@ -5,24 +5,42 @@ exports.EmailTemplateEditor = EmailTemplateEditor;
|
|
|
5
5
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
6
|
const react_1 = require("react");
|
|
7
7
|
const EmailBuilderCanvas_1 = require("./canvas/EmailBuilderCanvas");
|
|
8
|
+
const EditorErrorBoundary_1 = require("./EditorErrorBoundary");
|
|
9
|
+
const editor_shell_states_1 = require("./editor-shell-states");
|
|
8
10
|
const insertion_target_1 = require("./insertion-target");
|
|
9
11
|
const MetadataPanel_1 = require("./metadata/MetadataPanel");
|
|
10
12
|
const PreviewDataEditor_1 = require("./preview/PreviewDataEditor");
|
|
11
13
|
const PreviewPane_1 = require("./preview/PreviewPane");
|
|
12
14
|
const dirty_1 = require("./save-send/dirty");
|
|
13
15
|
const SaveSendBar_1 = require("./save-send/SaveSendBar");
|
|
14
|
-
const
|
|
16
|
+
const actions_1 = require("./save-send/actions");
|
|
17
|
+
const ValidationSummary_1 = require("./validation/ValidationSummary");
|
|
18
|
+
const validate_1 = require("./validation/validate");
|
|
15
19
|
const VariableCatalogue_1 = require("./variables/VariableCatalogue");
|
|
16
20
|
const working_files_1 = require("./working-files");
|
|
17
21
|
exports.EDITOR_CLASS_PREFIX = 'pk-editor-';
|
|
18
|
-
function EmailTemplateEditor(
|
|
22
|
+
function EmailTemplateEditor(props) {
|
|
23
|
+
const rootClassName = [`${exports.EDITOR_CLASS_PREFIX}root`, props.className].filter(Boolean).join(' ');
|
|
24
|
+
if (props.loading) {
|
|
25
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(editor_shell_states_1.EditorLoadingState, {}) }));
|
|
26
|
+
}
|
|
27
|
+
if (props.loadError) {
|
|
28
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(editor_shell_states_1.EditorLoadErrorState, { message: props.loadError }) }));
|
|
29
|
+
}
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(EditorErrorBoundary_1.EditorErrorBoundary, { children: (0, jsx_runtime_1.jsx)(EmailTemplateEditorInner, { ...props }) }) }));
|
|
31
|
+
}
|
|
32
|
+
function EmailTemplateEditorInner({ template, availableVariables, onSave, onSendTest, onDirtyChange, onValidationChange, onPreviewRendered, }) {
|
|
19
33
|
const [workingFiles, setWorkingFiles] = (0, react_1.useState)(template);
|
|
20
34
|
const [seedFiles, setSeedFiles] = (0, react_1.useState)(template);
|
|
21
35
|
const [saveFeedback, setSaveFeedback] = (0, react_1.useState)({ status: 'idle' });
|
|
22
36
|
const [sendFeedback, setSendFeedback] = (0, react_1.useState)({ status: 'idle' });
|
|
37
|
+
const [previewResult, setPreviewResult] = (0, react_1.useState)(null);
|
|
23
38
|
const onDirtyChangeRef = (0, react_1.useRef)(onDirtyChange);
|
|
24
39
|
onDirtyChangeRef.current = onDirtyChange;
|
|
40
|
+
const onValidationChangeRef = (0, react_1.useRef)(onValidationChange);
|
|
41
|
+
onValidationChangeRef.current = onValidationChange;
|
|
25
42
|
const lastDirtyRef = (0, react_1.useRef)(null);
|
|
43
|
+
const lastIssuesKeyRef = (0, react_1.useRef)(null);
|
|
26
44
|
const templateGenerationRef = (0, react_1.useRef)(0);
|
|
27
45
|
const seedFilesRef = (0, react_1.useRef)(seedFiles);
|
|
28
46
|
seedFilesRef.current = seedFiles;
|
|
@@ -33,6 +51,7 @@ function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest,
|
|
|
33
51
|
templateGenerationRef.current += 1;
|
|
34
52
|
setSaveFeedback({ status: 'idle' });
|
|
35
53
|
setSendFeedback({ status: 'idle' });
|
|
54
|
+
setPreviewResult(null);
|
|
36
55
|
setWorkingFiles(template);
|
|
37
56
|
setSeedFiles(template);
|
|
38
57
|
}, [template]);
|
|
@@ -44,6 +63,24 @@ function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest,
|
|
|
44
63
|
lastDirtyRef.current = dirty;
|
|
45
64
|
onDirtyChangeRef.current?.(dirty);
|
|
46
65
|
}, [seedFiles, workingFiles]);
|
|
66
|
+
const issues = (0, validate_1.validateTemplate)(workingFiles, {
|
|
67
|
+
renderError: previewResult && !previewResult.ok ? previewResult.error : null,
|
|
68
|
+
});
|
|
69
|
+
(0, react_1.useEffect)(() => {
|
|
70
|
+
const key = JSON.stringify(issues);
|
|
71
|
+
if (lastIssuesKeyRef.current === key) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
lastIssuesKeyRef.current = key;
|
|
75
|
+
onValidationChangeRef.current?.(issues);
|
|
76
|
+
}, [issues]);
|
|
77
|
+
const previewPending = previewResult === null;
|
|
78
|
+
const validationBlocked = (0, validate_1.hasValidationErrors)(issues) || previewPending;
|
|
79
|
+
const validationBlockedReason = previewPending
|
|
80
|
+
? 'Wait for the preview to finish before saving or sending a test.'
|
|
81
|
+
: 'Fix validation errors before saving or sending a test.';
|
|
82
|
+
const metadataIssues = issues.filter((i) => i.field === 'metadata' || i.field === 'subject');
|
|
83
|
+
const previewDataIssues = issues.filter((i) => i.field === 'previewData');
|
|
47
84
|
const handleDocumentChange = (0, react_1.useCallback)((document) => {
|
|
48
85
|
setWorkingFiles((current) => (0, working_files_1.withDocument)(current, document));
|
|
49
86
|
}, []);
|
|
@@ -58,73 +95,79 @@ function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest,
|
|
|
58
95
|
}, []);
|
|
59
96
|
const busy = saveFeedback.status === 'pending' || sendFeedback.status === 'pending';
|
|
60
97
|
const handleSave = (0, react_1.useCallback)(() => {
|
|
61
|
-
|
|
98
|
+
const started = (0, actions_1.startSaveAction)({
|
|
99
|
+
busy,
|
|
100
|
+
validationBlocked,
|
|
101
|
+
files: workingFiles,
|
|
102
|
+
});
|
|
103
|
+
if (started.status === 'noop') {
|
|
62
104
|
return;
|
|
63
105
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
payload = (0, types_1.buildSavePayload)(workingFiles);
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
setSaveFeedback({
|
|
70
|
-
status: 'failure',
|
|
71
|
-
message: error instanceof Error ? error.message : String(error),
|
|
72
|
-
});
|
|
106
|
+
if (started.status === 'serialize-error') {
|
|
107
|
+
setSaveFeedback({ status: 'failure', message: started.message });
|
|
73
108
|
return;
|
|
74
109
|
}
|
|
75
110
|
const generation = templateGenerationRef.current;
|
|
76
111
|
setSaveFeedback({ status: 'pending' });
|
|
77
112
|
void (async () => {
|
|
78
|
-
const result = await (0,
|
|
79
|
-
|
|
113
|
+
const result = await (0, actions_1.finishSaveAction)({
|
|
114
|
+
payload: started.payload,
|
|
115
|
+
isGenerationCurrent: () => generation === templateGenerationRef.current,
|
|
116
|
+
onSave,
|
|
117
|
+
});
|
|
118
|
+
if (result.status === 'stale') {
|
|
80
119
|
return;
|
|
81
120
|
}
|
|
82
|
-
if (result.
|
|
83
|
-
setSeedFiles(
|
|
121
|
+
if (result.status === 'success') {
|
|
122
|
+
setSeedFiles(result.files);
|
|
84
123
|
setSaveFeedback({ status: 'success', message: result.message });
|
|
85
124
|
}
|
|
86
125
|
else {
|
|
87
126
|
setSaveFeedback({
|
|
88
127
|
status: 'failure',
|
|
89
|
-
message: result.message
|
|
128
|
+
message: result.message,
|
|
90
129
|
});
|
|
91
130
|
}
|
|
92
131
|
})();
|
|
93
|
-
}, [busy, onSave, workingFiles]);
|
|
132
|
+
}, [busy, onSave, validationBlocked, workingFiles]);
|
|
94
133
|
const handleSendTest = (0, react_1.useCallback)((recipient) => {
|
|
95
|
-
|
|
134
|
+
const started = (0, actions_1.startSendTestAction)({
|
|
135
|
+
busy,
|
|
136
|
+
validationBlocked,
|
|
137
|
+
sendTestEnabled: typeof onSendTest === 'function',
|
|
138
|
+
files: workingFiles,
|
|
139
|
+
recipient,
|
|
140
|
+
});
|
|
141
|
+
if (started.status === 'noop') {
|
|
96
142
|
return;
|
|
97
143
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
payload = (0, types_1.buildSavePayload)(workingFiles);
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
setSendFeedback({
|
|
104
|
-
status: 'failure',
|
|
105
|
-
message: error instanceof Error ? error.message : String(error),
|
|
106
|
-
});
|
|
144
|
+
if (started.status === 'recipient-error' || started.status === 'serialize-error') {
|
|
145
|
+
setSendFeedback({ status: 'failure', message: started.message });
|
|
107
146
|
return;
|
|
108
147
|
}
|
|
109
148
|
const generation = templateGenerationRef.current;
|
|
110
149
|
setSendFeedback({ status: 'pending' });
|
|
111
150
|
void (async () => {
|
|
112
|
-
const result = await (0,
|
|
113
|
-
|
|
151
|
+
const result = await (0, actions_1.finishSendTestAction)({
|
|
152
|
+
payload: started.payload,
|
|
153
|
+
recipient: started.recipient,
|
|
154
|
+
isGenerationCurrent: () => generation === templateGenerationRef.current,
|
|
155
|
+
onSendTest: onSendTest,
|
|
156
|
+
});
|
|
157
|
+
if (result.status === 'stale') {
|
|
114
158
|
return;
|
|
115
159
|
}
|
|
116
|
-
if (result.
|
|
160
|
+
if (result.status === 'success') {
|
|
117
161
|
setSendFeedback({ status: 'success', message: result.message });
|
|
118
162
|
}
|
|
119
163
|
else {
|
|
120
164
|
setSendFeedback({
|
|
121
165
|
status: 'failure',
|
|
122
|
-
message: result.message
|
|
166
|
+
message: result.message,
|
|
123
167
|
});
|
|
124
168
|
}
|
|
125
169
|
})();
|
|
126
|
-
}, [busy, onSendTest, workingFiles]);
|
|
127
|
-
|
|
128
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(insertion_target_1.InsertionTargetProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}layout`, "data-testid": `${exports.EDITOR_CLASS_PREFIX}layout`, children: [(0, jsx_runtime_1.jsxs)("aside", { className: `${exports.EDITOR_CLASS_PREFIX}sidebar`, children: [(0, jsx_runtime_1.jsx)(SaveSendBar_1.SaveSendBar, { saveFeedback: saveFeedback, sendFeedback: sendFeedback, busy: busy, showSendTest: typeof onSendTest === 'function', onSave: handleSave, onSendTest: handleSendTest }), (0, jsx_runtime_1.jsx)(MetadataPanel_1.MetadataPanel, { metadata: workingFiles.metadata, previewData: workingFiles.previewData, onChange: handleMetadataChange }), (0, jsx_runtime_1.jsx)(VariableCatalogue_1.VariableCatalogue, { availableVariables: availableVariables, metadataVariables: workingFiles.metadata.variables, onMetadataVariablesChange: handleMetadataVariablesChange }), (0, jsx_runtime_1.jsx)(PreviewDataEditor_1.PreviewDataEditor, { declaredVariables: workingFiles.metadata.variables, previewData: workingFiles.previewData, onChange: handlePreviewDataChange })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}main`, children: [(0, jsx_runtime_1.jsx)(EmailBuilderCanvas_1.EmailBuilderCanvas, { document: workingFiles.templateJson, onChange: handleDocumentChange }), (0, jsx_runtime_1.jsx)(PreviewPane_1.PreviewPane, { files: workingFiles, onPreviewRendered: onPreviewRendered })] })] }) }) }));
|
|
170
|
+
}, [busy, onSendTest, validationBlocked, workingFiles]);
|
|
171
|
+
return ((0, jsx_runtime_1.jsx)(insertion_target_1.InsertionTargetProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}layout`, "data-testid": `${exports.EDITOR_CLASS_PREFIX}layout`, children: [(0, jsx_runtime_1.jsxs)("aside", { className: `${exports.EDITOR_CLASS_PREFIX}sidebar`, "aria-label": "Template settings", children: [(0, jsx_runtime_1.jsx)(SaveSendBar_1.SaveSendBar, { saveFeedback: saveFeedback, sendFeedback: sendFeedback, busy: busy, validationBlocked: validationBlocked, validationBlockedReason: validationBlockedReason, showSendTest: typeof onSendTest === 'function', onSave: handleSave, onSendTest: handleSendTest }), (0, jsx_runtime_1.jsx)(ValidationSummary_1.ValidationSummary, { issues: issues }), (0, jsx_runtime_1.jsx)(MetadataPanel_1.MetadataPanel, { metadata: workingFiles.metadata, previewData: workingFiles.previewData, onChange: handleMetadataChange, issues: metadataIssues }), (0, jsx_runtime_1.jsx)(VariableCatalogue_1.VariableCatalogue, { availableVariables: availableVariables, metadataVariables: workingFiles.metadata.variables, onMetadataVariablesChange: handleMetadataVariablesChange }), (0, jsx_runtime_1.jsx)(PreviewDataEditor_1.PreviewDataEditor, { declaredVariables: workingFiles.metadata.variables, previewData: workingFiles.previewData, onChange: handlePreviewDataChange, issues: previewDataIssues })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}main`, "aria-label": "Template canvas and preview", children: [(0, jsx_runtime_1.jsx)(EmailBuilderCanvas_1.EmailBuilderCanvas, { document: workingFiles.templateJson, onChange: handleDocumentChange }), (0, jsx_runtime_1.jsx)(PreviewPane_1.PreviewPane, { files: workingFiles, onPreviewRendered: onPreviewRendered, onPreviewResultChange: setPreviewResult })] })] }) }));
|
|
129
172
|
}
|
|
130
173
|
//# sourceMappingURL=email-template-editor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-template-editor.js","sourceRoot":"","sources":["../src/email-template-editor.tsx"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"email-template-editor.js","sourceRoot":"","sources":["../src/email-template-editor.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { EDITOR_CLASS_PREFIX, EmailTemplateEditor, type EmailTemplateEditorProps
|
|
|
3
3
|
export { EmailBuilderCanvas, type EmailBuilderCanvasProps } from './canvas/EmailBuilderCanvas';
|
|
4
4
|
export { loadTemplateSource, serializeTemplateSource, TemplateSourceError } from './serialization';
|
|
5
5
|
export type { SerializedTemplateSource, SaveResult, SendTestResult } from './save-send/types';
|
|
6
|
+
export { validateTemplate, hasValidationErrors, type ValidationIssue, type ValidationSeverity, type ValidateTemplateOptions, } from './validation/validate';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TemplateSourceError = exports.serializeTemplateSource = exports.loadTemplateSource = exports.EmailBuilderCanvas = exports.EmailTemplateEditor = exports.EDITOR_CLASS_PREFIX = void 0;
|
|
3
|
+
exports.hasValidationErrors = exports.validateTemplate = exports.TemplateSourceError = exports.serializeTemplateSource = exports.loadTemplateSource = exports.EmailBuilderCanvas = exports.EmailTemplateEditor = exports.EDITOR_CLASS_PREFIX = void 0;
|
|
4
4
|
var email_template_editor_1 = require("./email-template-editor");
|
|
5
5
|
Object.defineProperty(exports, "EDITOR_CLASS_PREFIX", { enumerable: true, get: function () { return email_template_editor_1.EDITOR_CLASS_PREFIX; } });
|
|
6
6
|
Object.defineProperty(exports, "EmailTemplateEditor", { enumerable: true, get: function () { return email_template_editor_1.EmailTemplateEditor; } });
|
|
@@ -10,4 +10,7 @@ var serialization_1 = require("./serialization");
|
|
|
10
10
|
Object.defineProperty(exports, "loadTemplateSource", { enumerable: true, get: function () { return serialization_1.loadTemplateSource; } });
|
|
11
11
|
Object.defineProperty(exports, "serializeTemplateSource", { enumerable: true, get: function () { return serialization_1.serializeTemplateSource; } });
|
|
12
12
|
Object.defineProperty(exports, "TemplateSourceError", { enumerable: true, get: function () { return serialization_1.TemplateSourceError; } });
|
|
13
|
+
var validate_1 = require("./validation/validate");
|
|
14
|
+
Object.defineProperty(exports, "validateTemplate", { enumerable: true, get: function () { return validate_1.validateTemplate; } });
|
|
15
|
+
Object.defineProperty(exports, "hasValidationErrors", { enumerable: true, get: function () { return validate_1.hasValidationErrors; } });
|
|
13
16
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAKA,iEAIiC;AAH/B,4HAAA,mBAAmB,OAAA;AACnB,4HAAA,mBAAmB,OAAA;AAGrB,kEAA+F;AAAtF,wHAAA,kBAAkB,OAAA;AAC3B,iDAAmG;AAA1F,mHAAA,kBAAkB,OAAA;AAAE,wHAAA,uBAAuB,OAAA;AAAE,oHAAA,mBAAmB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAKA,iEAIiC;AAH/B,4HAAA,mBAAmB,OAAA;AACnB,4HAAA,mBAAmB,OAAA;AAGrB,kEAA+F;AAAtF,wHAAA,kBAAkB,OAAA;AAC3B,iDAAmG;AAA1F,mHAAA,kBAAkB,OAAA;AAAE,wHAAA,uBAAuB,OAAA;AAAE,oHAAA,mBAAmB,OAAA;AAEzE,kDAM+B;AAL7B,4GAAA,gBAAgB,OAAA;AAChB,+GAAA,mBAAmB,OAAA"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { TemplatePreviewData, TemplateSourceMetadata } from '@singleton-sd/post-kit-types';
|
|
2
|
+
import { type ValidationIssue } from '../validation/validate';
|
|
2
3
|
export interface MetadataPanelProps {
|
|
3
4
|
metadata: TemplateSourceMetadata;
|
|
4
5
|
previewData: TemplatePreviewData;
|
|
5
6
|
onChange: (metadata: TemplateSourceMetadata) => void;
|
|
7
|
+
issues?: readonly ValidationIssue[];
|
|
6
8
|
}
|
|
7
|
-
export declare function MetadataPanel({ metadata, previewData, onChange, }: MetadataPanelProps): JSX.Element;
|
|
9
|
+
export declare function MetadataPanel({ metadata, previewData, onChange, issues, }: MetadataPanelProps): JSX.Element;
|
|
@@ -5,12 +5,24 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const react_1 = require("react");
|
|
6
6
|
const email_template_editor_1 = require("../email-template-editor");
|
|
7
7
|
const insertion_target_1 = require("../insertion-target");
|
|
8
|
+
const validate_1 = require("../validation/validate");
|
|
8
9
|
const insert_at_cursor_1 = require("./insert-at-cursor");
|
|
9
10
|
const subject_preview_1 = require("./subject-preview");
|
|
10
11
|
const template_key_1 = require("./template-key");
|
|
11
|
-
function MetadataPanel({ metadata, previewData, onChange, }) {
|
|
12
|
+
function MetadataPanel({ metadata, previewData, onChange, issues = [], }) {
|
|
12
13
|
const [keyError, setKeyError] = (0, react_1.useState)(null);
|
|
13
14
|
const subjectRef = (0, react_1.useRef)(null);
|
|
15
|
+
const keyIssues = issues.filter((i) => i.field === 'metadata' && (i.code === 'missing-key' || i.code === 'invalid-key'));
|
|
16
|
+
const nameIssues = issues.filter((i) => i.field === 'metadata' && i.code === 'missing-name');
|
|
17
|
+
const subjectIssues = issues.filter((i) => i.field === 'subject');
|
|
18
|
+
const keyDescribedBy = [
|
|
19
|
+
keyError ? `${email_template_editor_1.EDITOR_CLASS_PREFIX}meta-key-error` : null,
|
|
20
|
+
...keyIssues.map((i) => (0, validate_1.inlineIssueId)(i.code)),
|
|
21
|
+
]
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.join(' ');
|
|
24
|
+
const nameDescribedBy = nameIssues.map((i) => (0, validate_1.inlineIssueId)(i.code)).join(' ');
|
|
25
|
+
const subjectDescribedBy = subjectIssues.map((i) => (0, validate_1.inlineIssueId)(i.code, i.variable)).join(' ');
|
|
14
26
|
const patch = (0, react_1.useCallback)((partial) => {
|
|
15
27
|
onChange({ ...metadata, ...partial });
|
|
16
28
|
}, [metadata, onChange]);
|
|
@@ -43,7 +55,7 @@ function MetadataPanel({ metadata, previewData, onChange, }) {
|
|
|
43
55
|
const subjectFocus = (0, insertion_target_1.useRegisterInsertionTarget)('subject field', insertIntoSubject);
|
|
44
56
|
const subjectPreview = (0, subject_preview_1.previewSubject)(metadata.subject, previewData);
|
|
45
57
|
const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
|
|
46
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className: `${p}metadata`, "data-testid": `${p}metadata`, children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}metadata-heading`, children: "Template metadata" }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-key`, children: "Key" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-key`, className: `${p}metadata-input`, type: "text", value: metadata.key, onChange: handleKeyChange, autoComplete: "off", spellCheck: false, "data-testid": `${p}meta-key
|
|
58
|
+
return ((0, jsx_runtime_1.jsxs)("section", { className: `${p}metadata`, "data-testid": `${p}metadata`, children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}metadata-heading`, children: "Template metadata" }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-key`, children: "Key" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-key`, className: `${p}metadata-input`, type: "text", value: metadata.key, onChange: handleKeyChange, autoComplete: "off", spellCheck: false, "data-testid": `${p}meta-key`, "aria-invalid": keyError !== null || keyIssues.length > 0, "aria-describedby": keyDescribedBy || undefined }), keyError ? ((0, jsx_runtime_1.jsx)("p", { id: `${p}meta-key-error`, className: `${p}metadata-error`, "data-testid": `${p}meta-key-error`, role: "status", children: keyError })) : null, keyIssues.map((issue) => ((0, jsx_runtime_1.jsxs)("p", { id: (0, validate_1.inlineIssueId)(issue.code), className: `${p}metadata-error`, "data-testid": `${p}meta-key-validation`, role: "status", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "!" }), " ", issue.message] }, issue.code)))] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-name`, children: "Name" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-name`, className: `${p}metadata-input`, type: "text", value: metadata.name, onChange: (event) => patch({ name: event.target.value }), "data-testid": `${p}meta-name`, "aria-invalid": nameIssues.length > 0, "aria-describedby": nameDescribedBy || undefined }), nameIssues.map((issue) => ((0, jsx_runtime_1.jsxs)("p", { id: (0, validate_1.inlineIssueId)(issue.code), className: `${p}metadata-error`, "data-testid": `${p}meta-name-validation`, role: "status", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "!" }), " ", issue.message] }, issue.code)))] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-description`, children: "Description" }), (0, jsx_runtime_1.jsx)("textarea", { id: `${p}meta-description`, className: `${p}metadata-textarea`, value: metadata.description ?? '', onChange: (event) => {
|
|
47
59
|
const description = event.target.value;
|
|
48
60
|
if (description === '') {
|
|
49
61
|
const { description: _removed, ...rest } = metadata;
|
|
@@ -51,6 +63,6 @@ function MetadataPanel({ metadata, previewData, onChange, }) {
|
|
|
51
63
|
return;
|
|
52
64
|
}
|
|
53
65
|
patch({ description });
|
|
54
|
-
}, rows: 3, "data-testid": `${p}meta-description` })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-subject`, children: "Subject" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-subject`, ref: subjectRef, className: `${p}metadata-input`, type: "text", value: metadata.subject, onChange: (event) => patch({ subject: event.target.value }), onFocus: subjectFocus.onFocus, onBlur: subjectFocus.onBlur, "data-testid": `${p}meta-subject
|
|
66
|
+
}, rows: 3, "data-testid": `${p}meta-description` })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-subject`, children: "Subject" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-subject`, ref: subjectRef, className: `${p}metadata-input`, type: "text", value: metadata.subject, onChange: (event) => patch({ subject: event.target.value }), onFocus: subjectFocus.onFocus, onBlur: subjectFocus.onBlur, "data-testid": `${p}meta-subject`, "aria-invalid": subjectIssues.length > 0, "aria-describedby": subjectDescribedBy || undefined }), subjectIssues.map((issue) => ((0, jsx_runtime_1.jsxs)("p", { id: (0, validate_1.inlineIssueId)(issue.code, issue.variable), className: `${p}metadata-error`, "data-testid": `${p}meta-subject-validation`, role: "status", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "!" }), " ", issue.message] }, `${issue.code}:${issue.variable ?? ''}`))), (0, jsx_runtime_1.jsxs)("p", { className: `${p}metadata-subject-preview`, "data-testid": `${p}meta-subject-preview`, children: ["Preview: ", subjectPreview] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("span", { className: `${p}metadata-label`, children: "Schema version" }), (0, jsx_runtime_1.jsx)("span", { className: `${p}metadata-readonly`, "data-testid": `${p}meta-schema-version`, children: metadata.schemaVersion })] })] }));
|
|
55
67
|
}
|
|
56
68
|
//# sourceMappingURL=MetadataPanel.js.map
|