@firecms/core 3.0.1 → 3.1.0-canary.02232f4
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/README.md +1 -1
- package/dist/components/AIIcon.d.ts +16 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
- package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
- package/dist/components/EntityCollectionView/Board.d.ts +2 -0
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
- package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
- package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
- package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
- package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +44 -0
- package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
- 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/SelectableTable/SelectableTable.d.ts +5 -1
- package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
- package/dist/components/UnsavedChangesDialog.d.ts +1 -0
- package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +4 -1
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +17 -1
- package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
- package/dist/components/VirtualTable/types.d.ts +3 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/contexts/index.d.ts +10 -0
- package/dist/core/DrawerNavigationGroup.d.ts +45 -0
- package/dist/core/index.d.ts +1 -0
- 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/form/validation.d.ts +3 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/hooks/useCollapsedGroups.d.ts +6 -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 +30146 -15178
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +30032 -15085
- 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/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/DatePreview.d.ts +13 -3
- package/dist/preview/components/ImagePreview.d.ts +5 -1
- package/dist/preview/components/StorageThumbnail.d.ts +2 -1
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/collections.d.ts +88 -2
- package/dist/types/customization_controller.d.ts +2 -1
- package/dist/types/datasource.d.ts +0 -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 +69 -1
- package/dist/types/properties.d.ts +268 -12
- package/dist/types/storage.d.ts +1 -0
- package/dist/types/translations.d.ts +669 -0
- package/dist/util/__tests__/conditions.test.d.ts +1 -0
- package/dist/util/__tests__/objects.test.d.ts +1 -0
- package/dist/util/conditions.d.ts +26 -0
- package/dist/util/entities.d.ts +2 -3
- package/dist/util/index.d.ts +3 -1
- package/dist/util/lazy_eager.d.ts +7 -0
- package/dist/util/objects.d.ts +1 -0
- package/dist/util/property_utils.d.ts +2 -1
- package/dist/util/resolutions.d.ts +3 -3
- package/dist/util/useStorageUploadController.d.ts +10 -1
- package/package.json +51 -12
- package/src/app/Scaffold.tsx +20 -19
- package/src/components/AIIcon.tsx +41 -0
- package/src/components/ArrayContainer.tsx +7 -8
- package/src/components/ClearFilterSortButton.tsx +25 -19
- package/src/components/ConfirmationDialog.tsx +4 -4
- package/src/components/DeleteEntityDialog.tsx +12 -11
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +82 -43
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +24 -44
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
- 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/Board.tsx +324 -0
- package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
- package/src/components/EntityCollectionView/BoardSortableList.tsx +174 -0
- package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
- package/src/components/EntityCollectionView/EntityCard.tsx +235 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +706 -0
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +236 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +531 -209
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +35 -22
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +86 -15
- package/src/components/EntityCollectionView/FiltersDialog.tsx +252 -0
- package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
- package/src/components/EntityCollectionView/board_types.ts +113 -0
- package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
- package/src/components/EntityJsonPreview.tsx +2 -1
- package/src/components/EntityView.tsx +3 -2
- package/src/components/ErrorBoundary.tsx +27 -15
- package/src/components/ErrorTooltip.tsx +2 -1
- package/src/components/HomePage/DefaultHomePage.tsx +65 -22
- package/src/components/HomePage/HomePageDnD.tsx +59 -42
- package/src/components/HomePage/NavigationCard.tsx +20 -18
- package/src/components/HomePage/NavigationGroup.tsx +20 -17
- package/src/components/HomePage/RenameGroupDialog.tsx +15 -15
- package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
- package/src/components/LanguageToggle.tsx +66 -0
- package/src/components/NotFoundPage.tsx +5 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +12 -17
- package/src/components/ReferenceWidget.tsx +5 -6
- package/src/components/SearchIconsView.tsx +3 -1
- package/src/components/SelectableTable/SelectableTable.tsx +75 -67
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +50 -40
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +53 -40
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +60 -58
- package/src/components/UnsavedChangesDialog.tsx +6 -6
- package/src/components/UserDisplay.tsx +4 -4
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
- package/src/components/VirtualTable/VirtualTable.tsx +275 -119
- package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
- package/src/components/VirtualTable/VirtualTableHeader.tsx +76 -64
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +163 -42
- package/src/components/VirtualTable/VirtualTableProps.tsx +21 -2
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +19 -6
- package/src/components/VirtualTable/types.tsx +3 -0
- package/src/components/common/default_entity_actions.tsx +4 -0
- package/src/components/common/useColumnsIds.tsx +95 -3
- package/src/components/common/useDataSourceTableController.tsx +12 -4
- package/src/components/index.tsx +5 -0
- package/src/contexts/BreacrumbsContext.tsx +15 -8
- package/src/contexts/index.ts +10 -0
- package/src/core/DefaultAppBar.tsx +49 -32
- package/src/core/DefaultDrawer.tsx +49 -57
- package/src/core/DrawerNavigationGroup.tsx +120 -0
- package/src/core/DrawerNavigationItem.tsx +4 -3
- package/src/core/EntityEditView.tsx +94 -50
- package/src/core/EntityEditViewFormActions.tsx +24 -17
- package/src/core/EntitySidePanel.tsx +34 -30
- package/src/core/FireCMS.tsx +33 -6
- package/src/core/SideDialogs.tsx +4 -2
- package/src/core/field_configs.tsx +18 -11
- package/src/core/index.tsx +1 -0
- 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 +68 -51
- package/src/form/components/ErrorFocus.tsx +3 -3
- package/src/form/components/LocalChangesMenu.tsx +13 -13
- package/src/form/components/StorageItemPreview.tsx +5 -3
- 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 -10
- package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -17
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -25
- package/src/form/field_bindings/MapFieldBinding.tsx +88 -70
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +93 -52
- 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 +77 -38
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
- package/src/form/validation.ts +245 -160
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useBreadcrumbsController.tsx +18 -0
- package/src/hooks/useBuildNavigationController.tsx +91 -41
- package/src/hooks/useCollapsedGroups.ts +18 -9
- 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 +68 -34
- package/src/internal/useBuildSideDialogsController.tsx +11 -8
- package/src/internal/useBuildSideEntityController.tsx +24 -24
- package/src/internal/useRestoreScroll.tsx +26 -14
- 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 +43 -33
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/DatePreview.tsx +72 -4
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +37 -21
- package/src/preview/components/ReferencePreview.tsx +2 -1
- package/src/preview/components/StorageThumbnail.tsx +16 -12
- package/src/preview/components/UrlComponentPreview.tsx +32 -27
- package/src/preview/components/UserPreview.tsx +3 -1
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
- package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
- package/src/routes/CustomCMSRoute.tsx +1 -0
- package/src/routes/FireCMSRoute.tsx +87 -65
- package/src/types/analytics.ts +10 -0
- package/src/types/collections.ts +97 -3
- package/src/types/customization_controller.tsx +2 -1
- package/src/types/datasource.ts +54 -56
- 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 +77 -1
- package/src/types/properties.ts +359 -37
- package/src/types/storage.ts +2 -1
- package/src/types/translations.ts +752 -0
- package/src/util/__tests__/conditions.test.ts +506 -0
- package/src/util/__tests__/objects.test.ts +196 -0
- package/src/util/callbacks.ts +6 -3
- package/src/util/collections.ts +51 -6
- package/src/util/conditions.ts +339 -0
- package/src/util/entities.ts +29 -30
- package/src/util/entity_cache.ts +2 -1
- package/src/util/index.ts +3 -1
- package/src/util/join_collections.ts +10 -8
- package/src/util/lazy_eager.tsx +33 -0
- package/src/util/objects.ts +46 -13
- package/src/util/{references.ts → previews.ts} +16 -2
- package/src/util/property_utils.tsx +37 -11
- package/src/util/resolutions.ts +62 -58
- package/src/util/useStorageUploadController.tsx +23 -29
- /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
|
@@ -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
|
|
|
@@ -140,8 +139,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
140
139
|
|
|
141
140
|
const fullCollectionPath = cleanBasePath ? `/${cleanBasePath}/${cleanBaseCollectionPath}` : `/${cleanBaseCollectionPath}`;
|
|
142
141
|
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
|
|
143
|
+
const buildCMSUrlPath = useCallback((path: string): string => {
|
|
144
|
+
// Strip trailing /* wildcard from paths (used for nested routes in React Router)
|
|
145
|
+
const cleanPath = path.replace(/\/\*$/, "");
|
|
146
|
+
return cleanBasePath ? `/${cleanBasePath}/${encodePath(cleanPath)}` : `/${encodePath(cleanPath)}`;
|
|
147
|
+
}, [cleanBasePath]);
|
|
145
148
|
|
|
146
149
|
const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
|
|
147
150
|
[baseCollectionPath]);
|
|
@@ -185,7 +188,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
185
188
|
path: pathKey,
|
|
186
189
|
collection,
|
|
187
190
|
description: collection.description?.trim(),
|
|
188
|
-
group: groupName
|
|
191
|
+
group: groupName
|
|
189
192
|
});
|
|
190
193
|
return acc;
|
|
191
194
|
}, [] as NavigationEntry[]),
|
|
@@ -213,7 +216,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
213
216
|
path: view.path,
|
|
214
217
|
view,
|
|
215
218
|
description: view.description?.trim(),
|
|
216
|
-
group: groupName
|
|
219
|
+
group: groupName
|
|
217
220
|
});
|
|
218
221
|
return acc;
|
|
219
222
|
}, [] as NavigationEntry[]),
|
|
@@ -238,7 +241,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
238
241
|
}, [] as NavigationEntry[])
|
|
239
242
|
];
|
|
240
243
|
|
|
241
|
-
const groupOrderValue = (groupName?: string): number => {
|
|
244
|
+
const groupOrderValue = (groupName?: string | null): number => {
|
|
242
245
|
if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
|
|
243
246
|
return 0; // Other groups
|
|
244
247
|
};
|
|
@@ -262,7 +265,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
262
265
|
|
|
263
266
|
const collectedGroupsFromEntries = navigationEntries
|
|
264
267
|
.map(e => e.group)
|
|
265
|
-
.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");
|
|
266
272
|
|
|
267
273
|
// Preserve order from finalNavigationGroupMappings (persisted order)
|
|
268
274
|
const groupsFromMappings = finalNavigationGroupMappings.map(g => g.name);
|
|
@@ -280,7 +286,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
280
286
|
const uniqueGroupsArray = [...new Set(allDefinedGroups)];
|
|
281
287
|
const adminGroups = uniqueGroupsArray.filter(g => g === NAVIGATION_ADMIN_GROUP_NAME);
|
|
282
288
|
const nonAdminGroups = uniqueGroupsArray.filter(g => g !== NAVIGATION_ADMIN_GROUP_NAME);
|
|
283
|
-
|
|
289
|
+
// Place null (ungrouped) first if there are ungrouped entries
|
|
290
|
+
const uniqueGroups: (string | null)[] = [
|
|
291
|
+
...(hasUngroupedEntries ? [null] : []),
|
|
292
|
+
...nonAdminGroups,
|
|
293
|
+
...adminGroups
|
|
294
|
+
];
|
|
284
295
|
|
|
285
296
|
return {
|
|
286
297
|
allowDragAndDrop: plugins?.some(plugin => plugin.homePage?.allowDragAndDrop) ?? false,
|
|
@@ -331,10 +342,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
331
342
|
try {
|
|
332
343
|
|
|
333
344
|
const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
345
|
+
resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
|
|
346
|
+
resolveCMSViews(viewsProp, authController, dataSourceDelegate, plugins),
|
|
347
|
+
resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
|
|
348
|
+
]
|
|
338
349
|
);
|
|
339
350
|
|
|
340
351
|
const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder, undefined, onNavigationEntriesOrderUpdate);
|
|
@@ -483,8 +494,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
483
494
|
|
|
484
495
|
const urlPathToDataPath = useCallback((path: string): string => {
|
|
485
496
|
const decodedPath = decodeURIComponent(path);
|
|
486
|
-
|
|
487
|
-
|
|
497
|
+
const withoutHash = decodedPath.split("#")[0];
|
|
498
|
+
const cleanPath = withoutHash.split("?")[0];
|
|
499
|
+
if (cleanPath.startsWith(fullCollectionPath))
|
|
500
|
+
return cleanPath.replace(fullCollectionPath, "");
|
|
488
501
|
throw Error("Expected path starting with " + fullCollectionPath);
|
|
489
502
|
}, [fullCollectionPath]);
|
|
490
503
|
|
|
@@ -561,9 +574,27 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
561
574
|
}
|
|
562
575
|
|
|
563
576
|
function encodePath(input: string) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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;
|
|
567
598
|
}
|
|
568
599
|
|
|
569
600
|
function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
|
|
@@ -597,10 +628,10 @@ function applyPluginModifyCollection(resolvedCollections: EntityCollection[], mo
|
|
|
597
628
|
}
|
|
598
629
|
|
|
599
630
|
async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
631
|
+
collectionPermissions: PermissionsBuilder | undefined,
|
|
632
|
+
authController: AuthController,
|
|
633
|
+
dataSource: DataSourceDelegate,
|
|
634
|
+
plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
|
|
604
635
|
let resolvedCollections: EntityCollection[] = [];
|
|
605
636
|
if (typeof collections === "function") {
|
|
606
637
|
resolvedCollections = await collections({
|
|
@@ -629,7 +660,12 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
|
|
|
629
660
|
return resolvedCollections;
|
|
630
661
|
}
|
|
631
662
|
|
|
632
|
-
async function resolveCMSViews(
|
|
663
|
+
async function resolveCMSViews(
|
|
664
|
+
baseViews: CMSView[] | CMSViewsBuilder | undefined,
|
|
665
|
+
authController: AuthController,
|
|
666
|
+
dataSource: DataSourceDelegate,
|
|
667
|
+
plugins?: FireCMSPlugin[]
|
|
668
|
+
) {
|
|
633
669
|
let resolvedViews: CMSView[] = [];
|
|
634
670
|
if (typeof baseViews === "function") {
|
|
635
671
|
resolvedViews = await baseViews({
|
|
@@ -640,15 +676,25 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
|
|
|
640
676
|
} else if (Array.isArray(baseViews)) {
|
|
641
677
|
resolvedViews = baseViews;
|
|
642
678
|
}
|
|
679
|
+
|
|
680
|
+
// Inject views from plugins
|
|
681
|
+
if (plugins) {
|
|
682
|
+
for (const plugin of plugins) {
|
|
683
|
+
if (plugin.views && plugin.views.length > 0) {
|
|
684
|
+
resolvedViews = [...resolvedViews, ...plugin.views];
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
643
689
|
return resolvedViews;
|
|
644
690
|
}
|
|
645
691
|
|
|
646
|
-
function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
|
|
692
|
+
function getGroup(collectionOrView: EntityCollection<any, any> | CMSView): string | null {
|
|
647
693
|
const trimmed = collectionOrView.group?.trim();
|
|
648
694
|
if (!trimmed || trimmed === "") {
|
|
649
|
-
return
|
|
695
|
+
return null;
|
|
650
696
|
}
|
|
651
|
-
return trimmed
|
|
697
|
+
return trimmed;
|
|
652
698
|
}
|
|
653
699
|
|
|
654
700
|
function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
|
|
@@ -688,8 +734,8 @@ function useCustomBlocker(): NavigationBlocker {
|
|
|
688
734
|
let blocker: any;
|
|
689
735
|
try {
|
|
690
736
|
blocker = useBlocker(({
|
|
691
|
-
|
|
692
|
-
|
|
737
|
+
nextLocation
|
|
738
|
+
}) => {
|
|
693
739
|
const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
|
|
694
740
|
if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
|
|
695
741
|
return false;
|
|
@@ -729,11 +775,11 @@ function useCustomBlocker(): NavigationBlocker {
|
|
|
729
775
|
}
|
|
730
776
|
|
|
731
777
|
function computeNavigationGroups({
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
778
|
+
navigationGroupMappings,
|
|
779
|
+
collections,
|
|
780
|
+
views,
|
|
781
|
+
plugins
|
|
782
|
+
}: {
|
|
737
783
|
navigationGroupMappings?: NavigationGroupMapping[],
|
|
738
784
|
collections?: EntityCollection[],
|
|
739
785
|
views?: CMSView[],
|
|
@@ -743,6 +789,7 @@ function computeNavigationGroups({
|
|
|
743
789
|
let result = navigationGroupMappings;
|
|
744
790
|
|
|
745
791
|
// Merge plugin navigation entries
|
|
792
|
+
// IMPORTANT: Deep clone the groups to avoid mutating the original input
|
|
746
793
|
result = plugins ? plugins?.reduce((acc, plugin) => {
|
|
747
794
|
if (plugin.homePage?.navigationEntries) {
|
|
748
795
|
plugin.homePage.navigationEntries.forEach((entry) => {
|
|
@@ -763,7 +810,10 @@ function computeNavigationGroups({
|
|
|
763
810
|
|
|
764
811
|
}
|
|
765
812
|
return acc;
|
|
766
|
-
},
|
|
813
|
+
}, (result ?? []).map(g => ({
|
|
814
|
+
name: g.name,
|
|
815
|
+
entries: [...g.entries]
|
|
816
|
+
}))) : result;
|
|
767
817
|
|
|
768
818
|
// Track all entries that are already assigned to groups
|
|
769
819
|
const assignedEntries = new Set<string>();
|
|
@@ -780,7 +830,7 @@ function computeNavigationGroups({
|
|
|
780
830
|
(collections ?? []).forEach(collection => {
|
|
781
831
|
const entry = collection.id ?? collection.path;
|
|
782
832
|
if (!assignedEntries.has(entry)) {
|
|
783
|
-
const groupName = getGroup(collection);
|
|
833
|
+
const groupName = getGroup(collection) ?? "__default__";
|
|
784
834
|
if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
|
|
785
835
|
unassignedGroupMap[groupName].push(entry);
|
|
786
836
|
}
|
|
@@ -790,7 +840,7 @@ function computeNavigationGroups({
|
|
|
790
840
|
(views ?? []).forEach(view => {
|
|
791
841
|
const entry = view.path;
|
|
792
842
|
if (!assignedEntries.has(entry)) {
|
|
793
|
-
const groupName = getGroup(view);
|
|
843
|
+
const groupName = getGroup(view) ?? "__default__";
|
|
794
844
|
if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
|
|
795
845
|
unassignedGroupMap[groupName].push(entry);
|
|
796
846
|
}
|
|
@@ -818,7 +868,7 @@ function computeNavigationGroups({
|
|
|
818
868
|
|
|
819
869
|
// Add collections
|
|
820
870
|
(collections ?? []).forEach(collection => {
|
|
821
|
-
const name = getGroup(collection);
|
|
871
|
+
const name = getGroup(collection) ?? "__default__";
|
|
822
872
|
const entry = collection.id ?? collection.path;
|
|
823
873
|
if (!groupMap[name]) groupMap[name] = [];
|
|
824
874
|
groupMap[name].push(entry);
|
|
@@ -826,7 +876,7 @@ function computeNavigationGroups({
|
|
|
826
876
|
|
|
827
877
|
// Add views
|
|
828
878
|
(views ?? []).forEach(view => {
|
|
829
|
-
const name = getGroup(view);
|
|
879
|
+
const name = getGroup(view) ?? "__default__";
|
|
830
880
|
const entry = view.path;
|
|
831
881
|
if (!groupMap[name]) groupMap[name] = [];
|
|
832
882
|
groupMap[name].push(entry);
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from "react";
|
|
2
2
|
|
|
3
|
+
const STORAGE_KEY_PREFIX = "firecms-collapsed-groups";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Custom hook for managing collapsed/expanded state of navigation groups
|
|
5
7
|
* with localStorage persistence. Automatically cleans up stale group entries
|
|
6
8
|
* when groups are removed from the navigation.
|
|
9
|
+
*
|
|
10
|
+
* @param groupNames - Array of group names to track
|
|
11
|
+
* @param namespace - Namespace for localStorage key (e.g., "home", "drawer") to allow independent state
|
|
7
12
|
*/
|
|
8
|
-
export function useCollapsedGroups(groupNames: string[]) {
|
|
13
|
+
export function useCollapsedGroups(groupNames: (string | null)[], namespace: string = "default") {
|
|
14
|
+
const storageKey = `${STORAGE_KEY_PREFIX}-${namespace}`;
|
|
15
|
+
|
|
9
16
|
// Load collapsed groups from localStorage on mount
|
|
10
17
|
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>(() => {
|
|
11
18
|
try {
|
|
12
|
-
const stored = localStorage.getItem(
|
|
19
|
+
const stored = localStorage.getItem(storageKey);
|
|
13
20
|
return stored ? JSON.parse(stored) : {};
|
|
14
21
|
} catch {
|
|
15
22
|
return {};
|
|
@@ -19,18 +26,18 @@ export function useCollapsedGroups(groupNames: string[]) {
|
|
|
19
26
|
// Save to localStorage whenever collapsedGroups changes
|
|
20
27
|
useEffect(() => {
|
|
21
28
|
try {
|
|
22
|
-
localStorage.setItem(
|
|
29
|
+
localStorage.setItem(storageKey, JSON.stringify(collapsedGroups));
|
|
23
30
|
} catch {
|
|
24
31
|
// Silently fail if localStorage is not available
|
|
25
32
|
}
|
|
26
|
-
}, [collapsedGroups]);
|
|
33
|
+
}, [collapsedGroups, storageKey]);
|
|
27
34
|
|
|
28
35
|
// Clean up collapsed groups state when groups change - remove entries for groups that no longer exist
|
|
29
36
|
useEffect(() => {
|
|
30
37
|
// Only clean up if we have actual groups loaded (avoid cleaning up during initial load)
|
|
31
38
|
if (groupNames.length === 0) return;
|
|
32
39
|
|
|
33
|
-
const currentGroupNames = new Set(groupNames);
|
|
40
|
+
const currentGroupNames = new Set(groupNames.map(g => g ?? "__default__"));
|
|
34
41
|
|
|
35
42
|
setCollapsedGroups(prev => {
|
|
36
43
|
const cleaned = Object.fromEntries(
|
|
@@ -49,12 +56,13 @@ export function useCollapsedGroups(groupNames: string[]) {
|
|
|
49
56
|
});
|
|
50
57
|
}, [groupNames]);
|
|
51
58
|
|
|
52
|
-
const isGroupCollapsed = useCallback((name
|
|
53
|
-
return !!collapsedGroups[name];
|
|
59
|
+
const isGroupCollapsed = useCallback((name?: string | null) => {
|
|
60
|
+
return !!collapsedGroups[name ?? "__default__"];
|
|
54
61
|
}, [collapsedGroups]);
|
|
55
62
|
|
|
56
|
-
const toggleGroupCollapsed = useCallback((name
|
|
57
|
-
|
|
63
|
+
const toggleGroupCollapsed = useCallback((name?: string | null) => {
|
|
64
|
+
const key = name ?? "__default__";
|
|
65
|
+
setCollapsedGroups(prev => ({ ...prev, [key]: !prev[key] }));
|
|
58
66
|
}, []);
|
|
59
67
|
|
|
60
68
|
return {
|
|
@@ -62,3 +70,4 @@ export function useCollapsedGroups(groupNames: string[]) {
|
|
|
62
70
|
toggleGroupCollapsed
|
|
63
71
|
};
|
|
64
72
|
}
|
|
73
|
+
|
|
@@ -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
|
+
}
|
|
@@ -51,7 +51,7 @@ export function useValidateAuthenticator<USER extends User = any>
|
|
|
51
51
|
* We use this ref to check the authentication only if the user has
|
|
52
52
|
* changed.
|
|
53
53
|
*/
|
|
54
|
-
const checkedUserRef = useRef<User | undefined>();
|
|
54
|
+
const checkedUserRef = useRef<User | undefined>(undefined);
|
|
55
55
|
|
|
56
56
|
const checkAuthentication = useCallback(async () => {
|
|
57
57
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React, { PropsWithChildren, useEffect, useRef } from "react";
|
|
2
|
+
import i18next, { i18n } from "i18next";
|
|
3
|
+
import { I18nextProvider, initReactI18next } from "react-i18next";
|
|
4
|
+
import { en } from "../locales/en";
|
|
5
|
+
import { es } from "../locales/es";
|
|
6
|
+
import { de } from "../locales/de";
|
|
7
|
+
import { fr } from "../locales/fr";
|
|
8
|
+
import { it } from "../locales/it";
|
|
9
|
+
import { hi } from "../locales/hi";
|
|
10
|
+
import { pt } from "../locales/pt";
|
|
11
|
+
import { FireCMSTranslations } from "../types/translations";
|
|
12
|
+
|
|
13
|
+
const FIRECMS_NS = "firecms_core";
|
|
14
|
+
|
|
15
|
+
export const FIRECMS_LOCALE_STORAGE_KEY = "firecms_locale";
|
|
16
|
+
|
|
17
|
+
/** DeepPartial helper — allows partial overrides at any nesting level */
|
|
18
|
+
type DeepPartial<T> = T extends object
|
|
19
|
+
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
20
|
+
: T;
|
|
21
|
+
|
|
22
|
+
export interface FireCMSi18nProviderProps {
|
|
23
|
+
/** BCP-47 locale tag, e.g. "en", "es", "fr". Defaults to "en". */
|
|
24
|
+
locale?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Override or extend any FireCMS UI string, keyed by locale.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* translations={{
|
|
30
|
+
* en: { save: "Publish" },
|
|
31
|
+
* es: { save: "Publicar", discard: "Descartar" }
|
|
32
|
+
* }}
|
|
33
|
+
*/
|
|
34
|
+
translations?: {
|
|
35
|
+
[locale: string]: DeepPartial<FireCMSTranslations>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initialises a dedicated i18next instance for FireCMS's internal UI strings.
|
|
41
|
+
*
|
|
42
|
+
* This instance is isolated from any app-level i18next configuration the
|
|
43
|
+
* consumer may have. Mount this at the top of the FireCMS component tree.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export function FireCMSi18nProvider({
|
|
48
|
+
locale = "en",
|
|
49
|
+
translations,
|
|
50
|
+
children
|
|
51
|
+
}: PropsWithChildren<FireCMSi18nProviderProps>) {
|
|
52
|
+
const i18nRef = useRef<i18n | null>(null);
|
|
53
|
+
const [ready, setReady] = React.useState(false);
|
|
54
|
+
|
|
55
|
+
if (!i18nRef.current) {
|
|
56
|
+
const instance = i18next.createInstance();
|
|
57
|
+
|
|
58
|
+
// Build the initial resources: English baseline + any consumer overrides
|
|
59
|
+
const resources = buildResources(translations);
|
|
60
|
+
|
|
61
|
+
let initialLocale = locale;
|
|
62
|
+
if (typeof window !== "undefined") {
|
|
63
|
+
const stored = localStorage.getItem(FIRECMS_LOCALE_STORAGE_KEY);
|
|
64
|
+
if (stored) initialLocale = stored;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
instance
|
|
68
|
+
.use(initReactI18next)
|
|
69
|
+
.init({
|
|
70
|
+
lng: initialLocale,
|
|
71
|
+
fallbackLng: "en",
|
|
72
|
+
ns: [FIRECMS_NS],
|
|
73
|
+
defaultNS: FIRECMS_NS,
|
|
74
|
+
resources,
|
|
75
|
+
interpolation: {
|
|
76
|
+
// React already escapes — don't double-escape
|
|
77
|
+
escapeValue: false,
|
|
78
|
+
},
|
|
79
|
+
}, () => {
|
|
80
|
+
setReady(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
instance.on("languageChanged", (lng) => {
|
|
84
|
+
if (typeof window !== "undefined") {
|
|
85
|
+
localStorage.setItem(FIRECMS_LOCALE_STORAGE_KEY, lng);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
i18nRef.current = instance;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// When `locale` prop changes, switch language on the existing instance
|
|
93
|
+
// ONLY if the user hasn't explicitly set a preference
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (i18nRef.current && i18nRef.current.language !== locale) {
|
|
96
|
+
const hasUserPreference = typeof window !== "undefined" && Boolean(localStorage.getItem(FIRECMS_LOCALE_STORAGE_KEY));
|
|
97
|
+
if (!hasUserPreference) {
|
|
98
|
+
i18nRef.current.changeLanguage(locale);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}, [locale]);
|
|
102
|
+
|
|
103
|
+
// When consumer translations prop changes, update the resource bundles
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!i18nRef.current) return;
|
|
106
|
+
const resources = buildResources(translations);
|
|
107
|
+
for (const [lang, bundle] of Object.entries(resources)) {
|
|
108
|
+
i18nRef.current.addResourceBundle(
|
|
109
|
+
lang,
|
|
110
|
+
FIRECMS_NS,
|
|
111
|
+
bundle[FIRECMS_NS],
|
|
112
|
+
true, // deep merge
|
|
113
|
+
true // overwrite existing keys
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}, [translations]);
|
|
117
|
+
|
|
118
|
+
if (!ready || !i18nRef.current) return null;
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<I18nextProvider i18n={i18nRef.current}>
|
|
122
|
+
{children}
|
|
123
|
+
</I18nextProvider>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build an i18next resources object from the English baseline plus any
|
|
129
|
+
* consumer-provided overrides.
|
|
130
|
+
*/
|
|
131
|
+
function buildResources(
|
|
132
|
+
translations?: { [locale: string]: DeepPartial<FireCMSTranslations> }
|
|
133
|
+
): Record<string, Record<string, object>> {
|
|
134
|
+
const resources: Record<string, Record<string, object>> = {
|
|
135
|
+
en: { [FIRECMS_NS]: { ...en } },
|
|
136
|
+
es: { [FIRECMS_NS]: { ...es } },
|
|
137
|
+
de: { [FIRECMS_NS]: { ...de } },
|
|
138
|
+
fr: { [FIRECMS_NS]: { ...fr } },
|
|
139
|
+
it: { [FIRECMS_NS]: { ...it } },
|
|
140
|
+
hi: { [FIRECMS_NS]: { ...hi } },
|
|
141
|
+
pt: { [FIRECMS_NS]: { ...pt } },
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (!translations) return resources;
|
|
145
|
+
|
|
146
|
+
for (const [lang, overrides] of Object.entries(translations)) {
|
|
147
|
+
if (!resources[lang]) {
|
|
148
|
+
// For non-English/Spanish locales, start from English as the fallback base
|
|
149
|
+
resources[lang] = { [FIRECMS_NS]: { ...en } };
|
|
150
|
+
}
|
|
151
|
+
// Merge consumer overrides (shallow merge is enough since translations
|
|
152
|
+
// is a flat record — deepMerge option in addResourceBundle handles deeper)
|
|
153
|
+
resources[lang][FIRECMS_NS] = {
|
|
154
|
+
...resources[lang][FIRECMS_NS],
|
|
155
|
+
...overrides,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return resources;
|
|
160
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,3 +7,8 @@ export * from "./hooks";
|
|
|
7
7
|
export * from "./components";
|
|
8
8
|
export * from "./util";
|
|
9
9
|
export * from "./contexts";
|
|
10
|
+
export * from "./i18n/FireCMSi18nProvider";
|
|
11
|
+
export * from "./locales/en";
|
|
12
|
+
export * from "./locales/es";
|
|
13
|
+
export * from "./editor";
|
|
14
|
+
export * from "./util/objects";
|