@cosmicdrift/kumiko-document-designer 0.377.0 → 0.379.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.d.ts +6 -2
- package/dist/feature.js +10 -10
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/list-definition.js +14 -2
- package/dist/web/cells.js +3 -4
- package/dist/web/client-plugin.js +3 -4
- package/dist/{constants.d.ts → web/constants.d.ts} +1 -0
- package/dist/{constants.js → web/constants.js} +2 -1
- package/dist/web/default-translations.d.ts +2 -0
- package/dist/web/default-translations.js +218 -0
- package/dist/web/editor-header.js +1 -2
- package/dist/web/editor-locale.js +1 -2
- package/dist/web/editor-shell.js +8 -4
- package/dist/web/errors.js +0 -1
- package/dist/web/field-issue-text.js +0 -1
- package/dist/web/file-name.js +0 -1
- package/dist/web/i18n.d.ts +6 -2
- package/dist/web/i18n.js +873 -219
- package/dist/web/index.d.ts +1 -1
- package/dist/web/index.js +1 -2
- package/dist/web/layout-editor-body.js +6 -6
- package/dist/web/layout-editor.js +1 -2
- package/dist/web/layout-html.js +0 -1
- package/dist/web/layout-meta.js +0 -1
- package/dist/web/layout-rails.js +3 -3
- package/dist/web/layout-users.js +1 -2
- package/dist/web/mail-dialogs.js +0 -1
- package/dist/web/mail-draft.js +4 -3
- package/dist/web/mail-edit-center.js +0 -1
- package/dist/web/mail-editor-body.js +7 -4
- package/dist/web/mail-editor.js +1 -2
- package/dist/web/mail-footer.d.ts +1 -1
- package/dist/web/mail-footer.js +2 -2
- package/dist/web/mail-left-rail.d.ts +2 -1
- package/dist/web/mail-left-rail.js +2 -3
- package/dist/web/mail-missing-locale.js +0 -1
- package/dist/web/mail-origin-bar.js +0 -1
- package/dist/web/mail-pdf-link-modal.js +5 -2
- package/dist/web/mail-preview-side.d.ts +2 -0
- package/dist/web/mail-preview-side.js +3 -2
- package/dist/web/mail-preview.js +1 -2
- package/dist/web/mail-right-rail.js +1 -2
- package/dist/web/mail-types.d.ts +4 -0
- package/dist/web/mail-upload-modal.js +0 -1
- package/dist/web/mail-versions.js +0 -1
- package/dist/web/origin-bar.js +2 -3
- package/dist/web/overrides-drawer.js +0 -1
- package/dist/web/overview-row.d.ts +4 -1
- package/dist/web/overview-row.js +3 -1
- package/dist/web/page-canvas.d.ts +1 -0
- package/dist/web/page-canvas.js +1 -2
- package/dist/web/pdf-left-rail.d.ts +2 -1
- package/dist/web/pdf-left-rail.js +2 -3
- package/dist/web/pdf-meta.js +0 -1
- package/dist/web/pdf-template-editor.js +40 -7
- package/dist/web/preview-record-picker.js +0 -1
- package/dist/web/rail.js +8 -4
- package/dist/web/settings-panel.js +0 -1
- package/dist/web/template-rows.js +0 -1
- package/dist/web/types.d.ts +1 -1
- package/dist/web/use-files-capability.d.ts +1 -0
- package/dist/web/use-files-capability.js +7 -0
- package/dist/web/use-mail-persistence.js +0 -1
- package/dist/web/use-overview-rows.js +0 -1
- package/dist/web/use-own-address.js +1 -2
- package/dist/web/use-settled-value.js +0 -1
- package/dist/web/use-test-mail.d.ts +6 -0
- package/dist/web/use-test-mail.js +8 -4
- package/dist/web/used-by-label.d.ts +1 -0
- package/dist/web/used-by-label.js +10 -5
- package/package.json +11 -9
- package/src/changes.json +43 -0
- package/src/web/styles.css +12 -0
- package/dist/i18n.d.ts +0 -6
- package/dist/i18n.js +0 -836
- package/dist/web/pdf-preview.d.ts +0 -4
- package/dist/web/pdf-preview.js +0 -112
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// @runtime client
|
|
3
2
|
import { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
4
3
|
import { useDispatcher, usePrimitives, useQuery, useTranslation, useUserRoles, } from "@cosmicdrift/kumiko-renderer";
|
|
5
|
-
import { ErrorState, LoadingState, uploadFileRef } from "@cosmicdrift/kumiko-renderer/web";
|
|
4
|
+
import { ErrorState, LoadingState, PdfPreview, uploadFileRef, } from "@cosmicdrift/kumiko-renderer/web";
|
|
6
5
|
import { RendererPuppeteerClientQueries } from "@cosmicdrift/kumiko-renderer-puppeteer-client/qualified-names";
|
|
7
6
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
-
import { BRANDING_QUERY_QN } from "
|
|
7
|
+
import { BRANDING_QUERY_QN } from "./constants.js";
|
|
9
8
|
import { EditorHeader, EditorLocaleSwitch, EditorModeSwitch, originOf, } from "./editor-header.js";
|
|
10
9
|
import { EditorBody, EditorFooter, EditorFrame, EditorInfoBar } from "./editor-shell.js";
|
|
11
10
|
import { describeError, isAlreadyActive } from "./errors.js";
|
|
@@ -17,16 +16,41 @@ import { PageCanvas } from "./page-canvas.js";
|
|
|
17
16
|
import { pdfFooterMessage } from "./pdf-footer.js";
|
|
18
17
|
import { PdfResourceSection, visibleSystemPlaceholders } from "./pdf-left-rail.js";
|
|
19
18
|
import { EMPTY_PDF_META, parsePdfMeta, serializePdfMeta } from "./pdf-meta.js";
|
|
20
|
-
import { PdfPreview } from "./pdf-preview.js";
|
|
21
19
|
import { SettingsPanel } from "./settings-panel.js";
|
|
22
20
|
import { draftBesideActiveSentence } from "./status-label.js";
|
|
23
21
|
import { localesOf, parseJsonObject, pickRow, resolveLocale } from "./template-rows.js";
|
|
24
22
|
import { DEFAULT_LAYOUT_SLUG, DOCUMENT_PDF_KIND, PDF_LAYOUT_KIND, } from "./types.js";
|
|
23
|
+
import { useFilesCapability } from "./use-files-capability.js";
|
|
25
24
|
import { useInsertTargets } from "./use-insert-targets.js";
|
|
26
25
|
import { useOverviewRows } from "./use-overview-rows.js";
|
|
27
26
|
import { templateTitle } from "./used-by-label.js";
|
|
28
27
|
const SETTLE_MS = 500;
|
|
29
28
|
const NO_VARIABLES = {};
|
|
29
|
+
// Only the label of an images variable is needed, for the Images block and its picker.
|
|
30
|
+
function imageLabelSource(variables) {
|
|
31
|
+
const result = {};
|
|
32
|
+
for (const [name, info] of Object.entries(variables)) {
|
|
33
|
+
if (info.type === "images" && info.label !== undefined) {
|
|
34
|
+
result[name] = { type: "text", label: info.label };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function textVariablesOf(variables) {
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const [name, info] of Object.entries(variables)) {
|
|
42
|
+
if (info.type === "images")
|
|
43
|
+
continue;
|
|
44
|
+
const { item, ...rest } = info;
|
|
45
|
+
const columns = Object.entries(item ?? {}).flatMap(([column, def]) => def.type === "images" ? [] : [[column, { ...def, type: def.type }]]);
|
|
46
|
+
result[name] = {
|
|
47
|
+
...rest,
|
|
48
|
+
type: info.type,
|
|
49
|
+
...(item !== undefined && { item: Object.fromEntries(columns) }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
30
54
|
// Opening the preview prints the page without a click; a failed print is not retried for the same draft.
|
|
31
55
|
function RunOnMount({ run }) {
|
|
32
56
|
useEffect(() => {
|
|
@@ -44,6 +68,7 @@ export function PdfTemplateEditor({ slug, locale: requestedLocale, onLocaleChang
|
|
|
44
68
|
const dispatcher = useDispatcher();
|
|
45
69
|
const { Button, Banner, Dialog } = usePrimitives();
|
|
46
70
|
const insert = useInsertTargets();
|
|
71
|
+
const filesAvailable = useFilesCapability();
|
|
47
72
|
const overview = useOverviewRows(DOCUMENT_PDF_KIND);
|
|
48
73
|
const roles = useUserRoles();
|
|
49
74
|
const scope = roles?.includes("SystemAdmin") === true ? "system" : "tenant";
|
|
@@ -112,10 +137,17 @@ export function PdfTemplateEditor({ slug, locale: requestedLocale, onLocaleChang
|
|
|
112
137
|
const unavailableMessage = statusQuery.data?.available === false ? (statusQuery.data.message ?? "") : "";
|
|
113
138
|
const showOrigin = scope === "system";
|
|
114
139
|
const overridesQuery = useQuery(TemplateResolverQueries.tenantOverrides, { slug, kind: DOCUMENT_PDF_KIND, locale }, { enabled: showOrigin && detail !== null });
|
|
115
|
-
const
|
|
140
|
+
const allVariables = validation?.variables ?? NO_VARIABLES;
|
|
141
|
+
// Photos are record data, never an inline placeholder: images variables only feed the Images block.
|
|
142
|
+
const variables = useMemo(() => textVariablesOf(allVariables), [allVariables]);
|
|
143
|
+
const imageVariables = useMemo(() => Object.keys(allVariables).filter((name) => allVariables[name]?.type === "images"), [allVariables]);
|
|
116
144
|
const systemVariables = useMemo(() => visibleSystemPlaceholders(validation?.systemPlaceholders ?? []), [validation?.systemPlaceholders]);
|
|
117
145
|
const systemLabels = useMemo(() => systemLabelMap(t, systemVariables), [t, systemVariables]);
|
|
118
|
-
const variableLabels = useMemo(() => ({
|
|
146
|
+
const variableLabels = useMemo(() => ({
|
|
147
|
+
...systemLabels,
|
|
148
|
+
...variableLabelMap(t, variables),
|
|
149
|
+
...variableLabelMap(t, imageLabelSource(allVariables)),
|
|
150
|
+
}), [systemLabels, variables, allVariables, t]);
|
|
119
151
|
const variableNames = useMemo(() => Object.keys(variables), [variables]);
|
|
120
152
|
const linkedResources = detail?.linkedResources ?? null;
|
|
121
153
|
// The editor shows resource images from the stored copies; no extra request is made here.
|
|
@@ -256,6 +288,7 @@ export function PdfTemplateEditor({ slug, locale: requestedLocale, onLocaleChang
|
|
|
256
288
|
? null
|
|
257
289
|
: {
|
|
258
290
|
...validation,
|
|
291
|
+
variables,
|
|
259
292
|
systemPlaceholders: visibleSystemPlaceholders(validation.systemPlaceholders),
|
|
260
293
|
};
|
|
261
294
|
const layoutFills = (name) => layout !== null &&
|
|
@@ -302,7 +335,7 @@ export function PdfTemplateEditor({ slug, locale: requestedLocale, onLocaleChang
|
|
|
302
335
|
onPublish: () => void publish(),
|
|
303
336
|
}, children: _jsx(EditorModeSwitch, { testIdPrefix: "pdf", mode: mode, onMode: setMode }) }), _jsxs(EditorInfoBar, { tone: pdfUnavailableNote ? "warn" : detail.scope === "tenant" ? "active" : "muted", testId: "pdf-origin-bar", origin: originOf(detail.scope === "system", systemVersion !== null), controls: _jsxs(_Fragment, { children: [_jsx(EditorLocaleSwitch, { testIdPrefix: "pdf", locales: locales.length > 0 ? locales : [locale], locale: locale, onLocale: requestLocale }), mode === "preview" && (_jsx(Button, { variant: "secondary", disabled: !pdfAvailable || busy !== null, loading: busy === "pdf", onClick: () => void generatePdf(), testId: "pdf-generate-header", children: t("documentDesigner.actions.generatePdf") }))] }), overlays: _jsx(OverridesDrawer, { open: overridesOpen, onOpenChange: setOverridesOpen, slug: slug, locale: locale, overrides: overridesQuery.data }), links: showOverrideLinks && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "link", onClick: () => void exportBundle(), children: t("documentDesigner.originBar.export") }), _jsx(Button, { variant: "link", onClick: () => setOverridesOpen(true), children: t("documentDesigner.originBar.showOverrides") })] })), children: [originText, besideActiveSentence !== null && ` ${besideActiveSentence}`, pdfUnavailableNote && (_jsxs(_Fragment, { children: [" ", _jsx("span", { "data-testid": "pdf-unavailable", children: unavailableMessage === ""
|
|
304
337
|
? t("documentDesigner.pdfUnavailableNoReason")
|
|
305
|
-
: t("documentDesigner.pdfUnavailable", { message: unavailableMessage }) })] }))] }), _jsx(EditorBody, { left: mode === "edit" ? (_jsxs(_Fragment, { children: [_jsx(PlaceholderSection, { validation: placeholderSource, onInsert: insert.insertPlaceholder, idPrefix: "pdf" }), _jsx(SystemPlaceholderSection, { validation: placeholderSource, onInsert: insert.insertPlaceholder, idPrefix: "pdf", isLayoutOwned: layoutFills }), _jsx(PdfResourceSection, { resourceKeys: resourceKeys, canUpload: detail.scope === scope, layoutName: layoutName, onInsert: insert.insertResource, onUpload: uploadResource })] })) : undefined, center: mode === "edit" ? (_jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", onFocusCapture: insert.markFocused("body"), children: _jsx(PageCanvas, { meta: current.draft.meta, content: current.draft.content, onContentChange: (content) => updateDraft({ ...current.draft, content }), regions: layout?.regions ?? null, layoutName: layoutName, resourceSrc: layoutResourceSrc, variableLabels: variableLabels, systemVariables: systemVariables, systemLabels: systemLabels, variableNames: variableNames, resourcePreviews: resourcePreviews, onEditorReady: insert.register("body") }) })) : (_jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: generated !== null && generatedFresh ? (_jsx(PdfPreview, {
|
|
338
|
+
: t("documentDesigner.pdfUnavailable", { message: unavailableMessage }) })] }))] }), _jsx(EditorBody, { left: mode === "edit" ? (_jsxs(_Fragment, { children: [_jsx(PlaceholderSection, { validation: placeholderSource, onInsert: insert.insertPlaceholder, idPrefix: "pdf" }), _jsx(SystemPlaceholderSection, { validation: placeholderSource, onInsert: insert.insertPlaceholder, idPrefix: "pdf", isLayoutOwned: layoutFills }), _jsx(PdfResourceSection, { resourceKeys: resourceKeys, canUpload: detail.scope === scope, filesAvailable: filesAvailable, layoutName: layoutName, onInsert: insert.insertResource, onUpload: uploadResource })] })) : undefined, center: mode === "edit" ? (_jsx("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col", onFocusCapture: insert.markFocused("body"), children: _jsx(PageCanvas, { meta: current.draft.meta, content: current.draft.content, onContentChange: (content) => updateDraft({ ...current.draft, content }), regions: layout?.regions ?? null, layoutName: layoutName, resourceSrc: layoutResourceSrc, variableLabels: variableLabels, systemVariables: systemVariables, systemLabels: systemLabels, variableNames: variableNames, imageVariables: imageVariables, resourcePreviews: resourcePreviews, onEditorReady: insert.register("body") }) })) : (_jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: generated !== null && generatedFresh ? (_jsx(PdfPreview, { source: { base64: generated.result.pdfBase64 }, testId: "document-designer-pdf-preview" })) : (_jsxs("div", { className: "flex flex-col items-start gap-3 p-6", "data-testid": "document-designer-preview-stale", children: [pdfAvailable && busy === null && _jsx(RunOnMount, { run: printOnce }), _jsx("p", { className: "text-sm text-muted-foreground", children: busy === "pdf"
|
|
306
339
|
? t("documentDesigner.preview.loading")
|
|
307
340
|
: t("documentDesigner.preview.stale") }), _jsx(Button, { variant: "secondary", disabled: !pdfAvailable || busy !== null, onClick: () => void generatePdf(), children: t("documentDesigner.actions.generatePdf") })] })) })), right: _jsx(SettingsPanel, { meta: current.draft.meta, onMetaChange: (meta) => updateDraft({ ...current.draft, meta }), layouts: layouts.rows, locale: locale, version: { id: detail.id, version: detail.version, status: detail.status }, vocabulary: vocabulary, insert: insert, draftKey: `${current.key}:${draftEpoch}` }) }), _jsx(EditorFooter, { testIdPrefix: "pdf-editor", message: footer, saveError: actionError, modifiedAt: detail.modifiedAt ?? null, dirty: dirty, busy: busy === "save", canSave: busy === null && validation?.canSave !== false, canPublish: busy === null && canPublish, onDiscard: () => resetDraft(detail), onSave: () => void save(), onPublish: () => void publish() }), _jsx(Dialog, { open: pendingLocale !== null, onOpenChange: (open) => {
|
|
308
341
|
if (!open)
|
package/dist/web/rail.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// @runtime client
|
|
3
2
|
import { cn } from "@cosmicdrift/kumiko-renderer/web";
|
|
4
3
|
export function RailSection({ title, action, testId, children, }) {
|
|
5
|
-
return (_jsxs("section", { className: "flex flex-col border-b pb-1", "data-testid": testId, children: [_jsxs("div", { className: "flex items-center gap-2 px-3.5 pt-2.5 pb-1", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: title }), _jsx("div", { className: "
|
|
4
|
+
return (_jsxs("section", { className: "flex flex-col border-b pb-1", "data-testid": testId, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 px-3.5 pt-2.5 pb-1", children: [_jsx("span", { className: "min-w-0 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: title }), action !== undefined && _jsx("div", { className: "ml-auto", children: action })] }), children] }));
|
|
6
5
|
}
|
|
7
6
|
export function RailField({ children }) {
|
|
8
7
|
return _jsx("div", { className: "flex flex-col gap-3 px-3.5 py-2", children: children });
|
|
@@ -18,8 +17,13 @@ function RequiredLabel({ label }) {
|
|
|
18
17
|
return (_jsxs(_Fragment, { children: [label.slice(0, lastSpace + 1), _jsxs("span", { className: "whitespace-nowrap", children: [label.slice(lastSpace + 1), _jsx("span", { className: "font-semibold text-status-bad", children: " *" })] })] }));
|
|
19
18
|
}
|
|
20
19
|
export function RailRow({ symbol, label, nameHint, required, trailing, trailingTone = "muted", action, bad, testId, onPress, }) {
|
|
21
|
-
const content = (_jsxs(_Fragment, { children: [symbol !== undefined && (_jsx("span", { className: "inline-flex h-[18px] w-[22px] shrink-0 items-center justify-center rounded-sm bg-muted font-mono text-xs text-muted-foreground", children: symbol })), _jsx("span", { className: cn(
|
|
22
|
-
|
|
20
|
+
const content = (_jsxs(_Fragment, { children: [symbol !== undefined && (_jsx("span", { className: "inline-flex h-[18px] w-[22px] shrink-0 items-center justify-center rounded-sm bg-muted font-mono text-xs text-muted-foreground", children: symbol })), _jsx("span", { className: cn(
|
|
21
|
+
// anywhere, not break-word: only it lets a file name without spaces narrow the min-content
|
|
22
|
+
// width, so the label wraps instead of running under the action.
|
|
23
|
+
"min-w-0 grow wrap-anywhere text-sm", trailing !== undefined && "min-w-min max-w-[65%] shrink-0", bad === true && "text-status-bad"), title: nameHint !== undefined && nameHint !== label ? nameHint : undefined, children: required === true ? _jsx(RequiredLabel, { label: label }) : label }), trailing !== undefined && (_jsx("span", { className: cn("min-w-0 text-xs", action !== undefined ? "basis-full wrap-anywhere" : "truncate text-right", bad === true ? "text-status-bad" : TRAILING_TONE[trailingTone]), title: typeof trailing === "string" ? trailing : undefined, children: trailing }))] }));
|
|
24
|
+
// Beside an action the row is too narrow for label and trailing text: the trailing text takes its
|
|
25
|
+
// own line and wraps there instead of being clipped to an ellipsis.
|
|
26
|
+
const rowClass = cn("flex min-h-10 min-w-0 grow items-center gap-2 px-3.5 text-left", action !== undefined && "flex-wrap gap-y-0.5 py-1");
|
|
23
27
|
return (_jsxs("div", { className: cn("flex items-center border-b", bad === true && "bg-status-bad-surface"), "data-testid": testId, "data-bad": bad === true ? "true" : undefined, children: [onPress === undefined ? (_jsx("div", { className: rowClass, children: content })) : (_jsx("button", { type: "button", onClick: onPress, className: cn(rowClass, "hover:bg-muted"), ...(testId !== undefined && { "data-testid": `${testId}-insert` }), children: content })), action !== undefined && _jsx("div", { className: "shrink-0 pr-2", children: action })] }));
|
|
24
28
|
}
|
|
25
29
|
export function RailHint({ children }) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// @runtime client
|
|
3
2
|
import { PDF_ORIENTATIONS, PDF_PAGE_FORMATS, PlaceholderLineEditor, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
4
3
|
import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
5
4
|
import { languageName } from "./mail-origin-bar.js";
|
package/dist/web/types.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type ServedVersionView = {
|
|
|
39
39
|
readonly version: number;
|
|
40
40
|
readonly since: string;
|
|
41
41
|
};
|
|
42
|
-
export type VariableType = "text" | "link" | "money" | "date" | "percent" | "list";
|
|
42
|
+
export type VariableType = "text" | "link" | "money" | "date" | "percent" | "list" | "images";
|
|
43
43
|
export type VariableInfo = {
|
|
44
44
|
readonly type: VariableType;
|
|
45
45
|
readonly label?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function useFilesCapability(): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useAppFeatures } from "@cosmicdrift/kumiko-renderer";
|
|
2
|
+
import { FILES_FEATURE_NAME } from "./constants.js";
|
|
3
|
+
// The app schema lists every mounted feature, screenless ones included, so this is the server's
|
|
4
|
+
// answer: without the files feature there is no upload route.
|
|
5
|
+
export function useFilesCapability() {
|
|
6
|
+
return useAppFeatures().some((feature) => feature.featureName === FILES_FEATURE_NAME);
|
|
7
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// @runtime client
|
|
2
1
|
import { useAppFeatures, useQuery } from "@cosmicdrift/kumiko-renderer";
|
|
3
|
-
import { PROFILE_FEATURE_NAME, PROFILE_SCREEN_ID, USER_ME_QUERY_QN } from "
|
|
2
|
+
import { PROFILE_FEATURE_NAME, PROFILE_SCREEN_ID, USER_ME_QUERY_QN } from "./constants.js";
|
|
4
3
|
export function useOwnAddress() {
|
|
5
4
|
const features = useAppFeatures();
|
|
6
5
|
const me = useQuery(USER_ME_QUERY_QN, {});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SendTestStatus } from "./mail-types.ts";
|
|
1
2
|
export type TestMailState = {
|
|
2
3
|
readonly kind: "idle";
|
|
3
4
|
} | {
|
|
@@ -7,7 +8,12 @@ export type TestMailState = {
|
|
|
7
8
|
readonly kind: "error";
|
|
8
9
|
readonly text: string;
|
|
9
10
|
};
|
|
11
|
+
export type TestMailAvailability = {
|
|
12
|
+
readonly canSend: boolean;
|
|
13
|
+
readonly reason: SendTestStatus["reason"];
|
|
14
|
+
};
|
|
10
15
|
export type TestMail = {
|
|
16
|
+
readonly availability: TestMailAvailability;
|
|
11
17
|
readonly state: TestMailState;
|
|
12
18
|
readonly pending: boolean;
|
|
13
19
|
readonly send: (payload: Readonly<Record<string, unknown>>) => Promise<void>;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import { dispatcherErrorText, useMutation, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
1
|
+
import { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
|
|
2
|
+
import { dispatcherErrorText, useMutation, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
|
|
4
3
|
import { useState } from "react";
|
|
5
4
|
const TOO_MANY_REQUESTS = 429;
|
|
6
5
|
// The recipient is always the caller; the server never returns the address.
|
|
7
6
|
export function useTestMail() {
|
|
8
7
|
const t = useTranslation();
|
|
9
8
|
const sendTest = useMutation(TemplateResolverHandlers.sendTest);
|
|
9
|
+
const status = useQuery(TemplateResolverQueries.sendTestStatus, {});
|
|
10
|
+
const availability = {
|
|
11
|
+
canSend: status.data?.canSend === true,
|
|
12
|
+
reason: status.data?.reason ?? null,
|
|
13
|
+
};
|
|
10
14
|
const [state, setState] = useState({ kind: "idle" });
|
|
11
15
|
const send = async (payload) => {
|
|
12
16
|
setState({ kind: "idle" });
|
|
@@ -23,5 +27,5 @@ export function useTestMail() {
|
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
};
|
|
26
|
-
return { state, pending: sendTest.pending, send };
|
|
30
|
+
return { availability, state, pending: sendTest.pending, send };
|
|
27
31
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OverviewRowView, OverviewUsageView } from "./overview-row.ts";
|
|
2
2
|
type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
|
|
3
3
|
export declare function variableLabelText(t: Translate, name: string, label: string | undefined): string;
|
|
4
|
+
export declare function templateTitleText(t: Translate, title: string): string;
|
|
4
5
|
export declare function templateTitle(t: Translate, row: Pick<OverviewRowView, "title" | "slug" | "kind">): string;
|
|
5
6
|
export declare function usageParts(t: Translate, usage: OverviewUsageView): {
|
|
6
7
|
readonly feature: string;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { EMPTY_VALUE_PLACEHOLDER } from "@cosmicdrift/kumiko-renderer/headless";
|
|
2
|
-
|
|
3
|
-
import { DEFAULT_LAYOUT_SLUG } from "../constants.js";
|
|
1
|
+
import { EMPTY_VALUE_PLACEHOLDER, pluralKey } from "@cosmicdrift/kumiko-renderer/headless";
|
|
2
|
+
import { DEFAULT_LAYOUT_SLUG } from "./constants.js";
|
|
4
3
|
// t() returns the key itself when no translation exists.
|
|
5
4
|
function translateOrRaw(t, key, raw) {
|
|
6
5
|
const translated = t(key);
|
|
@@ -11,11 +10,15 @@ export function variableLabelText(t, name, label) {
|
|
|
11
10
|
return label === undefined ? name : translateOrRaw(t, label, label);
|
|
12
11
|
}
|
|
13
12
|
const NOTIFICATION_SLUG = /^([^:]+):notify:([^:]+)$/;
|
|
13
|
+
// A seeded system template may store an i18n key as its title; plain text stays as typed.
|
|
14
|
+
export function templateTitleText(t, title) {
|
|
15
|
+
return translateOrRaw(t, title, title);
|
|
16
|
+
}
|
|
14
17
|
// A seeded system mail has no stored title, so its notification label from the
|
|
15
18
|
// owning feature names it; the slug stays the last resort.
|
|
16
19
|
export function templateTitle(t, row) {
|
|
17
20
|
if (row.title !== row.slug)
|
|
18
|
-
return row.title;
|
|
21
|
+
return templateTitleText(t, row.title);
|
|
19
22
|
const match = row.kind === "mail" ? NOTIFICATION_SLUG.exec(row.slug) : null;
|
|
20
23
|
if (match === null)
|
|
21
24
|
return row.slug;
|
|
@@ -40,7 +43,9 @@ export function usedByLabel(t, row) {
|
|
|
40
43
|
}
|
|
41
44
|
return row.usedByCount === 0
|
|
42
45
|
? EMPTY_VALUE_PLACEHOLDER
|
|
43
|
-
: t("documentDesigner.usedBy.templates",
|
|
46
|
+
: t(pluralKey("documentDesigner.usedBy.templates", row.usedByCount), {
|
|
47
|
+
count: row.usedByCount,
|
|
48
|
+
});
|
|
44
49
|
}
|
|
45
50
|
const first = row.usedBy[0];
|
|
46
51
|
return first === undefined ? EMPTY_VALUE_PLACEHOLDER : usageLabel(t, first);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-document-designer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.379.0",
|
|
4
4
|
"description": "Document designer for Kumiko: template overview, layout editor (mail layout, PDF letterhead) and the PDF template editor.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"./web": {
|
|
17
17
|
"types": "./dist/web/index.d.ts",
|
|
18
18
|
"default": "./dist/web/index.js"
|
|
19
|
-
}
|
|
19
|
+
},
|
|
20
|
+
"./web/styles.css": "./src/web/styles.css"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
23
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
24
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
25
|
-
"@cosmicdrift/kumiko-renderer-puppeteer-client": "0.
|
|
26
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
27
|
-
"pdfjs-dist": "6.3.289",
|
|
23
|
+
"@cosmicdrift/kumiko-bundled-features": "0.379.0",
|
|
24
|
+
"@cosmicdrift/kumiko-framework": "0.379.0",
|
|
25
|
+
"@cosmicdrift/kumiko-renderer": "0.379.0",
|
|
26
|
+
"@cosmicdrift/kumiko-renderer-puppeteer-client": "0.379.0",
|
|
27
|
+
"@cosmicdrift/kumiko-types": "0.379.0",
|
|
28
28
|
"react": "^19.2.6"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
@@ -51,11 +51,13 @@
|
|
|
51
51
|
"./web": {
|
|
52
52
|
"types": "./dist/web/index.d.ts",
|
|
53
53
|
"default": "./dist/web/index.js"
|
|
54
|
-
}
|
|
54
|
+
},
|
|
55
|
+
"./web/styles.css": "./src/web/styles.css"
|
|
55
56
|
}
|
|
56
57
|
},
|
|
57
58
|
"files": [
|
|
58
59
|
"dist",
|
|
60
|
+
"src/web/styles.css",
|
|
59
61
|
"src/changes.json",
|
|
60
62
|
"README.md",
|
|
61
63
|
"LICENSE"
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.379.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Designer rails wrap instead of clipping",
|
|
6
|
+
"detail": "A rail section wraps its action below the heading instead of clipping it, a resource name without spaces wraps inside its row instead of running under \"Remove\" and its meta text takes its own line instead of an ellipsis, the footer no longer offers a test mail when send-test-status says none can be sent, and the header label placeholder reads \"e.g. Reminder\" instead of an incident example. In a narrow window (1024px beside the app sidebar) both rails share the left column so the page keeps its room, and the header leaves save and publish to the footer below 1280px so the template title stays readable."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.379.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Long template names wrap in the template overview",
|
|
12
|
+
"detail": "In the template overview, a long template name such as \"Kündigungshinweis an den Betreiber\" now wraps to a second line instead of widening the first column. The overview fits at 1280 px again when billing-foundation templates are listed."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.379.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "Template overview shows locked tenant overrides",
|
|
18
|
+
"detail": "Rows with lockedOverride and lockedOverrideI18nKey show the status text as a warning badge in the status column. The row action \"Remove own version\" calls the existing resetToSystem with the new overview field lockedOverrideId (the tenant row; one locale per click). The status shows a short label with the long sentence as tooltip."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.378.0",
|
|
22
|
+
"type": "fix",
|
|
23
|
+
"title": "Mail preview fills the editor column",
|
|
24
|
+
"detail": "The preview frame (HTML and plain text) had a fixed height of 560px, so long mails were cut off mid-sentence with an empty grey area below. The frame now grows with the center column (at least 420px) and scrolls inside."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.378.0",
|
|
28
|
+
"type": "breaking",
|
|
29
|
+
"title": "Designer screens are SystemAdmin-only unless the app passes access",
|
|
30
|
+
"detail": "The templates screen, layout, PDF and mail editors are visible to SystemAdmin only by default. createDocumentDesignerFeature takes an optional access rule whose roles are added.",
|
|
31
|
+
"migration": "Pass createDocumentDesignerFeature({ access: templateEditAccess(tenantEditable) }) with the same entries given to the template resolver when TenantAdmins edit templates."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"version": "0.378.0",
|
|
35
|
+
"type": "improvement",
|
|
36
|
+
"title": "Stylesheet export for the designer",
|
|
37
|
+
"detail": "\"@cosmicdrift/kumiko-document-designer/web/styles.css\" registers the designer's own Tailwind sources. Apps that embed the designer import it after the renderer stylesheet."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version": "0.378.0",
|
|
41
|
+
"type": "fix",
|
|
42
|
+
"title": "Upload and test mail follow the server's capabilities",
|
|
43
|
+
"detail": "Upload is disabled with a hint when the files feature is not mounted, and the test mail button is disabled with a hint when the server has no email channel or no address for the user."
|
|
44
|
+
},
|
|
2
45
|
{
|
|
3
46
|
"version": "0.372.0",
|
|
4
47
|
"type": "fix",
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* Tailwind v4 resolves relative @source paths against the REALPATH of the
|
|
2
|
+
imported CSS file, so only the package itself knows where its sources live
|
|
3
|
+
in every install layout (workspace, node_modules, isolated or pnpm stores);
|
|
4
|
+
a path from the renderer's stylesheet would point at a sibling that is not
|
|
5
|
+
there. `./` is this package's src/web, `../../dist` its compiled twin
|
|
6
|
+
(class names survive as string literals in the .js). An app imports this
|
|
7
|
+
file after `@cosmicdrift/kumiko-renderer/web/styles.css`. */
|
|
8
|
+
@source "./**/*.{ts,tsx}";
|
|
9
|
+
@source "../../dist/**/*.js";
|
|
10
|
+
/* `tsc --build` emits compiled tests into dist in the monorepo; published dist has none. */
|
|
11
|
+
@source not "./**/__tests__/**";
|
|
12
|
+
@source not "../../dist/**/__tests__/**";
|