@firecms/core 3.4.0-canary.8635253 → 3.4.0-canary.d20cc85
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/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 +7 -0
- package/dist/components/EntityCollectionView/useBoardDataController.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 +7 -1
- package/dist/components/common/useTableSearchHelper.d.ts +7 -1
- package/dist/core/EntityEditView.d.ts +7 -1
- package/dist/form/EntityForm.d.ts +6 -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 +6 -1
- package/dist/index.es.js +949 -622
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +948 -621
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +7 -0
- package/dist/types/datasource.d.ts +59 -4
- package/dist/types/entities.d.ts +21 -1
- package/dist/types/properties.d.ts +17 -1
- package/dist/types/side_entity_controller.d.ts +6 -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/index.d.ts +1 -0
- package/dist/util/navigation_from_path.d.ts +7 -0
- package/dist/util/navigation_utils.d.ts +3 -1
- package/dist/util/parent_references_from_path.d.ts +1 -0
- package/dist/util/urls.d.ts +22 -0
- package/package.json +4 -4
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +1 -0
- 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 +4 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +29 -3
- package/src/components/EntityCollectionView/useBoardDataController.tsx +11 -0
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +11 -0
- 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/common/table_url_params.ts +172 -0
- package/src/components/common/useDataSourceTableController.tsx +64 -175
- package/src/components/common/useTableSearchHelper.ts +21 -2
- package/src/contexts/SnackbarProvider.tsx +14 -1
- package/src/core/EntityEditView.tsx +21 -0
- package/src/core/EntitySidePanel.tsx +2 -0
- package/src/form/EntityForm.tsx +9 -3
- package/src/hooks/data/delete.ts +2 -0
- package/src/hooks/data/save.ts +2 -0
- package/src/hooks/data/useCollectionFetch.tsx +13 -0
- package/src/hooks/data/useEntityFetch.tsx +18 -0
- package/src/hooks/useResolvedNavigationFrom.tsx +2 -0
- package/src/hooks/useValidateAuthenticator.tsx +4 -0
- package/src/internal/useBuildDataSource.ts +25 -4
- package/src/internal/useBuildSideEntityController.tsx +6 -0
- package/src/locales/de.ts +5 -0
- package/src/locales/en.ts +5 -0
- package/src/locales/es.ts +5 -0
- package/src/locales/fr.ts +5 -0
- package/src/locales/hi.ts +5 -0
- package/src/locales/it.ts +5 -0
- package/src/locales/pl.ts +5 -0
- package/src/locales/pt.ts +5 -0
- package/src/preview/components/StorageThumbnail.tsx +24 -2
- package/src/routes/FireCMSRoute.tsx +3 -0
- package/src/types/collections.ts +8 -0
- package/src/types/datasource.ts +59 -4
- package/src/types/entities.ts +24 -1
- package/src/types/properties.ts +17 -0
- package/src/types/side_entity_controller.tsx +6 -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 +3 -1
- package/src/util/index.ts +1 -0
- package/src/util/navigation_from_path.ts +19 -1
- package/src/util/navigation_utils.ts +4 -0
- package/src/util/parent_references_from_path.ts +10 -2
- package/src/util/previews.ts +8 -1
- package/src/util/resolutions.ts +8 -0
- package/src/util/urls.ts +52 -0
|
@@ -87,6 +87,13 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
|
|
|
87
87
|
* If not specified, the first property simple text property will be used.
|
|
88
88
|
*/
|
|
89
89
|
titleProperty?: keyof M;
|
|
90
|
+
/**
|
|
91
|
+
* Property used as the image thumbnail in Cards and Kanban view modes.
|
|
92
|
+
* Must reference a storage/image property (or an array of them), or a
|
|
93
|
+
* string property with `url: "image"`.
|
|
94
|
+
* If not specified, the first matching image property will be used.
|
|
95
|
+
*/
|
|
96
|
+
imageProperty?: keyof M;
|
|
90
97
|
/**
|
|
91
98
|
* When editing an entity, you can choose to open the entity in a side dialog
|
|
92
99
|
* or in a full screen dialog. Defaults to `full_screen`.
|
|
@@ -7,6 +7,20 @@ import { FireCMSContext } from "./firecms_context";
|
|
|
7
7
|
*/
|
|
8
8
|
export interface FetchEntityProps<M extends Record<string, any> = any> {
|
|
9
9
|
path: string;
|
|
10
|
+
/**
|
|
11
|
+
* The path split at its real segment boundaries, e.g.
|
|
12
|
+
* `["nodes", "node/42", "edges"]`.
|
|
13
|
+
*
|
|
14
|
+
* `path` is a single flattened string, so a parent entity id containing "/" cannot be
|
|
15
|
+
* recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
|
|
16
|
+
* nesting. This is the unambiguous form: one array element per segment, entity ids
|
|
17
|
+
* kept whole however many slashes they contain.
|
|
18
|
+
*
|
|
19
|
+
* For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
|
|
20
|
+
* delegates that do not care can keep using `path`. Delegates that do should prefer
|
|
21
|
+
* `pathSegments ?? path.split("/")`.
|
|
22
|
+
*/
|
|
23
|
+
pathSegments?: string[];
|
|
10
24
|
entityId: string;
|
|
11
25
|
databaseId?: string;
|
|
12
26
|
collection?: EntityCollection<M, any>;
|
|
@@ -23,6 +37,20 @@ export type ListenEntityProps<M extends Record<string, any> = any> = FetchEntity
|
|
|
23
37
|
*/
|
|
24
38
|
export interface FetchCollectionProps<M extends Record<string, any> = any> {
|
|
25
39
|
path: string;
|
|
40
|
+
/**
|
|
41
|
+
* The path split at its real segment boundaries, e.g.
|
|
42
|
+
* `["nodes", "node/42", "edges"]`.
|
|
43
|
+
*
|
|
44
|
+
* `path` is a single flattened string, so a parent entity id containing "/" cannot be
|
|
45
|
+
* recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
|
|
46
|
+
* nesting. This is the unambiguous form: one array element per segment, entity ids
|
|
47
|
+
* kept whole however many slashes they contain.
|
|
48
|
+
*
|
|
49
|
+
* For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
|
|
50
|
+
* delegates that do not care can keep using `path`. Delegates that do should prefer
|
|
51
|
+
* `pathSegments ?? path.split("/")`.
|
|
52
|
+
*/
|
|
53
|
+
pathSegments?: string[];
|
|
26
54
|
collection?: EntityCollection<M> | ResolvedEntityCollection<M>;
|
|
27
55
|
filter?: FilterValues<Extract<keyof M, string>>;
|
|
28
56
|
limit?: number;
|
|
@@ -43,6 +71,20 @@ export type ListenCollectionProps<M extends Record<string, any> = any> = FetchCo
|
|
|
43
71
|
*/
|
|
44
72
|
export interface SaveEntityProps<M extends Record<string, any> = any> {
|
|
45
73
|
path: string;
|
|
74
|
+
/**
|
|
75
|
+
* The path split at its real segment boundaries, e.g.
|
|
76
|
+
* `["nodes", "node/42", "edges"]`.
|
|
77
|
+
*
|
|
78
|
+
* `path` is a single flattened string, so a parent entity id containing "/" cannot be
|
|
79
|
+
* recovered from it — `"nodes/node/42/edges"` is indistinguishable from a three-level
|
|
80
|
+
* nesting. This is the unambiguous form: one array element per segment, entity ids
|
|
81
|
+
* kept whole however many slashes they contain.
|
|
82
|
+
*
|
|
83
|
+
* For any backend whose ids cannot contain "/" this is exactly `path.split("/")`, so
|
|
84
|
+
* delegates that do not care can keep using `path`. Delegates that do should prefer
|
|
85
|
+
* `pathSegments ?? path.split("/")`.
|
|
86
|
+
*/
|
|
87
|
+
pathSegments?: string[];
|
|
46
88
|
values: Partial<EntityValues<M>>;
|
|
47
89
|
entityId?: string;
|
|
48
90
|
previousValues?: Partial<EntityValues<M>>;
|
|
@@ -54,6 +96,11 @@ export interface SaveEntityProps<M extends Record<string, any> = any> {
|
|
|
54
96
|
*/
|
|
55
97
|
export interface DeleteEntityProps<M extends Record<string, any> = any> {
|
|
56
98
|
entity: Entity<M>;
|
|
99
|
+
/**
|
|
100
|
+
* `entity.path` split at its real segment boundaries, when the caller knows them.
|
|
101
|
+
* Never derived from `entity.path` — see `pathSegments` on FetchCollectionProps.
|
|
102
|
+
*/
|
|
103
|
+
pathSegments?: string[];
|
|
57
104
|
collection?: EntityCollection<M> | ResolvedEntityCollection<M>;
|
|
58
105
|
}
|
|
59
106
|
/**
|
|
@@ -133,11 +180,17 @@ export interface DataSource {
|
|
|
133
180
|
* @param entityId
|
|
134
181
|
* @return `true` if there are no other fields besides the given entity
|
|
135
182
|
*/
|
|
136
|
-
checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection
|
|
183
|
+
checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection,
|
|
184
|
+
/**
|
|
185
|
+
* Appended last, and optional, so existing implementations and callers keep
|
|
186
|
+
* working unchanged — a function declaring fewer parameters stays assignable.
|
|
187
|
+
* See `pathSegments` on FetchCollectionProps.
|
|
188
|
+
*/
|
|
189
|
+
pathSegments?: string[]): Promise<boolean>;
|
|
137
190
|
/**
|
|
138
191
|
* Generate an id for a new entity
|
|
139
192
|
*/
|
|
140
|
-
generateEntityId(path: string, collection: EntityCollection): string;
|
|
193
|
+
generateEntityId(path: string, collection: EntityCollection, pathSegments?: string[]): string;
|
|
141
194
|
/**
|
|
142
195
|
* Count the number of entities in a collection
|
|
143
196
|
*/
|
|
@@ -160,6 +213,8 @@ export interface DataSource {
|
|
|
160
213
|
}) => Promise<boolean>;
|
|
161
214
|
}
|
|
162
215
|
export type FilterCombinationValidProps = {
|
|
216
|
+
/** `path` split at its real segment boundaries — see `pathSegments` on FetchCollectionProps. */
|
|
217
|
+
pathSegments?: string[];
|
|
163
218
|
path: string;
|
|
164
219
|
collection: EntityCollection<any>;
|
|
165
220
|
filterValues: FilterValues<any>;
|
|
@@ -245,11 +300,11 @@ export interface DataSourceDelegate {
|
|
|
245
300
|
* @param collection
|
|
246
301
|
* @return `true` if there are no other fields besides the given entity
|
|
247
302
|
*/
|
|
248
|
-
checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection): Promise<boolean>;
|
|
303
|
+
checkUniqueField(path: string, name: string, value: any, entityId?: string, collection?: EntityCollection, pathSegments?: string[]): Promise<boolean>;
|
|
249
304
|
/**
|
|
250
305
|
* Generate an id for a new entity
|
|
251
306
|
*/
|
|
252
|
-
generateEntityId(path: string, collection?: EntityCollection): string;
|
|
307
|
+
generateEntityId(path: string, collection?: EntityCollection, pathSegments?: string[]): string;
|
|
253
308
|
/**
|
|
254
309
|
* Count the number of entities in a collection
|
|
255
310
|
*/
|
package/dist/types/entities.d.ts
CHANGED
|
@@ -17,6 +17,16 @@ export interface Entity<M extends object = any> {
|
|
|
17
17
|
* to the root of the database).
|
|
18
18
|
*/
|
|
19
19
|
path: string;
|
|
20
|
+
/**
|
|
21
|
+
* `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
|
|
22
|
+
*
|
|
23
|
+
* `path` is a single flattened string, so a parent entity id containing "/" cannot be
|
|
24
|
+
* recovered from it. This keeps each segment whole.
|
|
25
|
+
*
|
|
26
|
+
* Optional, and never derived by splitting `path` — a guess would be wrong in exactly
|
|
27
|
+
* the case the field exists for. Absent means "not known here", not "no slashes".
|
|
28
|
+
*/
|
|
29
|
+
pathSegments?: string[];
|
|
20
30
|
/**
|
|
21
31
|
* Current values
|
|
22
32
|
*/
|
|
@@ -46,7 +56,17 @@ export declare class EntityReference {
|
|
|
46
56
|
* Optional database ID where the entity is stored (if multiple databases are used)
|
|
47
57
|
*/
|
|
48
58
|
readonly databaseId?: string;
|
|
49
|
-
|
|
59
|
+
/**
|
|
60
|
+
* `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
|
|
61
|
+
*
|
|
62
|
+
* `path` is a single flattened string, so a parent entity id containing "/" cannot be
|
|
63
|
+
* recovered from it. This keeps each segment whole.
|
|
64
|
+
*
|
|
65
|
+
* Optional, and never derived by splitting `path` — a guess would be wrong in exactly
|
|
66
|
+
* the case the field exists for. Absent means "not known here", not "no slashes".
|
|
67
|
+
*/
|
|
68
|
+
readonly pathSegments?: string[];
|
|
69
|
+
constructor(id: string, path: string, databaseId?: string, pathSegments?: string[]);
|
|
50
70
|
get pathWithId(): string;
|
|
51
71
|
get pathWithIdAndDatabase(): string;
|
|
52
72
|
isEntityReference(): boolean;
|
|
@@ -433,6 +433,22 @@ export type PropertyBuilderProps<M extends Record<string, any> = any> = {
|
|
|
433
433
|
* Current value of this property
|
|
434
434
|
*/
|
|
435
435
|
propertyValue?: any;
|
|
436
|
+
/**
|
|
437
|
+
* Path of this property within the entity, in dot notation, e.g.
|
|
438
|
+
* `my_map.my_field` or `my_array.0.my_field`.
|
|
439
|
+
* Useful to look up sibling values in `values` with `getValueInPath`.
|
|
440
|
+
*
|
|
441
|
+
* When a property builder is used as the `of` prop of an array property,
|
|
442
|
+
* it is resolved twice: once per existing element, with an index
|
|
443
|
+
* qualified key (`my_array.0`), and once as the template for the element
|
|
444
|
+
* shape, with the array's own key (`my_array`). In the latter case
|
|
445
|
+
* `propertyValue` is the whole array, since the template is not bound to
|
|
446
|
+
* any element.
|
|
447
|
+
*
|
|
448
|
+
* It may be undefined if the property is resolved outside the context of
|
|
449
|
+
* an entity.
|
|
450
|
+
*/
|
|
451
|
+
propertyKey?: string;
|
|
436
452
|
/**
|
|
437
453
|
* Index of this property (only for arrays)
|
|
438
454
|
*/
|
|
@@ -456,7 +472,7 @@ export type PropertyBuilderProps<M extends Record<string, any> = any> = {
|
|
|
456
472
|
* current value of the property, as well as the path and entity ID.
|
|
457
473
|
* @group Entity properties
|
|
458
474
|
*/
|
|
459
|
-
export type PropertyBuilder<T extends CMSType = any, M extends Record<string, any> = any> = ({ values, previousValues, propertyValue, index, path, entityId, authController }: PropertyBuilderProps<M>) => Property<T> | null;
|
|
475
|
+
export type PropertyBuilder<T extends CMSType = any, M extends Record<string, any> = any> = ({ values, previousValues, propertyValue, propertyKey, index, path, entityId, authController }: PropertyBuilderProps<M>) => Property<T> | null;
|
|
460
476
|
/**
|
|
461
477
|
* @group Entity properties
|
|
462
478
|
*/
|
|
@@ -15,6 +15,12 @@ export interface EntitySidePanelProps<M extends Record<string, any> = any> {
|
|
|
15
15
|
* Full CMS path of the entity, including the collection and sub-collections.
|
|
16
16
|
*/
|
|
17
17
|
fullIdPath?: string;
|
|
18
|
+
/**
|
|
19
|
+
* `path` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
|
|
20
|
+
* Optional: when omitted the panel falls back to splitting `path`, which is correct for
|
|
21
|
+
* any backend whose entity ids cannot contain "/".
|
|
22
|
+
*/
|
|
23
|
+
pathSegments?: string[];
|
|
18
24
|
/**
|
|
19
25
|
* ID of the entity, if not set, it means we are creating a new entity
|
|
20
26
|
*/
|
|
@@ -368,6 +368,8 @@ export interface FireCMSTranslations {
|
|
|
368
368
|
download: string;
|
|
369
369
|
large_number_of_documents: string;
|
|
370
370
|
include_undefined_values: string;
|
|
371
|
+
/** Toggle in the export dialog, only shown when a filter or sort is active */
|
|
372
|
+
export_apply_filter_sort: string;
|
|
371
373
|
submit: string;
|
|
372
374
|
no_filterable_properties: string;
|
|
373
375
|
apply_filters: string;
|
|
@@ -547,6 +549,10 @@ export interface FireCMSTranslations {
|
|
|
547
549
|
order_property_not_found: string;
|
|
548
550
|
no_number_properties: string;
|
|
549
551
|
order_property_description: string;
|
|
552
|
+
image_property: string;
|
|
553
|
+
image_property_not_found: string;
|
|
554
|
+
no_image_properties: string;
|
|
555
|
+
image_property_description: string;
|
|
550
556
|
display_settings: string;
|
|
551
557
|
default_row_size: string;
|
|
552
558
|
side_dialog_width: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -9,4 +9,4 @@ export declare function resolveDefaultSelectedView(defaultSelectedView: string |
|
|
|
9
9
|
* @param permissionsBuilder
|
|
10
10
|
*/
|
|
11
11
|
export declare const applyPermissionsFunctionIfEmpty: (collections: EntityCollection[], permissionsBuilder?: PermissionsBuilder<any, any>) => EntityCollection[];
|
|
12
|
-
export declare function getLocalChangesBackup(collection: EntityCollection): "manual_apply" | "auto_apply";
|
|
12
|
+
export declare function getLocalChangesBackup(collection: EntityCollection): false | "manual_apply" | "auto_apply";
|
package/dist/util/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface NavigationViewEntityInternal<M extends Record<string, any>> {
|
|
|
6
6
|
path: string;
|
|
7
7
|
fullIdPath: string;
|
|
8
8
|
fullPath: string;
|
|
9
|
+
/** `path` split at its real boundaries — see `pathSegments` on the datasource props. */
|
|
10
|
+
pathSegments: string[];
|
|
9
11
|
parentCollection: EntityCollection<M>;
|
|
10
12
|
}
|
|
11
13
|
export interface NavigationViewCollectionInternal<M extends Record<string, any>> {
|
|
@@ -14,6 +16,8 @@ export interface NavigationViewCollectionInternal<M extends Record<string, any>>
|
|
|
14
16
|
path: string;
|
|
15
17
|
fullIdPath: string;
|
|
16
18
|
fullPath: string;
|
|
19
|
+
/** `path` split at its real boundaries — see `pathSegments` on the datasource props. */
|
|
20
|
+
pathSegments: string[];
|
|
17
21
|
collection: EntityCollection<M>;
|
|
18
22
|
}
|
|
19
23
|
export interface NavigationViewEntityCustomInternal<M extends Record<string, any>> {
|
|
@@ -21,6 +25,8 @@ export interface NavigationViewEntityCustomInternal<M extends Record<string, any
|
|
|
21
25
|
path: string;
|
|
22
26
|
fullIdPath: string;
|
|
23
27
|
fullPath: string;
|
|
28
|
+
/** `path` split at its real boundaries — see `pathSegments` on the datasource props. */
|
|
29
|
+
pathSegments: string[];
|
|
24
30
|
entityId: string;
|
|
25
31
|
view: EntityCustomView<M>;
|
|
26
32
|
}
|
|
@@ -39,5 +45,6 @@ export declare function getNavigationEntriesFromPath(props: {
|
|
|
39
45
|
currentFullPath?: string;
|
|
40
46
|
currentFullIdPath?: string;
|
|
41
47
|
currentFullUrlPath?: string;
|
|
48
|
+
currentPathSegments?: string[];
|
|
42
49
|
contextEntityViews?: EntityCustomView<any>[];
|
|
43
50
|
}): NavigationViewInternal[];
|
|
@@ -38,7 +38,7 @@ export declare function getCollectionByPathOrId(pathOrId: string, collections: E
|
|
|
38
38
|
* @param subpaths
|
|
39
39
|
*/
|
|
40
40
|
export declare function getCollectionPathsCombinations(subpaths: string[]): string[];
|
|
41
|
-
export declare function navigateToEntity({ openEntityMode, collection, entityId, copy, path, fullIdPath, selectedTab, sideEntityController, onClose, navigation }: {
|
|
41
|
+
export declare function navigateToEntity({ openEntityMode, collection, entityId, copy, path, fullIdPath, pathSegments, selectedTab, sideEntityController, onClose, navigation }: {
|
|
42
42
|
openEntityMode: "side_panel" | "full_screen";
|
|
43
43
|
collection?: EntityCollection;
|
|
44
44
|
entityId?: string;
|
|
@@ -46,6 +46,8 @@ export declare function navigateToEntity({ openEntityMode, collection, entityId,
|
|
|
46
46
|
copy?: boolean;
|
|
47
47
|
path: string;
|
|
48
48
|
fullIdPath?: string;
|
|
49
|
+
/** `path` split at its real segment boundaries. */
|
|
50
|
+
pathSegments?: string[];
|
|
49
51
|
sideEntityController: SideEntityController;
|
|
50
52
|
onClose?: () => void;
|
|
51
53
|
navigation: NavigationController;
|
|
@@ -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.0-canary.
|
|
4
|
+
"version": "3.4.0-canary.d20cc85",
|
|
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": "3.4.0-canary.
|
|
57
|
-
"@firecms/ui": "3.4.0-canary.
|
|
56
|
+
"@firecms/formex": "3.4.0-canary.d20cc85",
|
|
57
|
+
"@firecms/ui": "3.4.0-canary.d20cc85",
|
|
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",
|
|
@@ -89,7 +89,7 @@
|
|
|
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",
|
|
@@ -354,6 +354,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
|
|
|
354
354
|
onTextSearch={textSearchEnabled ? onTextSearch : undefined}
|
|
355
355
|
textSearchLoading={textSearchLoading}
|
|
356
356
|
onTextSearchClick={textSearchEnabled ? onTextSearchClick : undefined}
|
|
357
|
+
initialSearchString={tableController.searchString}
|
|
357
358
|
title={title}
|
|
358
359
|
actionsStart={actionsStart}
|
|
359
360
|
actions={actions}
|
|
@@ -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
|
|
|
@@ -26,6 +26,8 @@ import { OnCellValueChangeParams } from "../../../common";
|
|
|
26
26
|
interface PopupFormFieldProps<M extends Record<string, any>> {
|
|
27
27
|
customFieldValidator?: CustomFieldValidator;
|
|
28
28
|
path: string;
|
|
29
|
+
/** `path` split at its real segment boundaries; forwarded to the datasource. */
|
|
30
|
+
pathSegments?: string[];
|
|
29
31
|
entityId: string;
|
|
30
32
|
tableKey: string;
|
|
31
33
|
propertyKey?: Extract<keyof M, string>;
|
|
@@ -54,6 +56,7 @@ export function PopupFormFieldLoading<M extends Record<string, any>>({
|
|
|
54
56
|
propertyKey,
|
|
55
57
|
collection: inputCollection,
|
|
56
58
|
path,
|
|
59
|
+
pathSegments,
|
|
57
60
|
cellRect,
|
|
58
61
|
open,
|
|
59
62
|
onClose,
|
|
@@ -66,11 +69,12 @@ export function PopupFormFieldLoading<M extends Record<string, any>>({
|
|
|
66
69
|
if (entityId && inputCollection) {
|
|
67
70
|
dataSource.fetchEntity({
|
|
68
71
|
path,
|
|
72
|
+
pathSegments,
|
|
69
73
|
entityId,
|
|
70
74
|
collection: inputCollection
|
|
71
75
|
}).then(setEntity);
|
|
72
76
|
}
|
|
73
|
-
}, [entityId, inputCollection, dataSource, path]);
|
|
77
|
+
}, [entityId, inputCollection, dataSource, path, pathSegments]);
|
|
74
78
|
|
|
75
79
|
if (!entity) return null;
|
|
76
80
|
return <PopupFormFieldInternal {...{
|
|
@@ -42,6 +42,8 @@ export type EntityCollectionBoardViewProps<M extends Record<string, any> = any>
|
|
|
42
42
|
collection: EntityCollection<M>;
|
|
43
43
|
tableController: EntityTableController<M>;
|
|
44
44
|
fullPath: string;
|
|
45
|
+
/** `fullPath` split at its real segment boundaries; forwarded to the datasource. */
|
|
46
|
+
pathSegments?: string[];
|
|
45
47
|
parentCollectionIds?: string[];
|
|
46
48
|
columnProperty: string;
|
|
47
49
|
onEntityClick?: (entity: Entity<M>) => void;
|
|
@@ -60,6 +62,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
60
62
|
collection,
|
|
61
63
|
tableController,
|
|
62
64
|
fullPath,
|
|
65
|
+
pathSegments,
|
|
63
66
|
parentCollectionIds = [],
|
|
64
67
|
columnProperty,
|
|
65
68
|
onEntityClick,
|
|
@@ -169,6 +172,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
169
172
|
// Use the new per-column data controller
|
|
170
173
|
const boardDataController = useBoardDataController<M>({
|
|
171
174
|
fullPath,
|
|
175
|
+
pathSegments,
|
|
172
176
|
collection,
|
|
173
177
|
columnProperty,
|
|
174
178
|
columns,
|
|
@@ -106,6 +106,13 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
|
|
|
106
106
|
* Full path using navigation ids.
|
|
107
107
|
*/
|
|
108
108
|
fullIdPath?: string;
|
|
109
|
+
/**
|
|
110
|
+
* `fullPath` split at its real segment boundaries, e.g. `["nodes", "node/42", "edges"]`.
|
|
111
|
+
* Forwarded to the datasource so a parent entity id containing "/" stays unambiguous.
|
|
112
|
+
* Defaults to splitting `fullPath`, which is correct for any backend whose ids cannot
|
|
113
|
+
* contain "/".
|
|
114
|
+
*/
|
|
115
|
+
pathSegments?: string[];
|
|
109
116
|
/**
|
|
110
117
|
* If this is a subcollection, specify the parent collection ids.
|
|
111
118
|
*/
|
|
@@ -152,6 +159,7 @@ export const EntityCollectionView = React.memo(
|
|
|
152
159
|
function EntityCollectionView<M extends Record<string, any>>({
|
|
153
160
|
fullPath: fullPathProp,
|
|
154
161
|
fullIdPath,
|
|
162
|
+
pathSegments,
|
|
155
163
|
parentCollectionIds,
|
|
156
164
|
isSubCollection,
|
|
157
165
|
className,
|
|
@@ -317,6 +325,7 @@ export const EntityCollectionView = React.memo(
|
|
|
317
325
|
|
|
318
326
|
const tableController = useDataSourceTableController<M>({
|
|
319
327
|
fullPath,
|
|
328
|
+
pathSegments,
|
|
320
329
|
collection,
|
|
321
330
|
lastDeleteTimestamp,
|
|
322
331
|
scrollRestoration,
|
|
@@ -365,6 +374,7 @@ export const EntityCollectionView = React.memo(
|
|
|
365
374
|
collection,
|
|
366
375
|
entityId: undefined,
|
|
367
376
|
path: fullPath,
|
|
377
|
+
pathSegments,
|
|
368
378
|
fullIdPath,
|
|
369
379
|
sideEntityController,
|
|
370
380
|
navigation,
|
|
@@ -440,6 +450,7 @@ export const EntityCollectionView = React.memo(
|
|
|
440
450
|
const onViewModeChange = useCallback((mode: ViewMode) => {
|
|
441
451
|
analyticsController.onAnalyticsEvent?.("view_mode_changed", {
|
|
442
452
|
path: fullPath,
|
|
453
|
+
pathSegments,
|
|
443
454
|
from: viewMode,
|
|
444
455
|
to: mode
|
|
445
456
|
});
|
|
@@ -581,6 +592,7 @@ export const EntityCollectionView = React.memo(
|
|
|
581
592
|
const onKanbanPropertyChange = useCallback((property: string) => {
|
|
582
593
|
analyticsController.onAnalyticsEvent?.("kanban_property_changed", {
|
|
583
594
|
path: fullPath,
|
|
595
|
+
pathSegments,
|
|
584
596
|
property
|
|
585
597
|
});
|
|
586
598
|
setSelectedKanbanProperty(property);
|
|
@@ -645,6 +657,7 @@ export const EntityCollectionView = React.memo(
|
|
|
645
657
|
entityId: entity.id,
|
|
646
658
|
selectedTab: subcollection.id ?? subcollection.path,
|
|
647
659
|
path: fullPath,
|
|
660
|
+
pathSegments,
|
|
648
661
|
fullIdPath,
|
|
649
662
|
navigation,
|
|
650
663
|
sideEntityController
|
|
@@ -776,6 +789,7 @@ export const EntityCollectionView = React.memo(
|
|
|
776
789
|
// EntitiesCount fetches count and updates breadcrumb - no visual rendering needed here
|
|
777
790
|
const countFetcher = <EntitiesCount
|
|
778
791
|
fullPath={fullPath}
|
|
792
|
+
pathSegments={pathSegments}
|
|
779
793
|
collection={collection}
|
|
780
794
|
filter={tableController.filterValues}
|
|
781
795
|
sortBy={tableController.sortBy}
|
|
@@ -831,7 +845,11 @@ export const EntityCollectionView = React.memo(
|
|
|
831
845
|
} = useTableSearchHelper({
|
|
832
846
|
collection,
|
|
833
847
|
fullPath: resolvedFullPath,
|
|
834
|
-
parentCollectionIds
|
|
848
|
+
parentCollectionIds,
|
|
849
|
+
// When the search term comes from the URL instead of the search bar, text search
|
|
850
|
+
// has never been initialised for this collection. Initialise it so the search bar
|
|
851
|
+
// is usable (editable and clearable) right away.
|
|
852
|
+
initialSearchString: tableController.searchString
|
|
835
853
|
});
|
|
836
854
|
|
|
837
855
|
// Popover open state managed at parent level to prevent closing when view changes
|
|
@@ -881,6 +899,7 @@ export const EntityCollectionView = React.memo(
|
|
|
881
899
|
loading={tableController.dataLoading}
|
|
882
900
|
onTextSearch={textSearchEnabled && textSearchInitialised ? tableController.setSearchString : undefined}
|
|
883
901
|
onTextSearchClick={textSearchEnabled && !textSearchInitialised ? onTextSearchClick : undefined}
|
|
902
|
+
initialSearchString={tableController.searchString}
|
|
884
903
|
textSearchLoading={textSearchLoading}
|
|
885
904
|
viewModeToggle={viewModeToggleElement}
|
|
886
905
|
actionsStart={<EntityCollectionViewStartActions
|
|
@@ -915,6 +934,7 @@ export const EntityCollectionView = React.memo(
|
|
|
915
934
|
collection={collection}
|
|
916
935
|
tableController={tableController}
|
|
917
936
|
fullPath={fullPath}
|
|
937
|
+
pathSegments={pathSegments}
|
|
918
938
|
parentCollectionIds={parentCollectionIds}
|
|
919
939
|
columnProperty={selectedKanbanProperty}
|
|
920
940
|
onEntityClick={onEntityClick}
|
|
@@ -1045,6 +1065,7 @@ export const EntityCollectionView = React.memo(
|
|
|
1045
1065
|
|
|
1046
1066
|
{popupCell && <PopupFormField
|
|
1047
1067
|
key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`}
|
|
1068
|
+
pathSegments={pathSegments}
|
|
1048
1069
|
open={Boolean(popupCell)}
|
|
1049
1070
|
onClose={onPopupClose}
|
|
1050
1071
|
cellRect={popupCell?.cellRect}
|
|
@@ -1100,13 +1121,16 @@ function EntitiesCount({
|
|
|
1100
1121
|
collection,
|
|
1101
1122
|
filter,
|
|
1102
1123
|
sortBy,
|
|
1103
|
-
onCountChange
|
|
1124
|
+
onCountChange,
|
|
1125
|
+
pathSegments
|
|
1104
1126
|
}: {
|
|
1105
1127
|
fullPath: string,
|
|
1106
1128
|
collection: EntityCollection,
|
|
1107
1129
|
filter?: FilterValues<any>,
|
|
1108
1130
|
sortBy?: [string, "asc" | "desc"],
|
|
1109
1131
|
onCountChange?: (count: number) => void,
|
|
1132
|
+
/** `fullPath` split at its real segment boundaries; forwarded to countEntities. */
|
|
1133
|
+
pathSegments?: string[],
|
|
1110
1134
|
}) {
|
|
1111
1135
|
|
|
1112
1136
|
const dataSource = useDataSource(collection);
|
|
@@ -1117,17 +1141,19 @@ function EntitiesCount({
|
|
|
1117
1141
|
const sortByProperty = sortBy ? sortBy[0] : undefined;
|
|
1118
1142
|
const currentSort = sortBy ? sortBy[1] : undefined;
|
|
1119
1143
|
const resolvedPath = useMemo(() => navigation.resolveIdsFrom(fullPath), [fullPath, navigation.resolveIdsFrom]);
|
|
1144
|
+
const resolvedPathSegments = pathSegments;
|
|
1120
1145
|
|
|
1121
1146
|
useEffect(() => {
|
|
1122
1147
|
if (dataSource.countEntities)
|
|
1123
1148
|
dataSource.countEntities({
|
|
1124
1149
|
path: resolvedPath,
|
|
1150
|
+
pathSegments: resolvedPathSegments,
|
|
1125
1151
|
collection,
|
|
1126
1152
|
filter,
|
|
1127
1153
|
orderBy: sortByProperty,
|
|
1128
1154
|
order: currentSort
|
|
1129
1155
|
}).then(setCount).catch(setError);
|
|
1130
|
-
}, [fullPath, dataSource.countEntities, resolvedPath, collection, filter, sortByProperty, currentSort]);
|
|
1156
|
+
}, [fullPath, dataSource.countEntities, resolvedPath, resolvedPathSegments, collection, filter, sortByProperty, currentSort]);
|
|
1131
1157
|
|
|
1132
1158
|
useEffect(() => {
|
|
1133
1159
|
if (onCountChange && count !== undefined) {
|