@jini-ai/admin 0.4.3 → 0.4.4

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 (41) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/comments/comments.module.d.ts +4 -4
  3. package/dist/comments/messages.en.d.ts +4 -4
  4. package/dist/comments/messages.en.js +4 -4
  5. package/dist/comments/react/components/QueueSection.js +1 -1
  6. package/dist/comments/react/index.d.ts +16 -16
  7. package/dist/forms/forms.module.d.ts +3 -2
  8. package/dist/forms/html-rules.d.ts +14 -0
  9. package/dist/forms/html-rules.js +10 -0
  10. package/dist/forms/messages.en.d.ts +3 -2
  11. package/dist/forms/messages.en.js +4 -3
  12. package/dist/forms/react/components/FormEditorHeaderText.js +2 -2
  13. package/dist/forms/react/components/FormSubmissionDetail.d.ts +3 -1
  14. package/dist/forms/react/components/FormSubmissionDetail.js +3 -2
  15. package/dist/forms/react/components/FormSubmissions.js +1 -1
  16. package/dist/forms/react/hooks/forms-view.hooks.d.ts +1 -6
  17. package/dist/forms/react/hooks/forms-view.hooks.js +3 -4
  18. package/dist/forms/react/index.d.ts +9 -6
  19. package/dist/forms/react/pages/FormsList.js +2 -7
  20. package/dist/react/components/RowMenu/RowMenu.hooks.js +14 -3
  21. package/dist/redirects/messages.en.d.ts +9 -7
  22. package/dist/redirects/messages.en.js +14 -9
  23. package/dist/redirects/react/hooks/RedirectsPage.hooks.js +2 -2
  24. package/dist/redirects/react/index.d.ts +36 -28
  25. package/dist/redirects/react/pages/RedirectsPage.js +1 -1
  26. package/dist/redirects/redirects.module.d.ts +9 -7
  27. package/dist/seo/messages.en.d.ts +5 -4
  28. package/dist/seo/messages.en.js +6 -5
  29. package/dist/seo/react/components/MediaRefField.d.ts +4 -0
  30. package/dist/seo/react/components/MediaRefField.js +2 -2
  31. package/dist/seo/react/components/SitemapModal.js +4 -2
  32. package/dist/seo/react/hooks/MediaRefField.hooks.d.ts +4 -1
  33. package/dist/seo/react/hooks/MediaRefField.hooks.js +3 -3
  34. package/dist/seo/react/index.d.ts +15 -12
  35. package/dist/seo/react/options.d.ts +7 -0
  36. package/dist/seo/react/pages/SeoPage.js +23 -23
  37. package/dist/seo/rules.d.ts +6 -0
  38. package/dist/seo/rules.js +8 -0
  39. package/dist/seo/seo.module.d.ts +5 -4
  40. package/dist/widgets/react/pages/WidgetRegionEditor.js +1 -1
  41. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.4 — 2026-10-08
4
+
5
+ - Desktop 0.1.13 release fixes: see commits a2659d24 (admin), 5695a7c3 (chat, agent-runtime), 8ad4ac5c (devops).
6
+
3
7
  ## 0.4.3
4
8
 
5
9
  ### Patch Changes
@@ -51,10 +51,10 @@ export declare const commentsModule: {
51
51
  readonly "Saved.": "Saved.";
52
52
  readonly "failed to save Comments settings": "failed to save Comments settings";
53
53
  readonly "failed to load Comments settings": "failed to load Comments settings";
54
- readonly pending: "pending";
55
- readonly approved: "approved";
56
- readonly spam: "spam";
57
- readonly trash: "trash";
54
+ readonly pending: "Pending";
55
+ readonly approved: "Approved";
56
+ readonly spam: "Spam";
57
+ readonly trash: "Trash";
58
58
  };
59
59
  readonly pages: {
60
60
  readonly queue: {
@@ -43,10 +43,10 @@ export declare const commentsMessagesEn: {
43
43
  readonly "Saved.": "Saved.";
44
44
  readonly "failed to save Comments settings": "failed to save Comments settings";
45
45
  readonly "failed to load Comments settings": "failed to load Comments settings";
46
- readonly pending: "pending";
47
- readonly approved: "approved";
48
- readonly spam: "spam";
49
- readonly trash: "trash";
46
+ readonly pending: "Pending";
47
+ readonly approved: "Approved";
48
+ readonly spam: "Spam";
49
+ readonly trash: "Trash";
50
50
  };
51
51
  export type CommentsTranslator = (key: string, vars?: Record<string, string | number>) => string;
52
52
  /** Default copy; unknown keys remain visible and variables use the native string formatter. */
@@ -43,10 +43,10 @@ export const commentsMessagesEn = {
43
43
  "Saved.": "Saved.",
44
44
  "failed to save Comments settings": "failed to save Comments settings",
45
45
  "failed to load Comments settings": "failed to load Comments settings",
46
- "pending": "pending",
47
- "approved": "approved",
48
- "spam": "spam",
49
- "trash": "trash"
46
+ "pending": "Pending",
47
+ "approved": "Approved",
48
+ "spam": "Spam",
49
+ "trash": "Trash"
50
50
  };
51
51
  /** Default copy; unknown keys remain visible and variables use the native string formatter. */
52
52
  export const englishComments = (key, vars = {}) => {
@@ -10,7 +10,7 @@ const STATUS_OPTIONS = ["pending", "approved", "spam", "trash"];
10
10
  /** The status-filter `<select>` — pure presentation, no state of its own. */
11
11
  function QueueToolbar({ status, onStatusChange, t, }) {
12
12
  const { onChange } = useQueueToolbar({ onStatusChange });
13
- return (_jsx("div", { className: "toolbar", children: _jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: "comments-status-filter", children: t("Status") }), _jsx("select", { id: "comments-status-filter", value: status, onChange: onChange, ...agentHandle({ handle: "comments-status-filter" }, { role: "field", label: "Filter the moderation queue by status" }), children: STATUS_OPTIONS.map((s) => (_jsx("option", { value: s, children: s }, s))) })] }) }));
13
+ return (_jsx("div", { className: "toolbar", children: _jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: "comments-status-filter", children: t("Status") }), _jsx("select", { id: "comments-status-filter", value: status, onChange: onChange, ...agentHandle({ handle: "comments-status-filter" }, { role: "field", label: "Filter the moderation queue by status" }), children: STATUS_OPTIONS.map((s) => (_jsx("option", { value: s, children: t(s) }, s))) })] }) }));
14
14
  }
15
15
  /** The moderation-queue table's "More" column — a `RowMenu` built from `commentRowMenuItems`, or
16
16
  * an em dash when the operator's permissions leave no items, plus this row's own error (if the
@@ -64,10 +64,10 @@ export declare function comments(_required: Record<string, never>, { t }?: {
64
64
  readonly "Saved.": "Saved.";
65
65
  readonly "failed to save Comments settings": "failed to save Comments settings";
66
66
  readonly "failed to load Comments settings": "failed to load Comments settings";
67
- readonly pending: "pending";
68
- readonly approved: "approved";
69
- readonly spam: "spam";
70
- readonly trash: "trash";
67
+ readonly pending: "Pending";
68
+ readonly approved: "Approved";
69
+ readonly spam: "Spam";
70
+ readonly trash: "Trash";
71
71
  };
72
72
  pages: {
73
73
  readonly queue: {
@@ -140,10 +140,10 @@ export declare function comments(_required: Record<string, never>, { t }?: {
140
140
  readonly "Saved.": "Saved.";
141
141
  readonly "failed to save Comments settings": "failed to save Comments settings";
142
142
  readonly "failed to load Comments settings": "failed to load Comments settings";
143
- readonly pending: "pending";
144
- readonly approved: "approved";
145
- readonly spam: "spam";
146
- readonly trash: "trash";
143
+ readonly pending: "Pending";
144
+ readonly approved: "Approved";
145
+ readonly spam: "Spam";
146
+ readonly trash: "Trash";
147
147
  };
148
148
  pages: {
149
149
  readonly queue: {
@@ -214,10 +214,10 @@ export declare function comments(_required: Record<string, never>, { t }?: {
214
214
  readonly "Saved.": "Saved.";
215
215
  readonly "failed to save Comments settings": "failed to save Comments settings";
216
216
  readonly "failed to load Comments settings": "failed to load Comments settings";
217
- readonly pending: "pending";
218
- readonly approved: "approved";
219
- readonly spam: "spam";
220
- readonly trash: "trash";
217
+ readonly pending: "Pending";
218
+ readonly approved: "Approved";
219
+ readonly spam: "Spam";
220
+ readonly trash: "Trash";
221
221
  };
222
222
  pages: {
223
223
  readonly queue: {
@@ -292,10 +292,10 @@ export declare function comments(_required: Record<string, never>, { t }?: {
292
292
  readonly "Saved.": "Saved.";
293
293
  readonly "failed to save Comments settings": "failed to save Comments settings";
294
294
  readonly "failed to load Comments settings": "failed to load Comments settings";
295
- readonly pending: "pending";
296
- readonly approved: "approved";
297
- readonly spam: "spam";
298
- readonly trash: "trash";
295
+ readonly pending: "Pending";
296
+ readonly approved: "Approved";
297
+ readonly spam: "Spam";
298
+ readonly trash: "Trash";
299
299
  };
300
300
  pages: {
301
301
  readonly queue: {
@@ -70,8 +70,8 @@ export declare const formsModule: {
70
70
  readonly "No submissions yet.": "No submissions yet.";
71
71
  readonly "Create form": "Create form";
72
72
  readonly Form: "Form";
73
- readonly "Configure a new form's fields and email notifications.": "Configure a new form's fields and email notifications.";
74
- readonly "Configure this form's fields and notifications, or review its submissions.": "Configure this form's fields and notifications, or review its submissions.";
73
+ readonly "Configure a new form's fields.": "Configure a new form's fields.";
74
+ readonly "Configure this form's fields, or review its submissions.": "Configure this form's fields, or review its submissions.";
75
75
  readonly Submissions: "Submissions";
76
76
  readonly "Back to forms": "Back to forms";
77
77
  readonly "Actions for form \"{name}\"": "Actions for form \"{name}\"";
@@ -103,6 +103,7 @@ export declare const formsModule: {
103
103
  readonly "failed to load submissions": "failed to load submissions";
104
104
  readonly "failed to load submission": "failed to load submission";
105
105
  readonly "delete failed": "delete failed";
106
+ readonly "Unknown field": "Unknown field";
106
107
  };
107
108
  readonly pages: {
108
109
  readonly list: {
@@ -8,6 +8,20 @@ export interface FormAnswerColumn {
8
8
  export declare function formAnswerColumns({ fields }: {
9
9
  fields: readonly AdminFormField[];
10
10
  }, _optional?: {}): FormAnswerColumn[];
11
+ /** Detail rows reuse the table's persisted field labels, including HTML-derived descriptors.
12
+ * Removed fields retain their answers with a plain fallback instead of exposing their IDs.
13
+ * @complexity O(c + a) time and space for c columns and a submitted answers; no I/O.
14
+ */
15
+ export declare function formSubmissionRows({ data, answerColumns }: {
16
+ data: AdminFormSubmission['data'];
17
+ answerColumns: readonly FormAnswerColumn[];
18
+ }, { unknownFieldLabel }?: {
19
+ unknownFieldLabel?: string;
20
+ }): Array<{
21
+ key: string;
22
+ label: string;
23
+ value: string;
24
+ }>;
11
25
  /** Editable form BODY only: the server supplies the transport and spam-protection wrapper. */
12
26
  export declare function formHtmlStarter({ fields }: {
13
27
  fields: readonly AdminFormField[];
@@ -6,6 +6,16 @@ export function formAnswerColumns({ fields }, _optional = {}) {
6
6
  cell: (submission) => submission.data[field.id] === undefined ? "" : String(submission.data[field.id]),
7
7
  }));
8
8
  }
9
+ /** Detail rows reuse the table's persisted field labels, including HTML-derived descriptors.
10
+ * Removed fields retain their answers with a plain fallback instead of exposing their IDs.
11
+ * @complexity O(c + a) time and space for c columns and a submitted answers; no I/O.
12
+ */
13
+ export function formSubmissionRows({ data, answerColumns }, { unknownFieldLabel = "Unknown field" } = {}) {
14
+ const labels = new Map(answerColumns.map(column => [column.key, column.header]));
15
+ return Object.entries(data).map(([key, value]) => ({
16
+ key, label: labels.get(`answer:${key}`)?.trim() || unknownFieldLabel, value: String(value),
17
+ }));
18
+ }
9
19
  function escapeHtml(value) {
10
20
  return value.replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[char]);
11
21
  }
@@ -61,8 +61,8 @@ export declare const formsMessagesEn: {
61
61
  readonly "No submissions yet.": "No submissions yet.";
62
62
  readonly "Create form": "Create form";
63
63
  readonly Form: "Form";
64
- readonly "Configure a new form's fields and email notifications.": "Configure a new form's fields and email notifications.";
65
- readonly "Configure this form's fields and notifications, or review its submissions.": "Configure this form's fields and notifications, or review its submissions.";
64
+ readonly "Configure a new form's fields.": "Configure a new form's fields.";
65
+ readonly "Configure this form's fields, or review its submissions.": "Configure this form's fields, or review its submissions.";
66
66
  readonly Submissions: "Submissions";
67
67
  readonly "Back to forms": "Back to forms";
68
68
  readonly "Actions for form \"{name}\"": "Actions for form \"{name}\"";
@@ -94,6 +94,7 @@ export declare const formsMessagesEn: {
94
94
  readonly "failed to load submissions": "failed to load submissions";
95
95
  readonly "failed to load submission": "failed to load submission";
96
96
  readonly "delete failed": "delete failed";
97
+ readonly "Unknown field": "Unknown field";
97
98
  };
98
99
  /** Host translators may replace any key; the default preserves the original English bytes. */
99
100
  export declare const translateForms: FormsTranslator;
@@ -60,8 +60,8 @@ export const formsMessagesEn = {
60
60
  "No submissions yet.": "No submissions yet.",
61
61
  "Create form": "Create form",
62
62
  "Form": "Form",
63
- "Configure a new form's fields and email notifications.": "Configure a new form's fields and email notifications.",
64
- "Configure this form's fields and notifications, or review its submissions.": "Configure this form's fields and notifications, or review its submissions.",
63
+ "Configure a new form's fields.": "Configure a new form's fields.",
64
+ "Configure this form's fields, or review its submissions.": "Configure this form's fields, or review its submissions.",
65
65
  "Submissions": "Submissions",
66
66
  "Back to forms": "Back to forms",
67
67
  "Actions for form \"{name}\"": "Actions for form \"{name}\"",
@@ -92,7 +92,8 @@ export const formsMessagesEn = {
92
92
  "recipients": "recipients",
93
93
  "failed to load submissions": "failed to load submissions",
94
94
  "failed to load submission": "failed to load submission",
95
- "delete failed": "delete failed"
95
+ "delete failed": "delete failed",
96
+ "Unknown field": "Unknown field",
96
97
  };
97
98
  /** Host translators may replace any key; the default preserves the original English bytes. */
98
99
  export const translateForms = (key, vars = {}) => {
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  export function FormEditorHeaderText(props, _optional = {}) {
3
3
  const { isNew, name, t } = props;
4
4
  return (_jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t("Content") }), _jsx("h1", { className: "page-title", children: isNew ? t("New form") : name || t("Form") }), _jsx("p", { className: "page-description", children: isNew
5
- ? t("Configure a new form's fields and email notifications.")
6
- : t("Configure this form's fields and notifications, or review its submissions.") })] }));
5
+ ? t("Configure a new form's fields.")
6
+ : t("Configure this form's fields, or review its submissions.") })] }));
7
7
  }
8
8
  //# sourceMappingURL=FormEditorHeaderText.js.map
@@ -1,4 +1,5 @@
1
1
  import { useWiredFormSubmissionDetail } from '../hooks/use-form-submission-detail.hooks.js';
2
+ import { type FormAnswerColumn } from '../../html-rules.js';
2
3
  export interface FormSubmissionDetailProps {
3
4
  formId: string;
4
5
  submissionId: string;
@@ -8,6 +9,7 @@ export interface FormSubmissionDetailProps {
8
9
  useFormSubmissionDetailHook?: typeof useWiredFormSubmissionDetail;
9
10
  /** Translator closure — see `FormEditor()`'s own `t`. */
10
11
  t: (key: string) => string;
12
+ answerColumns?: readonly FormAnswerColumn[];
11
13
  }
12
- export declare function FormSubmissionDetail({ formId, submissionId, onBack, onDeleted, useFormSubmissionDetailHook, t, }: FormSubmissionDetailProps, _optional?: Record<string, never>): import("react").JSX.Element;
14
+ export declare function FormSubmissionDetail({ formId, submissionId, onBack, onDeleted, useFormSubmissionDetailHook, t, answerColumns, }: FormSubmissionDetailProps, _optional?: Record<string, never>): import("react").JSX.Element;
13
15
  //# sourceMappingURL=FormSubmissionDetail.d.ts.map
@@ -4,7 +4,8 @@ import { agentHandle } from '@jini-ai/agentic';
4
4
  import { useWiredFormSubmissionDetail } from '../hooks/use-form-submission-detail.hooks.js';
5
5
  import { useFormSubmissionDate } from '../hooks/use-form-submission-date.hooks.js';
6
6
  import { FormSubmissionDate } from "./FormSubmissionDate.js";
7
- export function FormSubmissionDetail({ formId, submissionId, onBack, onDeleted, useFormSubmissionDetailHook = useWiredFormSubmissionDetail, t, }, _optional = {}) {
7
+ import { formSubmissionRows } from '../../html-rules.js';
8
+ export function FormSubmissionDetail({ formId, submissionId, onBack, onDeleted, useFormSubmissionDetailHook = useWiredFormSubmissionDetail, t, answerColumns = [], }, _optional = {}) {
8
9
  const formatDate = useFormSubmissionDate({});
9
10
  const { submission, error, confirmOpen, deleting, requestDelete, cancelDelete, confirmDelete } = useFormSubmissionDetailHook({
10
11
  formId,
@@ -21,7 +22,7 @@ export function FormSubmissionDetail({ formId, submissionId, onBack, onDeleted,
21
22
  // button sat right on top of the table and Delete sat right underneath it. Same
22
23
  // flex-column-plus-gap idiom `.field-group` already uses elsewhere for vertical rhythm,
23
24
  // rather than one-off margins on each button.
24
- _jsxs("div", { className: "form-submission-detail", children: [_jsxs("button", { type: "button", className: "btn-secondary", onClick: onBack, "aria-label": t("Back to submissions"), ...agentHandle({ handle: "form-submission-back" }, { role: "link", label: "Back to this form's list of submissions" }), children: ["\u2190 ", t("Back")] }), error ? _jsx("div", { className: "notice error", children: error }) : null, _jsx("div", { className: "table-scroll", children: _jsx("table", { className: "list-table", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { children: t("Submitted at") }), _jsx("td", { children: _jsx(FormSubmissionDate, { date: formatDate({ iso: submission.submittedAt }) }) })] }), _jsxs("tr", { children: [_jsx("th", { children: t("Source IP") }), _jsx("td", { children: submission.sourceIp })] }), Object.entries(submission.data).map(([key, value]) => (_jsxs("tr", { children: [_jsx("th", { children: key }), _jsx("td", { children: String(value) })] }, key)))] }) }) }), _jsx("button", { type: "button", className: "btn-danger", disabled: deleting, onClick: requestDelete, ...agentHandle({ handle: "form-submission-delete" }, {
25
+ _jsxs("div", { className: "form-submission-detail", children: [_jsxs("button", { type: "button", className: "btn-secondary", onClick: onBack, "aria-label": t("Back to submissions"), ...agentHandle({ handle: "form-submission-back" }, { role: "link", label: "Back to this form's list of submissions" }), children: ["\u2190 ", t("Back")] }), error ? _jsx("div", { className: "notice error", children: error }) : null, _jsx("div", { className: "table-scroll", children: _jsx("table", { className: "list-table", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { children: t("Submitted at") }), _jsx("td", { children: _jsx(FormSubmissionDate, { date: formatDate({ iso: submission.submittedAt }) }) })] }), _jsxs("tr", { children: [_jsx("th", { children: t("Source IP") }), _jsx("td", { children: submission.sourceIp })] }), formSubmissionRows({ data: submission.data, answerColumns }, { unknownFieldLabel: t("Unknown field") }).map(({ key, label, value }) => (_jsxs("tr", { children: [_jsx("th", { className: "jini-submission-field-label", children: label }), _jsx("td", { children: value })] }, key)))] }) }) }), _jsx("button", { type: "button", className: "btn-danger", disabled: deleting, onClick: requestDelete, ...agentHandle({ handle: "form-submission-delete" }, {
25
26
  role: "button",
26
27
  label: "Move this submission to the trash. Opens a confirmation dialog first.",
27
28
  }), children: t("Delete submission") }), _jsx(ConfirmDialog, { open: confirmOpen, agentHandle: "form-submission-delete-confirm", title: t("Move to trash?"), body: _jsx("p", { children: t("It will disappear from this list. You can restore it from the Trash.") }), confirmLabel: t("Move to trash"), destructive: true, pending: deleting, onConfirm: confirmDelete, onCancel: cancelDelete })] }));
@@ -12,7 +12,7 @@ export function FormSubmissions({ formId, useFormSubmissionsHook = useWiredFormS
12
12
  const { submissions, nextCursor, error, selectedId, loadingMore } = controller;
13
13
  const { rows, back, loadMore } = useSubmissionsView({ controller });
14
14
  if (selectedId) {
15
- return (_jsx(FormSubmissionDetail, { formId: formId, submissionId: selectedId, onBack: back, onDeleted: back, t: t }));
15
+ return (_jsx(FormSubmissionDetail, { formId: formId, submissionId: selectedId, onBack: back, onDeleted: back, t: t, answerColumns: answerColumns }));
16
16
  }
17
17
  if (error && !submissions)
18
18
  return _jsx("div", { className: "notice error", children: error });
@@ -5,7 +5,7 @@ import type { FieldAttributesDialogController } from './use-field-attributes-dia
5
5
  import type { FormSubmissionsController } from './use-form-submissions.hooks.js';
6
6
  import type { FormEditorFieldsBodyProps } from '../components/FormEditorFieldsBody.js';
7
7
  import type { AdminFormDefinition, AdminFormField } from '../../models.js';
8
- /** Host recipient labels and publication actions keep their original DOM at typed slots. */
8
+ /** Publication actions keep their original DOM at the host slot. */
9
9
  export declare function useFormsListView({ forms, t, formatFormDates, toggleStatus, setPendingDelete }: {
10
10
  forms: AdminFormDefinition[] | null;
11
11
  t: (key: string) => string;
@@ -31,11 +31,6 @@ export declare function useFormsListView({ forms, t, formatFormDates, toggleStat
31
31
  notify: import("../../models.js").AdminFormNotify;
32
32
  }[];
33
33
  cancelDelete: () => void;
34
- RecipientLabel: import("react").ComponentType<{
35
- count: number;
36
- }> | (({ count }: {
37
- count: number;
38
- }) => string);
39
34
  headerActions: import("react").ReactNode;
40
35
  };
41
36
  /** Bind every field-row callback and focus ref outside the markup. */
@@ -2,11 +2,10 @@ import { buildAgentListHandles } from '@jini-ai/agentic';
2
2
  import { fieldDisplayName, formRowMenuItems, FORM_TABS } from '../../rules.js';
3
3
  import { useFormsOptions, FormsPortsContext } from './FormsPorts.hooks.js';
4
4
  import { useContext } from 'react';
5
- /** Host recipient labels and publication actions keep their original DOM at typed slots. */
5
+ /** Publication actions keep their original DOM at the host slot. */
6
6
  export function useFormsListView({ forms, t, formatFormDates, toggleStatus, setPendingDelete }, _optional = {}) {
7
7
  const ports = useContext(FormsPortsContext);
8
- const { slots, headerActions, adminBase } = useFormsOptions();
9
- const RecipientLabel = slots?.RecipientLabel ?? (({ count }) => t(count === 1 ? 'recipient' : 'recipients'));
8
+ const { headerActions, adminBase } = useFormsOptions();
10
9
  const rowHandles = buildAgentListHandles({ prefix: 'forms-row', ids: (forms ?? []).map(form => form.id) });
11
10
  const onEdit = (form) => {
12
11
  if (!ports?.formsNavigation)
@@ -17,7 +16,7 @@ export function useFormsListView({ forms, t, formatFormDates, toggleStatus, setP
17
16
  rows: (forms ?? []).map((form, index) => ({ ...form, rowHandle: rowHandles[index], dates: formatFormDates(form),
18
17
  menuLabel: t('Actions for form "{name}"').replace('{name}', form.name),
19
18
  menuItems: formRowMenuItems({ form, handlers: { onEdit, onToggleStatus: form => { void toggleStatus(form); }, onDelete: setPendingDelete }, t }), })),
20
- cancelDelete: () => setPendingDelete(null), RecipientLabel, headerActions,
19
+ cancelDelete: () => setPendingDelete(null), headerActions,
21
20
  };
22
21
  }
23
22
  /** Bind every field-row callback and focus ref outside the markup. */
@@ -86,8 +86,8 @@ export declare function forms(_required: Record<string, never>, options?: FormsR
86
86
  readonly "No submissions yet.": "No submissions yet.";
87
87
  readonly "Create form": "Create form";
88
88
  readonly Form: "Form";
89
- readonly "Configure a new form's fields and email notifications.": "Configure a new form's fields and email notifications.";
90
- readonly "Configure this form's fields and notifications, or review its submissions.": "Configure this form's fields and notifications, or review its submissions.";
89
+ readonly "Configure a new form's fields.": "Configure a new form's fields.";
90
+ readonly "Configure this form's fields, or review its submissions.": "Configure this form's fields, or review its submissions.";
91
91
  readonly Submissions: "Submissions";
92
92
  readonly "Back to forms": "Back to forms";
93
93
  readonly "Actions for form \"{name}\"": "Actions for form \"{name}\"";
@@ -119,6 +119,7 @@ export declare function forms(_required: Record<string, never>, options?: FormsR
119
119
  readonly "failed to load submissions": "failed to load submissions";
120
120
  readonly "failed to load submission": "failed to load submission";
121
121
  readonly "delete failed": "delete failed";
122
+ readonly "Unknown field": "Unknown field";
122
123
  };
123
124
  pages: {
124
125
  readonly list: {
@@ -231,8 +232,8 @@ export declare function forms(_required: Record<string, never>, options?: FormsR
231
232
  readonly "No submissions yet.": "No submissions yet.";
232
233
  readonly "Create form": "Create form";
233
234
  readonly Form: "Form";
234
- readonly "Configure a new form's fields and email notifications.": "Configure a new form's fields and email notifications.";
235
- readonly "Configure this form's fields and notifications, or review its submissions.": "Configure this form's fields and notifications, or review its submissions.";
235
+ readonly "Configure a new form's fields.": "Configure a new form's fields.";
236
+ readonly "Configure this form's fields, or review its submissions.": "Configure this form's fields, or review its submissions.";
236
237
  readonly Submissions: "Submissions";
237
238
  readonly "Back to forms": "Back to forms";
238
239
  readonly "Actions for form \"{name}\"": "Actions for form \"{name}\"";
@@ -264,6 +265,7 @@ export declare function forms(_required: Record<string, never>, options?: FormsR
264
265
  readonly "failed to load submissions": "failed to load submissions";
265
266
  readonly "failed to load submission": "failed to load submission";
266
267
  readonly "delete failed": "delete failed";
268
+ readonly "Unknown field": "Unknown field";
267
269
  };
268
270
  readonly pages: {
269
271
  readonly list: {
@@ -380,8 +382,8 @@ export declare function forms(_required: Record<string, never>, options?: FormsR
380
382
  readonly "No submissions yet.": "No submissions yet.";
381
383
  readonly "Create form": "Create form";
382
384
  readonly Form: "Form";
383
- readonly "Configure a new form's fields and email notifications.": "Configure a new form's fields and email notifications.";
384
- readonly "Configure this form's fields and notifications, or review its submissions.": "Configure this form's fields and notifications, or review its submissions.";
385
+ readonly "Configure a new form's fields.": "Configure a new form's fields.";
386
+ readonly "Configure this form's fields, or review its submissions.": "Configure this form's fields, or review its submissions.";
385
387
  readonly Submissions: "Submissions";
386
388
  readonly "Back to forms": "Back to forms";
387
389
  readonly "Actions for form \"{name}\"": "Actions for form \"{name}\"";
@@ -413,6 +415,7 @@ export declare function forms(_required: Record<string, never>, options?: FormsR
413
415
  readonly "failed to load submissions": "failed to load submissions";
414
416
  readonly "failed to load submission": "failed to load submission";
415
417
  readonly "delete failed": "delete failed";
418
+ readonly "Unknown field": "Unknown field";
416
419
  };
417
420
  readonly pages: {
418
421
  readonly list: {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ConfirmDialog } from '../../../react/components/ConfirmDialog/ConfirmDialog.js';
3
3
  import { DataTable } from '../../../react/components/DataTable.js';
4
4
  import { RowMenu } from '../../../react/components/RowMenu/RowMenu.js';
@@ -7,7 +7,7 @@ import { useWiredFormsList } from '../hooks/use-forms-list.hooks.js';
7
7
  import { useFormsListView } from '../hooks/forms-view.hooks.js';
8
8
  export function FormsList({ useFormsListHook = useWiredFormsList } = {}, _optional = {}) {
9
9
  const { forms, formatFormDates, error, rowSavingId, toggleStatus, pendingDelete, setPendingDelete, removeForm, t } = useFormsListHook();
10
- const { rows, cancelDelete, RecipientLabel, headerActions } = useFormsListView({ forms, t, formatFormDates, toggleStatus, setPendingDelete });
10
+ const { rows, cancelDelete, headerActions } = useFormsListView({ forms, t, formatFormDates, toggleStatus, setPendingDelete });
11
11
  if (error && !forms)
12
12
  return _jsx("div", { className: "notice error", children: error });
13
13
  if (!forms)
@@ -39,11 +39,6 @@ export function FormsList({ useFormsListHook = useWiredFormsList } = {}, _option
39
39
  cell: (form) => (_jsx("span", { className: "forms-dates", children: form.dates.map((line) => (_jsxs("time", { className: "forms-dates-line", dateTime: line.dateTime, title: line.label, children: [_jsx("span", { "aria-hidden": "true", children: line.text }), _jsx("span", { className: "visually-hidden", children: line.label })] }, line.kind))) })),
40
40
  },
41
41
  { key: "fields", header: t("Fields"), cell: (form) => form.fields.length },
42
- {
43
- key: "notify",
44
- header: t("Notify"),
45
- cell: (form) => (form.notify.enabled ? _jsxs(_Fragment, { children: [form.notify.recipients.length, " ", _jsx(RecipientLabel, { count: form.notify.recipients.length })] }) : t("off")),
46
- },
47
42
  {
48
43
  key: "actions",
49
44
  header: t("More"),
@@ -12,13 +12,14 @@ const VIEWPORT_MARGIN = 8;
12
12
  * visible jump. Recomputed on scroll and resize too: a portaled menu is a DOM sibling of its anchor,
13
13
  * not a child, so it does not track the anchor's position on its own the way an in-flow popup would.
14
14
  *
15
- * Click-outside closes. Scoped to the open window only, same lifecycle discipline as
15
+ * Click-outside and Escape close. Escape is document-scoped so it also works after focus
16
+ * moves away from the popup. Scoped to the open window only, same lifecycle discipline as
16
17
  * `ConfirmButton`'s armed-only document listeners — a closed, idle `RowMenu` costs nothing beyond
17
18
  * its trigger button even with many mounted per table.
18
19
  *
19
20
  * @complexity O(1) per call and per scroll/resize/mousedown event.
20
21
  */
21
- function trackOpenMenu({ trigger, menu }, { window, document, onPosition, onDismiss }) {
22
+ function trackOpenMenu({ trigger, menu }, { window, document, onPosition, onDismiss, onEscape }) {
22
23
  function reposition() {
23
24
  const rect = trigger.getBoundingClientRect();
24
25
  const menuHeight = menu.offsetHeight;
@@ -41,14 +42,23 @@ function trackOpenMenu({ trigger, menu }, { window, document, onPosition, onDism
41
42
  return;
42
43
  onDismiss();
43
44
  }
45
+ function onDocKeyDown(e) {
46
+ if (e.key !== 'Escape' || e.defaultPrevented)
47
+ return;
48
+ e.preventDefault();
49
+ e.stopPropagation();
50
+ onEscape();
51
+ }
44
52
  reposition();
45
53
  window.addEventListener('scroll', reposition, true);
46
54
  window.addEventListener('resize', reposition);
47
55
  document.addEventListener('mousedown', onDocMouseDown);
56
+ document.addEventListener('keydown', onDocKeyDown);
48
57
  return () => {
49
58
  window.removeEventListener('scroll', reposition, true);
50
59
  window.removeEventListener('resize', reposition);
51
60
  document.removeEventListener('mousedown', onDocMouseDown);
61
+ document.removeEventListener('keydown', onDocKeyDown);
52
62
  };
53
63
  }
54
64
  /**
@@ -89,7 +99,7 @@ export function useRowMenu({ itemCount, window, document }) {
89
99
  let stop;
90
100
  return (menu) => {
91
101
  if (menu)
92
- stop = trackOpenMenu({ trigger: anchor, menu }, { window, document, onPosition: setPosition, onDismiss: () => close(false) });
102
+ stop = trackOpenMenu({ trigger: anchor, menu }, { window, document, onPosition: setPosition, onDismiss: () => close(false), onEscape: () => close(true) });
93
103
  else
94
104
  stop();
95
105
  };
@@ -140,6 +150,7 @@ export function useRowMenu({ itemCount, window, document }) {
140
150
  break;
141
151
  case 'Escape':
142
152
  e.preventDefault();
153
+ e.stopPropagation();
143
154
  close(true);
144
155
  break;
145
156
  case 'Tab':
@@ -9,8 +9,7 @@ export declare const redirectsMessagesEn: {
9
9
  readonly Import: "Import";
10
10
  readonly Marketing: "Marketing";
11
11
  readonly Redirects: "Redirects";
12
- readonly "Manual URL redirect rules. Rules created automatically from a slug change (source": "Manual URL redirect rules. Rules created automatically from a slug change (source";
13
- readonly ") also show up here.": ") also show up here.";
12
+ readonly "Manage URL redirects, including those created automatically when a page's address changes.": "Manage URL redirects, including those created automatically when a page's address changes.";
14
13
  readonly "Match type": "Match type";
15
14
  readonly "From path": "From path";
16
15
  readonly "To target": "To target";
@@ -31,9 +30,9 @@ export declare const redirectsMessagesEn: {
31
30
  readonly "Loading redirects\u2026": "Loading redirects…";
32
31
  readonly "Not valid JSON.": "Not valid JSON.";
33
32
  readonly "Must be a JSON array of rule objects.": "Must be a JSON array of rule objects.";
34
- readonly exact: "exact";
35
- readonly prefix: "prefix";
36
- readonly wildcard: "wildcard";
33
+ readonly exact: "Exact match";
34
+ readonly prefix: "Starts with";
35
+ readonly wildcard: "Wildcard";
37
36
  readonly Status: "Status";
38
37
  readonly More: "More";
39
38
  readonly Delete: "Delete";
@@ -41,8 +40,8 @@ export declare const redirectsMessagesEn: {
41
40
  readonly Enable: "Enable";
42
41
  readonly "Saving\u2026": "Saving…";
43
42
  readonly Created: "Created";
44
- readonly active: "active";
45
- readonly disabled: "disabled";
43
+ readonly active: "Active";
44
+ readonly disabled: "Disabled";
46
45
  readonly "{created} created, {failed} failed.": "{created} created, {failed} failed.";
47
46
  readonly "Item {index} ({code})": "Item {index} ({code})";
48
47
  readonly "Actions for redirect rule from \"{fromPattern}\"": "Actions for redirect rule from \"{fromPattern}\"";
@@ -50,6 +49,9 @@ export declare const redirectsMessagesEn: {
50
49
  readonly " rule objects (1-500 items)": " rule objects (1-500 items)";
51
50
  readonly "Delete the redirect rule from \"": "Delete the redirect rule from \"";
52
51
  readonly "\"?": "\"?";
52
+ readonly manual: "Manual";
53
+ readonly auto_slug_change: "URL change";
54
+ readonly import: "Imported";
53
55
  };
54
56
  /** English fallback with the same named-placeholder interpolation as the host dictionary. */
55
57
  export declare const translateRedirectsEn: RedirectsTranslate;
@@ -8,8 +8,7 @@ export const redirectsMessagesEn = {
8
8
  "Import": "Import",
9
9
  "Marketing": "Marketing",
10
10
  "Redirects": "Redirects",
11
- "Manual URL redirect rules. Rules created automatically from a slug change (source": "Manual URL redirect rules. Rules created automatically from a slug change (source",
12
- ") also show up here.": ") also show up here.",
11
+ "Manage URL redirects, including those created automatically when a page's address changes.": "Manage URL redirects, including those created automatically when a page's address changes.",
13
12
  "Match type": "Match type",
14
13
  "From path": "From path",
15
14
  "To target": "To target",
@@ -30,9 +29,9 @@ export const redirectsMessagesEn = {
30
29
  "Loading redirects…": "Loading redirects…",
31
30
  "Not valid JSON.": "Not valid JSON.",
32
31
  "Must be a JSON array of rule objects.": "Must be a JSON array of rule objects.",
33
- "exact": "exact",
34
- "prefix": "prefix",
35
- "wildcard": "wildcard",
32
+ "exact": "Exact match",
33
+ "prefix": "Starts with",
34
+ "wildcard": "Wildcard",
36
35
  "Status": "Status",
37
36
  "More": "More",
38
37
  "Delete": "Delete",
@@ -40,16 +39,22 @@ export const redirectsMessagesEn = {
40
39
  "Enable": "Enable",
41
40
  "Saving…": "Saving…",
42
41
  "Created": "Created",
43
- "active": "active",
44
- "disabled": "disabled",
42
+ "active": "Active",
43
+ "disabled": "Disabled",
45
44
  "{created} created, {failed} failed.": "{created} created, {failed} failed.",
46
45
  "Item {index} ({code})": "Item {index} ({code})",
47
46
  "Actions for redirect rule from \"{fromPattern}\"": "Actions for redirect rule from \"{fromPattern}\"",
48
47
  "Paste a JSON array of ": "Paste a JSON array of ",
49
48
  " rule objects (1-500 items)": " rule objects (1-500 items)",
50
49
  "Delete the redirect rule from \"": "Delete the redirect rule from \"",
51
- "\"?": "\"?"
50
+ "\"?": "\"?",
51
+ "manual": "Manual",
52
+ "auto_slug_change": "URL change",
53
+ "import": "Imported",
52
54
  };
53
55
  /** English fallback with the same named-placeholder interpolation as the host dictionary. */
54
- export const translateRedirectsEn = (key, vars = {}) => key.replace(/\{(\w+)\}/g, (match, name) => String(vars[name] ?? match));
56
+ export const translateRedirectsEn = (key, vars = {}) => {
57
+ const template = Object.hasOwn(redirectsMessagesEn, key) ? redirectsMessagesEn[key] : key;
58
+ return template.replace(/\{(\w+)\}/g, (match, name) => String(vars[name] ?? match));
59
+ };
55
60
  //# sourceMappingURL=messages.en.js.map
@@ -12,9 +12,9 @@ export function useRedirectsPage({ useRedirectsHook = useWiredRedirects }, _opti
12
12
  const columns = [
13
13
  { key: 'from', header: t('From'), cell: (rule) => rule.fromPattern },
14
14
  { key: 'to', header: t('To'), cell: (rule) => rule.toTarget },
15
- { key: 'type', header: t('Type'), cell: (rule) => rule.matchType },
15
+ { key: 'type', header: t('Type'), cell: (rule) => t(rule.matchType, { serverLabel: rule.matchType }) },
16
16
  { key: 'code', header: t('Code'), cell: (rule) => rule.statusCode },
17
- { key: 'source', header: t('Source'), cell: (rule) => rule.source },
17
+ { key: 'source', header: t('Source'), cell: (rule) => t(rule.source, { serverLabel: rule.source }) },
18
18
  // The host's shared serverLabel owns enum localization, including unknown-value passthrough.
19
19
  { key: 'status', header: t('Status'), cell: (rule) => createElement('span', { className: `status status-${rule.status}` }, t(rule.status, { serverLabel: rule.status })) },
20
20
  { key: 'hits', header: t('Hits'), cell: (rule, index) => createElement(HitCountCell, { t, redirectId: rule.id, ...(rowMenuHandles[index] ? { agentHandleBase: rowMenuHandles[index] } : {}) }) },