@cosmicdrift/kumiko-document-designer 0.0.0-stage → 0.363.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.
Files changed (114) hide show
  1. package/LICENSE +57 -0
  2. package/README.md +37 -2
  3. package/dist/constants.d.ts +24 -0
  4. package/dist/constants.js +33 -0
  5. package/dist/feature.d.ts +2 -0
  6. package/dist/feature.js +85 -0
  7. package/dist/i18n.d.ts +6 -0
  8. package/dist/i18n.js +670 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +2 -0
  11. package/dist/list-definition.d.ts +6 -0
  12. package/dist/list-definition.js +109 -0
  13. package/dist/web/cells.d.ts +10 -0
  14. package/dist/web/cells.js +87 -0
  15. package/dist/web/client-plugin.d.ts +12 -0
  16. package/dist/web/client-plugin.js +43 -0
  17. package/dist/web/compose-layout.d.ts +6 -0
  18. package/dist/web/compose-layout.js +13 -0
  19. package/dist/web/errors.d.ts +6 -0
  20. package/dist/web/errors.js +24 -0
  21. package/dist/web/field-issue-text.d.ts +4 -0
  22. package/dist/web/field-issue-text.js +12 -0
  23. package/dist/web/file-name.d.ts +2 -0
  24. package/dist/web/file-name.js +17 -0
  25. package/dist/web/i18n.d.ts +2 -0
  26. package/dist/web/i18n.js +170 -0
  27. package/dist/web/index.d.ts +6 -0
  28. package/dist/web/index.js +7 -0
  29. package/dist/web/layout-editor-body.d.ts +14 -0
  30. package/dist/web/layout-editor-body.js +142 -0
  31. package/dist/web/layout-editor.d.ts +2 -0
  32. package/dist/web/layout-editor.js +41 -0
  33. package/dist/web/layout-html.d.ts +7 -0
  34. package/dist/web/layout-html.js +81 -0
  35. package/dist/web/layout-rails.d.ts +18 -0
  36. package/dist/web/layout-rails.js +107 -0
  37. package/dist/web/layout-types.d.ts +62 -0
  38. package/dist/web/layout-types.js +1 -0
  39. package/dist/web/layout-users.d.ts +3 -0
  40. package/dist/web/layout-users.js +8 -0
  41. package/dist/web/mail-dialogs.d.ts +29 -0
  42. package/dist/web/mail-dialogs.js +15 -0
  43. package/dist/web/mail-draft.d.ts +17 -0
  44. package/dist/web/mail-draft.js +132 -0
  45. package/dist/web/mail-edit-center.d.ts +17 -0
  46. package/dist/web/mail-edit-center.js +11 -0
  47. package/dist/web/mail-editor-body.d.ts +18 -0
  48. package/dist/web/mail-editor-body.js +193 -0
  49. package/dist/web/mail-editor.d.ts +2 -0
  50. package/dist/web/mail-editor.js +71 -0
  51. package/dist/web/mail-footer.d.ts +21 -0
  52. package/dist/web/mail-footer.js +44 -0
  53. package/dist/web/mail-header.d.ts +23 -0
  54. package/dist/web/mail-header.js +16 -0
  55. package/dist/web/mail-left-rail.d.ts +18 -0
  56. package/dist/web/mail-left-rail.js +53 -0
  57. package/dist/web/mail-origin-bar.d.ts +17 -0
  58. package/dist/web/mail-origin-bar.js +84 -0
  59. package/dist/web/mail-pdf-link-modal.d.ts +11 -0
  60. package/dist/web/mail-pdf-link-modal.js +54 -0
  61. package/dist/web/mail-preview-side.d.ts +12 -0
  62. package/dist/web/mail-preview-side.js +48 -0
  63. package/dist/web/mail-preview.d.ts +20 -0
  64. package/dist/web/mail-preview.js +43 -0
  65. package/dist/web/mail-right-rail.d.ts +19 -0
  66. package/dist/web/mail-right-rail.js +47 -0
  67. package/dist/web/mail-types.d.ts +112 -0
  68. package/dist/web/mail-types.js +1 -0
  69. package/dist/web/mail-upload-modal.d.ts +16 -0
  70. package/dist/web/mail-upload-modal.js +56 -0
  71. package/dist/web/mail-versions.d.ts +6 -0
  72. package/dist/web/mail-versions.js +43 -0
  73. package/dist/web/origin-bar.d.ts +13 -0
  74. package/dist/web/origin-bar.js +82 -0
  75. package/dist/web/overrides-drawer.d.ts +9 -0
  76. package/dist/web/overrides-drawer.js +19 -0
  77. package/dist/web/overview-row.d.ts +21 -0
  78. package/dist/web/overview-row.js +47 -0
  79. package/dist/web/page-canvas.d.ts +18 -0
  80. package/dist/web/page-canvas.js +38 -0
  81. package/dist/web/pdf-meta.d.ts +23 -0
  82. package/dist/web/pdf-meta.js +101 -0
  83. package/dist/web/pdf-preview.d.ts +4 -0
  84. package/dist/web/pdf-preview.js +99 -0
  85. package/dist/web/pdf-template-editor.d.ts +8 -0
  86. package/dist/web/pdf-template-editor.js +326 -0
  87. package/dist/web/placeholder-panel.d.ts +21 -0
  88. package/dist/web/placeholder-panel.js +35 -0
  89. package/dist/web/preview-record-picker.d.ts +12 -0
  90. package/dist/web/preview-record-picker.js +7 -0
  91. package/dist/web/rail.d.ts +18 -0
  92. package/dist/web/rail.js +14 -0
  93. package/dist/web/settings-panel.d.ts +17 -0
  94. package/dist/web/settings-panel.js +25 -0
  95. package/dist/web/status-label.d.ts +7 -0
  96. package/dist/web/status-label.js +18 -0
  97. package/dist/web/template-rows.d.ts +9 -0
  98. package/dist/web/template-rows.js +34 -0
  99. package/dist/web/types.d.ts +103 -0
  100. package/dist/web/types.js +3 -0
  101. package/dist/web/use-insert-targets.d.ts +9 -0
  102. package/dist/web/use-insert-targets.js +17 -0
  103. package/dist/web/use-mail-persistence.d.ts +15 -0
  104. package/dist/web/use-mail-persistence.js +49 -0
  105. package/dist/web/use-overview-rows.d.ts +7 -0
  106. package/dist/web/use-overview-rows.js +19 -0
  107. package/dist/web/use-settled-value.d.ts +2 -0
  108. package/dist/web/use-settled-value.js +18 -0
  109. package/dist/web/use-test-mail.d.ts +15 -0
  110. package/dist/web/use-test-mail.js +27 -0
  111. package/dist/web/used-by-label.d.ts +9 -0
  112. package/dist/web/used-by-label.js +32 -0
  113. package/package.json +63 -4
  114. package/src/changes.json +20 -0
@@ -0,0 +1,6 @@
1
+ import { type ListColumnSpec, type ListFacetSpec, type RowAction } from "@cosmicdrift/kumiko-framework/engine";
2
+ export declare function templateColumns(options: {
3
+ readonly platform: boolean;
4
+ }): ListColumnSpec[];
5
+ export declare const TEMPLATE_FACETS: readonly ListFacetSpec[];
6
+ export declare function templateRowActions(): RowAction[];
@@ -0,0 +1,109 @@
1
+ import { i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { DocumentDesignerCells, TEMPLATE_EDITOR_SCREENS, } from "./constants.js";
3
+ function cell(component) {
4
+ return { react: { __component: component } };
5
+ }
6
+ const BASE_COLUMNS = [
7
+ {
8
+ field: "title",
9
+ label: i18nKey("documentDesigner.col.template"),
10
+ renderer: cell(DocumentDesignerCells.title),
11
+ },
12
+ {
13
+ field: "kindGroup",
14
+ label: i18nKey("documentDesigner.col.kind"),
15
+ renderer: cell(DocumentDesignerCells.kind),
16
+ },
17
+ {
18
+ field: "origin",
19
+ label: i18nKey("documentDesigner.col.origin"),
20
+ renderer: cell(DocumentDesignerCells.origin),
21
+ },
22
+ {
23
+ field: "locales",
24
+ label: i18nKey("documentDesigner.col.locales"),
25
+ hideOnNarrow: true,
26
+ renderer: cell(DocumentDesignerCells.locales),
27
+ },
28
+ {
29
+ field: "status",
30
+ label: i18nKey("documentDesigner.col.status"),
31
+ renderer: cell(DocumentDesignerCells.status),
32
+ },
33
+ {
34
+ field: "usedBy",
35
+ label: i18nKey("documentDesigner.col.usedBy"),
36
+ hideOnNarrow: true,
37
+ renderer: cell(DocumentDesignerCells.usedBy),
38
+ },
39
+ {
40
+ field: "modifiedAt",
41
+ label: i18nKey("documentDesigner.col.modifiedAt"),
42
+ hideOnNarrow: true,
43
+ renderer: cell(DocumentDesignerCells.modifiedAt),
44
+ },
45
+ ];
46
+ const OVERRIDDEN_BY_COLUMN = {
47
+ field: "overriddenBy",
48
+ label: i18nKey("documentDesigner.col.overriddenBy"),
49
+ virtual: true,
50
+ hideOnNarrow: true,
51
+ renderer: cell(DocumentDesignerCells.overriddenBy),
52
+ };
53
+ export function templateColumns(options) {
54
+ return options.platform ? [...BASE_COLUMNS, OVERRIDDEN_BY_COLUMN] : [...BASE_COLUMNS];
55
+ }
56
+ export const TEMPLATE_FACETS = [
57
+ {
58
+ field: "kindGroup",
59
+ type: "select",
60
+ label: i18nKey("documentDesigner.facet.kind"),
61
+ options: [
62
+ { value: "mail", label: i18nKey("documentDesigner.kind.mail") },
63
+ { value: "pdf", label: i18nKey("documentDesigner.kind.pdf") },
64
+ { value: "layout", label: i18nKey("documentDesigner.kind.layout") },
65
+ ],
66
+ },
67
+ {
68
+ field: "origin",
69
+ type: "select",
70
+ label: i18nKey("documentDesigner.facet.origin"),
71
+ options: [
72
+ { value: "system", label: i18nKey("documentDesigner.source.system") },
73
+ { value: "tenant", label: i18nKey("documentDesigner.source.tenant") },
74
+ ],
75
+ },
76
+ {
77
+ field: "status",
78
+ type: "select",
79
+ label: i18nKey("documentDesigner.facet.status"),
80
+ options: [
81
+ { value: "active", label: i18nKey("documentDesigner.status.active") },
82
+ { value: "draft", label: i18nKey("documentDesigner.status.draft") },
83
+ { value: "archived", label: i18nKey("documentDesigner.status.archived") },
84
+ ],
85
+ },
86
+ ];
87
+ const EDITABLE_KINDS = [
88
+ "mail",
89
+ "document-pdf",
90
+ "mail-layout",
91
+ "pdf-layout",
92
+ ];
93
+ export function templateRowActions() {
94
+ return EDITABLE_KINDS.flatMap((kind) => {
95
+ const screen = TEMPLATE_EDITOR_SCREENS[kind];
96
+ if (screen === undefined)
97
+ return [];
98
+ return [
99
+ {
100
+ kind: "navigate",
101
+ id: `edit-${kind}`,
102
+ label: i18nKey("documentDesigner.action.edit"),
103
+ screen,
104
+ params: { pick: ["slug", "kind"] },
105
+ visible: { field: "kind", eq: kind },
106
+ },
107
+ ];
108
+ });
109
+ }
@@ -0,0 +1,10 @@
1
+ import { type ColumnRendererComponent, type ColumnRendererProps } from "@cosmicdrift/kumiko-renderer";
2
+ import type { ReactNode } from "react";
3
+ export declare function TitleCell({ row }: ColumnRendererProps): ReactNode;
4
+ export declare function KindCell({ row }: ColumnRendererProps): ReactNode;
5
+ export declare function OriginCell({ row }: ColumnRendererProps): ReactNode;
6
+ export declare function createLocalesCell(appLocales: readonly string[]): ColumnRendererComponent;
7
+ export declare function StatusCell({ row }: ColumnRendererProps): ReactNode;
8
+ export declare function UsedByCell({ row }: ColumnRendererProps): ReactNode;
9
+ export declare function ModifiedCell({ row }: ColumnRendererProps): ReactNode;
10
+ export declare function OverriddenByCell({ row }: ColumnRendererProps): ReactNode;
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
+ import { toInstant } from "@cosmicdrift/kumiko-headless";
5
+ import { formatWhen, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
6
+ import { StatusBadge } from "@cosmicdrift/kumiko-renderer-web";
7
+ import { parseOverviewRow } from "./overview-row.js";
8
+ import { statusLabel, statusTone } from "./status-label.js";
9
+ import { usedByLabel } from "./used-by-label.js";
10
+ const EMPTY_MARK = "–";
11
+ function withRow(row, render) {
12
+ const parsed = parseOverviewRow(row);
13
+ return parsed === null ? EMPTY_MARK : render(parsed);
14
+ }
15
+ export function TitleCell({ row }) {
16
+ return withRow(row, (r) => (_jsxs("div", { children: [_jsx("div", { className: "font-medium", children: r.title }), _jsx("span", { className: "block max-w-40 truncate font-mono text-xs text-muted-foreground", title: r.slug, children: r.slug })] })));
17
+ }
18
+ const KIND_KEY = {
19
+ mail: "documentDesigner.kind.mail",
20
+ "document-pdf": "documentDesigner.kind.pdf",
21
+ "mail-layout": "documentDesigner.kind.layout",
22
+ "pdf-layout": "documentDesigner.kind.layout",
23
+ };
24
+ export function KindCell({ row }) {
25
+ const t = useTranslation();
26
+ return withRow(row, (r) => t(KIND_KEY[r.kind]));
27
+ }
28
+ export function OriginCell({ row }) {
29
+ const t = useTranslation();
30
+ return withRow(row, (r) => (_jsx(StatusBadge, { tone: r.origin === "tenant" ? "accent" : "muted", testId: "document-designer-origin", children: t(r.origin === "tenant" ? "documentDesigner.source.tenant" : "documentDesigner.source.system") })));
31
+ }
32
+ export function createLocalesCell(appLocales) {
33
+ return function LocalesCell({ row }) {
34
+ return withRow(row, (r) => {
35
+ if (r.kind === "mail-layout" || r.kind === "pdf-layout")
36
+ return EMPTY_MARK;
37
+ const present = new Set(r.locales);
38
+ const shown = [...appLocales, ...r.locales.filter((l) => !appLocales.includes(l))];
39
+ return (_jsx("span", { className: "inline-flex gap-1", children: shown.map((locale) => (_jsx("span", { "data-testid": `document-designer-locale-${locale}`, "data-present": present.has(locale), className: present.has(locale)
40
+ ? "rounded border border-transparent bg-muted px-1.5 text-xs font-medium uppercase"
41
+ : "rounded border border-dashed px-1.5 text-xs uppercase text-muted-foreground", children: locale }, locale))) }));
42
+ });
43
+ };
44
+ }
45
+ export function StatusCell({ row }) {
46
+ const t = useTranslation();
47
+ return withRow(row, (r) => (_jsx(StatusBadge, { tone: statusTone(r.status), testId: "document-designer-status", children: statusLabel(t, r.status, r.version) })));
48
+ }
49
+ export function UsedByCell({ row }) {
50
+ const t = useTranslation();
51
+ return withRow(row, (r) => {
52
+ const label = usedByLabel(t, r);
53
+ return (_jsx("span", { className: "block max-w-28 truncate", title: label, children: label }));
54
+ });
55
+ }
56
+ // Date only keeps the column narrow enough for the overview to fit without
57
+ // horizontal scroll; the full timestamp stays reachable via the tooltip.
58
+ function formatModifiedDate(value) {
59
+ try {
60
+ return toInstant(value).toLocaleString(undefined, { dateStyle: "medium" });
61
+ }
62
+ catch {
63
+ return value;
64
+ }
65
+ }
66
+ export function ModifiedCell({ row }) {
67
+ return withRow(row, (r) => r.modifiedAt === "" ? (EMPTY_MARK) : (_jsx("span", { className: "whitespace-nowrap", title: formatWhen(r.modifiedAt), children: formatModifiedDate(r.modifiedAt) })));
68
+ }
69
+ function OverriddenByCount({ slug, kind, }) {
70
+ const t = useTranslation();
71
+ const { data } = useQuery(TemplateResolverQueries.tenantOverrides, {
72
+ slug,
73
+ kind,
74
+ });
75
+ if (data === null)
76
+ return EMPTY_MARK;
77
+ const label = data.totalTenants === null
78
+ ? t("documentDesigner.overriddenBy.tenants", { active: data.activeOverrides })
79
+ : t("documentDesigner.overriddenBy.ofTotal", {
80
+ active: data.activeOverrides,
81
+ total: data.totalTenants,
82
+ });
83
+ return _jsx("span", { className: "whitespace-nowrap", children: label });
84
+ }
85
+ export function OverriddenByCell({ row }) {
86
+ return withRow(row, (r) => r.origin === "system" ? _jsx(OverriddenByCount, { slug: r.slug, kind: r.kind }) : EMPTY_MARK);
87
+ }
@@ -0,0 +1,12 @@
1
+ import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer-web";
2
+ import type { ReactNode } from "react";
3
+ /**
4
+ * Custom screen body: the template comes from the URL (`?slug=...&locale=...`),
5
+ * because custom screens receive no props.
6
+ */
7
+ export declare function PdfTemplateEditorScreen(): ReactNode;
8
+ export type DocumentDesignerClientOptions = {
9
+ /** Locales shown as boxes in the overview's languages column; templates in other locales are appended. */
10
+ readonly appLocales?: readonly string[];
11
+ };
12
+ export declare function documentDesignerClient(options?: DocumentDesignerClientOptions): ClientFeatureDefinition;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @runtime client
3
+ import { textBlocksClient } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
+ import { mergeTranslations, translationsByLocaleFromKeys, useNav, } from "@cosmicdrift/kumiko-renderer";
5
+ import { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, DocumentDesignerCells, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, } from "../constants.js";
6
+ import { DOCUMENT_DESIGNER_I18N } from "../i18n.js";
7
+ import { createLocalesCell, KindCell, ModifiedCell, OriginCell, OverriddenByCell, StatusCell, TitleCell, UsedByCell, } from "./cells.js";
8
+ import { documentDesignerTranslations } from "./i18n.js";
9
+ import { LayoutEditor } from "./layout-editor.js";
10
+ import { createMailEditor } from "./mail-editor.js";
11
+ import { PdfTemplateEditor } from "./pdf-template-editor.js";
12
+ /**
13
+ * Custom screen body: the template comes from the URL (`?slug=...&locale=...`),
14
+ * because custom screens receive no props.
15
+ */
16
+ export function PdfTemplateEditorScreen() {
17
+ const { searchParams, setSearchParams } = useNav();
18
+ const slug = searchParams["slug"];
19
+ if (slug === undefined || slug === "")
20
+ return null;
21
+ return (_jsx(PdfTemplateEditor, { slug: slug, ...(searchParams["locale"] !== undefined && { locale: searchParams["locale"] }), onLocaleChange: (locale) => setSearchParams({ locale }) }));
22
+ }
23
+ export function documentDesignerClient(options = {}) {
24
+ return {
25
+ name: DOCUMENT_DESIGNER_FEATURE,
26
+ columnRenderers: {
27
+ [DocumentDesignerCells.title]: TitleCell,
28
+ [DocumentDesignerCells.kind]: KindCell,
29
+ [DocumentDesignerCells.origin]: OriginCell,
30
+ [DocumentDesignerCells.locales]: createLocalesCell(options.appLocales ?? DEFAULT_TEMPLATE_LOCALES),
31
+ [DocumentDesignerCells.status]: StatusCell,
32
+ [DocumentDesignerCells.usedBy]: UsedByCell,
33
+ [DocumentDesignerCells.modifiedAt]: ModifiedCell,
34
+ [DocumentDesignerCells.overriddenBy]: OverriddenByCell,
35
+ },
36
+ components: {
37
+ [LAYOUT_EDITOR_SCREEN_ID]: LayoutEditor,
38
+ [PDF_TEMPLATE_EDITOR_SCREEN_ID]: PdfTemplateEditorScreen,
39
+ [MAIL_EDITOR_SCREEN_ID]: createMailEditor(options.appLocales ?? DEFAULT_TEMPLATE_LOCALES),
40
+ },
41
+ translations: mergeTranslations(mergeTranslations(textBlocksClient().translations ?? {}, documentDesignerTranslations), translationsByLocaleFromKeys(DOCUMENT_DESIGNER_I18N)),
42
+ };
43
+ }
@@ -0,0 +1,6 @@
1
+ export type LayoutRegionValues = {
2
+ readonly header: string;
3
+ readonly signature: string;
4
+ readonly footer: string;
5
+ };
6
+ export declare function composeLayoutHtml(regions: LayoutRegionValues): string;
@@ -0,0 +1,13 @@
1
+ // An untouched rich-text editor reports an empty paragraph rather than "".
2
+ const EMPTY_REGION = /^(\s|<p>(\s|<br\s*\/?>)*<\/p>)*$/;
3
+ // Same top-level div contract the server reads with readLayoutRegions: empty regions are
4
+ // left out, the content slot is always present.
5
+ export function composeLayoutHtml(regions) {
6
+ const region = (name, regionHtml) => EMPTY_REGION.test(regionHtml) ? "" : `<div data-${name}>${regionHtml}</div>`;
7
+ return [
8
+ region("header", regions.header),
9
+ "<div data-content></div>",
10
+ region("signature", regions.signature),
11
+ region("footer", regions.footer),
12
+ ].join("");
13
+ }
@@ -0,0 +1,6 @@
1
+ import type { DispatcherError } from "@cosmicdrift/kumiko-headless";
2
+ type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
3
+ export declare function isAlreadyActive(error: DispatcherError): boolean;
4
+ /** Plain-language text for a failed call; falls back to the server message when no translation exists. */
5
+ export declare function describeError(t: Translate, error: DispatcherError): string;
6
+ export {};
@@ -0,0 +1,24 @@
1
+ // @runtime client
2
+ import { TemplateResolverErrors } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
3
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
4
+ function reasonOf(error) {
5
+ const reason = propertyOf(error.details, "reason");
6
+ return typeof reason === "string" ? reason : undefined;
7
+ }
8
+ export function isAlreadyActive(error) {
9
+ return (error.code === TemplateResolverErrors.alreadyActive ||
10
+ reasonOf(error) === TemplateResolverErrors.alreadyActive);
11
+ }
12
+ const REASON_KEYS = {
13
+ [TemplateResolverErrors.resourceTooLarge]: "documentDesigner.error.resourceTooLarge",
14
+ [TemplateResolverErrors.resourceUnsupportedImage]: "documentDesigner.error.resourceUnsupportedImage",
15
+ };
16
+ /** Plain-language text for a failed call; falls back to the server message when no translation exists. */
17
+ export function describeError(t, error) {
18
+ const reason = reasonOf(error);
19
+ const reasonKey = reason === undefined ? undefined : REASON_KEYS[reason];
20
+ if (reasonKey !== undefined)
21
+ return t(reasonKey);
22
+ const translated = t(error.i18nKey, error.i18nParams);
23
+ return translated === error.i18nKey ? error.message : translated;
24
+ }
@@ -0,0 +1,4 @@
1
+ import type { DispatcherError } from "@cosmicdrift/kumiko-headless";
2
+ type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
3
+ export declare function errorTextWithFieldIssues(error: DispatcherError, translate: Translate): string;
4
+ export {};
@@ -0,0 +1,12 @@
1
+ // @runtime client
2
+ import { dispatcherErrorText } from "@cosmicdrift/kumiko-renderer";
3
+ // A validation_error alone says nothing; the server names the offending field
4
+ // and reason per issue, so the user gets those as text.
5
+ export function errorTextWithFieldIssues(error, translate) {
6
+ const headline = dispatcherErrorText(error, translate);
7
+ const issues = (error.details?.fields ?? []).map((issue) => {
8
+ const text = translate(issue.i18nKey, issue.params);
9
+ return text === issue.i18nKey ? `${issue.path}: ${issue.code}` : text;
10
+ });
11
+ return issues.length === 0 ? headline : `${headline} ${issues.join(" ")}`;
12
+ }
@@ -0,0 +1,2 @@
1
+ export declare function fetchStoredFileName(fileRefId: string): Promise<string | null>;
2
+ export declare const fileUrl: (fileRefId: string) => string;
@@ -0,0 +1,17 @@
1
+ // @runtime client
2
+ // The upload input only hands back the FileRef id; resources are keyed and named after the file.
3
+ export async function fetchStoredFileName(fileRefId) {
4
+ try {
5
+ const res = await fetch(`/api/files/${encodeURIComponent(fileRefId)}/meta`);
6
+ if (!res.ok)
7
+ return null;
8
+ const meta = await res.json();
9
+ if (typeof meta !== "object" || meta === null || !("fileName" in meta))
10
+ return null;
11
+ return typeof meta.fileName === "string" ? meta.fileName : null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ export const fileUrl = (fileRefId) => `/api/files/${encodeURIComponent(fileRefId)}`;
@@ -0,0 +1,2 @@
1
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
2
+ export declare const documentDesignerTranslations: TranslationsByLocale;
@@ -0,0 +1,170 @@
1
+ export const documentDesignerTranslations = {
2
+ en: {
3
+ "documentDesigner.actions.discard": "Discard changes",
4
+ "documentDesigner.actions.generatePdf": "Generate PDF",
5
+ "documentDesigner.actions.publish": "Publish",
6
+ "documentDesigner.actions.save": "Save",
7
+ "documentDesigner.breadcrumb.platform": "Platform",
8
+ "documentDesigner.breadcrumb.templates": "Templates",
9
+ "documentDesigner.canvas.caption": 'Page 1 · {format} · The preview shows page breaks only roughly; "Generate PDF" is authoritative.',
10
+ "documentDesigner.canvas.fromLayout": 'from layout "{layout}"',
11
+ "documentDesigner.error.resourceTooLarge": "The image is too large. Please choose a smaller file.",
12
+ "documentDesigner.error.resourceUnsupportedImage": "This image format is not supported. Use PNG, JPEG, GIF or WebP.",
13
+ "documentDesigner.locale": "Language",
14
+ "documentDesigner.localeDialog.cancel": "Cancel",
15
+ "documentDesigner.localeDialog.description": "The changes of this language have not been saved yet.",
16
+ "documentDesigner.localeDialog.discard": "Discard changes",
17
+ "documentDesigner.localeDialog.save": "Save and switch",
18
+ "documentDesigner.localeDialog.title": "Unsaved changes",
19
+ "documentDesigner.mode.edit": "Edit",
20
+ "documentDesigner.mode.label": "View",
21
+ "documentDesigner.mode.preview": "Preview",
22
+ "documentDesigner.notFound": 'Template "{slug}" was not found.',
23
+ "documentDesigner.origin.system": "System template",
24
+ "documentDesigner.origin.tenant": "Tenant customisation",
25
+ "documentDesigner.originBar.export": "Export as bundle",
26
+ "documentDesigner.originBar.noTotal": "System template for all tenants. {count} tenants have their own version and do not receive changes made here.",
27
+ "documentDesigner.originBar.withTotal": "System template for all tenants. {count} of {total} tenants have their own version and do not receive changes made here.",
28
+ "documentDesigner.originBar.showOverrides": "Show tenant customisations",
29
+ "documentDesigner.overrides.active": "Customised and active",
30
+ "documentDesigner.overrides.draftOnly": "Draft only",
31
+ "documentDesigner.overrides.none": "No own version",
32
+ "documentDesigner.overrides.title": "Tenant customisations",
33
+ "documentDesigner.overrides.total": "Tenants in total",
34
+ "documentDesigner.overrides.usedBy": "Used by",
35
+ "documentDesigner.overrides.usedByEmpty": "No registered notification uses this template directly.",
36
+ "documentDesigner.pdfUnavailable": "PDF generation is not available: {message}",
37
+ "documentDesigner.placeholders.fromLayout": "Layout",
38
+ "documentDesigner.placeholders.layoutHint": 'Images travel with the PDF as data URIs. Header and footer come from the layout "{layout}" and are the same on every page.',
39
+ "documentDesigner.placeholders.missing": "missing",
40
+ "documentDesigner.placeholders.system": "System placeholders",
41
+ "documentDesigner.placeholders.title": "Placeholders",
42
+ "documentDesigner.preview.loading": "Generating PDF...",
43
+ "documentDesigner.preview.pageOf": "Page {page} of {pages}",
44
+ "documentDesigner.preview.renderFailed": "The PDF could not be displayed.",
45
+ "documentDesigner.preview.stale": "The preview is out of date. Generate the PDF again.",
46
+ "documentDesigner.resources.empty": "No images yet.",
47
+ "documentDesigner.resources.insert": "Insert",
48
+ "documentDesigner.resources.insertNamed": "Insert image {key}",
49
+ "documentDesigner.resources.saveFirst": "Save the template first, then you can upload images.",
50
+ "documentDesigner.resources.title": "Resources",
51
+ "documentDesigner.resources.upload": "+ Upload",
52
+ "documentDesigner.settings.fileName": "File name",
53
+ "documentDesigner.settings.landscape": "Landscape",
54
+ "documentDesigner.settings.layout": "Layout",
55
+ "documentDesigner.settings.layoutDefault": "Default",
56
+ "documentDesigner.settings.layoutHeaderFooter": "Header and footer from layout",
57
+ "documentDesigner.settings.marginBottom": "Bottom (mm)",
58
+ "documentDesigner.settings.marginLeft": "Left (mm)",
59
+ "documentDesigner.settings.marginRight": "Right (mm)",
60
+ "documentDesigner.settings.marginTop": "Top (mm)",
61
+ "documentDesigner.settings.metaAuthor": "Author",
62
+ "documentDesigner.settings.metadata": "PDF metadata",
63
+ "documentDesigner.settings.metaLanguage": "Language",
64
+ "documentDesigner.settings.metaTitle": "Title",
65
+ "documentDesigner.settings.orientation": "Orientation",
66
+ "documentDesigner.settings.page": "Page",
67
+ "documentDesigner.settings.pageFormat": "Page format",
68
+ "documentDesigner.settings.portrait": "Portrait",
69
+ "documentDesigner.settings.printBackground": "Print backgrounds and colours",
70
+ "documentDesigner.settings.storage": "Storage",
71
+ "documentDesigner.settings.storageHint": "Every generated PDF carries the template id and version. Invoices stay verifiable even if the template changes later.",
72
+ "documentDesigner.settings.versions": "Versions",
73
+ "documentDesigner.status.active": "Active",
74
+ "documentDesigner.status.activeVersion": "Active v{version}",
75
+ "documentDesigner.status.draft": "Draft",
76
+ "documentDesigner.status.draftVersion": "Draft v{version}",
77
+ "documentDesigner.system.imprint": "Imprint",
78
+ "documentDesigner.system.page": "Page / Pages",
79
+ "documentDesigner.system.pages": "Pages",
80
+ "documentDesigner.system.signature": "Signature",
81
+ "documentDesigner.system.tenantName": "Tenant name",
82
+ "documentDesigner.validation.lastProbe": "Last test run: {pages} pages, {size}, {seconds} s",
83
+ "documentDesigner.validation.missingRequired": 'Required placeholder "{name}" is missing. Publishing is blocked.',
84
+ "documentDesigner.validation.ok": "All required placeholders are present.",
85
+ },
86
+ de: {
87
+ "documentDesigner.actions.discard": "Änderungen verwerfen",
88
+ "documentDesigner.actions.generatePdf": "PDF erzeugen",
89
+ "documentDesigner.actions.publish": "Veröffentlichen",
90
+ "documentDesigner.actions.save": "Speichern",
91
+ "documentDesigner.breadcrumb.platform": "Plattform",
92
+ "documentDesigner.breadcrumb.templates": "Vorlagen",
93
+ "documentDesigner.canvas.caption": 'Seite 1 · {format} · Die Vorschau zeigt Seitengrenzen nur ungefähr, verbindlich ist "PDF erzeugen".',
94
+ "documentDesigner.canvas.fromLayout": 'aus Layout "{layout}"',
95
+ "documentDesigner.error.resourceTooLarge": "Das Bild ist zu groß. Bitte eine kleinere Datei wählen.",
96
+ "documentDesigner.error.resourceUnsupportedImage": "Dieses Bildformat wird nicht unterstützt. Nutzen Sie PNG, JPEG, GIF oder WebP.",
97
+ "documentDesigner.locale": "Sprache",
98
+ "documentDesigner.localeDialog.cancel": "Abbrechen",
99
+ "documentDesigner.localeDialog.description": "Die Änderungen in dieser Sprache sind noch nicht gespeichert.",
100
+ "documentDesigner.localeDialog.discard": "Änderungen verwerfen",
101
+ "documentDesigner.localeDialog.save": "Speichern und wechseln",
102
+ "documentDesigner.localeDialog.title": "Ungespeicherte Änderungen",
103
+ "documentDesigner.mode.edit": "Bearbeiten",
104
+ "documentDesigner.mode.label": "Ansicht",
105
+ "documentDesigner.mode.preview": "Vorschau",
106
+ "documentDesigner.notFound": 'Die Vorlage "{slug}" wurde nicht gefunden.',
107
+ "documentDesigner.origin.system": "System-Vorlage",
108
+ "documentDesigner.origin.tenant": "Mandanten-Anpassung",
109
+ "documentDesigner.originBar.export": "Als Bundle exportieren",
110
+ "documentDesigner.originBar.noTotal": "System-Vorlage für alle Mandanten. {count} Mandanten haben eine eigene Fassung und bekommen Änderungen hier nicht.",
111
+ "documentDesigner.originBar.withTotal": "System-Vorlage für alle Mandanten. {count} von {total} Mandanten haben eine eigene Fassung und bekommen Änderungen hier nicht.",
112
+ "documentDesigner.originBar.showOverrides": "Anpassungen der Mandanten anzeigen",
113
+ "documentDesigner.overrides.active": "Angepasst und aktiv",
114
+ "documentDesigner.overrides.draftOnly": "Nur Entwurf",
115
+ "documentDesigner.overrides.none": "Ohne eigene Fassung",
116
+ "documentDesigner.overrides.title": "Anpassungen der Mandanten",
117
+ "documentDesigner.overrides.total": "Mandanten gesamt",
118
+ "documentDesigner.overrides.usedBy": "Wird verwendet von",
119
+ "documentDesigner.overrides.usedByEmpty": "Keine registrierte Benachrichtigung nutzt diese Vorlage direkt.",
120
+ "documentDesigner.pdfUnavailable": "PDF-Erzeugung nicht verfügbar: {message}",
121
+ "documentDesigner.placeholders.fromLayout": "Layout",
122
+ "documentDesigner.placeholders.layoutHint": 'Bilder gehen im PDF als Data-URI mit. Kopf- und Fußzeile kommen aus dem Layout "{layout}" und sind auf allen Seiten gleich.',
123
+ "documentDesigner.placeholders.missing": "fehlt",
124
+ "documentDesigner.placeholders.system": "System-Platzhalter",
125
+ "documentDesigner.placeholders.title": "Platzhalter",
126
+ "documentDesigner.preview.loading": "PDF wird erzeugt ...",
127
+ "documentDesigner.preview.pageOf": "Seite {page} von {pages}",
128
+ "documentDesigner.preview.renderFailed": "Das PDF konnte nicht angezeigt werden.",
129
+ "documentDesigner.preview.stale": "Die Vorschau ist veraltet. Bitte das PDF neu erzeugen.",
130
+ "documentDesigner.resources.empty": "Noch keine Bilder.",
131
+ "documentDesigner.resources.insert": "Einfügen",
132
+ "documentDesigner.resources.insertNamed": "Bild {key} einfügen",
133
+ "documentDesigner.resources.saveFirst": "Speichern Sie die Vorlage zuerst, danach können Bilder hochgeladen werden.",
134
+ "documentDesigner.resources.title": "Ressourcen",
135
+ "documentDesigner.resources.upload": "+ Hochladen",
136
+ "documentDesigner.settings.fileName": "Dateiname",
137
+ "documentDesigner.settings.landscape": "Querformat",
138
+ "documentDesigner.settings.layout": "Layout",
139
+ "documentDesigner.settings.layoutDefault": "Standard",
140
+ "documentDesigner.settings.layoutHeaderFooter": "Kopf- und Fußzeile aus Layout",
141
+ "documentDesigner.settings.marginBottom": "Unten (mm)",
142
+ "documentDesigner.settings.marginLeft": "Links (mm)",
143
+ "documentDesigner.settings.marginRight": "Rechts (mm)",
144
+ "documentDesigner.settings.marginTop": "Oben (mm)",
145
+ "documentDesigner.settings.metaAuthor": "Autor",
146
+ "documentDesigner.settings.metadata": "Metadaten im PDF",
147
+ "documentDesigner.settings.metaLanguage": "Sprache",
148
+ "documentDesigner.settings.metaTitle": "Titel",
149
+ "documentDesigner.settings.orientation": "Ausrichtung",
150
+ "documentDesigner.settings.page": "Seite",
151
+ "documentDesigner.settings.pageFormat": "Seitenformat",
152
+ "documentDesigner.settings.portrait": "Hochformat",
153
+ "documentDesigner.settings.printBackground": "Hintergründe und Farben drucken",
154
+ "documentDesigner.settings.storage": "Ablage",
155
+ "documentDesigner.settings.storageHint": "Jedes erzeugte PDF trägt Vorlagen-Id und Version. Rechnungen bleiben so nachweisbar, auch wenn die Vorlage später geändert wird.",
156
+ "documentDesigner.settings.versions": "Versionen",
157
+ "documentDesigner.status.active": "Aktiv",
158
+ "documentDesigner.status.activeVersion": "Aktiv v{version}",
159
+ "documentDesigner.status.draft": "Entwurf",
160
+ "documentDesigner.status.draftVersion": "Entwurf v{version}",
161
+ "documentDesigner.system.imprint": "Impressum",
162
+ "documentDesigner.system.page": "Seite / Seiten",
163
+ "documentDesigner.system.pages": "Seiten",
164
+ "documentDesigner.system.signature": "Signatur",
165
+ "documentDesigner.system.tenantName": "Mandantenname",
166
+ "documentDesigner.validation.lastProbe": "Letzte Probe-Erzeugung: {pages} Seiten, {size}, {seconds} s",
167
+ "documentDesigner.validation.missingRequired": 'Pflicht-Platzhalter "{name}" fehlt. Veröffentlichen ist gesperrt.',
168
+ "documentDesigner.validation.ok": "Alle Pflicht-Platzhalter vorhanden.",
169
+ },
170
+ };
@@ -0,0 +1,6 @@
1
+ export { type DocumentDesignerClientOptions, documentDesignerClient, PdfTemplateEditorScreen, } from "./client-plugin.js";
2
+ export { composeLayoutHtml, type LayoutRegionValues } from "./compose-layout.js";
3
+ export { documentDesignerTranslations } from "./i18n.js";
4
+ export { PdfTemplateEditor, type PdfTemplateEditorProps } from "./pdf-template-editor.js";
5
+ export { type PreviewRecordPicker, type PreviewRecordPickerProps, PreviewRecordPickerProvider, } from "./preview-record-picker.js";
6
+ export { usageLabel, usedByLabel } from "./used-by-label.js";
@@ -0,0 +1,7 @@
1
+ // @runtime client
2
+ export { documentDesignerClient, PdfTemplateEditorScreen, } from "./client-plugin.js";
3
+ export { composeLayoutHtml } from "./compose-layout.js";
4
+ export { documentDesignerTranslations } from "./i18n.js";
5
+ export { PdfTemplateEditor } from "./pdf-template-editor.js";
6
+ export { PreviewRecordPickerProvider, } from "./preview-record-picker.js";
7
+ export { usageLabel, usedByLabel } from "./used-by-label.js";
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from "react";
2
+ import type { LayoutKind, LayoutSourceView, TenantOverrideCounts } from "./layout-types.js";
3
+ import type { OverviewRowView } from "./overview-row.js";
4
+ export declare function LayoutEditorBody({ kind, slug, isSystemAdmin, source, overviewRows, overrides, onKindChange, onReload, onBack, }: {
5
+ readonly kind: LayoutKind;
6
+ readonly slug: string;
7
+ readonly isSystemAdmin: boolean;
8
+ readonly source: LayoutSourceView;
9
+ readonly overviewRows: readonly OverviewRowView[];
10
+ readonly overrides: TenantOverrideCounts | null;
11
+ readonly onKindChange: (kind: LayoutKind) => void;
12
+ readonly onReload: () => Promise<void>;
13
+ readonly onBack: () => void;
14
+ }): ReactNode;