@cosmicdrift/kumiko-document-designer 0.378.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.
Files changed (66) hide show
  1. package/dist/feature.js +2 -2
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/list-definition.js +14 -2
  5. package/dist/web/cells.js +3 -4
  6. package/dist/web/client-plugin.js +3 -4
  7. package/dist/{constants.js → web/constants.js} +0 -1
  8. package/dist/web/default-translations.d.ts +2 -0
  9. package/dist/web/default-translations.js +218 -0
  10. package/dist/web/editor-header.js +1 -2
  11. package/dist/web/editor-locale.js +1 -2
  12. package/dist/web/editor-shell.js +8 -4
  13. package/dist/web/errors.js +0 -1
  14. package/dist/web/field-issue-text.js +0 -1
  15. package/dist/web/file-name.js +0 -1
  16. package/dist/web/i18n.d.ts +6 -2
  17. package/dist/web/i18n.js +873 -216
  18. package/dist/web/index.d.ts +1 -1
  19. package/dist/web/index.js +1 -2
  20. package/dist/web/layout-editor-body.js +4 -4
  21. package/dist/web/layout-editor.js +1 -2
  22. package/dist/web/layout-html.js +0 -1
  23. package/dist/web/layout-meta.js +0 -1
  24. package/dist/web/layout-rails.js +3 -3
  25. package/dist/web/layout-users.js +1 -2
  26. package/dist/web/mail-dialogs.js +0 -1
  27. package/dist/web/mail-draft.js +4 -3
  28. package/dist/web/mail-edit-center.js +0 -1
  29. package/dist/web/mail-editor-body.js +3 -3
  30. package/dist/web/mail-editor.js +1 -2
  31. package/dist/web/mail-footer.d.ts +1 -1
  32. package/dist/web/mail-footer.js +2 -2
  33. package/dist/web/mail-left-rail.js +0 -1
  34. package/dist/web/mail-missing-locale.js +0 -1
  35. package/dist/web/mail-origin-bar.js +0 -1
  36. package/dist/web/mail-pdf-link-modal.js +5 -2
  37. package/dist/web/mail-preview-side.js +0 -1
  38. package/dist/web/mail-preview.js +0 -1
  39. package/dist/web/mail-right-rail.js +1 -2
  40. package/dist/web/mail-upload-modal.js +0 -1
  41. package/dist/web/mail-versions.js +0 -1
  42. package/dist/web/origin-bar.js +2 -3
  43. package/dist/web/overrides-drawer.js +0 -1
  44. package/dist/web/overview-row.d.ts +4 -1
  45. package/dist/web/overview-row.js +3 -1
  46. package/dist/web/page-canvas.js +0 -1
  47. package/dist/web/pdf-left-rail.js +0 -1
  48. package/dist/web/pdf-meta.js +0 -1
  49. package/dist/web/pdf-template-editor.js +1 -2
  50. package/dist/web/preview-record-picker.js +0 -1
  51. package/dist/web/rail.js +8 -4
  52. package/dist/web/settings-panel.js +0 -1
  53. package/dist/web/template-rows.js +0 -1
  54. package/dist/web/use-files-capability.js +1 -2
  55. package/dist/web/use-mail-persistence.js +0 -1
  56. package/dist/web/use-overview-rows.js +0 -1
  57. package/dist/web/use-own-address.js +1 -2
  58. package/dist/web/use-settled-value.js +0 -1
  59. package/dist/web/use-test-mail.js +0 -1
  60. package/dist/web/used-by-label.d.ts +1 -0
  61. package/dist/web/used-by-label.js +10 -5
  62. package/package.json +6 -6
  63. package/src/changes.json +18 -0
  64. package/dist/i18n.d.ts +0 -6
  65. package/dist/i18n.js +0 -848
  66. /package/dist/{constants.d.ts → web/constants.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  export { type DocumentDesignerClientOptions, documentDesignerClient, PdfTemplateEditorScreen, } from "./client-plugin.tsx";
2
2
  export { composeLayoutHtml, type LayoutRegionValues } from "./compose-layout.ts";
3
- export { documentDesignerTranslations } from "./i18n.ts";
3
+ export { documentDesignerTranslations } from "./default-translations.ts";
4
4
  export { PdfTemplateEditor, type PdfTemplateEditorProps } from "./pdf-template-editor.tsx";
5
5
  export { type PreviewRecordPicker, type PreviewRecordPickerProps, PreviewRecordPickerProvider, } from "./preview-record-picker.tsx";
6
6
  export { usageLabel, usedByLabel } from "./used-by-label.ts";
package/dist/web/index.js CHANGED
@@ -1,7 +1,6 @@
1
- // @runtime client
2
1
  export { documentDesignerClient, PdfTemplateEditorScreen, } from "./client-plugin.js";
3
2
  export { composeLayoutHtml } from "./compose-layout.js";
4
- export { documentDesignerTranslations } from "./i18n.js";
3
+ export { documentDesignerTranslations } from "./default-translations.js";
5
4
  export { PdfTemplateEditor } from "./pdf-template-editor.js";
6
5
  export { PreviewRecordPickerProvider, } from "./preview-record-picker.js";
7
6
  export { usageLabel, usedByLabel } from "./used-by-label.js";
@@ -1,11 +1,11 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { DocumentContentEditor, MAIL_DOCUMENT_BLOCKS, PDF_DOCUMENT_BLOCKS, TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { dispatcherErrorText, useMutation, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
4
+ import { pluralKey } from "@cosmicdrift/kumiko-renderer/headless";
5
5
  import { cn, ModeSwitch, TextareaField } from "@cosmicdrift/kumiko-renderer/web";
6
6
  import { useState } from "react";
7
- import { BRANDING_QUERY_QN } from "../constants.js";
8
7
  import { composeLayoutHtml } from "./compose-layout.js";
8
+ import { BRANDING_QUERY_QN } from "./constants.js";
9
9
  import { EditorHeader } from "./editor-header.js";
10
10
  import { EditorBody, EditorFooter, EditorFrame } from "./editor-shell.js";
11
11
  import { fileUrl } from "./file-name.js";
@@ -137,9 +137,9 @@ export function LayoutEditorBody({ kind, slug, isSystemAdmin, source, overviewRo
137
137
  ? { tone: "bad", text: issueText(t, blockingIssue) }
138
138
  : {
139
139
  tone: "ok",
140
- text: t(isMail
140
+ text: t(pluralKey(isMail
141
141
  ? "documentDesigner.editor.footer.okMail"
142
- : "documentDesigner.editor.footer.okPdf", { count: userCount }),
142
+ : "documentDesigner.editor.footer.okPdf", userCount), { count: userCount }),
143
143
  };
144
144
  const accentColor = branding.data?.accentColor ?? "";
145
145
  const showAccentLine = isMail && appearanceEditable && appearance.accentLine === "branding" && isHexColor(accentColor);
@@ -1,10 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { useNav, useQuery, useUserRoles } from "@cosmicdrift/kumiko-renderer";
5
4
  import { ErrorState, LoadingState } from "@cosmicdrift/kumiko-renderer/web";
6
5
  import { useState } from "react";
7
- import { DEFAULT_LAYOUT_SLUG } from "../constants.js";
6
+ import { DEFAULT_LAYOUT_SLUG } from "./constants.js";
8
7
  import { LayoutEditorBody } from "./layout-editor-body.js";
9
8
  import { layoutUserKind } from "./layout-users.js";
10
9
  import { parseOverviewRow } from "./overview-row.js";
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { cn } from "@cosmicdrift/kumiko-renderer/web";
4
3
  import { createElement, useMemo } from "react";
5
4
  // Layout regions arrive server-sanitised, but the preview still renders them
@@ -1,4 +1,3 @@
1
- // @runtime client
2
1
  // Mirrors the mail-layout meta of template-resolver (template-meta.ts); the server parses it again on every write and render.
3
2
  export const LAYOUT_WIDTHS = [560, 600, 640];
4
3
  export const LAYOUT_FONTS = ["sans", "serif"];
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { useAppFeatures, useNav, usePrimitives, useTranslation, } from "@cosmicdrift/kumiko-renderer";
4
- import { BRANDING_SCREEN_QN } from "../constants.js";
3
+ import { BRANDING_SCREEN_QN } from "./constants.js";
5
4
  import { isHexColor, LAYOUT_ACCENT_LINES, LAYOUT_BUTTON_STYLES, LAYOUT_FONTS, LAYOUT_WIDTHS, } from "./layout-meta.js";
6
5
  import { layoutUsers } from "./layout-users.js";
7
6
  import { PLACEHOLDER_SYMBOL, SYSTEM_PLACEHOLDER_TYPE, systemPlaceholderLabel, } from "./mail-draft.js";
8
7
  import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
9
8
  import { statusLabel } from "./status-label.js";
9
+ import { templateTitleText } from "./used-by-label.js";
10
10
  const BRANDING_SCREEN_SHORT_ID = BRANDING_SCREEN_QN.slice(BRANDING_SCREEN_QN.lastIndexOf(":") + 1);
11
11
  function logoName(logoUrl) {
12
12
  const path = logoUrl.split("?")[0] ?? "";
@@ -90,7 +90,7 @@ export function AppearanceRail({ appearance, onChange, fromMjml, }) {
90
90
  export function UsedByRail({ layoutSlug, kind, rows, }) {
91
91
  const t = useTranslation();
92
92
  const users = layoutUsers(rows, kind, layoutSlug);
93
- return (_jsx(RailSection, { title: t("documentDesigner.usedBy.title"), testId: "document-designer-used-by", children: users.length === 0 ? (_jsx(RailHint, { children: t("documentDesigner.usedBy.none") })) : (users.map((row) => (_jsx(RailRow, { testId: `used-by-${row.id}`, label: row.title, trailing: row.origin === "system"
93
+ return (_jsx(RailSection, { title: t("documentDesigner.usedBy.title"), testId: "document-designer-used-by", children: users.length === 0 ? (_jsx(RailHint, { children: t("documentDesigner.usedBy.none") })) : (users.map((row) => (_jsx(RailRow, { testId: `used-by-${row.id}`, label: templateTitleText(t, row.title), trailing: row.origin === "system"
94
94
  ? t("documentDesigner.source.system")
95
95
  : statusLabel(t, row.status, row.version) }, row.id)))) }));
96
96
  }
@@ -1,5 +1,4 @@
1
- // @runtime client
2
- import { DEFAULT_LAYOUT_SLUG } from "../constants.js";
1
+ import { DEFAULT_LAYOUT_SLUG } from "./constants.js";
3
2
  export function layoutUserKind(kind) {
4
3
  return kind === "mail-layout" ? "mail" : "document-pdf";
5
4
  }
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
3
  export function MailConfirmDialogs({ dialog, onClose, onDiscard, onRemove, onLeaveSave, onLeaveDiscard, }) {
5
4
  const t = useTranslation();
@@ -1,5 +1,4 @@
1
- // @runtime client
2
- import { DOCUMENT_FALLBACK_CURRENCY, formatDocumentDate, formatDocumentMoney, formatDocumentPercent, } from "@cosmicdrift/kumiko-renderer/headless";
1
+ import { DOCUMENT_FALLBACK_CURRENCY, formatDocumentDate, formatDocumentMoney, formatDocumentPercent, pluralKey, } from "@cosmicdrift/kumiko-renderer/headless";
3
2
  import { variableLabelText } from "./used-by-label.js";
4
3
  export const EMPTY_DRAFT = {
5
4
  subject: "",
@@ -167,7 +166,9 @@ export function variableLabelMap(t, variables) {
167
166
  }
168
167
  export function displayValue(t, value, type, format) {
169
168
  if (Array.isArray(value))
170
- return t("documentDesigner.mail.preview.entries", { count: value.length });
169
+ return t(pluralKey("documentDesigner.mail.preview.entries", value.length), {
170
+ count: value.length,
171
+ });
171
172
  if (type === "money") {
172
173
  if (typeof value === "number" && format.tenantCurrency === undefined)
173
174
  return String(value);
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { DocumentContentEditor, MAIL_DOCUMENT_BLOCKS, PlaceholderLineEditor, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { useTranslation } from "@cosmicdrift/kumiko-renderer";
5
4
  const renderMailPaper = (editable) => (_jsx("div", { className: "mx-auto w-full max-w-[600px] bg-card px-10 py-7 shadow-md", children: editable }));
@@ -1,5 +1,4 @@
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 { dispatcherErrorText, useMutation, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { useState } from "react";
@@ -25,6 +24,7 @@ import { useOverviewRows } from "./use-overview-rows.js";
25
24
  import { useOwnAddress } from "./use-own-address.js";
26
25
  import { useSettledValue } from "./use-settled-value.js";
27
26
  import { useTestMail } from "./use-test-mail.js";
27
+ import { templateTitleText } from "./used-by-label.js";
28
28
  // The editor replaces this token with the decoded image size once the preview image loaded.
29
29
  const SIZE_TOKEN = "{size}";
30
30
  function uniqueValues(values) {
@@ -116,7 +116,7 @@ export function MailEditorBody(props) {
116
116
  perform(target);
117
117
  };
118
118
  const resources = parseLinkedResources(row?.linkedResources);
119
- const pdfTitles = new Map(pdfTemplates.rows.map((template) => [template.slug, template.title]));
119
+ const pdfTitles = new Map(pdfTemplates.rows.map((template) => [template.slug, templateTitleText(t, template.title)]));
120
120
  const resourcePreviews = Object.fromEntries(resources.flatMap((resource) => resource.kind === "image"
121
121
  ? [
122
122
  [
@@ -169,7 +169,7 @@ export function MailEditorBody(props) {
169
169
  const attachments = resources.filter((resource) => resource.kind !== "image");
170
170
  const footer = locked || validation === undefined || validation === null
171
171
  ? null
172
- : footerMessage(t, validation, mode === "preview");
172
+ : footerMessage(t, validation, mode === "preview" && testMail.availability.canSend);
173
173
  const fieldErrors = locked ? {} : metaFieldMessages(t, validation ?? null);
174
174
  const publish = () => void saveAndReload(true);
175
175
  const controls = (_jsx(EditorLocaleSwitch, { testIdPrefix: "mail", locales: props.locales, locale: locale, onLocale: (next) => leave({ kind: "locale", locale: next }) }));
@@ -1,10 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { useLocale, useNav, useQuery, useTranslation, useUserRoles, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { LoadingState } from "@cosmicdrift/kumiko-renderer/web";
6
5
  import { useState } from "react";
7
- import { DEFAULT_TEMPLATE_LOCALES } from "../constants.js";
6
+ import { DEFAULT_TEMPLATE_LOCALES } from "./constants.js";
8
7
  import { defaultLocaleOf } from "./editor-locale.js";
9
8
  import { MailEditorBody } from "./mail-editor-body.js";
10
9
  import { useOverviewRows } from "./use-overview-rows.js";
@@ -1,6 +1,6 @@
1
1
  import type { MailValidation } from "./mail-types.ts";
2
2
  type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
3
- export declare function footerMessage(t: Translate, validation: MailValidation, previewMode: boolean): {
3
+ export declare function footerMessage(t: Translate, validation: MailValidation, offersTestMail: boolean): {
4
4
  readonly tone: "bad" | "warn" | "ok";
5
5
  readonly text: string;
6
6
  };
@@ -10,11 +10,11 @@ function missingLabels(t, validation) {
10
10
  }
11
11
  // The footer sentence: missing required placeholders first, then other blocking issues,
12
12
  // then hints (not-yet-uploaded resources), else "ready".
13
- export function footerMessage(t, validation, previewMode) {
13
+ export function footerMessage(t, validation, offersTestMail) {
14
14
  if (validation.missingRequired.length > 0) {
15
15
  return {
16
16
  tone: "bad",
17
- text: t(previewMode
17
+ text: t(offersTestMail
18
18
  ? "documentDesigner.mail.footer.missingPreview"
19
19
  : "documentDesigner.mail.footer.missing", { names: missingLabels(t, validation) }),
20
20
  };
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
3
  import { LAYOUT_OWNED_PLACEHOLDERS, PLACEHOLDER_SYMBOL, SYSTEM_PLACEHOLDER_TYPE, systemPlaceholderLabel, } from "./mail-draft.js";
5
4
  import { RailHint, RailRow, RailSection } from "./rail.js";
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { dispatcherErrorText, useMutation, usePrimitives, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { useState } from "react";
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { dispatcherErrorText, formatWhen, useMutation, usePrimitives, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { EMPTY_VALUE_PLACEHOLDER } from "@cosmicdrift/kumiko-renderer/headless";
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { PlaceholderLineEditor, TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { dispatcherErrorText, useMutation, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { EmptyState } from "@cosmicdrift/kumiko-renderer/web";
6
5
  import { useState } from "react";
7
6
  import { lastSlugSegment, parseJsonRecord, uniqueResourceKey } from "./mail-draft.js";
7
+ import { templateTitleText } from "./used-by-label.js";
8
8
  function pdfRowIdOf(summaries, slug) {
9
9
  const ofSlug = (summaries ?? []).filter((row) => row.slug === slug);
10
10
  return (ofSlug.find((row) => row.scope === "tenant") ?? ofSlug[0])?.id ?? null;
@@ -42,7 +42,10 @@ function PdfLinkForm({ templates, variableLabels, templateId, takenKeys, onLinke
42
42
  return (_jsxs("div", { className: "flex flex-col gap-4", children: [error !== null && (_jsx(Banner, { variant: "error", testId: "mail-pdf-link-error", children: error })), _jsx(Field, { id: "mail-pdf-template", label: t("documentDesigner.mail.pdfLink.template"), children: _jsx(Input, { kind: "select", id: "mail-pdf-template", name: "mail-pdf-template", value: slug, onChange: (next) => {
43
43
  setSlug(next);
44
44
  setEditedName(null);
45
- }, options: templates.map((template) => ({ value: template.slug, label: template.title })) }) }), _jsx(Field, { id: "mail-pdf-file-name", label: t("documentDesigner.mail.pdfLink.fileName"), description: t("documentDesigner.mail.pdfLink.fileNameHint"), children: _jsx(PlaceholderLineEditor, { id: "mail-pdf-file-name", value: fileName, onChange: setEditedName, variableLabels: variableLabels, "aria-label": t("documentDesigner.mail.pdfLink.fileName") }) }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { disabled: slug === "" || fileName === "" || linkPdf.pending, loading: linkPdf.pending, onClick: link, testId: "mail-pdf-link-confirm", children: t("documentDesigner.mail.pdfLink.confirm") }) })] }));
45
+ }, options: templates.map((template) => ({
46
+ value: template.slug,
47
+ label: templateTitleText(t, template.title),
48
+ })) }) }), _jsx(Field, { id: "mail-pdf-file-name", label: t("documentDesigner.mail.pdfLink.fileName"), description: t("documentDesigner.mail.pdfLink.fileNameHint"), children: _jsx(PlaceholderLineEditor, { id: "mail-pdf-file-name", value: fileName, onChange: setEditedName, variableLabels: variableLabels, "aria-label": t("documentDesigner.mail.pdfLink.fileName") }) }), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { disabled: slug === "" || fileName === "" || linkPdf.pending, loading: linkPdf.pending, onClick: link, testId: "mail-pdf-link-confirm", children: t("documentDesigner.mail.pdfLink.confirm") }) })] }));
46
49
  }
47
50
  export function PdfLinkModal({ open, onOpenChange, templates, ...form }) {
48
51
  const t = useTranslation();
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { ConfigQueries } from "@cosmicdrift/kumiko-bundled-features/config/web";
4
3
  import { useNav, usePrimitives, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
5
4
  import { DOCUMENT_FALLBACK_CURRENCY } from "@cosmicdrift/kumiko-renderer/headless";
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { dispatcherErrorText, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { LoadingState, ModeSwitch } from "@cosmicdrift/kumiko-renderer/web";
@@ -1,9 +1,8 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { MAIL_TONES, PlaceholderLineEditor, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { useAppFeatures, useNav, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
6
- import { DEFAULT_LAYOUT_SLUG, TENANT_MAIL_SENDING_SCREEN_ID } from "../constants.js";
5
+ import { DEFAULT_LAYOUT_SLUG, TENANT_MAIL_SENDING_SCREEN_ID } from "./constants.js";
7
6
  import { labelPlaceholders } from "./mail-draft.js";
8
7
  import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
9
8
  import { usageParts } from "./used-by-label.js";
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { dispatcherErrorText, useMutation, usePrimitives, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
4
  import { useRef, useState } from "react";
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { formatWhen, useAppFeatures, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
3
  import { EMPTY_VALUE_PLACEHOLDER } from "@cosmicdrift/kumiko-renderer/headless";
5
4
  import { StatusBadge } from "@cosmicdrift/kumiko-renderer/web";
@@ -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 { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { dispatcherErrorText, useMutation, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
5
- import { EMPTY_VALUE_PLACEHOLDER } from "@cosmicdrift/kumiko-renderer/headless";
4
+ import { EMPTY_VALUE_PLACEHOLDER, pluralKey } from "@cosmicdrift/kumiko-renderer/headless";
6
5
  import { EmptyState, ErrorState, LoadingState } from "@cosmicdrift/kumiko-renderer/web";
7
6
  import { useState } from "react";
8
7
  import { originOf } from "./editor-header.js";
@@ -73,7 +72,7 @@ function originText(t, args) {
73
72
  }
74
73
  const usesKey = kind === "mail-layout" ? "mails" : "pdfs";
75
74
  if (source.tenant !== null) {
76
- return t(`documentDesigner.editor.origin.tenant.${usesKey}`, {
75
+ return t(pluralKey(`documentDesigner.editor.origin.tenant.${usesKey}`, userCount), {
77
76
  version: systemVersion,
78
77
  count: userCount,
79
78
  });
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { formatWhen, usePrimitives, useQuery, useTranslation } from "@cosmicdrift/kumiko-renderer";
5
4
  import { Drawer, EmptyState, LoadingState } from "@cosmicdrift/kumiko-renderer/web";
@@ -1,4 +1,4 @@
1
- import type { TemplateEditorKind } from "../constants.ts";
1
+ import type { TemplateEditorKind } from "./constants.ts";
2
2
  export type OverviewUsageView = {
3
3
  readonly feature: string;
4
4
  readonly notification: string;
@@ -16,6 +16,9 @@ export type OverviewRowView = {
16
16
  /** An open draft beside the served version, never another tenant's. */
17
17
  readonly draftVersion: number | null;
18
18
  readonly systemVersion: number | null;
19
+ /** SystemAdmin only: a stored tenant override that is not delivered. */
20
+ readonly lockedOverride: boolean;
21
+ readonly lockedOverrideI18nKey: string | null;
19
22
  readonly modifiedAt: string;
20
23
  readonly usedBy: readonly OverviewUsageView[];
21
24
  readonly usedByCount: number;
@@ -21,7 +21,7 @@ function usageViewOf(value) {
21
21
  const stringOr = (value, fallback) => typeof value === "string" ? value : fallback;
22
22
  const numberOr = (value, fallback) => typeof value === "number" ? value : fallback;
23
23
  export function parseOverviewRow(row) {
24
- const { id, title, layoutSlug, slug, kind, origin, locales, status, version, draftVersion, systemVersion, modifiedAt, usedBy, usedByCount, } = row;
24
+ const { id, title, layoutSlug, slug, kind, origin, locales, status, version, draftVersion, systemVersion, lockedOverride, lockedOverrideI18nKey, modifiedAt, usedBy, usedByCount, } = row;
25
25
  if (typeof slug !== "string" || !isTemplateKind(kind) || !isStatus(status))
26
26
  return null;
27
27
  if (origin !== "tenant" && origin !== "system")
@@ -41,6 +41,8 @@ export function parseOverviewRow(row) {
41
41
  version: numberOr(version, 0),
42
42
  draftVersion: numberOr(draftVersion, null),
43
43
  systemVersion: numberOr(systemVersion, null),
44
+ lockedOverride: lockedOverride === true,
45
+ lockedOverrideI18nKey: typeof lockedOverrideI18nKey === "string" ? lockedOverrideI18nKey : null,
44
46
  modifiedAt: stringOr(modifiedAt, ""),
45
47
  usedBy: usages,
46
48
  usedByCount: numberOr(usedByCount, usages.length),
@@ -1,5 +1,4 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { DocumentContentEditor, PDF_DEFAULT_MARGIN_MM, PDF_DOCUMENT_BLOCKS, PDF_FOOTER_MARGIN_MM, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
4
3
  import { useTranslation } from "@cosmicdrift/kumiko-renderer";
5
4
  import { cn } from "@cosmicdrift/kumiko-renderer/web";
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // @runtime client
3
2
  import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
3
  import { UploadZone } from "@cosmicdrift/kumiko-renderer/web";
5
4
  import { RailField, RailHint, RailRow, RailSection } from "./rail.js";
@@ -1,4 +1,3 @@
1
- // @runtime client
2
1
  import { PDF_ORIENTATIONS, PDF_PAGE_FORMATS, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
3
2
  import { isOneOf, isPlainObject, propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
4
3
  export const MARGIN_SIDES = [
@@ -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
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 "../constants.js";
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";
@@ -1,4 +1,3 @@
1
- // @runtime client
2
1
  import { createContext, useContext } from "react";
3
2
  const PreviewRecordPickerContext = createContext(null);
4
3
  export const PreviewRecordPickerProvider = PreviewRecordPickerContext.Provider;
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: "grow" }), action] }), children] }));
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("min-w-0 grow break-words 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 truncate text-right text-xs", bad === true ? "text-status-bad" : TRAILING_TONE[trailingTone]), title: typeof trailing === "string" ? trailing : undefined, children: trailing }))] }));
22
- const rowClass = "flex min-h-10 min-w-0 grow items-center gap-2 px-3.5 text-left";
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";
@@ -1,4 +1,3 @@
1
- // @runtime client
2
1
  import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
3
2
  /**
4
3
  * The row the editor works on. A tenant sees its own customisation, or the
@@ -1,6 +1,5 @@
1
- // @runtime client
2
1
  import { useAppFeatures } from "@cosmicdrift/kumiko-renderer";
3
- import { FILES_FEATURE_NAME } from "../constants.js";
2
+ import { FILES_FEATURE_NAME } from "./constants.js";
4
3
  // The app schema lists every mounted feature, screenless ones included, so this is the server's
5
4
  // answer: without the files feature there is no upload route.
6
5
  export function useFilesCapability() {
@@ -1,4 +1,3 @@
1
- // @runtime client
2
1
  import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
3
2
  import { useMutation, useTranslation } from "@cosmicdrift/kumiko-renderer";
4
3
  import { useState } from "react";
@@ -1,4 +1,3 @@
1
- // @runtime client
2
1
  import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
3
2
  import { useQuery } from "@cosmicdrift/kumiko-renderer";
4
3
  import { parseOverviewRow } from "./overview-row.js";
@@ -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 "../constants.js";
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,4 +1,3 @@
1
- // @runtime client
2
1
  import { useCallback, useRef, useState } from "react";
3
2
  export const SETTLE_DELAY_MS = 400;
4
3
  // Handler-driven debounce: the timer starts in the change handler, so no effect is needed.
@@ -1,4 +1,3 @@
1
- // @runtime client
2
1
  import { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
3
2
  import { dispatcherErrorText, useMutation, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
4
3
  import { useState } from "react";
@@ -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
- // @runtime client
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", { count: row.usedByCount });
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.378.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>",
@@ -20,11 +20,11 @@
20
20
  "./web/styles.css": "./src/web/styles.css"
21
21
  },
22
22
  "dependencies": {
23
- "@cosmicdrift/kumiko-bundled-features": "0.378.0",
24
- "@cosmicdrift/kumiko-framework": "0.378.0",
25
- "@cosmicdrift/kumiko-renderer": "0.378.0",
26
- "@cosmicdrift/kumiko-renderer-puppeteer-client": "0.378.0",
27
- "@cosmicdrift/kumiko-types": "0.378.0",
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": {
package/src/changes.json CHANGED
@@ -1,4 +1,22 @@
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
+ },
2
20
  {
3
21
  "version": "0.378.0",
4
22
  "type": "fix",
package/dist/i18n.d.ts DELETED
@@ -1,6 +0,0 @@
1
- type Localized = {
2
- readonly en: string;
3
- readonly de: string;
4
- };
5
- export declare const DOCUMENT_DESIGNER_I18N: Readonly<Record<string, Localized>>;
6
- export {};