@firecms/core 3.1.0-canary.1df3b2c → 3.1.0-canary.501d471
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
- package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
- package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +5 -10
- package/dist/components/ErrorBoundary.d.ts +4 -2
- package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
- package/dist/components/LanguageToggle.d.ts +1 -0
- package/dist/components/UnsavedChangesDialog.d.ts +1 -0
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +6 -1
- package/dist/components/VirtualTable/types.d.ts +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/core/DrawerNavigationGroup.d.ts +2 -2
- package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
- package/dist/editor/components/editor-bubble-item.d.ts +8 -0
- package/dist/editor/components/editor-bubble.d.ts +8 -0
- package/dist/editor/components/image-bubble.d.ts +5 -0
- package/dist/editor/components/index.d.ts +16 -0
- package/dist/editor/components/table-bubble.d.ts +5 -0
- package/dist/editor/editor.d.ts +30 -0
- package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
- package/dist/editor/extensions/Image/index.d.ts +6 -0
- package/dist/editor/extensions/Image.d.ts +6 -0
- package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
- package/dist/editor/extensions/clipboard.d.ts +7 -0
- package/dist/editor/extensions/custom-keymap.d.ts +1 -0
- package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
- package/dist/editor/hooks/useProseMirror.d.ts +13 -0
- package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/markdown.d.ts +5 -0
- package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
- package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
- package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
- package/dist/editor/nodeViews/index.d.ts +6 -0
- package/dist/editor/plugins/index.d.ts +2 -0
- package/dist/editor/plugins/inputrules.d.ts +6 -0
- package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
- package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
- package/dist/editor/schema.d.ts +2 -0
- package/dist/editor/selectors/ai-selector.d.ts +0 -0
- package/dist/editor/selectors/color-selector.d.ts +10 -0
- package/dist/editor/selectors/link-selector.d.ts +8 -0
- package/dist/editor/selectors/node-selector.d.ts +15 -0
- package/dist/editor/selectors/text-buttons.d.ts +1 -0
- package/dist/editor/types.d.ts +5 -0
- package/dist/editor/useProseMirror.d.ts +16 -0
- package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
- package/dist/editor/utils/remove_classes.d.ts +1 -0
- package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
- package/dist/form/components/ErrorFocus.d.ts +1 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/hooks/useCollapsedGroups.d.ts +3 -3
- package/dist/hooks/useTranslation.d.ts +17 -0
- package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +29682 -18363
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +29681 -18382
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useRestoreScroll.d.ts +1 -1
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/en.d.ts +10 -0
- package/dist/locales/es.d.ts +10 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/hi.d.ts +2 -0
- package/dist/locales/it.d.ts +2 -0
- package/dist/locales/pt.d.ts +7 -0
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/collections.d.ts +46 -0
- package/dist/types/customization_controller.d.ts +2 -1
- package/dist/types/firecms.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/navigation.d.ts +2 -2
- package/dist/types/plugins.d.ts +23 -0
- package/dist/types/properties.d.ts +9 -8
- package/dist/types/storage.d.ts +1 -0
- package/dist/types/translations.d.ts +669 -0
- package/dist/util/entities.d.ts +1 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/lazy_eager.d.ts +7 -0
- package/dist/util/objects.d.ts +1 -0
- package/dist/util/resolutions.d.ts +2 -2
- package/dist/util/useStorageUploadController.d.ts +10 -1
- package/package.json +49 -13
- package/src/app/Scaffold.tsx +7 -5
- package/src/components/AIIcon.tsx +3 -1
- package/src/components/ArrayContainer.tsx +6 -4
- package/src/components/ClearFilterSortButton.tsx +6 -3
- package/src/components/ConfirmationDialog.tsx +4 -2
- package/src/components/DeleteEntityDialog.tsx +10 -7
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -3
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -1
- package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
- package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
- package/src/components/EntityCollectionView/BoardSortableList.tsx +3 -1
- package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +1 -1
- package/src/components/EntityCollectionView/EntityCard.tsx +4 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +39 -46
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +17 -25
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +71 -31
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -3
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -2
- package/src/components/EntityCollectionView/FiltersDialog.tsx +8 -5
- package/src/components/EntityCollectionView/ViewModeToggle.tsx +37 -37
- package/src/components/EntityJsonPreview.tsx +2 -1
- package/src/components/EntityView.tsx +3 -2
- package/src/components/ErrorBoundary.tsx +27 -15
- package/src/components/HomePage/DefaultHomePage.tsx +19 -13
- package/src/components/HomePage/HomePageDnD.tsx +3 -1
- package/src/components/HomePage/NavigationGroup.tsx +3 -1
- package/src/components/HomePage/RenameGroupDialog.tsx +15 -13
- package/src/components/LanguageToggle.tsx +66 -0
- package/src/components/NotFoundPage.tsx +5 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -7
- package/src/components/ReferenceWidget.tsx +3 -2
- package/src/components/SearchIconsView.tsx +3 -1
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +11 -0
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +15 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +11 -0
- package/src/components/UnsavedChangesDialog.tsx +6 -4
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
- package/src/components/VirtualTable/VirtualTable.tsx +121 -116
- package/src/components/VirtualTable/VirtualTableHeader.tsx +59 -56
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +9 -4
- package/src/components/VirtualTable/VirtualTableProps.tsx +7 -1
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +3 -3
- package/src/components/VirtualTable/types.tsx +1 -0
- package/src/components/common/default_entity_actions.tsx +4 -0
- package/src/components/common/useDataSourceTableController.tsx +12 -4
- package/src/components/index.tsx +1 -0
- package/src/core/DefaultAppBar.tsx +15 -11
- package/src/core/DefaultDrawer.tsx +8 -2
- package/src/core/DrawerNavigationGroup.tsx +5 -3
- package/src/core/EntityEditView.tsx +54 -8
- package/src/core/EntityEditViewFormActions.tsx +24 -17
- package/src/core/EntitySidePanel.tsx +34 -30
- package/src/core/FireCMS.tsx +33 -6
- package/src/core/field_configs.tsx +18 -11
- package/src/editor/components/SlashCommandMenu.tsx +516 -0
- package/src/editor/components/editor-bubble-item.tsx +32 -0
- package/src/editor/components/editor-bubble.tsx +118 -0
- package/src/editor/components/image-bubble.tsx +156 -0
- package/src/editor/components/index.ts +14 -0
- package/src/editor/components/table-bubble.tsx +165 -0
- package/src/editor/editor.tsx +455 -0
- package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
- package/src/editor/extensions/Image/index.ts +133 -0
- package/src/editor/extensions/Image.ts +159 -0
- package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
- package/src/editor/extensions/clipboard.ts +72 -0
- package/src/editor/extensions/custom-keymap.ts +24 -0
- package/src/editor/extensions/drag-and-drop.tsx +480 -0
- package/src/editor/hooks/useProseMirror.ts +124 -0
- package/src/editor/hooks/useProseMirrorContext.ts +15 -0
- package/src/editor/index.ts +2 -0
- package/src/editor/markdown.ts +172 -0
- package/src/editor/nodeViews/ImageComponent.tsx +20 -0
- package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
- package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
- package/src/editor/nodeViews/index.ts +35 -0
- package/src/editor/plugins/index.ts +58 -0
- package/src/editor/plugins/inputrules.ts +82 -0
- package/src/editor/plugins/placeholderPlugin.ts +55 -0
- package/src/editor/plugins/slashCommandPlugin.ts +61 -0
- package/src/editor/schema.ts +240 -0
- package/src/editor/selectors/ai-selector.tsx +111 -0
- package/src/editor/selectors/color-selector.tsx +200 -0
- package/src/editor/selectors/link-selector.tsx +118 -0
- package/src/editor/selectors/node-selector.tsx +157 -0
- package/src/editor/selectors/text-buttons.tsx +86 -0
- package/src/editor/types.ts +6 -0
- package/src/editor/useProseMirror.ts +126 -0
- package/src/editor/utils/prosemirror-utils.ts +108 -0
- package/src/editor/utils/remove_classes.ts +17 -0
- package/src/editor/utils/useDebouncedCallback.ts +25 -0
- package/src/form/EntityForm.tsx +149 -67
- package/src/form/EntityFormActions.tsx +19 -12
- package/src/form/PropertyFieldBinding.tsx +10 -8
- package/src/form/components/ErrorFocus.tsx +3 -3
- package/src/form/components/LocalChangesMenu.tsx +13 -13
- package/src/form/components/StorageItemPreview.tsx +3 -2
- package/src/form/components/StorageUploadProgress.tsx +18 -3
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +22 -9
- package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -24
- package/src/form/field_bindings/MapFieldBinding.tsx +27 -11
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +74 -37
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
- package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
- package/src/form/field_bindings/RepeatFieldBinding.tsx +21 -6
- package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +110 -92
- package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
- package/src/form/field_bindings/TextFieldBinding.tsx +10 -7
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useBuildNavigationController.tsx +49 -22
- package/src/hooks/useCollapsedGroups.ts +7 -6
- package/src/hooks/useTranslation.ts +31 -0
- package/src/hooks/useValidateAuthenticator.tsx +1 -1
- package/src/i18n/FireCMSi18nProvider.tsx +160 -0
- package/src/index.ts +5 -0
- package/src/internal/useBuildDataSource.ts +1 -2
- package/src/internal/useBuildSideEntityController.tsx +22 -20
- package/src/locales/de.ts +718 -0
- package/src/locales/en.ts +730 -0
- package/src/locales/es.ts +730 -0
- package/src/locales/fr.ts +718 -0
- package/src/locales/hi.ts +718 -0
- package/src/locales/it.ts +718 -0
- package/src/locales/pt.ts +727 -0
- package/src/preview/PropertyPreview.tsx +4 -2
- package/src/preview/components/ReferencePreview.tsx +2 -1
- package/src/preview/components/UrlComponentPreview.tsx +4 -2
- package/src/preview/components/UserPreview.tsx +3 -1
- package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
- package/src/routes/FireCMSRoute.tsx +63 -54
- package/src/types/analytics.ts +10 -0
- package/src/types/collections.ts +49 -0
- package/src/types/customization_controller.tsx +2 -1
- package/src/types/firecms.tsx +2 -1
- package/src/types/index.ts +1 -0
- package/src/types/navigation.ts +2 -2
- package/src/types/plugins.tsx +26 -0
- package/src/types/properties.ts +12 -10
- package/src/types/storage.ts +2 -1
- package/src/types/translations.ts +752 -0
- package/src/util/entities.ts +1 -1
- package/src/util/index.ts +1 -0
- package/src/util/join_collections.ts +10 -8
- package/src/util/lazy_eager.tsx +33 -0
- package/src/util/objects.ts +15 -0
- package/src/util/previews.ts +2 -2
- package/src/util/property_utils.tsx +1 -1
- package/src/util/resolutions.ts +5 -3
- package/src/util/useStorageUploadController.tsx +23 -29
|
@@ -38,9 +38,12 @@ import {
|
|
|
38
38
|
fieldBackgroundDisabledMixin,
|
|
39
39
|
fieldBackgroundHoverMixin,
|
|
40
40
|
fieldBackgroundMixin,
|
|
41
|
-
Typography
|
|
41
|
+
Typography,
|
|
42
|
+
IconButton,
|
|
43
|
+
CloseIcon
|
|
42
44
|
} from "@firecms/ui";
|
|
43
45
|
import { useClearRestoreValue } from "../useClearRestoreValue";
|
|
46
|
+
import { useTranslation } from "../../hooks/useTranslation";
|
|
44
47
|
|
|
45
48
|
const dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent min-h-[254px] outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
|
|
46
49
|
const disabledClasses = fieldBackgroundDisabledMixin;
|
|
@@ -52,18 +55,18 @@ const rejectDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0
|
|
|
52
55
|
type StorageUploadFieldProps = FieldProps<string | string[]>;
|
|
53
56
|
|
|
54
57
|
export function StorageUploadFieldBinding({
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
propertyKey,
|
|
59
|
+
value,
|
|
60
|
+
setValue,
|
|
61
|
+
error,
|
|
62
|
+
showError,
|
|
63
|
+
autoFocus,
|
|
64
|
+
minimalistView,
|
|
65
|
+
property,
|
|
66
|
+
includeDescription,
|
|
67
|
+
context,
|
|
68
|
+
isSubmitting,
|
|
69
|
+
}: StorageUploadFieldProps) {
|
|
67
70
|
|
|
68
71
|
const authController = useAuthController();
|
|
69
72
|
|
|
@@ -100,7 +103,7 @@ export function StorageUploadFieldBinding({
|
|
|
100
103
|
});
|
|
101
104
|
|
|
102
105
|
const resolvedProperty = resolveProperty({
|
|
103
|
-
propertyOrBuilder: property as PropertyOrBuilder
|
|
106
|
+
propertyOrBuilder: property as PropertyOrBuilder<string>,
|
|
104
107
|
authController
|
|
105
108
|
}) as ResolvedStringProperty | ResolvedArrayProperty<string[]>;
|
|
106
109
|
|
|
@@ -109,12 +112,27 @@ export function StorageUploadFieldBinding({
|
|
|
109
112
|
<>
|
|
110
113
|
|
|
111
114
|
{!minimalistView &&
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
<div className="flex items-center w-full">
|
|
116
|
+
<LabelWithIconAndTooltip
|
|
117
|
+
propertyKey={propertyKey}
|
|
118
|
+
icon={getIconForProperty(property, "small")}
|
|
119
|
+
required={property.validation?.required}
|
|
120
|
+
title={property.name}
|
|
121
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"} />
|
|
122
|
+
<div className="flex-grow"/>
|
|
123
|
+
{(property.nullable || property.clearable) && !disabled && (
|
|
124
|
+
<IconButton
|
|
125
|
+
size="small"
|
|
126
|
+
onClick={(e) => {
|
|
127
|
+
e.stopPropagation();
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
setValue(null);
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<CloseIcon size={"small"}/>
|
|
133
|
+
</IconButton>
|
|
134
|
+
)}
|
|
135
|
+
</div>}
|
|
118
136
|
|
|
119
137
|
<StorageUpload
|
|
120
138
|
value={internalValue}
|
|
@@ -128,14 +146,13 @@ export function StorageUploadFieldBinding({
|
|
|
128
146
|
onFileUploadComplete={onFileUploadComplete}
|
|
129
147
|
storagePathBuilder={storagePathBuilder}
|
|
130
148
|
storage={storage}
|
|
131
|
-
multipleFilesSupported={multipleFilesSupported}/>
|
|
149
|
+
multipleFilesSupported={multipleFilesSupported} />
|
|
132
150
|
|
|
133
151
|
<FieldHelperText includeDescription={includeDescription}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
showError={showError}
|
|
153
|
+
error={error}
|
|
154
|
+
disabled={disabled}
|
|
155
|
+
property={property} />
|
|
139
156
|
</>
|
|
140
157
|
);
|
|
141
158
|
}
|
|
@@ -154,15 +171,15 @@ interface SortableStorageItemProps {
|
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
function SortableStorageItem({
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
id,
|
|
175
|
+
entry,
|
|
176
|
+
property,
|
|
177
|
+
metadata,
|
|
178
|
+
storagePathBuilder,
|
|
179
|
+
onFileUploadComplete,
|
|
180
|
+
onClear,
|
|
181
|
+
disabled,
|
|
182
|
+
}: SortableStorageItemProps) {
|
|
166
183
|
|
|
167
184
|
const {
|
|
168
185
|
attributes,
|
|
@@ -201,7 +218,7 @@ function SortableStorageItem({
|
|
|
201
218
|
disabled={disabled}
|
|
202
219
|
value={entry.storagePathOrDownloadUrl}
|
|
203
220
|
onRemove={() => onClear(entry.storagePathOrDownloadUrl!)}
|
|
204
|
-
size={entry.size}/>
|
|
221
|
+
size={entry.size} />
|
|
205
222
|
);
|
|
206
223
|
} else if (entry.file) {
|
|
207
224
|
child = (
|
|
@@ -231,21 +248,21 @@ function SortableStorageItem({
|
|
|
231
248
|
}
|
|
232
249
|
|
|
233
250
|
function FileDropComponent({
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
251
|
+
storage,
|
|
252
|
+
disabled,
|
|
253
|
+
onFilesAdded,
|
|
254
|
+
multipleFilesSupported,
|
|
255
|
+
autoFocus,
|
|
256
|
+
internalValue,
|
|
257
|
+
property,
|
|
258
|
+
onClear,
|
|
259
|
+
metadata,
|
|
260
|
+
storagePathBuilder,
|
|
261
|
+
onFileUploadComplete,
|
|
262
|
+
name,
|
|
263
|
+
helpText,
|
|
264
|
+
isDndItemDragging
|
|
265
|
+
}: {
|
|
249
266
|
storage: StorageConfig,
|
|
250
267
|
disabled: boolean,
|
|
251
268
|
onFilesAdded: (acceptedFiles: File[]) => Promise<void>,
|
|
@@ -271,33 +288,33 @@ function FileDropComponent({
|
|
|
271
288
|
isDragAccept,
|
|
272
289
|
isDragReject
|
|
273
290
|
} = useDropzone({
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
291
|
+
accept: storage.acceptedFiles ? storage.acceptedFiles.reduce((acc, ext) => ({
|
|
292
|
+
...acc,
|
|
293
|
+
[ext]: []
|
|
294
|
+
}), {}) : undefined,
|
|
295
|
+
disabled: disabled || isDndItemDragging,
|
|
296
|
+
noDragEventsBubbling: true,
|
|
297
|
+
maxSize: storage.maxSize,
|
|
298
|
+
onDrop: onFilesAdded,
|
|
299
|
+
onDropRejected: (fileRejections) => {
|
|
300
|
+
for (const fileRejection of fileRejections) {
|
|
301
|
+
for (const error of fileRejection.errors) {
|
|
302
|
+
console.error("Error uploading file: ", error);
|
|
303
|
+
if (error.code === "file-too-large") {
|
|
304
|
+
snackbarContext.open({
|
|
305
|
+
type: "error",
|
|
306
|
+
message: `Error uploading file: File is larger than ${storage.maxSize} bytes`
|
|
307
|
+
});
|
|
308
|
+
} else if (error.code === "file-invalid-type") {
|
|
309
|
+
snackbarContext.open({
|
|
310
|
+
type: "error",
|
|
311
|
+
message: "Error uploading file: File type is not supported"
|
|
312
|
+
});
|
|
297
313
|
}
|
|
298
314
|
}
|
|
299
315
|
}
|
|
300
316
|
}
|
|
317
|
+
}
|
|
301
318
|
);
|
|
302
319
|
|
|
303
320
|
return (
|
|
@@ -349,8 +366,8 @@ function FileDropComponent({
|
|
|
349
366
|
<div
|
|
350
367
|
className="flex-grow min-h-[38px] box-border m-2 text-center">
|
|
351
368
|
<Typography align={"center"}
|
|
352
|
-
|
|
353
|
-
|
|
369
|
+
variant={"label"}
|
|
370
|
+
className={disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : ""}>
|
|
354
371
|
{helpText}
|
|
355
372
|
</Typography>
|
|
356
373
|
</div>
|
|
@@ -374,19 +391,20 @@ export interface StorageUploadProps {
|
|
|
374
391
|
}
|
|
375
392
|
|
|
376
393
|
export function StorageUpload({
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
394
|
+
property,
|
|
395
|
+
name,
|
|
396
|
+
value, // This is internalValue from useStorageUploadController
|
|
397
|
+
setInternalValue,
|
|
398
|
+
onChange,
|
|
399
|
+
multipleFilesSupported,
|
|
400
|
+
onFileUploadComplete,
|
|
401
|
+
disabled,
|
|
402
|
+
onFilesAdded,
|
|
403
|
+
autoFocus,
|
|
404
|
+
storage,
|
|
405
|
+
storagePathBuilder,
|
|
406
|
+
}: StorageUploadProps) {
|
|
407
|
+
const { t } = useTranslation();
|
|
390
408
|
|
|
391
409
|
if (multipleFilesSupported) {
|
|
392
410
|
const arrayProperty = property as ResolvedArrayProperty<string[]>;
|
|
@@ -461,8 +479,8 @@ export function StorageUpload({
|
|
|
461
479
|
}, [value, multipleFilesSupported, onChange, setInternalValue]);
|
|
462
480
|
|
|
463
481
|
const helpText = multipleFilesSupported
|
|
464
|
-
? "
|
|
465
|
-
: "
|
|
482
|
+
? t("drag_drop_multiple")
|
|
483
|
+
: t("drag_drop_single");
|
|
466
484
|
|
|
467
485
|
const renderProperty: ResolvedStringProperty = multipleFilesSupported
|
|
468
486
|
? (property as ResolvedArrayProperty<string[]>).of as ResolvedStringProperty
|
|
@@ -500,6 +518,6 @@ export function StorageUpload({
|
|
|
500
518
|
);
|
|
501
519
|
} else {
|
|
502
520
|
// For single file, no D&D context is needed
|
|
503
|
-
return <FileDropComponent {...fileDropProps} isDndItemDragging={false}/>;
|
|
521
|
+
return <FileDropComponent {...fileDropProps} isDndItemDragging={false} />;
|
|
504
522
|
}
|
|
505
523
|
}
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
import { FieldProps } from "../../types";
|
|
4
4
|
import { getIconForProperty } from "../../util";
|
|
5
5
|
import { FieldHelperText, LabelWithIcon } from "../components";
|
|
6
|
-
import { BooleanSwitchWithLabel } from "@firecms/ui";
|
|
6
|
+
import { BooleanSwitchWithLabel, IconButton, CloseIcon } from "@firecms/ui";
|
|
7
7
|
import { useClearRestoreValue } from "../useClearRestoreValue";
|
|
8
8
|
import { PropertyIdCopyTooltip } from "../../components";
|
|
9
9
|
|
|
@@ -39,19 +39,36 @@ export const SwitchFieldBinding = function SwitchFieldBinding({
|
|
|
39
39
|
<>
|
|
40
40
|
|
|
41
41
|
<PropertyIdCopyTooltip propertyKey={propertyKey}>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
<div className="flex items-center">
|
|
43
|
+
<BooleanSwitchWithLabel
|
|
44
|
+
value={value}
|
|
45
|
+
onValueChange={(v) => setValue(v)}
|
|
46
|
+
error={showError}
|
|
47
|
+
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
48
|
+
label={<LabelWithIcon
|
|
49
|
+
icon={getIconForProperty(property, "small")}
|
|
50
|
+
required={property.validation?.required}
|
|
51
|
+
title={property.name}/>}
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
autoFocus={autoFocus}
|
|
54
|
+
size={size}
|
|
55
|
+
switchAdornment={
|
|
56
|
+
(property.nullable || property.clearable) && !disabled && value !== null && (
|
|
57
|
+
<IconButton
|
|
58
|
+
size="small"
|
|
59
|
+
onClick={(e) => {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
setValue(null);
|
|
63
|
+
}}
|
|
64
|
+
className="mr-2"
|
|
65
|
+
>
|
|
66
|
+
<CloseIcon size={"small"}/>
|
|
67
|
+
</IconButton>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
55
72
|
</PropertyIdCopyTooltip>
|
|
56
73
|
|
|
57
74
|
<FieldHelperText includeDescription={includeDescription}
|
|
@@ -100,10 +100,10 @@ export function TextFieldBinding<T extends string | number>({
|
|
|
100
100
|
showError && error ? "text-red-500 dark:text-red-600" : ""
|
|
101
101
|
)}
|
|
102
102
|
/>
|
|
103
|
-
{property.clearable && (
|
|
103
|
+
{(property.nullable || property.clearable) && value !== null && value !== undefined && (
|
|
104
104
|
<div className="flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-4">
|
|
105
|
-
<IconButton onClick={handleClearClick}>
|
|
106
|
-
<CloseIcon />
|
|
105
|
+
<IconButton size="small" onClick={handleClearClick}>
|
|
106
|
+
<CloseIcon size="small" />
|
|
107
107
|
</IconButton>
|
|
108
108
|
</div>
|
|
109
109
|
)}
|
|
@@ -119,10 +119,13 @@ export function TextFieldBinding<T extends string | number>({
|
|
|
119
119
|
type={inputType}
|
|
120
120
|
disabled={disabled}
|
|
121
121
|
endAdornment={
|
|
122
|
-
property.clearable &&
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
(property.nullable || property.clearable) && value !== null && value !== undefined ? (
|
|
123
|
+
<IconButton
|
|
124
|
+
size="small"
|
|
125
|
+
onClick={handleClearClick}>
|
|
126
|
+
<CloseIcon size="small" />
|
|
127
|
+
</IconButton>
|
|
128
|
+
) : undefined
|
|
126
129
|
}
|
|
127
130
|
error={showError ? error : undefined}
|
|
128
131
|
inputClassName={error ? "text-red-500 dark:text-red-600" : ""} />
|
|
@@ -59,11 +59,13 @@ export function UserSelectFieldBinding({
|
|
|
59
59
|
/>
|
|
60
60
|
</PropertyIdCopyTooltip>}
|
|
61
61
|
endAdornment={
|
|
62
|
-
property.clearable && !disabled && value &&
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
(property.nullable || property.clearable) && !disabled && value !== null && value !== undefined ? (
|
|
63
|
+
<IconButton
|
|
64
|
+
size="small"
|
|
65
|
+
onClick={handleClearClick}>
|
|
66
|
+
<CloseIcon size={"small"}/>
|
|
67
|
+
</IconButton>
|
|
68
|
+
) : undefined
|
|
67
69
|
}
|
|
68
70
|
onValueChange={(updatedValue: string) => {
|
|
69
71
|
const newValue = updatedValue || null;
|
package/src/hooks/index.tsx
CHANGED
|
@@ -34,7 +34,6 @@ import { getParentReferencesFromPath } from "../util/parent_references_from_path
|
|
|
34
34
|
const DEFAULT_BASE_PATH = "/";
|
|
35
35
|
const DEFAULT_COLLECTION_PATH = "/c";
|
|
36
36
|
|
|
37
|
-
export const NAVIGATION_DEFAULT_GROUP_NAME = "Views";
|
|
38
37
|
export const NAVIGATION_ADMIN_GROUP_NAME = "Admin";
|
|
39
38
|
|
|
40
39
|
export type BuildNavigationContextProps<EC extends EntityCollection, USER extends User> = {
|
|
@@ -122,10 +121,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
122
121
|
|
|
123
122
|
const navigate = useNavigate();
|
|
124
123
|
|
|
125
|
-
const collectionsRef = useRef<EntityCollection[] | undefined>();
|
|
126
|
-
const viewsRef = useRef<CMSView[] | undefined>();
|
|
127
|
-
const adminViewsRef = useRef<CMSView[] | undefined>();
|
|
128
|
-
const navigationEntriesOrderRef = useRef<string[] | undefined>();
|
|
124
|
+
const collectionsRef = useRef<EntityCollection[] | undefined>(undefined);
|
|
125
|
+
const viewsRef = useRef<CMSView[] | undefined>(undefined);
|
|
126
|
+
const adminViewsRef = useRef<CMSView[] | undefined>(undefined);
|
|
127
|
+
const navigationEntriesOrderRef = useRef<string[] | undefined>(undefined);
|
|
129
128
|
|
|
130
129
|
const [initialised, setInitialised] = useState<boolean>(false);
|
|
131
130
|
|
|
@@ -189,7 +188,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
189
188
|
path: pathKey,
|
|
190
189
|
collection,
|
|
191
190
|
description: collection.description?.trim(),
|
|
192
|
-
group: groupName
|
|
191
|
+
group: groupName
|
|
193
192
|
});
|
|
194
193
|
return acc;
|
|
195
194
|
}, [] as NavigationEntry[]),
|
|
@@ -217,7 +216,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
217
216
|
path: view.path,
|
|
218
217
|
view,
|
|
219
218
|
description: view.description?.trim(),
|
|
220
|
-
group: groupName
|
|
219
|
+
group: groupName
|
|
221
220
|
});
|
|
222
221
|
return acc;
|
|
223
222
|
}, [] as NavigationEntry[]),
|
|
@@ -242,7 +241,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
242
241
|
}, [] as NavigationEntry[])
|
|
243
242
|
];
|
|
244
243
|
|
|
245
|
-
const groupOrderValue = (groupName?: string): number => {
|
|
244
|
+
const groupOrderValue = (groupName?: string | null): number => {
|
|
246
245
|
if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
|
|
247
246
|
return 0; // Other groups
|
|
248
247
|
};
|
|
@@ -266,7 +265,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
266
265
|
|
|
267
266
|
const collectedGroupsFromEntries = navigationEntries
|
|
268
267
|
.map(e => e.group)
|
|
269
|
-
.filter(
|
|
268
|
+
.filter((g): g is string => g !== null && Boolean(g));
|
|
269
|
+
|
|
270
|
+
// Check if there are any ungrouped entries
|
|
271
|
+
const hasUngroupedEntries = navigationEntries.some(e => e.group === null && e.type !== "admin");
|
|
270
272
|
|
|
271
273
|
// Preserve order from finalNavigationGroupMappings (persisted order)
|
|
272
274
|
const groupsFromMappings = finalNavigationGroupMappings.map(g => g.name);
|
|
@@ -284,7 +286,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
284
286
|
const uniqueGroupsArray = [...new Set(allDefinedGroups)];
|
|
285
287
|
const adminGroups = uniqueGroupsArray.filter(g => g === NAVIGATION_ADMIN_GROUP_NAME);
|
|
286
288
|
const nonAdminGroups = uniqueGroupsArray.filter(g => g !== NAVIGATION_ADMIN_GROUP_NAME);
|
|
287
|
-
|
|
289
|
+
// Place null (ungrouped) first if there are ungrouped entries
|
|
290
|
+
const uniqueGroups: (string | null)[] = [
|
|
291
|
+
...(hasUngroupedEntries ? [null] : []),
|
|
292
|
+
...nonAdminGroups,
|
|
293
|
+
...adminGroups
|
|
294
|
+
];
|
|
288
295
|
|
|
289
296
|
return {
|
|
290
297
|
allowDragAndDrop: plugins?.some(plugin => plugin.homePage?.allowDragAndDrop) ?? false,
|
|
@@ -487,8 +494,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
487
494
|
|
|
488
495
|
const urlPathToDataPath = useCallback((path: string): string => {
|
|
489
496
|
const decodedPath = decodeURIComponent(path);
|
|
490
|
-
|
|
491
|
-
|
|
497
|
+
const withoutHash = decodedPath.split("#")[0];
|
|
498
|
+
const cleanPath = withoutHash.split("?")[0];
|
|
499
|
+
if (cleanPath.startsWith(fullCollectionPath))
|
|
500
|
+
return cleanPath.replace(fullCollectionPath, "");
|
|
492
501
|
throw Error("Expected path starting with " + fullCollectionPath);
|
|
493
502
|
}, [fullCollectionPath]);
|
|
494
503
|
|
|
@@ -565,9 +574,27 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
565
574
|
}
|
|
566
575
|
|
|
567
576
|
function encodePath(input: string) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
577
|
+
const cleanInput = removeInitialAndTrailingSlashes(input);
|
|
578
|
+
const [pathPart, rest] = cleanInput.split("?", 2);
|
|
579
|
+
|
|
580
|
+
let encodedPath = encodeURIComponent(pathPart).replaceAll("%2F", "/");
|
|
581
|
+
let result = encodedPath;
|
|
582
|
+
|
|
583
|
+
if (rest !== undefined) {
|
|
584
|
+
const [searchPart, hashPart] = rest.split("#", 2);
|
|
585
|
+
result += `?${searchPart}`;
|
|
586
|
+
if (hashPart !== undefined) {
|
|
587
|
+
result += `#${hashPart}`;
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
const [pathOnly, hashOnly] = cleanInput.split("#", 2);
|
|
591
|
+
if (hashOnly !== undefined) {
|
|
592
|
+
encodedPath = encodeURIComponent(pathOnly).replaceAll("%2F", "/");
|
|
593
|
+
result = `${encodedPath}#${hashOnly}`;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return result;
|
|
571
598
|
}
|
|
572
599
|
|
|
573
600
|
function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
|
|
@@ -662,12 +689,12 @@ async function resolveCMSViews(
|
|
|
662
689
|
return resolvedViews;
|
|
663
690
|
}
|
|
664
691
|
|
|
665
|
-
function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
|
|
692
|
+
function getGroup(collectionOrView: EntityCollection<any, any> | CMSView): string | null {
|
|
666
693
|
const trimmed = collectionOrView.group?.trim();
|
|
667
694
|
if (!trimmed || trimmed === "") {
|
|
668
|
-
return
|
|
695
|
+
return null;
|
|
669
696
|
}
|
|
670
|
-
return trimmed
|
|
697
|
+
return trimmed;
|
|
671
698
|
}
|
|
672
699
|
|
|
673
700
|
function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
|
|
@@ -803,7 +830,7 @@ function computeNavigationGroups({
|
|
|
803
830
|
(collections ?? []).forEach(collection => {
|
|
804
831
|
const entry = collection.id ?? collection.path;
|
|
805
832
|
if (!assignedEntries.has(entry)) {
|
|
806
|
-
const groupName = getGroup(collection);
|
|
833
|
+
const groupName = getGroup(collection) ?? "__default__";
|
|
807
834
|
if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
|
|
808
835
|
unassignedGroupMap[groupName].push(entry);
|
|
809
836
|
}
|
|
@@ -813,7 +840,7 @@ function computeNavigationGroups({
|
|
|
813
840
|
(views ?? []).forEach(view => {
|
|
814
841
|
const entry = view.path;
|
|
815
842
|
if (!assignedEntries.has(entry)) {
|
|
816
|
-
const groupName = getGroup(view);
|
|
843
|
+
const groupName = getGroup(view) ?? "__default__";
|
|
817
844
|
if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
|
|
818
845
|
unassignedGroupMap[groupName].push(entry);
|
|
819
846
|
}
|
|
@@ -841,7 +868,7 @@ function computeNavigationGroups({
|
|
|
841
868
|
|
|
842
869
|
// Add collections
|
|
843
870
|
(collections ?? []).forEach(collection => {
|
|
844
|
-
const name = getGroup(collection);
|
|
871
|
+
const name = getGroup(collection) ?? "__default__";
|
|
845
872
|
const entry = collection.id ?? collection.path;
|
|
846
873
|
if (!groupMap[name]) groupMap[name] = [];
|
|
847
874
|
groupMap[name].push(entry);
|
|
@@ -849,7 +876,7 @@ function computeNavigationGroups({
|
|
|
849
876
|
|
|
850
877
|
// Add views
|
|
851
878
|
(views ?? []).forEach(view => {
|
|
852
|
-
const name = getGroup(view);
|
|
879
|
+
const name = getGroup(view) ?? "__default__";
|
|
853
880
|
const entry = view.path;
|
|
854
881
|
if (!groupMap[name]) groupMap[name] = [];
|
|
855
882
|
groupMap[name].push(entry);
|
|
@@ -10,7 +10,7 @@ const STORAGE_KEY_PREFIX = "firecms-collapsed-groups";
|
|
|
10
10
|
* @param groupNames - Array of group names to track
|
|
11
11
|
* @param namespace - Namespace for localStorage key (e.g., "home", "drawer") to allow independent state
|
|
12
12
|
*/
|
|
13
|
-
export function useCollapsedGroups(groupNames: string[], namespace: string = "default") {
|
|
13
|
+
export function useCollapsedGroups(groupNames: (string | null)[], namespace: string = "default") {
|
|
14
14
|
const storageKey = `${STORAGE_KEY_PREFIX}-${namespace}`;
|
|
15
15
|
|
|
16
16
|
// Load collapsed groups from localStorage on mount
|
|
@@ -37,7 +37,7 @@ export function useCollapsedGroups(groupNames: string[], namespace: string = "de
|
|
|
37
37
|
// Only clean up if we have actual groups loaded (avoid cleaning up during initial load)
|
|
38
38
|
if (groupNames.length === 0) return;
|
|
39
39
|
|
|
40
|
-
const currentGroupNames = new Set(groupNames);
|
|
40
|
+
const currentGroupNames = new Set(groupNames.map(g => g ?? "__default__"));
|
|
41
41
|
|
|
42
42
|
setCollapsedGroups(prev => {
|
|
43
43
|
const cleaned = Object.fromEntries(
|
|
@@ -56,12 +56,13 @@ export function useCollapsedGroups(groupNames: string[], namespace: string = "de
|
|
|
56
56
|
});
|
|
57
57
|
}, [groupNames]);
|
|
58
58
|
|
|
59
|
-
const isGroupCollapsed = useCallback((name
|
|
60
|
-
return !!collapsedGroups[name];
|
|
59
|
+
const isGroupCollapsed = useCallback((name?: string | null) => {
|
|
60
|
+
return !!collapsedGroups[name ?? "__default__"];
|
|
61
61
|
}, [collapsedGroups]);
|
|
62
62
|
|
|
63
|
-
const toggleGroupCollapsed = useCallback((name
|
|
64
|
-
|
|
63
|
+
const toggleGroupCollapsed = useCallback((name?: string | null) => {
|
|
64
|
+
const key = name ?? "__default__";
|
|
65
|
+
setCollapsedGroups(prev => ({ ...prev, [key]: !prev[key] }));
|
|
65
66
|
}, []);
|
|
66
67
|
|
|
67
68
|
return {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useTranslation as useI18nTranslation } from "react-i18next";
|
|
2
|
+
|
|
3
|
+
const FIRECMS_NS = "firecms_core";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal hook for translating FireCMS UI strings.
|
|
7
|
+
*
|
|
8
|
+
* Uses the `firecms_core` i18next namespace that is initialised by
|
|
9
|
+
* `FireCMSi18nProvider`. Do NOT use `react-i18next` directly in internal
|
|
10
|
+
* components — always go through this hook so the namespace is consistent.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const { t } = useTranslation();
|
|
14
|
+
* <Button>{t("save")}</Button>
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export function useTranslation() {
|
|
19
|
+
const { t, i18n } = useI18nTranslation(FIRECMS_NS);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Typed translation function scoped to FirecmsTranslations keys.
|
|
23
|
+
* Also supports i18next interpolation variables, e.g.
|
|
24
|
+
* t("add_to_field", { fieldName: "Tags" })
|
|
25
|
+
* t("error_deleting", { message: err.message })
|
|
26
|
+
*/
|
|
27
|
+
const typedT = (key: string, vars?: Record<string, string>): string =>
|
|
28
|
+
t(key, vars) as string;
|
|
29
|
+
|
|
30
|
+
return { t: typedT, i18n };
|
|
31
|
+
}
|