@cosmicdrift/kumiko-document-designer 0.363.0 → 0.365.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/dist/constants.d.ts +3 -0
- package/dist/constants.js +4 -0
- package/dist/i18n.js +208 -50
- package/dist/web/cells.js +4 -3
- package/dist/web/editor-header.d.ts +37 -0
- package/dist/web/editor-header.js +43 -0
- package/dist/web/editor-locale.d.ts +1 -0
- package/dist/web/editor-locale.js +15 -0
- package/dist/web/editor-shell.d.ts +40 -0
- package/dist/web/editor-shell.js +36 -0
- package/dist/web/i18n.js +35 -59
- package/dist/web/layout-editor-body.d.ts +2 -2
- package/dist/web/layout-editor-body.js +101 -52
- package/dist/web/layout-editor.js +14 -7
- package/dist/web/layout-html.d.ts +2 -0
- package/dist/web/layout-html.js +13 -1
- package/dist/web/layout-meta.d.ts +29 -0
- package/dist/web/layout-meta.js +32 -0
- package/dist/web/layout-rails.d.ts +15 -5
- package/dist/web/layout-rails.js +61 -68
- package/dist/web/layout-types.d.ts +4 -0
- package/dist/web/layout-users.d.ts +1 -0
- package/dist/web/layout-users.js +4 -1
- package/dist/web/mail-dialogs.d.ts +1 -6
- package/dist/web/mail-dialogs.js +2 -2
- package/dist/web/mail-draft.d.ts +8 -6
- package/dist/web/mail-draft.js +45 -11
- package/dist/web/mail-edit-center.d.ts +3 -1
- package/dist/web/mail-edit-center.js +5 -4
- package/dist/web/mail-editor-body.d.ts +0 -1
- package/dist/web/mail-editor-body.js +60 -45
- package/dist/web/mail-editor.js +9 -14
- package/dist/web/mail-footer.d.ts +3 -14
- package/dist/web/mail-footer.js +12 -13
- package/dist/web/mail-left-rail.d.ts +9 -4
- package/dist/web/mail-left-rail.js +18 -15
- package/dist/web/mail-missing-locale.d.ts +10 -0
- package/dist/web/mail-missing-locale.js +45 -0
- package/dist/web/mail-origin-bar.d.ts +3 -5
- package/dist/web/mail-origin-bar.js +26 -50
- package/dist/web/mail-preview-side.d.ts +12 -5
- package/dist/web/mail-preview-side.js +14 -5
- package/dist/web/mail-preview.d.ts +9 -5
- package/dist/web/mail-preview.js +24 -13
- package/dist/web/mail-right-rail.d.ts +8 -0
- package/dist/web/mail-right-rail.js +65 -14
- package/dist/web/mail-types.d.ts +10 -1
- package/dist/web/mail-upload-modal.js +12 -2
- package/dist/web/mail-versions.js +3 -3
- package/dist/web/origin-bar.d.ts +11 -2
- package/dist/web/origin-bar.js +46 -24
- package/dist/web/overrides-drawer.d.ts +3 -2
- package/dist/web/overrides-drawer.js +87 -11
- package/dist/web/overview-row.d.ts +2 -0
- package/dist/web/overview-row.js +2 -1
- package/dist/web/page-canvas.d.ts +2 -1
- package/dist/web/page-canvas.js +23 -13
- package/dist/web/pdf-footer.d.ts +13 -0
- package/dist/web/pdf-footer.js +34 -0
- package/dist/web/pdf-left-rail.d.ts +9 -0
- package/dist/web/pdf-left-rail.js +16 -0
- package/dist/web/pdf-meta.d.ts +3 -5
- package/dist/web/pdf-meta.js +10 -16
- package/dist/web/pdf-preview.js +21 -8
- package/dist/web/pdf-template-editor.js +91 -76
- package/dist/web/rail.d.ts +13 -1
- package/dist/web/rail.js +15 -7
- package/dist/web/relative-time.d.ts +1 -0
- package/dist/web/relative-time.js +18 -0
- package/dist/web/settings-panel.d.ts +11 -5
- package/dist/web/settings-panel.js +24 -13
- package/dist/web/status-label.d.ts +7 -0
- package/dist/web/status-label.js +8 -0
- package/dist/web/types.d.ts +23 -0
- package/dist/web/use-debounced-value.d.ts +1 -0
- package/dist/web/use-debounced-value.js +11 -0
- package/dist/web/use-insert-targets.d.ts +2 -2
- package/dist/web/use-insert-targets.js +4 -1
- package/dist/web/use-mail-persistence.d.ts +2 -2
- package/dist/web/use-mail-persistence.js +4 -4
- package/dist/web/use-own-address.d.ts +5 -0
- package/dist/web/use-own-address.js +14 -0
- package/dist/web/used-by-label.d.ts +1 -0
- package/dist/web/used-by-label.js +11 -0
- package/package.json +8 -8
- package/src/changes.json +48 -0
- package/dist/web/mail-header.d.ts +0 -23
- package/dist/web/mail-header.js +0 -16
- package/dist/web/placeholder-panel.d.ts +0 -21
- package/dist/web/placeholder-panel.js +0 -35
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import
|
|
2
|
+
import { type LayoutAppearance } from "./layout-meta.js";
|
|
3
|
+
import type { BrandingView, LayoutKind, LayoutValidation } from "./layout-types.js";
|
|
3
4
|
import type { OverviewRowView } from "./overview-row.js";
|
|
4
|
-
export declare function BrandingRail(
|
|
5
|
+
export declare function BrandingRail({ data, unavailable, }: {
|
|
6
|
+
readonly data: BrandingView | null;
|
|
7
|
+
readonly unavailable: boolean;
|
|
8
|
+
}): ReactNode;
|
|
5
9
|
export declare function PlaceholderRail({ validation, }: {
|
|
6
10
|
readonly validation: LayoutValidation | null;
|
|
7
11
|
}): ReactNode;
|
|
8
|
-
export declare function
|
|
12
|
+
export declare function RendererRail({ kind }: {
|
|
9
13
|
readonly kind: LayoutKind;
|
|
10
14
|
}): ReactNode;
|
|
15
|
+
export declare function AppearanceRail({ appearance, onChange, fromMjml, }: {
|
|
16
|
+
readonly appearance: LayoutAppearance;
|
|
17
|
+
readonly onChange: (next: LayoutAppearance) => void;
|
|
18
|
+
/** An mjml layout brings its own look, so the controls would have no effect. */
|
|
19
|
+
readonly fromMjml: boolean;
|
|
20
|
+
}): ReactNode;
|
|
11
21
|
export declare function UsedByRail({ layoutSlug, kind, rows, }: {
|
|
12
22
|
readonly layoutSlug: string;
|
|
13
23
|
readonly kind: LayoutKind;
|
|
14
24
|
readonly rows: readonly OverviewRowView[];
|
|
15
25
|
}): ReactNode;
|
|
16
|
-
export declare function
|
|
17
|
-
readonly mjmlSource: string;
|
|
26
|
+
export declare function MjmlSourceRail({ mjmlSource }: {
|
|
27
|
+
readonly mjmlSource: string | null;
|
|
18
28
|
}): ReactNode;
|
package/dist/web/layout-rails.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// @runtime client
|
|
3
|
-
import { useAppFeatures, useNav, usePrimitives,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { useAppFeatures, useNav, usePrimitives, useTranslation, } from "@cosmicdrift/kumiko-renderer";
|
|
4
|
+
import { BRANDING_SCREEN_QN } from "../constants.js";
|
|
5
|
+
import { isHexColor, LAYOUT_ACCENT_LINES, LAYOUT_BUTTON_STYLES, LAYOUT_FONTS, LAYOUT_WIDTHS, } from "./layout-meta.js";
|
|
6
6
|
import { layoutUsers } from "./layout-users.js";
|
|
7
|
+
import { PLACEHOLDER_SYMBOL, SYSTEM_PLACEHOLDER_TYPE, systemPlaceholderLabel, } from "./mail-draft.js";
|
|
8
|
+
import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
|
|
7
9
|
import { statusLabel } from "./status-label.js";
|
|
8
|
-
const NOT_SET_MARK = "–";
|
|
9
|
-
const HEX_COLOR = /^#[0-9a-f]{3,8}$/i;
|
|
10
10
|
const BRANDING_SCREEN_SHORT_ID = BRANDING_SCREEN_QN.slice(BRANDING_SCREEN_QN.lastIndexOf(":") + 1);
|
|
11
11
|
function logoName(logoUrl) {
|
|
12
12
|
const path = logoUrl.split("?")[0] ?? "";
|
|
@@ -15,93 +15,86 @@ function logoName(logoUrl) {
|
|
|
15
15
|
function hostOf(siteUrl) {
|
|
16
16
|
return siteUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx("span", { "data-testid": "document-designer-accent-swatch", className: "inline-block size-3.5 rounded-sm border", style: { backgroundColor: color } }), _jsx("span", { className: "font-mono text-xs", children: color })] }));
|
|
18
|
+
// The color is user data, so it goes into a style attribute instead of a class.
|
|
19
|
+
function ColorSwatch({ color }) {
|
|
20
|
+
return (_jsx("span", { "data-testid": "document-designer-accent-swatch", className: "inline-block size-3.5 shrink-0 rounded-sm border", style: { backgroundColor: color } }));
|
|
22
21
|
}
|
|
23
|
-
export function BrandingRail() {
|
|
22
|
+
export function BrandingRail({ data, unavailable, }) {
|
|
24
23
|
const t = useTranslation();
|
|
25
24
|
const nav = useNav();
|
|
26
25
|
const features = useAppFeatures();
|
|
27
|
-
const { Link
|
|
28
|
-
const { data, loading, error } = useQuery(BRANDING_QUERY_QN, {});
|
|
26
|
+
const { Link } = usePrimitives();
|
|
29
27
|
const hasBrandingScreen = features.some((feature) => feature.featureName === "managed-pages" &&
|
|
30
28
|
feature.screens.some((screen) => screen.id === BRANDING_SCREEN_SHORT_ID));
|
|
31
29
|
const notSet = t("documentDesigner.branding.notSet");
|
|
30
|
+
const value = (text) => text === undefined || text === "" ? notSet : text;
|
|
32
31
|
const editLink = hasBrandingScreen ? (_jsx(Link, { href: nav.hrefFor({ screenId: BRANDING_SCREEN_SHORT_ID }), onPress: () => nav.navigate({ screenId: BRANDING_SCREEN_SHORT_ID }), testId: "document-designer-branding-edit", children: t("documentDesigner.branding.edit") })) : undefined;
|
|
33
|
-
if (
|
|
34
|
-
return (_jsx(
|
|
32
|
+
if (unavailable) {
|
|
33
|
+
return (_jsx(RailSection, { title: t("documentDesigner.branding.title"), testId: "document-designer-branding", children: _jsx(RailHint, { children: t("documentDesigner.branding.unavailable") }) }));
|
|
35
34
|
}
|
|
36
|
-
const
|
|
35
|
+
const accent = data?.accentColor ?? "";
|
|
37
36
|
const logo = data?.logoUrl !== undefined && data.logoUrl !== ""
|
|
38
37
|
? logoName(data.logoUrl)
|
|
39
38
|
: data?.logoFile !== undefined && data.logoFile !== ""
|
|
40
39
|
? t("documentDesigner.branding.logoUploaded")
|
|
41
40
|
: notSet;
|
|
42
|
-
return (_jsxs(
|
|
43
|
-
{ label: t("documentDesigner.branding.productName"), value: value(data?.title) },
|
|
44
|
-
{
|
|
45
|
-
label: t("documentDesigner.branding.accentColor"),
|
|
46
|
-
value: data?.accentColor === undefined || data.accentColor === "" ? (notSet) : (_jsx(BrandingSwatch, { color: data.accentColor })),
|
|
47
|
-
},
|
|
48
|
-
{ label: t("documentDesigner.branding.logo"), value: logo },
|
|
49
|
-
{
|
|
50
|
-
label: t("documentDesigner.branding.website"),
|
|
51
|
-
value: data?.siteUrl === undefined || data.siteUrl === "" ? notSet : hostOf(data.siteUrl),
|
|
52
|
-
},
|
|
53
|
-
] }), _jsx(Text, { variant: "muted", children: t("documentDesigner.branding.hint") })] }));
|
|
41
|
+
return (_jsxs(RailSection, { title: t("documentDesigner.branding.title"), action: editLink, testId: "document-designer-branding", children: [_jsx(RailRow, { label: t("documentDesigner.branding.productName"), trailing: value(data?.title) }), _jsx(RailRow, { label: t("documentDesigner.branding.accentColor"), trailing: isHexColor(accent) ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(ColorSwatch, { color: accent }), _jsx("code", { children: accent })] })) : (value(accent)) }), _jsx(RailRow, { label: t("documentDesigner.branding.logo"), trailing: logo }), _jsx(RailRow, { label: t("documentDesigner.branding.website"), trailing: data?.siteUrl === undefined || data.siteUrl === "" ? notSet : hostOf(data.siteUrl) }), _jsx(RailHint, { children: t("documentDesigner.branding.hint") })] }));
|
|
54
42
|
}
|
|
55
43
|
export function PlaceholderRail({ validation, }) {
|
|
56
44
|
const t = useTranslation();
|
|
57
45
|
const used = new Set(validation?.used ?? []);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return (_jsx(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
value: "✓",
|
|
67
|
-
},
|
|
68
|
-
...(validation?.systemPlaceholders ?? []).map((name) => ({
|
|
69
|
-
id: `placeholder-${name}`,
|
|
70
|
-
label: placeholderLabel(name),
|
|
71
|
-
value: used.has(name) ? "✓" : NOT_SET_MARK,
|
|
72
|
-
})),
|
|
73
|
-
] }) }));
|
|
46
|
+
return (_jsxs(RailSection, { title: t("documentDesigner.layoutPlaceholders.title"), testId: "document-designer-placeholders", children: [_jsx(RailRow, { symbol: "\u229E", label: t("documentDesigner.placeholders.content"), required: true, trailing: "\u2713", trailingTone: "ok" }), (validation?.systemPlaceholders ?? []).map((name) => (_jsx(RailRow, { testId: `document-designer-placeholder-${name}`, symbol: PLACEHOLDER_SYMBOL[SYSTEM_PLACEHOLDER_TYPE[name] ?? "text"], label: systemPlaceholderLabel(t, name), trailing: used.has(name) ? "✓" : "–", trailingTone: used.has(name) ? "ok" : "muted" }, name)))] }));
|
|
47
|
+
}
|
|
48
|
+
// Both renderers always run for their kind; the rows say which one produces the output.
|
|
49
|
+
export function RendererRail({ kind }) {
|
|
50
|
+
const t = useTranslation();
|
|
51
|
+
return (_jsx(RailSection, { title: t("documentDesigner.renderer.title"), testId: "document-designer-renderer", children: _jsx(RailRow, { label: t(kind === "mail-layout"
|
|
52
|
+
? "documentDesigner.renderer.mjml"
|
|
53
|
+
: "documentDesigner.renderer.chromium"), trailing: t("documentDesigner.renderer.active"), trailingTone: "ok" }) }));
|
|
74
54
|
}
|
|
75
|
-
export function AppearanceRail({
|
|
55
|
+
export function AppearanceRail({ appearance, onChange, fromMjml, }) {
|
|
76
56
|
const t = useTranslation();
|
|
77
|
-
const {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
57
|
+
const { Field, Input } = usePrimitives();
|
|
58
|
+
const patch = (changes) => onChange({ ...appearance, ...changes });
|
|
59
|
+
const backgroundInvalid = !isHexColor(appearance.outerBackground);
|
|
60
|
+
return (_jsxs(RailSection, { title: t("documentDesigner.appearance.title"), testId: "document-designer-appearance", children: [fromMjml ? (_jsx(RailHint, { children: t("documentDesigner.appearance.fromMjml") })) : (_jsxs(RailField, { children: [_jsx(Field, { id: "dd-layout-width", label: t("documentDesigner.appearance.width"), children: _jsx(Input, { kind: "select", id: "dd-layout-width", name: "width", value: String(appearance.width), onChange: (value) => {
|
|
61
|
+
const width = LAYOUT_WIDTHS.find((candidate) => String(candidate) === value);
|
|
62
|
+
if (width !== undefined)
|
|
63
|
+
patch({ width });
|
|
64
|
+
}, display: "dropdown", options: LAYOUT_WIDTHS.map((width) => ({
|
|
65
|
+
value: String(width),
|
|
66
|
+
label: `${width} px`,
|
|
67
|
+
})) }) }), _jsx(Field, { id: "dd-layout-font", label: t("documentDesigner.appearance.font"), children: _jsx(Input, { kind: "select", id: "dd-layout-font", name: "font", value: appearance.font, onChange: (value) => {
|
|
68
|
+
const font = LAYOUT_FONTS.find((candidate) => candidate === value);
|
|
69
|
+
if (font !== undefined)
|
|
70
|
+
patch({ font });
|
|
71
|
+
}, display: "dropdown", options: LAYOUT_FONTS.map((font) => ({
|
|
72
|
+
value: font,
|
|
73
|
+
label: t(`documentDesigner.appearance.font.${font}`),
|
|
74
|
+
})) }) }), _jsxs(Field, { id: "dd-layout-background", label: t("documentDesigner.appearance.background"), children: [_jsxs("div", { className: "flex items-center gap-2", children: [!backgroundInvalid && _jsx(ColorSwatch, { color: appearance.outerBackground }), _jsx(Input, { kind: "text", id: "dd-layout-background", name: "outerBackground", value: appearance.outerBackground, onChange: (outerBackground) => patch({ outerBackground }), hasError: backgroundInvalid })] }), backgroundInvalid && (_jsx("p", { role: "alert", className: "text-xs text-status-bad", children: t("documentDesigner.appearance.backgroundInvalid") }))] }), _jsx(Field, { id: "dd-layout-accent", label: t("documentDesigner.appearance.accentLine"), children: _jsx(Input, { kind: "select", id: "dd-layout-accent", name: "accentLine", value: appearance.accentLine, onChange: (value) => {
|
|
75
|
+
const accentLine = LAYOUT_ACCENT_LINES.find((candidate) => candidate === value);
|
|
76
|
+
if (accentLine !== undefined)
|
|
77
|
+
patch({ accentLine });
|
|
78
|
+
}, display: "dropdown", options: LAYOUT_ACCENT_LINES.map((accentLine) => ({
|
|
79
|
+
value: accentLine,
|
|
80
|
+
label: t(`documentDesigner.appearance.accentLine.${accentLine}`),
|
|
81
|
+
})) }) }), _jsx(Field, { id: "dd-layout-button", label: t("documentDesigner.appearance.buttonStyle"), children: _jsx(Input, { kind: "select", id: "dd-layout-button", name: "buttonStyle", value: appearance.buttonStyle, onChange: (value) => {
|
|
82
|
+
const buttonStyle = LAYOUT_BUTTON_STYLES.find((candidate) => candidate === value);
|
|
83
|
+
if (buttonStyle !== undefined)
|
|
84
|
+
patch({ buttonStyle });
|
|
85
|
+
}, display: "dropdown", options: LAYOUT_BUTTON_STYLES.map((buttonStyle) => ({
|
|
86
|
+
value: buttonStyle,
|
|
87
|
+
label: t(`documentDesigner.appearance.buttonStyle.${buttonStyle}`),
|
|
88
|
+
})) }) })] })), !fromMjml && _jsx(RailHint, { children: t("documentDesigner.appearance.fontHint") })] }));
|
|
92
89
|
}
|
|
93
90
|
export function UsedByRail({ layoutSlug, kind, rows, }) {
|
|
94
91
|
const t = useTranslation();
|
|
95
92
|
const users = layoutUsers(rows, kind, layoutSlug);
|
|
96
|
-
return (_jsx(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
value: row.origin === "system"
|
|
100
|
-
? t("documentDesigner.source.system")
|
|
101
|
-
: statusLabel(t, row.status, row.version),
|
|
102
|
-
})) })) }));
|
|
93
|
+
return (_jsx(RailSection, { title: t("documentDesigner.usedBy.title"), testId: "document-designer-used-by", children: users.length === 0 ? (_jsx(RailHint, { children: t("documentDesigner.usedBy.none") })) : (users.map((row) => (_jsx(RailRow, { testId: `used-by-${row.id}`, label: row.title, trailing: row.origin === "system"
|
|
94
|
+
? t("documentDesigner.source.system")
|
|
95
|
+
: statusLabel(t, row.status, row.version) }, row.id)))) }));
|
|
103
96
|
}
|
|
104
|
-
export function
|
|
97
|
+
export function MjmlSourceRail({ mjmlSource }) {
|
|
105
98
|
const t = useTranslation();
|
|
106
|
-
return (
|
|
99
|
+
return (_jsxs(RailSection, { title: t("documentDesigner.mjml.title"), testId: "document-designer-mjml-preview", children: [mjmlSource !== null && (_jsx("pre", { className: "mx-3.5 mt-1 mb-2 max-h-56 overflow-auto whitespace-pre rounded-sm border bg-muted p-2.5 font-mono text-xs text-muted-foreground", children: mjmlSource })), _jsx(RailHint, { children: t("documentDesigner.mjml.hint") })] }));
|
|
107
100
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LayoutMeta } from "./layout-meta.js";
|
|
2
|
+
import type { ServedVersionView } from "./types.js";
|
|
1
3
|
export type LayoutKind = "mail-layout" | "pdf-layout";
|
|
2
4
|
export type ValidationIssue = {
|
|
3
5
|
readonly path: string;
|
|
@@ -45,6 +47,7 @@ export type LayoutSourceRow = {
|
|
|
45
47
|
readonly version: number;
|
|
46
48
|
readonly status: string;
|
|
47
49
|
readonly locale: string;
|
|
50
|
+
readonly active?: ServedVersionView | null;
|
|
48
51
|
};
|
|
49
52
|
export type LayoutSourceView = {
|
|
50
53
|
readonly tenant: LayoutSourceRow | null;
|
|
@@ -58,5 +61,6 @@ export type LayoutSourceView = {
|
|
|
58
61
|
};
|
|
59
62
|
readonly effectiveFormat: "html" | "mjml" | null;
|
|
60
63
|
readonly mjmlSource: string | null;
|
|
64
|
+
readonly meta: LayoutMeta;
|
|
61
65
|
readonly locale: string;
|
|
62
66
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { LayoutKind } from "./layout-types.js";
|
|
2
2
|
import type { OverviewRowView } from "./overview-row.js";
|
|
3
|
+
export declare function layoutUserKind(kind: LayoutKind): "mail" | "document-pdf";
|
|
3
4
|
export declare function layoutUsers(rows: readonly OverviewRowView[], kind: LayoutKind, layoutSlug: string): readonly OverviewRowView[];
|
package/dist/web/layout-users.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// @runtime client
|
|
2
2
|
import { DEFAULT_LAYOUT_SLUG } from "../constants.js";
|
|
3
|
+
export function layoutUserKind(kind) {
|
|
4
|
+
return kind === "mail-layout" ? "mail" : "document-pdf";
|
|
5
|
+
}
|
|
3
6
|
// Templates whose content is wrapped by this layout. A template without an explicit
|
|
4
7
|
// layoutSlug uses the default layout.
|
|
5
8
|
export function layoutUsers(rows, kind, layoutSlug) {
|
|
6
|
-
const documentKind = kind
|
|
9
|
+
const documentKind = layoutUserKind(kind);
|
|
7
10
|
return rows.filter((row) => row.kind === documentKind && (row.layoutSlug ?? DEFAULT_LAYOUT_SLUG) === layoutSlug);
|
|
8
11
|
}
|
|
@@ -2,15 +2,11 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
export type LeaveTarget = {
|
|
3
3
|
readonly kind: "locale";
|
|
4
4
|
readonly locale: string;
|
|
5
|
-
} | {
|
|
6
|
-
readonly kind: "back";
|
|
7
5
|
};
|
|
8
6
|
export type ConfirmDialog = {
|
|
9
7
|
readonly kind: "none";
|
|
10
8
|
} | {
|
|
11
9
|
readonly kind: "discard";
|
|
12
|
-
} | {
|
|
13
|
-
readonly kind: "activeSave";
|
|
14
10
|
} | {
|
|
15
11
|
readonly kind: "remove";
|
|
16
12
|
readonly key: string;
|
|
@@ -18,11 +14,10 @@ export type ConfirmDialog = {
|
|
|
18
14
|
readonly kind: "leave";
|
|
19
15
|
readonly target: LeaveTarget;
|
|
20
16
|
};
|
|
21
|
-
export declare function MailConfirmDialogs({ dialog, onClose, onDiscard,
|
|
17
|
+
export declare function MailConfirmDialogs({ dialog, onClose, onDiscard, onRemove, onLeaveSave, onLeaveDiscard, }: {
|
|
22
18
|
readonly dialog: ConfirmDialog;
|
|
23
19
|
readonly onClose: () => void;
|
|
24
20
|
readonly onDiscard: () => void;
|
|
25
|
-
readonly onSaveDraft: () => Promise<void>;
|
|
26
21
|
readonly onRemove: (key: string) => Promise<void>;
|
|
27
22
|
readonly onLeaveSave: (target: LeaveTarget) => Promise<void>;
|
|
28
23
|
readonly onLeaveDiscard: (target: LeaveTarget) => void;
|
package/dist/web/mail-dialogs.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// @runtime client
|
|
3
3
|
import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
4
|
-
export function MailConfirmDialogs({ dialog, onClose, onDiscard,
|
|
4
|
+
export function MailConfirmDialogs({ dialog, onClose, onDiscard, onRemove, onLeaveSave, onLeaveDiscard, }) {
|
|
5
5
|
const t = useTranslation();
|
|
6
6
|
const { Button, Dialog } = usePrimitives();
|
|
7
7
|
const closeUnless = (open) => {
|
|
8
8
|
if (!open)
|
|
9
9
|
onClose();
|
|
10
10
|
};
|
|
11
|
-
return (_jsxs(_Fragment, { children: [_jsx(Dialog, { open: dialog.kind === "discard", onOpenChange: closeUnless, title: t("documentDesigner.mail.discard.title"), description: t("documentDesigner.mail.discard.description"), confirmLabel: t("documentDesigner.mail.discard.confirm"), variant: "danger", testId: "mail-discard-dialog", onConfirm: onDiscard }), _jsx(Dialog, { open: dialog.kind === "
|
|
11
|
+
return (_jsxs(_Fragment, { children: [_jsx(Dialog, { open: dialog.kind === "discard", onOpenChange: closeUnless, title: t("documentDesigner.mail.discard.title"), description: t("documentDesigner.mail.discard.description"), confirmLabel: t("documentDesigner.mail.discard.confirm"), variant: "danger", testId: "mail-discard-dialog", onConfirm: onDiscard }), _jsx(Dialog, { open: dialog.kind === "remove", onOpenChange: closeUnless, title: t("documentDesigner.mail.remove.title"), description: t("documentDesigner.mail.remove.description"), confirmLabel: t("documentDesigner.mail.remove.confirm"), variant: "danger", testId: "mail-remove-dialog", onConfirm: () => (dialog.kind === "remove" ? onRemove(dialog.key) : Promise.resolve()) }), _jsx(Dialog, { open: dialog.kind === "leave", onOpenChange: closeUnless, title: t("documentDesigner.mail.leave.title"), description: t("documentDesigner.mail.leave.description"), confirmLabel: t("documentDesigner.mail.leave.save"), testId: "mail-leave-dialog", onConfirm: () => (dialog.kind === "leave" ? onLeaveSave(dialog.target) : Promise.resolve()), children: _jsx(Button, { variant: "danger-ghost", onClick: () => {
|
|
12
12
|
if (dialog.kind === "leave")
|
|
13
13
|
onLeaveDiscard(dialog.target);
|
|
14
14
|
}, testId: "mail-leave-discard", children: t("documentDesigner.mail.leave.discard") }) })] }));
|
package/dist/web/mail-draft.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { LinkedResourceView, MailDraft, MailVariable, PlaceholderType, TemplateRowView } from "./mail-types.js";
|
|
2
2
|
export declare const EMPTY_DRAFT: MailDraft;
|
|
3
3
|
export declare const PLACEHOLDER_SYMBOL: Readonly<Record<PlaceholderType, string>>;
|
|
4
|
+
export declare const LAYOUT_OWNED_PLACEHOLDERS: ReadonlySet<string>;
|
|
5
|
+
export declare const SYSTEM_PLACEHOLDER_TYPE: Readonly<Record<string, PlaceholderType>>;
|
|
6
|
+
type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
|
|
7
|
+
export declare function systemPlaceholderLabel(t: Translate, name: string): string;
|
|
8
|
+
export declare function systemLabelMap(t: Translate, names: readonly string[]): Readonly<Record<string, string>>;
|
|
4
9
|
export declare function parseJsonRecord(value: unknown): Readonly<Record<string, unknown>>;
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
readonly sender: unknown;
|
|
8
|
-
};
|
|
9
|
-
export declare function loadedMailOf(row: TemplateRowView | null): LoadedMail;
|
|
10
|
-
export declare function buildMeta(draft: MailDraft, sender: unknown): Record<string, unknown>;
|
|
10
|
+
export declare function loadedMailOf(row: TemplateRowView | null): MailDraft;
|
|
11
|
+
export declare function buildMeta(draft: MailDraft): Record<string, unknown>;
|
|
11
12
|
export declare function isDraftDirty(current: MailDraft, saved: MailDraft): boolean;
|
|
12
13
|
export declare function parseLinkedResources(value: unknown): readonly LinkedResourceView[];
|
|
13
14
|
export declare function uniqueResourceKey(source: string, taken: readonly string[]): string;
|
|
@@ -15,3 +16,4 @@ export declare function lastSlugSegment(slug: string): string;
|
|
|
15
16
|
export declare function formatKilobytes(bytes: number): string;
|
|
16
17
|
export declare function variableLabelMap(variables: Readonly<Record<string, MailVariable>>): Record<string, string>;
|
|
17
18
|
export declare function displayValue(t: (key: string, params?: Readonly<Record<string, unknown>>) => string, value: unknown): string;
|
|
19
|
+
export {};
|
package/dist/web/mail-draft.js
CHANGED
|
@@ -3,6 +3,11 @@ export const EMPTY_DRAFT = {
|
|
|
3
3
|
previewText: "",
|
|
4
4
|
layoutSlug: "",
|
|
5
5
|
category: "",
|
|
6
|
+
senderName: "",
|
|
7
|
+
replyTo: "",
|
|
8
|
+
headerChip: "",
|
|
9
|
+
tone: "",
|
|
10
|
+
footerNote: "",
|
|
6
11
|
body: "",
|
|
7
12
|
};
|
|
8
13
|
export const PLACEHOLDER_SYMBOL = {
|
|
@@ -12,6 +17,24 @@ export const PLACEHOLDER_SYMBOL = {
|
|
|
12
17
|
link: "↗",
|
|
13
18
|
list: "≡",
|
|
14
19
|
};
|
|
20
|
+
// Filled in by the layout around the content, so a template that never uses them is fine.
|
|
21
|
+
export const LAYOUT_OWNED_PLACEHOLDERS = new Set([
|
|
22
|
+
"imprint",
|
|
23
|
+
"unsubscribeUrl",
|
|
24
|
+
]);
|
|
25
|
+
export const SYSTEM_PLACEHOLDER_TYPE = {
|
|
26
|
+
imprint: "link",
|
|
27
|
+
unsubscribeUrl: "link",
|
|
28
|
+
};
|
|
29
|
+
export function systemPlaceholderLabel(t, name) {
|
|
30
|
+
const key = `documentDesigner.placeholder.${name}`;
|
|
31
|
+
const translated = t(key);
|
|
32
|
+
return translated === key ? name : translated;
|
|
33
|
+
}
|
|
34
|
+
// Chips in the editors show these instead of the raw system names.
|
|
35
|
+
export function systemLabelMap(t, names) {
|
|
36
|
+
return Object.fromEntries(names.map((name) => [name, systemPlaceholderLabel(t, name)]));
|
|
37
|
+
}
|
|
15
38
|
function isRecord(value) {
|
|
16
39
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
40
|
}
|
|
@@ -37,26 +60,32 @@ function textOf(value) {
|
|
|
37
60
|
}
|
|
38
61
|
export function loadedMailOf(row) {
|
|
39
62
|
if (row === null)
|
|
40
|
-
return
|
|
63
|
+
return EMPTY_DRAFT;
|
|
41
64
|
const meta = parseJsonRecord(row.meta);
|
|
42
65
|
return {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
66
|
+
subject: textOf(meta["subject"]),
|
|
67
|
+
previewText: textOf(meta["previewText"]),
|
|
68
|
+
layoutSlug: textOf(meta["layoutSlug"]),
|
|
69
|
+
category: categoryOf(meta["category"]),
|
|
70
|
+
senderName: textOf(meta["senderName"]),
|
|
71
|
+
replyTo: textOf(meta["replyTo"]),
|
|
72
|
+
headerChip: textOf(meta["headerChip"]),
|
|
73
|
+
tone: textOf(meta["tone"]),
|
|
74
|
+
footerNote: textOf(meta["footerNote"]),
|
|
75
|
+
body: row.content,
|
|
51
76
|
};
|
|
52
77
|
}
|
|
53
|
-
export function buildMeta(draft
|
|
78
|
+
export function buildMeta(draft) {
|
|
54
79
|
return {
|
|
55
80
|
subject: draft.subject,
|
|
56
81
|
...(draft.previewText !== "" && { previewText: draft.previewText }),
|
|
57
82
|
...(draft.layoutSlug !== "" && { layoutSlug: draft.layoutSlug }),
|
|
58
83
|
...(draft.category !== "" && { category: draft.category }),
|
|
59
|
-
...(
|
|
84
|
+
...(draft.senderName !== "" && { senderName: draft.senderName }),
|
|
85
|
+
...(draft.replyTo !== "" && { replyTo: draft.replyTo }),
|
|
86
|
+
...(draft.headerChip !== "" && { headerChip: draft.headerChip }),
|
|
87
|
+
...(draft.tone !== "" && { tone: draft.tone }),
|
|
88
|
+
...(draft.footerNote !== "" && { footerNote: draft.footerNote }),
|
|
60
89
|
};
|
|
61
90
|
}
|
|
62
91
|
export function isDraftDirty(current, saved) {
|
|
@@ -64,6 +93,11 @@ export function isDraftDirty(current, saved) {
|
|
|
64
93
|
current.previewText !== saved.previewText ||
|
|
65
94
|
current.layoutSlug !== saved.layoutSlug ||
|
|
66
95
|
current.category !== saved.category ||
|
|
96
|
+
current.senderName !== saved.senderName ||
|
|
97
|
+
current.replyTo !== saved.replyTo ||
|
|
98
|
+
current.headerChip !== saved.headerChip ||
|
|
99
|
+
current.tone !== saved.tone ||
|
|
100
|
+
current.footerNote !== saved.footerNote ||
|
|
67
101
|
current.body !== saved.body);
|
|
68
102
|
}
|
|
69
103
|
export function parseLinkedResources(value) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DocumentResourcePreview } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
|
+
import type { MailMetaField } from "./mail-footer.js";
|
|
3
4
|
import type { MailDraft } from "./mail-types.js";
|
|
4
5
|
import type { InsertTargets } from "./use-insert-targets.js";
|
|
5
6
|
export type EditorVocabulary = {
|
|
@@ -7,11 +8,12 @@ export type EditorVocabulary = {
|
|
|
7
8
|
readonly labels: Readonly<Record<string, string>>;
|
|
8
9
|
readonly system: readonly string[];
|
|
9
10
|
};
|
|
10
|
-
export declare function MailEditCenter({ draft, vocabulary, resourcePreviews, insert, readOnly, onChange, }: {
|
|
11
|
+
export declare function MailEditCenter({ draft, vocabulary, resourcePreviews, insert, readOnly, fieldErrors, onChange, }: {
|
|
11
12
|
readonly draft: MailDraft;
|
|
12
13
|
readonly vocabulary: EditorVocabulary;
|
|
13
14
|
readonly resourcePreviews: Readonly<Record<string, DocumentResourcePreview>>;
|
|
14
15
|
readonly insert: InsertTargets;
|
|
15
16
|
readonly readOnly: boolean;
|
|
17
|
+
readonly fieldErrors: Partial<Record<MailMetaField, string>>;
|
|
16
18
|
readonly onChange: (patch: Partial<MailDraft>) => void;
|
|
17
19
|
}): ReactNode;
|
|
@@ -2,10 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
// @runtime client
|
|
3
3
|
import { DocumentContentEditor, MAIL_DOCUMENT_BLOCKS, PlaceholderLineEditor, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
4
4
|
import { useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const renderMailPaper = (editable) => (_jsx("div", { className: "mx-auto w-full max-w-[600px] bg-card px-10 py-7 shadow-md", children: editable }));
|
|
6
|
+
function MetaLine({ label, testId, error, onFocusCapture, children, }) {
|
|
7
|
+
return (_jsxs("div", { className: "grid min-h-10 grid-cols-[84px_minmax(0,1fr)] items-center border-b bg-background px-4", "data-testid": testId, onFocusCapture: onFocusCapture, children: [_jsx("span", { className: "text-xs text-muted-foreground", children: label }), _jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx("div", { className: "min-w-0 flex-1", children: children }), error !== undefined && (_jsx("span", { className: "shrink-0 text-xs text-status-bad", role: "alert", children: error }))] })] }));
|
|
7
8
|
}
|
|
8
|
-
export function MailEditCenter({ draft, vocabulary, resourcePreviews, insert, readOnly, onChange, }) {
|
|
9
|
+
export function MailEditCenter({ draft, vocabulary, resourcePreviews, insert, readOnly, fieldErrors, onChange, }) {
|
|
9
10
|
const t = useTranslation();
|
|
10
|
-
return (_jsxs("div", { className: "flex min-w-0 flex-
|
|
11
|
+
return (_jsxs("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", "data-testid": "mail-editor-center", children: [_jsx(MetaLine, { label: t("documentDesigner.mail.meta.subject"), testId: "mail-meta-subject", error: fieldErrors.subject, onFocusCapture: insert.markFocused("subject"), children: _jsx(PlaceholderLineEditor, { id: "mail-subject", value: draft.subject, onChange: (subject) => onChange({ subject }), variableLabels: vocabulary.labels, systemVariables: vocabulary.system, readOnly: readOnly, flat: true, onEditorReady: insert.register("subject"), "aria-label": t("documentDesigner.mail.meta.subject") }) }), _jsx(MetaLine, { label: t("documentDesigner.mail.meta.previewText"), testId: "mail-meta-preview-text", error: fieldErrors.previewText, onFocusCapture: insert.markFocused("previewText"), children: _jsx(PlaceholderLineEditor, { id: "mail-preview-text", value: draft.previewText, onChange: (previewText) => onChange({ previewText }), variableLabels: vocabulary.labels, systemVariables: vocabulary.system, readOnly: readOnly, flat: true, placeholder: t("documentDesigner.mail.meta.previewTextPlaceholder"), onEditorReady: insert.register("previewText"), "aria-label": t("documentDesigner.mail.meta.previewText") }) }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col", onFocusCapture: insert.markFocused("body"), "data-testid": "mail-content", children: _jsx(DocumentContentEditor, { id: "mail-body", value: draft.body, onChange: (body) => onChange({ body }), variables: vocabulary.names, readOnly: readOnly, blocks: MAIL_DOCUMENT_BLOCKS, variableLabels: vocabulary.labels, systemVariables: vocabulary.system, resourcePreviews: resourcePreviews, chrome: "canvas", renderPaper: renderMailPaper, onEditorReady: insert.register("body") }) })] }));
|
|
11
12
|
}
|
|
@@ -12,7 +12,6 @@ export type MailEditorBodyProps = {
|
|
|
12
12
|
readonly onLocaleChange: (locale: string) => void;
|
|
13
13
|
readonly onReload: () => Promise<void>;
|
|
14
14
|
readonly onRefreshRow: () => Promise<void>;
|
|
15
|
-
readonly onBack: () => void;
|
|
16
15
|
};
|
|
17
16
|
export declare function placeholderNames(validation: MailValidation | null): string[];
|
|
18
17
|
export declare function MailEditorBody(props: MailEditorBodyProps): ReactNode;
|