@firecms/core 3.3.0 → 3.4.0-canary.2575a08
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/app/useApp.d.ts +1 -0
- package/dist/components/EntityCollectionTable/column_utils.d.ts +4 -3
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +6 -1
- package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +3 -1
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +3 -1
- package/dist/components/EntityPreview.d.ts +3 -1
- package/dist/components/ReferenceTable/ReferenceSelectionTable.d.ts +10 -1
- package/dist/components/SelectableTable/filters/filter_text_input.d.ts +30 -0
- package/dist/components/SelectableTable/filters/filter_text_input.test.d.ts +1 -0
- package/dist/components/common/table_url_params.d.ts +24 -0
- package/dist/components/common/useDataSourceTableController.d.ts +9 -1
- package/dist/components/common/useTableSearchHelper.d.ts +9 -1
- package/dist/core/DefaultDrawer.d.ts +10 -3
- package/dist/core/EntityEditView.d.ts +9 -1
- package/dist/core/EntityEditViewFormActions.d.ts +1 -1
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/form/EntityForm.d.ts +8 -1
- package/dist/form/EntityFormActions.d.ts +2 -0
- package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
- package/dist/form/index.d.ts +1 -0
- package/dist/hooks/data/delete.d.ts +1 -1
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/data/useCollectionFetch.d.ts +7 -1
- package/dist/hooks/data/useEntityFetch.d.ts +9 -1
- package/dist/i18n/__tests__/FireCMSi18nProvider.test.d.ts +1 -0
- package/dist/i18n/__tests__/nested_provider_lifecycle.test.d.ts +1 -0
- package/dist/index.es.js +3658 -1820
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +3655 -1817
- package/dist/index.umd.js.map +1 -1
- package/dist/locales/pl.d.ts +2 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
- package/dist/types/collections.d.ts +31 -0
- package/dist/types/datasource.d.ts +67 -4
- package/dist/types/entities.d.ts +21 -1
- package/dist/types/entity_actions.d.ts +6 -0
- package/dist/types/entity_callbacks.d.ts +21 -0
- package/dist/types/navigation.d.ts +34 -5
- package/dist/types/plugins.d.ts +13 -0
- package/dist/types/properties.d.ts +17 -1
- package/dist/types/side_entity_controller.d.ts +7 -0
- package/dist/types/translations.d.ts +6 -0
- package/dist/util/__tests__/collections.test.d.ts +1 -0
- package/dist/util/__tests__/urls.test.d.ts +1 -0
- package/dist/util/collections.d.ts +1 -1
- package/dist/util/entities.d.ts +1 -0
- package/dist/util/entity_cache.d.ts +11 -0
- package/dist/util/geopoint.d.ts +22 -0
- package/dist/util/index.d.ts +2 -0
- package/dist/util/navigation_blocking.d.ts +22 -0
- package/dist/util/navigation_from_path.d.ts +17 -0
- package/dist/util/navigation_utils.d.ts +107 -3
- package/dist/util/parent_references_from_path.d.ts +16 -0
- package/dist/util/paths.d.ts +28 -0
- package/dist/util/permissions.d.ts +10 -4
- package/dist/util/urls.d.ts +22 -0
- package/package.json +17 -10
- package/src/app/Scaffold.tsx +34 -44
- package/src/app/useApp.tsx +1 -0
- package/src/components/DeleteEntityDialog.tsx +2 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +3 -1
- package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +7 -0
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -1
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +66 -15
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -2
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +8 -1
- package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
- package/src/components/EntityCollectionView/useBoardDataController.tsx +17 -1
- package/src/components/EntityPreview.tsx +45 -41
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +19 -2
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +33 -7
- package/src/components/SelectableTable/filters/filter_text_input.test.ts +184 -0
- package/src/components/SelectableTable/filters/filter_text_input.ts +71 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -8
- package/src/components/common/default_entity_actions.tsx +22 -2
- package/src/components/common/table_url_params.ts +172 -0
- package/src/components/common/useDataSourceTableController.tsx +126 -163
- package/src/components/common/useTableSearchHelper.ts +26 -2
- package/src/contexts/SnackbarProvider.tsx +14 -1
- package/src/core/DefaultDrawer.tsx +150 -64
- package/src/core/DrawerNavigationGroup.tsx +27 -29
- package/src/core/DrawerNavigationItem.tsx +10 -12
- package/src/core/EntityEditView.tsx +77 -35
- package/src/core/EntityEditViewFormActions.tsx +3 -2
- package/src/core/EntitySidePanel.tsx +8 -4
- package/src/core/field_configs.tsx +15 -0
- package/src/form/EntityForm.tsx +31 -10
- package/src/form/EntityFormActions.tsx +2 -0
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
- package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
- package/src/form/index.tsx +1 -0
- package/src/hooks/data/delete.ts +8 -0
- package/src/hooks/data/save.ts +6 -1
- package/src/hooks/data/useCollectionFetch.tsx +21 -1
- package/src/hooks/data/useEntityFetch.tsx +35 -4
- package/src/hooks/useBuildNavigationController.tsx +23 -8
- package/src/hooks/useResolvedNavigationFrom.tsx +3 -1
- package/src/hooks/useValidateAuthenticator.tsx +4 -0
- package/src/i18n/FireCMSi18nProvider.tsx +50 -4
- package/src/i18n/__tests__/FireCMSi18nProvider.test.tsx +91 -0
- package/src/i18n/__tests__/nested_provider_lifecycle.test.tsx +67 -0
- package/src/internal/useBuildDataSource.ts +37 -15
- package/src/internal/useBuildSideEntityController.tsx +20 -5
- package/src/locales/de.ts +7 -2
- package/src/locales/en.ts +7 -2
- package/src/locales/es.ts +7 -2
- package/src/locales/fr.ts +7 -2
- package/src/locales/hi.ts +7 -2
- package/src/locales/it.ts +7 -2
- package/src/locales/pl.ts +735 -0
- package/src/locales/pt.ts +7 -2
- package/src/preview/PropertyPreview.tsx +12 -0
- package/src/preview/components/ReferencePreview.tsx +7 -3
- package/src/preview/components/StorageThumbnail.tsx +24 -2
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
- package/src/routes/FireCMSRoute.tsx +54 -27
- package/src/types/collections.ts +34 -0
- package/src/types/datasource.ts +67 -4
- package/src/types/entities.ts +24 -1
- package/src/types/entity_actions.tsx +6 -0
- package/src/types/entity_callbacks.ts +24 -0
- package/src/types/navigation.ts +35 -5
- package/src/types/plugins.tsx +13 -0
- package/src/types/properties.ts +17 -0
- package/src/types/side_entity_controller.tsx +7 -0
- package/src/types/translations.ts +6 -0
- package/src/util/__tests__/collections.test.ts +32 -0
- package/src/util/__tests__/urls.test.ts +131 -0
- package/src/util/collections.ts +1 -1
- package/src/util/entities.ts +16 -1
- package/src/util/entity_cache.ts +18 -0
- package/src/util/geopoint.ts +81 -0
- package/src/util/index.ts +2 -0
- package/src/util/navigation_blocking.ts +45 -0
- package/src/util/navigation_from_path.ts +47 -7
- package/src/util/navigation_utils.ts +244 -4
- package/src/util/parent_references_from_path.ts +45 -4
- package/src/util/paths.ts +40 -3
- package/src/util/permissions.ts +22 -10
- package/src/util/previews.ts +8 -1
- package/src/util/resolutions.ts +8 -0
- package/src/util/urls.ts +52 -0
|
@@ -3,22 +3,124 @@ export declare function removeInitialAndTrailingSlashes(s: string): string;
|
|
|
3
3
|
export declare function removeInitialSlash(s: string): string;
|
|
4
4
|
export declare function removeTrailingSlash(s: string): string;
|
|
5
5
|
export declare function addInitialSlash(s: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Characters that would otherwise be read as structure once an entity id is joined into a
|
|
8
|
+
* path: "/" separates segments, "?" and "#" start the query and hash in a URL, and "%"
|
|
9
|
+
* has to be escaped first so that no escape sequence we introduce can be misread as one
|
|
10
|
+
* that was already part of the id.
|
|
11
|
+
*
|
|
12
|
+
* Entity ids are escaped ONLY inside URL-facing strings — anything handed to
|
|
13
|
+
* `buildUrlCollectionPath` or `navigate`. Every other path string (the `path` field of a
|
|
14
|
+
* navigation entry, datasource paths, `EntityReference.path`) carries raw ids.
|
|
15
|
+
*
|
|
16
|
+
* @group Hooks and utilities
|
|
17
|
+
*/
|
|
18
|
+
export declare function encodeEntityId(entityId: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Inverse of {@link encodeEntityId}. "%25" is undone last, mirroring the encoder, so an id
|
|
21
|
+
* that legitimately contains the text "%2F" survives the round trip.
|
|
22
|
+
*
|
|
23
|
+
* @group Hooks and utilities
|
|
24
|
+
*/
|
|
25
|
+
export declare function decodeEntityId(encodedEntityId: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Segments of a subcollection sitting under the entity `entityId` of `parentPathSegments`.
|
|
28
|
+
*
|
|
29
|
+
* The parent entity id is kept whole however many slashes it contains, while the
|
|
30
|
+
* subcollection's own configured path is spread — a collection path never contains an
|
|
31
|
+
* entity id, so splitting that one is unambiguous.
|
|
32
|
+
*
|
|
33
|
+
* So `["test"] + "test/test" + "accommodation"` yields three segments,
|
|
34
|
+
* `["test", "test/test", "accommodation"]`, where splitting the flattened path
|
|
35
|
+
* `"test/test/test/accommodation"` would wrongly yield four.
|
|
36
|
+
*
|
|
37
|
+
* Returns undefined when the parent segments are unknown: the parent chain cannot be
|
|
38
|
+
* recovered from a flattened path, so there is nothing honest to build on.
|
|
39
|
+
*
|
|
40
|
+
* @group Hooks and utilities
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildSubcollectionPathSegments(parentPathSegments: string[] | undefined, entityId: string | undefined, subcollectionPath: string): string[] | undefined;
|
|
6
43
|
export declare function getLastSegment(path: string): string;
|
|
7
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The outcome of walking a segment chain against a collection tree.
|
|
46
|
+
* @group Hooks and utilities
|
|
47
|
+
*/
|
|
48
|
+
export type PathSegmentsWalkStep = {
|
|
49
|
+
collection: EntityCollection;
|
|
50
|
+
/** Resolved path of this collection, e.g. `"products/pid/locales"`. */
|
|
51
|
+
collectionPath: string;
|
|
52
|
+
/** `collectionPath` split at its real segment boundaries. */
|
|
53
|
+
collectionSegments: string[];
|
|
54
|
+
/** The entity addressed inside it, when the chain continues past the collection. */
|
|
55
|
+
entityId?: string;
|
|
56
|
+
};
|
|
57
|
+
export type PathSegmentsWalk = {
|
|
58
|
+
/** Segments with every collection id replaced by the collection's real `path`. */
|
|
59
|
+
resolved: string[];
|
|
60
|
+
/** The collection the chain ends in, if the whole chain matched. */
|
|
61
|
+
collection?: EntityCollection;
|
|
62
|
+
/** One entry per collection level traversed, outermost first. */
|
|
63
|
+
steps: PathSegmentsWalkStep[];
|
|
64
|
+
/** The first segment that could not be matched, if the walk stopped early. */
|
|
65
|
+
unmatched?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Walk a segment chain against a collection tree, matching a collection by either its
|
|
69
|
+
* `path` or its `id` and treating exactly one segment as each entity id.
|
|
70
|
+
*
|
|
71
|
+
* This is the primitive the string-based helpers cannot have: working on the flattened
|
|
72
|
+
* path they must *guess* where an entity id ends by reading up to the next "/", which is
|
|
73
|
+
* wrong for any id that contains one. Here the boundaries are given.
|
|
74
|
+
*
|
|
75
|
+
* Stops at the first segment it cannot match and reports it, rather than throwing.
|
|
76
|
+
*
|
|
77
|
+
* @group Hooks and utilities
|
|
78
|
+
*/
|
|
79
|
+
export declare function walkPathSegments(pathSegments: string[], allCollections: EntityCollection[]): PathSegmentsWalk;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve collection aliases in a segment array: every collection id is replaced by the
|
|
82
|
+
* collection's real `path`, and every entity id is carried through untouched.
|
|
83
|
+
*
|
|
84
|
+
* The segment-wise counterpart of {@link resolveCollectionPathIds}, and the reason it
|
|
85
|
+
* exists: that one works on the flattened string, so an entity id containing "/" shifts
|
|
86
|
+
* the rest of the chain by a segment and resolution fails with
|
|
87
|
+
* "Collection definition not found for segment starting with …".
|
|
88
|
+
*
|
|
89
|
+
* Matching accepts either a collection's `path` or its `id`, so already-resolved segments
|
|
90
|
+
* pass through unchanged — the function is idempotent.
|
|
91
|
+
*
|
|
92
|
+
* Mirrors the string version when a chain cannot be matched: it warns and carries the
|
|
93
|
+
* remainder through unresolved, rather than throwing.
|
|
94
|
+
*
|
|
95
|
+
* @group Hooks and utilities
|
|
96
|
+
*/
|
|
97
|
+
export declare function resolveCollectionPathSegments(pathSegments: string[], allCollections: EntityCollection[]): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Find the collection a segment chain addresses, whether it ends on the collection itself
|
|
100
|
+
* or on one of its entities.
|
|
101
|
+
*
|
|
102
|
+
* The segment-wise counterpart of {@link getCollectionByPathOrId}, which asserts an odd
|
|
103
|
+
* number of "/"-separated parts — an assertion a slash-bearing entity id fails, throwing
|
|
104
|
+
* where it should simply have resolved.
|
|
105
|
+
*
|
|
106
|
+
* @group Hooks and utilities
|
|
107
|
+
*/
|
|
108
|
+
export declare function getCollectionByPathSegments(pathSegments: string[], collections: EntityCollection[]): EntityCollection | undefined;
|
|
109
|
+
export declare function resolveCollectionPathIds(path: string, allCollections: EntityCollection[], pathSegments?: string[]): string;
|
|
8
110
|
/**
|
|
9
111
|
* Find the corresponding view at any depth for a given path.
|
|
10
112
|
* Note that path or segments of the paths can be collection aliases.
|
|
11
113
|
* @param pathOrId
|
|
12
114
|
* @param collections
|
|
13
115
|
*/
|
|
14
|
-
export declare function getCollectionByPathOrId(pathOrId: string, collections: EntityCollection[]): EntityCollection | undefined;
|
|
116
|
+
export declare function getCollectionByPathOrId(pathOrId: string, collections: EntityCollection[], pathSegments?: string[]): EntityCollection | undefined;
|
|
15
117
|
/**
|
|
16
118
|
* Get the subcollection combinations from a path:
|
|
17
119
|
* "sites/es/locales" => ["sites/es/locales", "sites"]
|
|
18
120
|
* @param subpaths
|
|
19
121
|
*/
|
|
20
122
|
export declare function getCollectionPathsCombinations(subpaths: string[]): string[];
|
|
21
|
-
export declare function navigateToEntity({ openEntityMode, collection, entityId, copy, path, fullIdPath, selectedTab, sideEntityController, onClose, navigation }: {
|
|
123
|
+
export declare function navigateToEntity({ openEntityMode, collection, entityId, copy, path, fullIdPath, pathSegments, selectedTab, sideEntityController, onClose, navigation }: {
|
|
22
124
|
openEntityMode: "side_panel" | "full_screen";
|
|
23
125
|
collection?: EntityCollection;
|
|
24
126
|
entityId?: string;
|
|
@@ -26,6 +128,8 @@ export declare function navigateToEntity({ openEntityMode, collection, entityId,
|
|
|
26
128
|
copy?: boolean;
|
|
27
129
|
path: string;
|
|
28
130
|
fullIdPath?: string;
|
|
131
|
+
/** `path` split at its real segment boundaries. */
|
|
132
|
+
pathSegments?: string[];
|
|
29
133
|
sideEntityController: SideEntityController;
|
|
30
134
|
onClose?: () => void;
|
|
31
135
|
navigation: NavigationController;
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { EntityCollection, EntityReference } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* References to every parent entity in a path.
|
|
4
|
+
*
|
|
5
|
+
* NOTE on encoding: `path` is expected ESCAPED, because it comes from the URL — the entity
|
|
6
|
+
* ids in it are decoded on the way out. Pass `pathSegments` instead whenever the path at
|
|
7
|
+
* hand is a raw datasource path (`entity.path`), which is not escaped and whose ids may
|
|
8
|
+
* therefore contain a bare "/": splitting that would silently produce the wrong references.
|
|
9
|
+
*/
|
|
2
10
|
export declare function getParentReferencesFromPath(props: {
|
|
3
11
|
path: string;
|
|
4
12
|
collections: EntityCollection[] | undefined;
|
|
5
13
|
currentFullPath?: string;
|
|
14
|
+
currentPathSegments?: string[];
|
|
15
|
+
/** `path` split at its real segment boundaries, with RAW ids. Takes precedence. */
|
|
16
|
+
pathSegments?: string[];
|
|
6
17
|
}): EntityReference[];
|
|
18
|
+
/**
|
|
19
|
+
* The segment-wise counterpart: the boundaries are given, so an entity id keeps its
|
|
20
|
+
* slashes and nothing has to be decoded.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getParentReferencesFromPathSegments(pathSegments: string[], collections: EntityCollection[]): EntityReference[];
|
package/dist/util/paths.d.ts
CHANGED
|
@@ -9,6 +9,34 @@ export declare function segmentsToStrippedPath(paths: string[]): string;
|
|
|
9
9
|
/**
|
|
10
10
|
* Extract the collection path routes
|
|
11
11
|
* `products/B44RG6APH/locales` => [`products`, `locales`]
|
|
12
|
+
*
|
|
13
|
+
* Finds the entity ids by position, which only works while no id contains "/" — with one,
|
|
14
|
+
* every following segment shifts and the wrong elements are kept. Use
|
|
15
|
+
* {@link collectionSegmentsFrom} when the real boundaries are known.
|
|
16
|
+
*
|
|
12
17
|
* @param path
|
|
13
18
|
*/
|
|
14
19
|
export declare function fullPathToCollectionSegments(path: string): string[];
|
|
20
|
+
/**
|
|
21
|
+
* The collection chain of an already-segmented path:
|
|
22
|
+
* `['products', 'B44RG6APH', 'locales']` => [`products`, `locales`]
|
|
23
|
+
*
|
|
24
|
+
* The counterpart of {@link fullPathToCollectionSegments} for callers that know the real
|
|
25
|
+
* segment boundaries. Kept as a separate function rather than an extra parameter on that
|
|
26
|
+
* one: these helpers are small enough to be passed point-free, and `paths.map(fn)` would
|
|
27
|
+
* then hand the array index in as the new argument.
|
|
28
|
+
*/
|
|
29
|
+
export declare function collectionSegmentsFrom(pathSegments: string[]): string[];
|
|
30
|
+
/**
|
|
31
|
+
* The historical, positional reading of a flattened path:
|
|
32
|
+
* `products/B44RG6APH/locales` => [`products`, `locales`]
|
|
33
|
+
*
|
|
34
|
+
* This produces a **collection chain**, not a datasource `pathSegments` value, and must
|
|
35
|
+
* never be used as one — see `path_segments_no_fabrication.test.ts`. It is only correct
|
|
36
|
+
* while no entity id contains "/", which is why every caller prefers real segments when
|
|
37
|
+
* they are available, and why it is named for what it assumes.
|
|
38
|
+
*
|
|
39
|
+
* It is still the right reading for a path that came from a URL, where entity ids arrive
|
|
40
|
+
* escaped and so cannot contain a bare "/".
|
|
41
|
+
*/
|
|
42
|
+
export declare function positionalCollectionSegments(path: string): string[];
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { AuthController, Entity, EntityCollection, Permissions, User } from "../types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* `pathSegments` is `path` split at its real segment boundaries. It is forwarded so the
|
|
4
|
+
* collection chain handed to a permissions builder stays correct when an entity id contains
|
|
5
|
+
* "/" — splitting `path` positionally would keep the wrong elements. Optional everywhere:
|
|
6
|
+
* omitted, behaviour is exactly as before.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolvePermissions<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): Permissions | undefined;
|
|
9
|
+
export declare function canEditEntity<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): boolean;
|
|
10
|
+
export declare function canCreateEntity<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): boolean;
|
|
11
|
+
export declare function canDeleteEntity<M extends Record<string, any>, USER extends User>(collection: EntityCollection<M>, authController: AuthController<USER>, path: string, entity: Entity<M> | null, pathSegments?: string[]): boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PreviewType } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Is the given value an absolute `http(s)` URL, as opposed to a relative
|
|
4
|
+
* storage path such as `images/my_image.png`?
|
|
5
|
+
*
|
|
6
|
+
* Values saved in the datasource for storage properties can be either, e.g.
|
|
7
|
+
* when the property is configured with `storeUrl: true`, or when the value was
|
|
8
|
+
* written directly pointing to an externally hosted file.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isAbsoluteHttpUrl(value?: string | null): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Extract the lowercase file extension of a URL or path, ignoring any query
|
|
13
|
+
* string or hash fragment. Returns `undefined` when there is no extension.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getFileExtensionFromUrl(urlOrPath?: string | null): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Best effort guess of the preview type of a URL or path, based on its file
|
|
18
|
+
* extension. Used when no storage metadata (and therefore no content type) is
|
|
19
|
+
* available, e.g. for externally hosted files.
|
|
20
|
+
* Returns `undefined` when the extension is missing or not recognised.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getPreviewTypeFromUrl(urlOrPath?: string | null): PreviewType | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.4.0-canary.2575a08",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"@dnd-kit/core": "^6.3.1",
|
|
54
54
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
55
55
|
"@dnd-kit/sortable": "^10.0.0",
|
|
56
|
-
"@firecms/formex": "
|
|
57
|
-
"@firecms/ui": "
|
|
56
|
+
"@firecms/formex": "3.4.0-canary.2575a08",
|
|
57
|
+
"@firecms/ui": "3.4.0-canary.2575a08",
|
|
58
58
|
"@floating-ui/dom": "^1.7.4",
|
|
59
59
|
"@radix-ui/react-portal": "^1.1.10",
|
|
60
60
|
"@radix-ui/react-slot": "^1.2.4",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"date-fns": "^3.6.0",
|
|
64
64
|
"fuse.js": "^7.1.0",
|
|
65
65
|
"history": "^5.3.0",
|
|
66
|
-
"i18next": "^
|
|
66
|
+
"i18next": "^26.3.3",
|
|
67
67
|
"json-logic-js": "^2.0.5",
|
|
68
|
-
"markdown-it": "^14.
|
|
68
|
+
"markdown-it": "^14.2.0",
|
|
69
69
|
"markdown-it-ins": "^4.0.0",
|
|
70
70
|
"markdown-it-mark": "^4.0.0",
|
|
71
71
|
"markdown-it-task-lists": "^2.1.1",
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
"prosemirror-view": "^1.33.8",
|
|
90
90
|
"react-dropzone": "^14.3.8",
|
|
91
91
|
"react-fast-compare": "^3.2.2",
|
|
92
|
-
"react-i18next": "^
|
|
92
|
+
"react-i18next": "^17.0.11",
|
|
93
93
|
"react-image-crop": "^11.0.10",
|
|
94
94
|
"react-markdown": "^9.1.0",
|
|
95
95
|
"react-moveable": "^0.56.0",
|
|
96
96
|
"react-transition-group": "^4.4.5",
|
|
97
97
|
"react-use-measure": "^2.1.7",
|
|
98
98
|
"react-window": "^1.8.11",
|
|
99
|
-
"vite-plugin-static-copy": "
|
|
99
|
+
"vite-plugin-static-copy": "4.1.1",
|
|
100
100
|
"yup": "^1.7.1"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"@testing-library/user-event": "^14.6.1",
|
|
112
112
|
"@types/jest": "^29.5.14",
|
|
113
113
|
"@types/json-logic-js": "^2.0.8",
|
|
114
|
-
"@types/node": "^
|
|
114
|
+
"@types/node": "^26.1.1",
|
|
115
115
|
"@types/object-hash": "^3.0.6",
|
|
116
116
|
"@types/react": "^19.2.3",
|
|
117
117
|
"@types/react-dom": "^19.2.3",
|
|
@@ -123,12 +123,12 @@
|
|
|
123
123
|
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
|
124
124
|
"jest": "^29.7.0",
|
|
125
125
|
"jest-environment-jsdom": "^30.2.0",
|
|
126
|
-
"npm-run-
|
|
126
|
+
"npm-run-all2": "^6.2.6",
|
|
127
127
|
"react-router": "^6.30.2",
|
|
128
128
|
"react-router-dom": "^6.30.2",
|
|
129
129
|
"ts-jest": "^29.4.5",
|
|
130
130
|
"ts-node": "^10.9.2",
|
|
131
|
-
"tsd": "^0.
|
|
131
|
+
"tsd": "^0.33.0",
|
|
132
132
|
"typescript": "^5.9.3",
|
|
133
133
|
"vite": "^7.2.4"
|
|
134
134
|
},
|
|
@@ -163,6 +163,10 @@
|
|
|
163
163
|
"^.+\\.tsx?$": "ts-jest"
|
|
164
164
|
},
|
|
165
165
|
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
|
|
166
|
+
"testPathIgnorePatterns": [
|
|
167
|
+
"/node_modules/",
|
|
168
|
+
"/dist/"
|
|
169
|
+
],
|
|
166
170
|
"moduleFileExtensions": [
|
|
167
171
|
"ts",
|
|
168
172
|
"tsx",
|
|
@@ -172,6 +176,9 @@
|
|
|
172
176
|
"node"
|
|
173
177
|
],
|
|
174
178
|
"testEnvironment": "node",
|
|
179
|
+
"setupFiles": [
|
|
180
|
+
"<rootDir>/test/setup_jsdom.js"
|
|
181
|
+
],
|
|
175
182
|
"moduleNameMapper": {
|
|
176
183
|
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
|
|
177
184
|
}
|
package/src/app/Scaffold.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { PropsWithChildren, useCallback } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { cls, defaultBorderMixin, IconButton, MenuIcon, Sheet } from "@firecms/ui";
|
|
3
3
|
import equal from "react-fast-compare"
|
|
4
4
|
import { useLargeLayout } from "../hooks";
|
|
5
5
|
import { ErrorBoundary } from "../components";
|
|
@@ -80,7 +80,11 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
80
80
|
const [onHover, setOnHover] = React.useState(false);
|
|
81
81
|
|
|
82
82
|
const setOnHoverTrue = useCallback(() => setOnHover(true), []);
|
|
83
|
-
const setOnHoverFalse = useCallback(() =>
|
|
83
|
+
const setOnHoverFalse = useCallback(() => {
|
|
84
|
+
// Don't collapse the drawer while a popover/dropdown is open
|
|
85
|
+
if (typeof document !== "undefined" && document.querySelector("[data-radix-popper-content-wrapper]")) return;
|
|
86
|
+
setOnHover(false);
|
|
87
|
+
}, []);
|
|
84
88
|
|
|
85
89
|
const handleDrawerOpen = useCallback(() => {
|
|
86
90
|
setDrawerOpen(true);
|
|
@@ -96,17 +100,21 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
96
100
|
} catch { /* ignore */ }
|
|
97
101
|
}, []);
|
|
98
102
|
|
|
99
|
-
|
|
103
|
+
// Pinned-open state only; hover is tracked separately so it can *float*
|
|
104
|
+
// over the content instead of pushing it.
|
|
105
|
+
const computedDrawerOpen: boolean = drawerOpen;
|
|
106
|
+
const computedDrawerHovered: boolean = Boolean(largeLayout && onHover);
|
|
100
107
|
|
|
101
108
|
const hasAppBar = Boolean(appBarChildren.length > 0);
|
|
102
109
|
return (
|
|
103
110
|
<AppContext.Provider value={{
|
|
104
111
|
logo,
|
|
105
112
|
hasDrawer: includeDrawer,
|
|
106
|
-
drawerHovered:
|
|
113
|
+
drawerHovered: computedDrawerHovered,
|
|
107
114
|
drawerOpen: computedDrawerOpen,
|
|
108
115
|
closeDrawer: handleDrawerClose,
|
|
109
116
|
openDrawer: handleDrawerOpen,
|
|
117
|
+
closeHover: setOnHoverFalse,
|
|
110
118
|
autoOpenDrawer
|
|
111
119
|
}}>
|
|
112
120
|
<div
|
|
@@ -128,7 +136,7 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
128
136
|
onMouseMove={setOnHoverTrue}
|
|
129
137
|
onMouseLeave={setOnHoverFalse}
|
|
130
138
|
open={computedDrawerOpen}
|
|
131
|
-
hovered={
|
|
139
|
+
hovered={computedDrawerHovered}
|
|
132
140
|
setDrawerOpen={setDrawerOpen}>
|
|
133
141
|
{includeDrawer && drawerChildren}
|
|
134
142
|
</DrawerWrapper>
|
|
@@ -177,49 +185,30 @@ function DrawerWrapper(props: {
|
|
|
177
185
|
}) {
|
|
178
186
|
|
|
179
187
|
const { t } = useTranslation();
|
|
180
|
-
|
|
188
|
+
|
|
189
|
+
// Layout width is the space the drawer occupies in the flex row; the visual
|
|
190
|
+
// width is how wide the drawer actually renders. When hovered (but not
|
|
191
|
+
// pinned open) the layout width stays collapsed while the visual width
|
|
192
|
+
// expands, so the drawer *floats* over the content instead of pushing it.
|
|
193
|
+
const layoutWidth = !props.displayed ? 0 : (props.open ? DRAWER_WIDTH : 72);
|
|
194
|
+
const visualWidth = !props.displayed ? 0 : ((props.open || props.hovered) ? DRAWER_WIDTH : 72);
|
|
195
|
+
const isFloating = props.hovered && !props.open;
|
|
196
|
+
|
|
197
|
+
const transition = "left 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, opacity 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, width 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms";
|
|
198
|
+
|
|
181
199
|
const innerDrawer = <div
|
|
182
|
-
className={"
|
|
200
|
+
className={cls("h-full overflow-hidden", defaultBorderMixin,
|
|
201
|
+
isFloating
|
|
202
|
+
? "absolute top-0 left-0 bottom-0 z-30 bg-surface-50 dark:bg-surface-900 shadow-lg border-r"
|
|
203
|
+
: "relative bg-surface-50 dark:bg-surface-900")}
|
|
183
204
|
style={{
|
|
184
|
-
width,
|
|
185
|
-
transition
|
|
205
|
+
width: visualWidth,
|
|
206
|
+
transition
|
|
186
207
|
}}
|
|
187
208
|
>
|
|
188
|
-
|
|
189
|
-
{!props.open && props.displayed && (
|
|
190
|
-
<Tooltip title={t("open_menu")}
|
|
191
|
-
side="right"
|
|
192
|
-
sideOffset={12}
|
|
193
|
-
asChild={true}>
|
|
194
|
-
<div
|
|
195
|
-
className="ml-2 fixed top-1 left-2 sm:top-2 sm:left-2 !bg-surface-50 dark:!bg-surface-900 rounded-full w-fit z-20">
|
|
196
|
-
<IconButton
|
|
197
|
-
color="inherit"
|
|
198
|
-
aria-label={t("open_menu")}
|
|
199
|
-
onClick={() => props.setDrawerOpen(true)}
|
|
200
|
-
size="large"
|
|
201
|
-
>
|
|
202
|
-
<MenuIcon size="small" />
|
|
203
|
-
</IconButton>
|
|
204
|
-
</div>
|
|
205
|
-
</Tooltip>
|
|
206
|
-
)}
|
|
207
|
-
|
|
208
|
-
<div
|
|
209
|
-
className={`z-20 absolute right-0 top-4 ${props.open ? "opacity-100" : "opacity-0 invisible"
|
|
210
|
-
} transition-opacity duration-200 ease-in-out`}>
|
|
211
|
-
<IconButton
|
|
212
|
-
aria-label={t("close_drawer")}
|
|
213
|
-
onClick={() => props.setDrawerOpen(false)}
|
|
214
|
-
>
|
|
215
|
-
<ChevronLeftIcon />
|
|
216
|
-
</IconButton>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
209
|
<div className={"flex flex-col h-full"}>
|
|
220
210
|
{props.children}
|
|
221
211
|
</div>
|
|
222
|
-
|
|
223
212
|
</div>;
|
|
224
213
|
|
|
225
214
|
const largeLayout = useLargeLayout();
|
|
@@ -250,13 +239,14 @@ function DrawerWrapper(props: {
|
|
|
250
239
|
|
|
251
240
|
return (
|
|
252
241
|
<div
|
|
253
|
-
className="z-20 relative"
|
|
242
|
+
className="z-20 relative flex-shrink-0 overflow-visible"
|
|
254
243
|
onMouseEnter={props.onMouseEnter}
|
|
255
244
|
onMouseMove={props.onMouseMove}
|
|
256
245
|
onMouseLeave={props.onMouseLeave}
|
|
257
246
|
style={{
|
|
258
|
-
width,
|
|
259
|
-
|
|
247
|
+
width: layoutWidth,
|
|
248
|
+
minWidth: layoutWidth,
|
|
249
|
+
transition
|
|
260
250
|
}}>
|
|
261
251
|
|
|
262
252
|
{innerDrawer}
|
package/src/app/useApp.tsx
CHANGED
|
@@ -96,6 +96,8 @@ export function DeleteEntityDialog<M extends Record<string, any>>({
|
|
|
96
96
|
deleteEntityWithCallbacks({
|
|
97
97
|
dataSource,
|
|
98
98
|
entity,
|
|
99
|
+
// The entity is the authority on where it lives; stated rather than left implicit.
|
|
100
|
+
pathSegments: entity.pathSegments,
|
|
99
101
|
collection: resolvedCollection,
|
|
100
102
|
callbacks,
|
|
101
103
|
onDeleteSuccess,
|
|
@@ -3,7 +3,7 @@ import React, { MouseEvent, useCallback } from "react";
|
|
|
3
3
|
import { CollectionSize, Entity, EntityAction, EntityCollection, SelectionController } from "../../types";
|
|
4
4
|
import { Badge, Checkbox, cls, IconButton, Menu, MenuItem, MoreVertIcon, Skeleton, Tooltip } from "@firecms/ui";
|
|
5
5
|
import { useFireCMSContext, useLargeLayout } from "../../hooks";
|
|
6
|
-
import { getEntityFromCache } from "../../util/entity_cache";
|
|
6
|
+
import { entityCacheKey, getEntityFromCache } from "../../util/entity_cache";
|
|
7
7
|
import { getLocalChangesBackup } from "../../util";
|
|
8
8
|
import { getChanges } from "../../form/EntityForm";
|
|
9
9
|
|
|
@@ -82,7 +82,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
|
|
|
82
82
|
const collapsedActions = actions.filter(a => a.collapsed || a.collapsed === undefined);
|
|
83
83
|
const uncollapsedActions = actions.filter(a => a.collapsed === false);
|
|
84
84
|
const enableLocalChangesBackup = collection ? getLocalChangesBackup(collection) : false;
|
|
85
|
-
const cachedData = enableLocalChangesBackup ? getEntityFromCache(fullPath
|
|
85
|
+
const cachedData = enableLocalChangesBackup ? getEntityFromCache(entityCacheKey(fullPath, entity.id)) : undefined;
|
|
86
86
|
const hasDraft = (() => {
|
|
87
87
|
if (!cachedData || typeof cachedData !== "object" || Object.keys(cachedData).length === 0) return false;
|
|
88
88
|
const realChanges = getChanges(cachedData as any, (entity?.values ?? {}) as any);
|
|
@@ -245,7 +245,8 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
|
|
|
245
245
|
const columnsResult: VirtualTableColumn[] = propertiesToColumns({
|
|
246
246
|
properties,
|
|
247
247
|
sortable,
|
|
248
|
-
|
|
248
|
+
forcedFilters: tableController.forcedFilters,
|
|
249
|
+
allowedFilters: tableController.allowedFilters,
|
|
249
250
|
AdditionalHeaderWidget
|
|
250
251
|
});
|
|
251
252
|
|
|
@@ -353,6 +354,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
|
|
|
353
354
|
onTextSearch={textSearchEnabled ? onTextSearch : undefined}
|
|
354
355
|
textSearchLoading={textSearchLoading}
|
|
355
356
|
onTextSearchClick={textSearchEnabled ? onTextSearchClick : undefined}
|
|
357
|
+
initialSearchString={tableController.searchString}
|
|
356
358
|
title={title}
|
|
357
359
|
actionsStart={actionsStart}
|
|
358
360
|
actions={actions}
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { getTableCellAlignment, getTablePropertyColumnWidth } from "./internal/common";
|
|
3
3
|
import { FilterValues, ResolvedProperties, ResolvedProperty } from "../../types";
|
|
4
4
|
import { VirtualTableColumn } from "../VirtualTable";
|
|
5
|
-
import { getIconForProperty, getResolvedPropertyInPath } from "../../util";
|
|
5
|
+
import { getIconForProperty, getResolvedPropertyInPath, isDataTypeFilterable } from "../../util";
|
|
6
6
|
import { getColumnKeysForProperty } from "../common/useColumnsIds";
|
|
7
7
|
|
|
8
8
|
export function buildIdColumn(largeLayout?: boolean): VirtualTableColumn {
|
|
@@ -20,7 +20,8 @@ export function buildIdColumn(largeLayout?: boolean): VirtualTableColumn {
|
|
|
20
20
|
export interface PropertiesToColumnsParams<M extends Record<string, any>> {
|
|
21
21
|
properties: ResolvedProperties<M>;
|
|
22
22
|
sortable?: boolean;
|
|
23
|
-
|
|
23
|
+
forcedFilters?: (keyof M)[];
|
|
24
|
+
allowedFilters?: (keyof M)[];
|
|
24
25
|
AdditionalHeaderWidget?: React.ComponentType<{
|
|
25
26
|
property: ResolvedProperty,
|
|
26
27
|
propertyKey: string,
|
|
@@ -28,8 +29,7 @@ export interface PropertiesToColumnsParams<M extends Record<string, any>> {
|
|
|
28
29
|
}>;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export function propertiesToColumns<M extends Record<string, any>>({ properties, sortable,
|
|
32
|
-
const disabledFilter = Boolean(forceFilter);
|
|
32
|
+
export function propertiesToColumns<M extends Record<string, any>>({ properties, sortable, forcedFilters, AdditionalHeaderWidget, allowedFilters }: PropertiesToColumnsParams<M>): VirtualTableColumn[] {
|
|
33
33
|
return Object.entries<ResolvedProperty>(properties)
|
|
34
34
|
.flatMap(([key, property]) => getColumnKeysForProperty(property, key))
|
|
35
35
|
.map(({
|
|
@@ -39,14 +39,19 @@ export function propertiesToColumns<M extends Record<string, any>>({ properties,
|
|
|
39
39
|
const property = getResolvedPropertyInPath(properties, key);
|
|
40
40
|
if (!property)
|
|
41
41
|
throw Error("Internal error: no property found in path " + key);
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
const filterable = property.dataType === 'array' ? isDataTypeFilterable(property.of?.dataType, true) : isDataTypeFilterable(property.dataType);
|
|
44
|
+
const isFilterForced = forcedFilters?.includes(key) ?? false;
|
|
45
|
+
const isFilterAllowed = allowedFilters ? allowedFilters.includes(key) : filterable;
|
|
46
|
+
|
|
47
|
+
const filterEnabled = filterable && isFilterAllowed && !isFilterForced;
|
|
43
48
|
return {
|
|
44
49
|
key: key as string,
|
|
45
50
|
align: getTableCellAlignment(property),
|
|
46
51
|
icon: getIconForProperty(property, "small"),
|
|
47
52
|
title: property.name ?? key as string,
|
|
48
53
|
sortable: sortable,
|
|
49
|
-
filter:
|
|
54
|
+
filter: filterEnabled,
|
|
50
55
|
width: getTablePropertyColumnWidth(property),
|
|
51
56
|
resizable: true,
|
|
52
57
|
custom: {
|
|
@@ -59,16 +64,3 @@ export function propertiesToColumns<M extends Record<string, any>>({ properties,
|
|
|
59
64
|
} satisfies VirtualTableColumn;
|
|
60
65
|
});
|
|
61
66
|
}
|
|
62
|
-
|
|
63
|
-
function filterableProperty(property: ResolvedProperty, partOfArray = false): boolean {
|
|
64
|
-
if (partOfArray) {
|
|
65
|
-
return ["string", "number", "date", "reference"].includes(property.dataType);
|
|
66
|
-
}
|
|
67
|
-
if (property.dataType === "array") {
|
|
68
|
-
if (property.of)
|
|
69
|
-
return filterableProperty(property.of, true);
|
|
70
|
-
else
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return ["string", "number", "boolean", "date", "reference", "array"].includes(property.dataType);
|
|
74
|
-
}
|
|
@@ -70,7 +70,7 @@ export const TableReferenceFieldInternal = React.memo(
|
|
|
70
70
|
}, [updateValue]);
|
|
71
71
|
|
|
72
72
|
const onMultipleEntitiesSelected = useCallback((entities: Entity<any>[]) => {
|
|
73
|
-
updateValue(entities.map((e) => getReferenceFrom(e)));
|
|
73
|
+
updateValue(entities.filter(Boolean).map((e) => getReferenceFrom(e)));
|
|
74
74
|
}, [updateValue]);
|
|
75
75
|
|
|
76
76
|
const selectedEntityIds = internalValue
|
|
@@ -20,6 +20,11 @@ interface CollectionTableToolbarProps {
|
|
|
20
20
|
onTextSearchClick?: () => void;
|
|
21
21
|
onTextSearch?: (searchString?: string) => void;
|
|
22
22
|
textSearchLoading?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Search term the search bar should start with, typically the one restored from the
|
|
25
|
+
* URL query params by the table controller.
|
|
26
|
+
*/
|
|
27
|
+
initialSearchString?: string;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
export function CollectionTableToolbar({
|
|
@@ -29,6 +34,7 @@ export function CollectionTableToolbar({
|
|
|
29
34
|
onTextSearch,
|
|
30
35
|
onTextSearchClick,
|
|
31
36
|
textSearchLoading,
|
|
37
|
+
initialSearchString,
|
|
32
38
|
title,
|
|
33
39
|
viewModeToggle
|
|
34
40
|
}: CollectionTableToolbarProps) {
|
|
@@ -79,6 +85,7 @@ export function CollectionTableToolbar({
|
|
|
79
85
|
disabled={Boolean(onTextSearchClick)}
|
|
80
86
|
onClick={onTextSearchClick}
|
|
81
87
|
onTextSearch={onTextSearchClick ? undefined : onTextSearch}
|
|
88
|
+
initialValue={initialSearchString}
|
|
82
89
|
placeholder={t("search")}
|
|
83
90
|
expandable={true} />}
|
|
84
91
|
|