@cosmicdrift/kumiko-document-designer 0.388.0 → 0.390.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/feature.js +11 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list-definition.d.ts +3 -1
- package/dist/list-definition.js +10 -3
- package/dist/web/client-plugin.js +6 -2
- package/dist/web/constants.d.ts +1 -0
- package/dist/web/constants.js +1 -0
- package/dist/web/i18n.js +1 -0
- package/dist/web/layout-editor-body.d.ts +3 -14
- package/dist/web/layout-editor-body.js +25 -172
- package/dist/web/layout-editor-center.d.ts +7 -0
- package/dist/web/layout-editor-center.js +23 -0
- package/dist/web/layout-editor-logic.d.ts +97 -0
- package/dist/web/layout-editor-logic.js +143 -0
- package/dist/web/layout-editor-paper.d.ts +4 -0
- package/dist/web/layout-editor-paper.js +24 -0
- package/dist/web/layout-editor.d.ts +3 -1
- package/dist/web/layout-editor.js +7 -3
- package/dist/web/mail-editor-body.d.ts +4 -16
- package/dist/web/mail-editor-body.js +27 -204
- package/dist/web/mail-editor-logic.d.ts +70 -0
- package/dist/web/mail-editor-logic.js +81 -0
- package/dist/web/mail-editor-model.d.ts +105 -0
- package/dist/web/mail-editor-model.js +114 -0
- package/dist/web/mail-editor-render.d.ts +11 -0
- package/dist/web/mail-editor-render.js +51 -0
- package/dist/web/mail-preview-render.d.ts +5 -0
- package/dist/web/mail-preview-render.js +22 -0
- package/dist/web/pdf-editor-actions.d.ts +27 -0
- package/dist/web/pdf-editor-actions.js +146 -0
- package/dist/web/pdf-editor-chrome.d.ts +6 -0
- package/dist/web/pdf-editor-chrome.js +60 -0
- package/dist/web/pdf-editor-context.d.ts +22 -0
- package/dist/web/pdf-editor-context.js +1 -0
- package/dist/web/pdf-editor-draft.d.ts +23 -0
- package/dist/web/pdf-editor-draft.js +33 -0
- package/dist/web/pdf-editor-panes.d.ts +6 -0
- package/dist/web/pdf-editor-panes.js +40 -0
- package/dist/web/pdf-editor-view.d.ts +52 -0
- package/dist/web/pdf-editor-view.js +89 -0
- package/dist/web/pdf-template-editor.d.ts +1 -1
- package/dist/web/pdf-template-editor.js +40 -342
- package/dist/web/use-layout-editor-state.d.ts +62 -0
- package/dist/web/use-layout-editor-state.js +155 -0
- package/dist/web/use-mail-editor-parts.d.ts +42 -0
- package/dist/web/use-mail-editor-parts.js +74 -0
- package/dist/web/use-mail-editor-state.d.ts +89 -0
- package/dist/web/use-mail-editor-state.js +55 -0
- package/dist/web/use-pdf-editor-state.d.ts +77 -0
- package/dist/web/use-pdf-editor-state.js +161 -0
- package/package.json +6 -6
- package/src/changes.json +30 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type EditorTab, type Region, type Translate } from "./layout-editor-logic.ts";
|
|
2
|
+
import type { BrandingView, LayoutKind, LayoutSourceView, LayoutValidation, TenantOverrideCounts } from "./layout-types.ts";
|
|
3
|
+
import type { OverviewRowView } from "./overview-row.ts";
|
|
4
|
+
export type LayoutEditorBodyProps = {
|
|
5
|
+
readonly kind: LayoutKind;
|
|
6
|
+
readonly slug: string;
|
|
7
|
+
readonly isSystemAdmin: boolean;
|
|
8
|
+
readonly source: LayoutSourceView;
|
|
9
|
+
readonly overviewRows: readonly OverviewRowView[];
|
|
10
|
+
readonly usedByCount: number;
|
|
11
|
+
readonly overrides: TenantOverrideCounts | null;
|
|
12
|
+
readonly onKindChange: (kind: LayoutKind) => void;
|
|
13
|
+
readonly onReload: () => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export declare function useLayoutEditorState(props: LayoutEditorBodyProps, t: Translate): {
|
|
16
|
+
isMail: boolean;
|
|
17
|
+
showMjmlTab: boolean;
|
|
18
|
+
tab: EditorTab;
|
|
19
|
+
setTab: import("react").Dispatch<import("react").SetStateAction<EditorTab>>;
|
|
20
|
+
header: string;
|
|
21
|
+
setHeader: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
22
|
+
signature: string;
|
|
23
|
+
setSignature: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
24
|
+
footer: string;
|
|
25
|
+
setFooter: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
26
|
+
mjml: string;
|
|
27
|
+
setMjml: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
28
|
+
appearance: import("./layout-meta.ts").LayoutAppearance;
|
|
29
|
+
setAppearance: import("react").Dispatch<import("react").SetStateAction<import("./layout-meta.ts").LayoutAppearance>>;
|
|
30
|
+
activeRegion: Region;
|
|
31
|
+
setActiveRegion: import("react").Dispatch<import("react").SetStateAction<Region>>;
|
|
32
|
+
toolbarHost: HTMLElement | null;
|
|
33
|
+
setToolbarHost: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
|
|
34
|
+
pendingKind: LayoutKind | null;
|
|
35
|
+
setPendingKind: import("react").Dispatch<import("react").SetStateAction<LayoutKind | null>>;
|
|
36
|
+
saveError: string | null;
|
|
37
|
+
setSaveError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
38
|
+
branding: import("@cosmicdrift/kumiko-renderer").UseQueryResult<BrandingView>;
|
|
39
|
+
mjmlMode: boolean;
|
|
40
|
+
appearanceFromMjml: boolean;
|
|
41
|
+
dirty: boolean;
|
|
42
|
+
checked: LayoutValidation | null;
|
|
43
|
+
shownRow: import("./layout-types.ts").LayoutSourceRow | null;
|
|
44
|
+
userCount: number;
|
|
45
|
+
placeholders: readonly string[];
|
|
46
|
+
placeholderLabels: Readonly<Record<string, string>>;
|
|
47
|
+
requestKind: (next: LayoutKind) => void;
|
|
48
|
+
save: (publish: boolean) => Promise<void>;
|
|
49
|
+
busy: boolean;
|
|
50
|
+
canSave: boolean;
|
|
51
|
+
canPublish: boolean;
|
|
52
|
+
footerMessage: import("./layout-editor-logic.ts").FooterMessage;
|
|
53
|
+
accentColor: string;
|
|
54
|
+
showAccentLine: boolean;
|
|
55
|
+
blocks: ("align" | "button" | "divider" | "image" | "pageBreak" | "repeat" | "table")[];
|
|
56
|
+
resourcePreviews: Readonly<Record<string, {
|
|
57
|
+
readonly src: string;
|
|
58
|
+
readonly caption: string;
|
|
59
|
+
}>> | undefined;
|
|
60
|
+
discard: () => void;
|
|
61
|
+
};
|
|
62
|
+
export type LayoutEditorModel = ReturnType<typeof useLayoutEditorState>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
2
|
+
import { useMutation, useQuery } from "@cosmicdrift/kumiko-renderer";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { BRANDING_QUERY_QN } from "./constants.js";
|
|
5
|
+
import { accentLineShown, appearanceFlags, footerMessageOf, initialTab, layoutBlocks, layoutContent, layoutDirty, layoutSaveFlags, resourcePreviewsOf, saveLayout, shownRowOf, } from "./layout-editor-logic.js";
|
|
6
|
+
import { appearanceOf } from "./layout-meta.js";
|
|
7
|
+
import { systemLabelMap } from "./mail-draft.js";
|
|
8
|
+
import { useDebouncedValue } from "./use-debounced-value.js";
|
|
9
|
+
// Server-side validation compiles the whole layout (mjml), so wait for a pause in typing.
|
|
10
|
+
const VALIDATE_DEBOUNCE_MS = 400;
|
|
11
|
+
export function useLayoutEditorState(props, t) {
|
|
12
|
+
const { kind, slug, isSystemAdmin, source, usedByCount, onKindChange, onReload } = props;
|
|
13
|
+
const isMail = kind === "mail-layout";
|
|
14
|
+
const showMjmlTab = isSystemAdmin && source.mjmlSource !== null;
|
|
15
|
+
const [tab, setTab] = useState(initialTab(showMjmlTab, source));
|
|
16
|
+
const [header, setHeader] = useState(source.regions.header);
|
|
17
|
+
const [signature, setSignature] = useState(source.regions.signature);
|
|
18
|
+
const [footer, setFooter] = useState(source.regions.footer);
|
|
19
|
+
const [mjml, setMjml] = useState(source.mjmlSource ?? "");
|
|
20
|
+
const storedAppearance = appearanceOf(source.meta);
|
|
21
|
+
const [appearance, setAppearance] = useState(storedAppearance);
|
|
22
|
+
const [activeRegion, setActiveRegion] = useState("header");
|
|
23
|
+
const [toolbarHost, setToolbarHost] = useState(null);
|
|
24
|
+
const [pendingKind, setPendingKind] = useState(null);
|
|
25
|
+
const [saveError, setSaveError] = useState(null);
|
|
26
|
+
const upsertTenant = useMutation(TemplateResolverHandlers.upsertTenant);
|
|
27
|
+
const upsertSystem = useMutation(TemplateResolverHandlers.upsertSystem);
|
|
28
|
+
const publishSystem = useMutation(TemplateResolverHandlers.publishSystem);
|
|
29
|
+
const branding = useQuery(BRANDING_QUERY_QN, {});
|
|
30
|
+
const mjmlMode = tab === "mjml";
|
|
31
|
+
const { appearanceFromMjml, appearanceEditable, appearanceValid } = appearanceFlags({
|
|
32
|
+
isMail,
|
|
33
|
+
mjmlMode,
|
|
34
|
+
source,
|
|
35
|
+
appearance,
|
|
36
|
+
});
|
|
37
|
+
const dirty = layoutDirty({
|
|
38
|
+
mjmlMode,
|
|
39
|
+
mjml,
|
|
40
|
+
header,
|
|
41
|
+
signature,
|
|
42
|
+
footer,
|
|
43
|
+
appearance,
|
|
44
|
+
storedAppearance,
|
|
45
|
+
appearanceEditable,
|
|
46
|
+
source,
|
|
47
|
+
});
|
|
48
|
+
const content = layoutContent({ mjmlMode, mjml, header, signature, footer, isMail });
|
|
49
|
+
const debouncedContent = useDebouncedValue(content, VALIDATE_DEBOUNCE_MS);
|
|
50
|
+
const validation = useQuery(TemplateResolverQueries.validate, {
|
|
51
|
+
slug,
|
|
52
|
+
kind,
|
|
53
|
+
contentFormat: mjmlMode ? "mjml" : "html",
|
|
54
|
+
content: debouncedContent,
|
|
55
|
+
locale: source.locale,
|
|
56
|
+
scope: isSystemAdmin ? "system" : "tenant",
|
|
57
|
+
});
|
|
58
|
+
const checked = validation.data;
|
|
59
|
+
const shownRow = shownRowOf(isSystemAdmin, source);
|
|
60
|
+
const userCount = usedByCount;
|
|
61
|
+
const placeholders = checked?.systemPlaceholders ?? [];
|
|
62
|
+
const placeholderLabels = systemLabelMap(t, placeholders);
|
|
63
|
+
const requestKind = (next) => {
|
|
64
|
+
// skip: the requested kind is already open
|
|
65
|
+
if (next === kind)
|
|
66
|
+
return;
|
|
67
|
+
if (dirty)
|
|
68
|
+
setPendingKind(next);
|
|
69
|
+
else
|
|
70
|
+
onKindChange(next);
|
|
71
|
+
};
|
|
72
|
+
const save = (publish) => saveLayout({
|
|
73
|
+
publish,
|
|
74
|
+
kind,
|
|
75
|
+
slug,
|
|
76
|
+
source,
|
|
77
|
+
content,
|
|
78
|
+
mjmlMode,
|
|
79
|
+
isMail,
|
|
80
|
+
isSystemAdmin,
|
|
81
|
+
appearanceEditable,
|
|
82
|
+
appearance,
|
|
83
|
+
t,
|
|
84
|
+
upsertTenant,
|
|
85
|
+
upsertSystem,
|
|
86
|
+
publishSystem,
|
|
87
|
+
setSaveError,
|
|
88
|
+
onReload,
|
|
89
|
+
});
|
|
90
|
+
const busy = upsertTenant.pending || upsertSystem.pending || publishSystem.pending;
|
|
91
|
+
const { canSave, canPublish } = layoutSaveFlags({
|
|
92
|
+
dirty,
|
|
93
|
+
checked,
|
|
94
|
+
appearanceValid,
|
|
95
|
+
busy,
|
|
96
|
+
shownRow,
|
|
97
|
+
});
|
|
98
|
+
const footerMessage = footerMessageOf({ t, checked, appearanceValid, isMail, userCount });
|
|
99
|
+
const accentColor = branding.data?.accentColor ?? "";
|
|
100
|
+
const showAccentLine = accentLineShown({ isMail, appearanceEditable, appearance, accentColor });
|
|
101
|
+
const blocks = layoutBlocks(isMail);
|
|
102
|
+
const resourcePreviews = resourcePreviewsOf(branding.data, isMail, t);
|
|
103
|
+
const discard = () => {
|
|
104
|
+
setHeader(source.regions.header);
|
|
105
|
+
setSignature(source.regions.signature);
|
|
106
|
+
setFooter(source.regions.footer);
|
|
107
|
+
setMjml(source.mjmlSource ?? "");
|
|
108
|
+
setAppearance(storedAppearance);
|
|
109
|
+
setSaveError(null);
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
isMail,
|
|
113
|
+
showMjmlTab,
|
|
114
|
+
tab,
|
|
115
|
+
setTab,
|
|
116
|
+
header,
|
|
117
|
+
setHeader,
|
|
118
|
+
signature,
|
|
119
|
+
setSignature,
|
|
120
|
+
footer,
|
|
121
|
+
setFooter,
|
|
122
|
+
mjml,
|
|
123
|
+
setMjml,
|
|
124
|
+
appearance,
|
|
125
|
+
setAppearance,
|
|
126
|
+
activeRegion,
|
|
127
|
+
setActiveRegion,
|
|
128
|
+
toolbarHost,
|
|
129
|
+
setToolbarHost,
|
|
130
|
+
pendingKind,
|
|
131
|
+
setPendingKind,
|
|
132
|
+
saveError,
|
|
133
|
+
setSaveError,
|
|
134
|
+
branding,
|
|
135
|
+
mjmlMode,
|
|
136
|
+
appearanceFromMjml,
|
|
137
|
+
dirty,
|
|
138
|
+
checked,
|
|
139
|
+
shownRow,
|
|
140
|
+
userCount,
|
|
141
|
+
placeholders,
|
|
142
|
+
placeholderLabels,
|
|
143
|
+
requestKind,
|
|
144
|
+
save,
|
|
145
|
+
busy,
|
|
146
|
+
canSave,
|
|
147
|
+
canPublish,
|
|
148
|
+
footerMessage,
|
|
149
|
+
accentColor,
|
|
150
|
+
showAccentLine,
|
|
151
|
+
blocks,
|
|
152
|
+
resourcePreviews,
|
|
153
|
+
discard,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EditorMode } from "./editor-header.tsx";
|
|
2
|
+
import type { ConfirmDialog } from "./mail-dialogs.tsx";
|
|
3
|
+
import type { MailEditorBodyProps } from "./mail-editor-logic.ts";
|
|
4
|
+
export declare function useMailUiState(row: MailEditorBodyProps["row"]): {
|
|
5
|
+
baseline: import("./mail-types.ts").MailDraft;
|
|
6
|
+
draft: import("./mail-types.ts").MailDraft;
|
|
7
|
+
setDraft: import("react").Dispatch<import("react").SetStateAction<import("./mail-types.ts").MailDraft>>;
|
|
8
|
+
settled: import("./mail-types.ts").MailDraft;
|
|
9
|
+
settle: (next: import("./mail-types.ts").MailDraft) => void;
|
|
10
|
+
mode: EditorMode;
|
|
11
|
+
setMode: import("react").Dispatch<import("react").SetStateAction<EditorMode>>;
|
|
12
|
+
creating: boolean;
|
|
13
|
+
setCreating: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
14
|
+
dialog: ConfirmDialog;
|
|
15
|
+
setDialog: import("react").Dispatch<import("react").SetStateAction<ConfirmDialog>>;
|
|
16
|
+
uploadOpen: boolean;
|
|
17
|
+
setUploadOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
18
|
+
pdfOpen: boolean;
|
|
19
|
+
setPdfOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
20
|
+
resourceError: string | null;
|
|
21
|
+
setResourceError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
22
|
+
resetError: string | null;
|
|
23
|
+
setResetError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
24
|
+
editorEpoch: number;
|
|
25
|
+
setEditorEpoch: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
26
|
+
includeAttachments: boolean;
|
|
27
|
+
setIncludeAttachments: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
28
|
+
prefixSubject: boolean;
|
|
29
|
+
setPrefixSubject: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
30
|
+
};
|
|
31
|
+
export type MailUiState = ReturnType<typeof useMailUiState>;
|
|
32
|
+
export type MailServices = ReturnType<typeof useMailServices>;
|
|
33
|
+
export declare function useMailServices(props: MailEditorBodyProps): {
|
|
34
|
+
insert: import("./use-insert-targets.ts").InsertTargets;
|
|
35
|
+
persistence: import("./use-mail-persistence.ts").MailPersistence;
|
|
36
|
+
testMail: import("./use-test-mail.ts").TestMail;
|
|
37
|
+
own: import("./use-own-address.ts").OwnAddress;
|
|
38
|
+
filesAvailable: boolean;
|
|
39
|
+
resourceRemove: import("@cosmicdrift/kumiko-renderer").UseMutationResult<unknown>;
|
|
40
|
+
layouts: import("./use-overview-rows.ts").OverviewRows;
|
|
41
|
+
pdfTemplates: import("./use-overview-rows.ts").OverviewRows;
|
|
42
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
2
|
+
import { useMutation } from "@cosmicdrift/kumiko-renderer";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { loadedMailOf } from "./mail-draft.js";
|
|
5
|
+
import { useFilesCapability } from "./use-files-capability.js";
|
|
6
|
+
import { useInsertTargets } from "./use-insert-targets.js";
|
|
7
|
+
import { useMailPersistence } from "./use-mail-persistence.js";
|
|
8
|
+
import { useOverviewRows } from "./use-overview-rows.js";
|
|
9
|
+
import { useOwnAddress } from "./use-own-address.js";
|
|
10
|
+
import { useSettledValue } from "./use-settled-value.js";
|
|
11
|
+
import { useTestMail } from "./use-test-mail.js";
|
|
12
|
+
export function useMailUiState(row) {
|
|
13
|
+
const [baseline] = useState(() => loadedMailOf(row));
|
|
14
|
+
const [draft, setDraft] = useState(baseline);
|
|
15
|
+
const [settled, settle] = useSettledValue(baseline);
|
|
16
|
+
const [mode, setMode] = useState("edit");
|
|
17
|
+
const [creating, setCreating] = useState(false);
|
|
18
|
+
const [dialog, setDialog] = useState({ kind: "none" });
|
|
19
|
+
const [uploadOpen, setUploadOpen] = useState(false);
|
|
20
|
+
const [pdfOpen, setPdfOpen] = useState(false);
|
|
21
|
+
const [resourceError, setResourceError] = useState(null);
|
|
22
|
+
const [resetError, setResetError] = useState(null);
|
|
23
|
+
const [editorEpoch, setEditorEpoch] = useState(0);
|
|
24
|
+
const [includeAttachments, setIncludeAttachments] = useState(true);
|
|
25
|
+
const [prefixSubject, setPrefixSubject] = useState(true);
|
|
26
|
+
return {
|
|
27
|
+
baseline,
|
|
28
|
+
draft,
|
|
29
|
+
setDraft,
|
|
30
|
+
settled,
|
|
31
|
+
settle,
|
|
32
|
+
mode,
|
|
33
|
+
setMode,
|
|
34
|
+
creating,
|
|
35
|
+
setCreating,
|
|
36
|
+
dialog,
|
|
37
|
+
setDialog,
|
|
38
|
+
uploadOpen,
|
|
39
|
+
setUploadOpen,
|
|
40
|
+
pdfOpen,
|
|
41
|
+
setPdfOpen,
|
|
42
|
+
resourceError,
|
|
43
|
+
setResourceError,
|
|
44
|
+
resetError,
|
|
45
|
+
setResetError,
|
|
46
|
+
editorEpoch,
|
|
47
|
+
setEditorEpoch,
|
|
48
|
+
includeAttachments,
|
|
49
|
+
setIncludeAttachments,
|
|
50
|
+
prefixSubject,
|
|
51
|
+
setPrefixSubject,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function useMailServices(props) {
|
|
55
|
+
const { slug, locale, title, isSystemAdmin, row } = props;
|
|
56
|
+
const insert = useInsertTargets();
|
|
57
|
+
const persistence = useMailPersistence({ slug, locale, title, isSystemAdmin, row });
|
|
58
|
+
const testMail = useTestMail();
|
|
59
|
+
const own = useOwnAddress();
|
|
60
|
+
const filesAvailable = useFilesCapability();
|
|
61
|
+
const resourceRemove = useMutation(TemplateResolverHandlers.resourceRemove);
|
|
62
|
+
const layouts = useOverviewRows("mail-layout");
|
|
63
|
+
const pdfTemplates = useOverviewRows("document-pdf");
|
|
64
|
+
return {
|
|
65
|
+
insert,
|
|
66
|
+
persistence,
|
|
67
|
+
testMail,
|
|
68
|
+
own,
|
|
69
|
+
filesAvailable,
|
|
70
|
+
resourceRemove,
|
|
71
|
+
layouts,
|
|
72
|
+
pdfTemplates,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { MailEditorBodyProps } from "./mail-editor-logic.ts";
|
|
2
|
+
import type { MailValidation, PickedValues } from "./mail-types.ts";
|
|
3
|
+
type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
|
|
4
|
+
export declare function useMailEditorState(props: MailEditorBodyProps, t: Translate): {
|
|
5
|
+
update: (patch: Partial<import("./mail-types.ts").MailDraft>) => void;
|
|
6
|
+
reset: () => void;
|
|
7
|
+
saveAndReload: (publish: boolean) => Promise<void>;
|
|
8
|
+
perform: (target: import("./mail-dialogs.tsx").LeaveTarget) => void;
|
|
9
|
+
leave: (target: import("./mail-dialogs.tsx").LeaveTarget) => void;
|
|
10
|
+
leaveSave: (target: import("./mail-dialogs.tsx").LeaveTarget) => Promise<void>;
|
|
11
|
+
removeResource: (key: string) => Promise<void>;
|
|
12
|
+
sendTestMail: () => void;
|
|
13
|
+
ui: {
|
|
14
|
+
baseline: import("./mail-types.ts").MailDraft;
|
|
15
|
+
draft: import("./mail-types.ts").MailDraft;
|
|
16
|
+
setDraft: import("react").Dispatch<import("react").SetStateAction<import("./mail-types.ts").MailDraft>>;
|
|
17
|
+
settled: import("./mail-types.ts").MailDraft;
|
|
18
|
+
settle: (next: import("./mail-types.ts").MailDraft) => void;
|
|
19
|
+
mode: import("./editor-header.tsx").EditorMode;
|
|
20
|
+
setMode: import("react").Dispatch<import("react").SetStateAction<import("./editor-header.tsx").EditorMode>>;
|
|
21
|
+
creating: boolean;
|
|
22
|
+
setCreating: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
23
|
+
dialog: import("./mail-dialogs.tsx").ConfirmDialog;
|
|
24
|
+
setDialog: import("react").Dispatch<import("react").SetStateAction<import("./mail-dialogs.tsx").ConfirmDialog>>;
|
|
25
|
+
uploadOpen: boolean;
|
|
26
|
+
setUploadOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
27
|
+
pdfOpen: boolean;
|
|
28
|
+
setPdfOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
29
|
+
resourceError: string | null;
|
|
30
|
+
setResourceError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
31
|
+
resetError: string | null;
|
|
32
|
+
setResetError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
33
|
+
editorEpoch: number;
|
|
34
|
+
setEditorEpoch: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
35
|
+
includeAttachments: boolean;
|
|
36
|
+
setIncludeAttachments: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
37
|
+
prefixSubject: boolean;
|
|
38
|
+
setPrefixSubject: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
39
|
+
};
|
|
40
|
+
services: {
|
|
41
|
+
insert: import("./use-insert-targets.ts").InsertTargets;
|
|
42
|
+
persistence: import("./use-mail-persistence.ts").MailPersistence;
|
|
43
|
+
testMail: import("./use-test-mail.ts").TestMail;
|
|
44
|
+
own: import("./use-own-address.ts").OwnAddress;
|
|
45
|
+
filesAvailable: boolean;
|
|
46
|
+
resourceRemove: import("@cosmicdrift/kumiko-renderer").UseMutationResult<unknown>;
|
|
47
|
+
layouts: import("./use-overview-rows.ts").OverviewRows;
|
|
48
|
+
pdfTemplates: import("./use-overview-rows.ts").OverviewRows;
|
|
49
|
+
};
|
|
50
|
+
validation: MailValidation | null;
|
|
51
|
+
picked: Readonly<Record<string, unknown>> | null;
|
|
52
|
+
setPicked: (next: PickedValues | null) => void;
|
|
53
|
+
pickedVariables: Readonly<Record<string, unknown>> | null;
|
|
54
|
+
variableSchema: Readonly<Record<string, unknown>>;
|
|
55
|
+
locked: boolean;
|
|
56
|
+
dirty: boolean;
|
|
57
|
+
busy: boolean;
|
|
58
|
+
canSave: boolean;
|
|
59
|
+
canPublish: boolean;
|
|
60
|
+
resources: readonly import("./mail-types.ts").LinkedResourceView[];
|
|
61
|
+
pdfTitles: Map<string, string>;
|
|
62
|
+
uploadTemplateId: string | null;
|
|
63
|
+
takenKeys: string[];
|
|
64
|
+
attachments: ({
|
|
65
|
+
readonly kind: "file";
|
|
66
|
+
readonly key: string;
|
|
67
|
+
readonly fileRefId: string;
|
|
68
|
+
readonly fileName: string;
|
|
69
|
+
} | {
|
|
70
|
+
readonly kind: "pdf";
|
|
71
|
+
readonly key: string;
|
|
72
|
+
readonly templateSlug: string;
|
|
73
|
+
readonly fileName: string;
|
|
74
|
+
})[];
|
|
75
|
+
resourcePreviews: {
|
|
76
|
+
[k: string]: {
|
|
77
|
+
src: string;
|
|
78
|
+
caption: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
railValidation: MailValidation | null;
|
|
82
|
+
vocabulary: import("./mail-edit-center.tsx").EditorVocabulary;
|
|
83
|
+
footer: {
|
|
84
|
+
readonly tone: "bad" | "warn" | "ok";
|
|
85
|
+
readonly text: string;
|
|
86
|
+
} | null;
|
|
87
|
+
fieldErrors: Partial<Record<"previewText" | "subject", string>>;
|
|
88
|
+
};
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
2
|
+
import { useQuery, useUnsavedInputSource } from "@cosmicdrift/kumiko-renderer";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { buildMeta, isDraftDirty, parseJsonRecord, pickDeclaredVariables } from "./mail-draft.js";
|
|
5
|
+
import { buildMailModel } from "./mail-editor-model.js";
|
|
6
|
+
import { useMailServices, useMailUiState } from "./use-mail-editor-parts.js";
|
|
7
|
+
export function useMailEditorState(props, t) {
|
|
8
|
+
const { slug, locale, isSystemAdmin, row } = props;
|
|
9
|
+
const ui = useMailUiState(row);
|
|
10
|
+
const [picked, setPicked] = useState(null);
|
|
11
|
+
const services = useMailServices(props);
|
|
12
|
+
const locked = row === null && !ui.creating;
|
|
13
|
+
const variableSchema = parseJsonRecord(row?.variableSchema);
|
|
14
|
+
const validation = useQuery(TemplateResolverQueries.validate, {
|
|
15
|
+
slug,
|
|
16
|
+
kind: "mail",
|
|
17
|
+
contentFormat: "html",
|
|
18
|
+
content: ui.settled.body,
|
|
19
|
+
meta: buildMeta(ui.settled),
|
|
20
|
+
variableSchema,
|
|
21
|
+
locale,
|
|
22
|
+
scope: isSystemAdmin ? "system" : "tenant",
|
|
23
|
+
}).data;
|
|
24
|
+
const pickedVariables = pickDeclaredVariables(picked, validation?.variables);
|
|
25
|
+
// A pick belongs to the variables it was made for: after the declared set changes it is dropped,
|
|
26
|
+
// so a re-added variable does not revive an old record value.
|
|
27
|
+
const declaredVariables = validation?.variables;
|
|
28
|
+
const declaredVariableNames = declaredVariables === undefined ? null : Object.keys(declaredVariables).sort().join("|");
|
|
29
|
+
const previousDeclaredNames = useRef(declaredVariableNames);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
// skip: validation has not loaded, so there is no declared set to compare
|
|
32
|
+
if (declaredVariableNames === null)
|
|
33
|
+
return;
|
|
34
|
+
if (previousDeclaredNames.current !== null &&
|
|
35
|
+
previousDeclaredNames.current !== declaredVariableNames) {
|
|
36
|
+
setPicked(null);
|
|
37
|
+
}
|
|
38
|
+
previousDeclaredNames.current = declaredVariableNames;
|
|
39
|
+
}, [declaredVariableNames]);
|
|
40
|
+
const dirty = isDraftDirty(ui.draft, ui.baseline);
|
|
41
|
+
useUnsavedInputSource(() => dirty);
|
|
42
|
+
return buildMailModel({
|
|
43
|
+
props,
|
|
44
|
+
t,
|
|
45
|
+
ui,
|
|
46
|
+
picked,
|
|
47
|
+
setPicked,
|
|
48
|
+
services,
|
|
49
|
+
validation,
|
|
50
|
+
variableSchema,
|
|
51
|
+
pickedVariables,
|
|
52
|
+
locked,
|
|
53
|
+
dirty,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { EditorMode } from "./editor-header.tsx";
|
|
2
|
+
import { type EditorState, type GeneratedPdf } from "./pdf-editor-draft.ts";
|
|
3
|
+
import { type PuppeteerStatus, type TemplateDetail, type TemplateListRow, type TemplateScope, type TenantOverrides, type ValidationResult } from "./types.ts";
|
|
4
|
+
type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
|
|
5
|
+
export declare function usePdfEditorState(input: {
|
|
6
|
+
readonly slug: string;
|
|
7
|
+
readonly requestedLocale: string | undefined;
|
|
8
|
+
}, t: Translate): {
|
|
9
|
+
host: {
|
|
10
|
+
insert: import("./use-insert-targets.ts").InsertTargets;
|
|
11
|
+
filesAvailable: boolean;
|
|
12
|
+
overview: import("./use-overview-rows.ts").OverviewRows;
|
|
13
|
+
scope: TemplateScope;
|
|
14
|
+
};
|
|
15
|
+
doc: {
|
|
16
|
+
rowsQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<readonly TemplateListRow[]>;
|
|
17
|
+
locales: readonly string[];
|
|
18
|
+
locale: string;
|
|
19
|
+
row: TemplateListRow | undefined;
|
|
20
|
+
detailQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<TemplateDetail | null>;
|
|
21
|
+
detail: TemplateDetail | null;
|
|
22
|
+
setState: import("react").Dispatch<import("react").SetStateAction<EditorState | null>>;
|
|
23
|
+
current: EditorState | null;
|
|
24
|
+
};
|
|
25
|
+
settleTimer: import("react").RefObject<NodeJS.Timeout | null>;
|
|
26
|
+
ui: {
|
|
27
|
+
overridesOpen: boolean;
|
|
28
|
+
setOverridesOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
29
|
+
draftEpoch: number;
|
|
30
|
+
setDraftEpoch: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
31
|
+
mode: EditorMode;
|
|
32
|
+
setMode: import("react").Dispatch<import("react").SetStateAction<EditorMode>>;
|
|
33
|
+
busy: "pdf" | "publish" | "save" | null;
|
|
34
|
+
setBusy: import("react").Dispatch<import("react").SetStateAction<"pdf" | "publish" | "save" | null>>;
|
|
35
|
+
actionError: string | null;
|
|
36
|
+
setActionError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
37
|
+
generated: GeneratedPdf | null;
|
|
38
|
+
setGenerated: import("react").Dispatch<import("react").SetStateAction<GeneratedPdf | null>>;
|
|
39
|
+
autoPrintedFor: import("react").RefObject<string | null>;
|
|
40
|
+
pendingLocale: string | null;
|
|
41
|
+
setPendingLocale: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
42
|
+
};
|
|
43
|
+
server: {
|
|
44
|
+
variableSchema: Record<string, unknown>;
|
|
45
|
+
validationQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<ValidationResult>;
|
|
46
|
+
validation: ValidationResult | null;
|
|
47
|
+
layout: {
|
|
48
|
+
readonly slug: string;
|
|
49
|
+
readonly scope: TemplateScope;
|
|
50
|
+
readonly html: string;
|
|
51
|
+
readonly regions: import("./types.ts").LayoutRegionsHtml;
|
|
52
|
+
} | null;
|
|
53
|
+
layouts: import("./use-overview-rows.ts").OverviewRows;
|
|
54
|
+
brandingLogoFile: string;
|
|
55
|
+
statusQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<PuppeteerStatus>;
|
|
56
|
+
showOrigin: boolean;
|
|
57
|
+
overridesQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<TenantOverrides>;
|
|
58
|
+
};
|
|
59
|
+
vocabulary: {
|
|
60
|
+
variables: Readonly<Record<string, import("./mail-types.ts").MailVariable>>;
|
|
61
|
+
imageVariables: string[];
|
|
62
|
+
systemVariables: readonly string[];
|
|
63
|
+
systemLabels: Readonly<Record<string, string>>;
|
|
64
|
+
variableLabels: {
|
|
65
|
+
[x: string]: string;
|
|
66
|
+
};
|
|
67
|
+
variableNames: string[];
|
|
68
|
+
resourcePreviews: {
|
|
69
|
+
[k: string]: {
|
|
70
|
+
src: string;
|
|
71
|
+
caption: string;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
export type PdfEditorState = ReturnType<typeof usePdfEditorState>;
|
|
77
|
+
export {};
|