@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
|
@@ -135,7 +135,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
135
135
|
const [columns, setColumns] = useState(columnsProp);
|
|
136
136
|
|
|
137
137
|
const tableRef = useRef<HTMLDivElement>(null);
|
|
138
|
-
const
|
|
138
|
+
const lastEndReachedDataLength = useRef<number | undefined>(undefined);
|
|
139
139
|
|
|
140
140
|
const debouncedScroll = useDebounceCallback(onScrollProp, 200);
|
|
141
141
|
|
|
@@ -242,7 +242,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
242
242
|
}, [filterInput]);
|
|
243
243
|
|
|
244
244
|
const scrollToTop = useCallback(() => {
|
|
245
|
-
|
|
245
|
+
lastEndReachedDataLength.current = undefined;
|
|
246
246
|
if (tableRef.current) {
|
|
247
247
|
tableRef.current.scrollTo(tableRef.current?.scrollLeft, 0);
|
|
248
248
|
}
|
|
@@ -278,13 +278,24 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
278
278
|
|
|
279
279
|
const maxScroll = Math.max((data?.length ?? 0) * rowHeight - bounds.height, 0);
|
|
280
280
|
|
|
281
|
-
const onEndReachedInternal = useCallback((
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
const onEndReachedInternal = useCallback(() => {
|
|
282
|
+
const dataLength = data?.length ?? 0;
|
|
283
|
+
|
|
284
|
+
if (onEndReached && dataLength > 0 && lastEndReachedDataLength.current !== dataLength) {
|
|
285
|
+
lastEndReachedDataLength.current = dataLength;
|
|
284
286
|
onEndReached();
|
|
285
287
|
}
|
|
286
288
|
}, [data?.length, onEndReached]);
|
|
287
289
|
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
if (!onEndReached || loading || !data?.length || !bounds.height || !tableRef.current) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (tableRef.current.scrollHeight <= tableRef.current.clientHeight) {
|
|
295
|
+
onEndReachedInternal();
|
|
296
|
+
}
|
|
297
|
+
}, [bounds.height, data?.length, loading, onEndReached, onEndReachedInternal]);
|
|
298
|
+
|
|
288
299
|
const onScroll = useCallback(({
|
|
289
300
|
scrollDirection,
|
|
290
301
|
scrollOffset,
|
|
@@ -302,12 +313,12 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
302
313
|
})
|
|
303
314
|
}
|
|
304
315
|
if (!scrollUpdateWasRequested && (scrollOffset >= maxScroll - endOffset))
|
|
305
|
-
onEndReachedInternal(
|
|
306
|
-
}, [maxScroll, onEndReachedInternal]);
|
|
316
|
+
onEndReachedInternal();
|
|
317
|
+
}, [endOffset, maxScroll, onEndReachedInternal]);
|
|
307
318
|
|
|
308
319
|
const onFilterUpdateInternal = useCallback((column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, any]) => {
|
|
309
320
|
|
|
310
|
-
|
|
321
|
+
lastEndReachedDataLength.current = undefined;
|
|
311
322
|
const filter = filterRef.current;
|
|
312
323
|
let newFilterValue: VirtualTableFilterValues<any> = filter ? { ...filter } : {};
|
|
313
324
|
|
|
@@ -18,6 +18,7 @@ export const editEntityAction: EntityAction = {
|
|
|
18
18
|
entity,
|
|
19
19
|
collection,
|
|
20
20
|
fullPath,
|
|
21
|
+
pathSegments,
|
|
21
22
|
fullIdPath,
|
|
22
23
|
context,
|
|
23
24
|
highlightEntity,
|
|
@@ -41,6 +42,12 @@ export const editEntityAction: EntityAction = {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
const path = collection?.collectionGroup ? entity.path : (fullPath ?? collection?.path ?? entity.path);
|
|
45
|
+
// In a collection group `path` is the entity's own, so the entity's segments describe
|
|
46
|
+
// it. Otherwise the caller's segments win, falling back to the ones the entity was
|
|
47
|
+
// loaded with — both describe the same collection. Never derived from `path`.
|
|
48
|
+
const resolvedPathSegments = collection?.collectionGroup
|
|
49
|
+
? entity.pathSegments
|
|
50
|
+
: (pathSegments ?? entity.pathSegments);
|
|
44
51
|
const newFullIdPath = collection?.collectionGroup ? collection.id : (fullIdPath ?? collection?.id ?? entity.path);
|
|
45
52
|
const defaultSelectedView = resolveDefaultSelectedView(
|
|
46
53
|
collection ? collection.defaultSelectedView : undefined,
|
|
@@ -54,6 +61,7 @@ export const editEntityAction: EntityAction = {
|
|
|
54
61
|
collection,
|
|
55
62
|
entityId: entity.id,
|
|
56
63
|
path,
|
|
64
|
+
pathSegments: resolvedPathSegments,
|
|
57
65
|
fullIdPath: newFullIdPath,
|
|
58
66
|
sideEntityController: context.sideEntityController,
|
|
59
67
|
onClose: () => unhighlightEntity?.(entity),
|
|
@@ -75,6 +83,8 @@ export const copyEntityAction: EntityAction = {
|
|
|
75
83
|
collection,
|
|
76
84
|
context,
|
|
77
85
|
fullPath,
|
|
86
|
+
pathSegments,
|
|
87
|
+
fullIdPath,
|
|
78
88
|
highlightEntity,
|
|
79
89
|
unhighlightEntity,
|
|
80
90
|
openEntityMode
|
|
@@ -89,13 +99,23 @@ export const copyEntityAction: EntityAction = {
|
|
|
89
99
|
});
|
|
90
100
|
|
|
91
101
|
const path = collection?.collectionGroup ? collection.path : (fullPath ?? collection?.path ?? entity.path);
|
|
92
|
-
|
|
102
|
+
// In a collection group `path` is the group's own root, which the entity's segments
|
|
103
|
+
// (its real nested location) do not describe — so they are deliberately not reused,
|
|
104
|
+
// and no substitute is invented.
|
|
105
|
+
const resolvedPathSegments = collection?.collectionGroup
|
|
106
|
+
? undefined
|
|
107
|
+
: (pathSegments ?? entity.pathSegments);
|
|
108
|
+
// `fullIdPath` is the ESCAPED chain and is what becomes the URL; `fullPath` is the raw
|
|
109
|
+
// datasource path, whose parent ids may contain "/". Using the latter here wrote a URL
|
|
110
|
+
// that read back as a different entity. Mirrors editEntityAction.
|
|
111
|
+
const newFullIdPath = collection?.collectionGroup ? collection.id : (fullIdPath ?? collection?.id);
|
|
93
112
|
navigateToEntity({
|
|
94
113
|
openEntityMode,
|
|
95
114
|
collection,
|
|
96
115
|
entityId: entity.id,
|
|
97
116
|
path,
|
|
98
|
-
|
|
117
|
+
pathSegments: resolvedPathSegments,
|
|
118
|
+
fullIdPath: newFullIdPath,
|
|
99
119
|
copy: true,
|
|
100
120
|
sideEntityController: context.sideEntityController,
|
|
101
121
|
onClose: () => unhighlightEntity?.(entity),
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { EntityReference, FilterValues, WhereFilterOp } from "../../types";
|
|
2
|
+
import { decodeEntityId, encodeEntityId } from "../../util/navigation_utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal helpers used by `useDataSourceTableController` to persist the state of a
|
|
6
|
+
* collection table (filters, sort and text search) in the URL query string.
|
|
7
|
+
*
|
|
8
|
+
* These functions are intentionally NOT re-exported by the package barrel files:
|
|
9
|
+
* they are pure and live in their own module only so that they can be unit tested.
|
|
10
|
+
* `encodeFilterAndSort` and `parseFilterAndSort` must stay exact inverses of each
|
|
11
|
+
* other, otherwise state written to the URL is silently dropped on reload.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Serialise the state of a collection table into a URL query string,
|
|
16
|
+
* without the leading `?`. Returns an empty string when there is no state to persist.
|
|
17
|
+
*/
|
|
18
|
+
export function encodeFilterAndSort(filterValues?: FilterValues<string>,
|
|
19
|
+
sortBy?: [string, "asc" | "desc"] | undefined,
|
|
20
|
+
searchString?: string) {
|
|
21
|
+
const entries: Record<string, string> = {};
|
|
22
|
+
if (sortBy) {
|
|
23
|
+
entries["__sort"] = encodeURIComponent(sortBy[0]);
|
|
24
|
+
entries["__sort_order"] = encodeURIComponent(sortBy[1]);
|
|
25
|
+
}
|
|
26
|
+
if (filterValues) {
|
|
27
|
+
Object.entries(filterValues).forEach(([key, value]) => {
|
|
28
|
+
if (value) {
|
|
29
|
+
const [op, val] = value;
|
|
30
|
+
let encodedValue: any = val;
|
|
31
|
+
try {
|
|
32
|
+
if (typeof val === "object") {
|
|
33
|
+
if (val instanceof Date) {
|
|
34
|
+
encodedValue = val.toISOString();
|
|
35
|
+
} else if (Array.isArray(val)) {
|
|
36
|
+
encodedValue = JSON.stringify(val, (key, value) => {
|
|
37
|
+
if (value instanceof EntityReference) {
|
|
38
|
+
return encodeRef(value);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
});
|
|
42
|
+
} else if (val instanceof EntityReference) {
|
|
43
|
+
encodedValue = encodeRef(val);
|
|
44
|
+
}
|
|
45
|
+
} else if (typeof val === "string") {
|
|
46
|
+
// JSON.stringify wraps the string in quotes (e.g. "4" → '"4"')
|
|
47
|
+
// so that decodeString's JSON.parse restores the string type,
|
|
48
|
+
// not a number. Without this, "4" round-trips as the number 4.
|
|
49
|
+
encodedValue = JSON.stringify(val);
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
encodedValue = val;
|
|
53
|
+
}
|
|
54
|
+
if (encodedValue !== undefined) {
|
|
55
|
+
entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
|
|
56
|
+
// Note: check for null/undefined explicitly instead of truthiness,
|
|
57
|
+
// otherwise falsy-but-valid values (boolean `false`, number `0`)
|
|
58
|
+
// would be serialized as "null" and round-trip back as `null`,
|
|
59
|
+
// breaking filters like `archived == false`.
|
|
60
|
+
entries[encodeURIComponent(`${key}_value`)] = encodedValue !== null && encodedValue !== undefined
|
|
61
|
+
? encodeURIComponent(encodedValue.toString())
|
|
62
|
+
: "null";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// The text search term is encoded once here; `parseFilterAndSort` reads it back with
|
|
68
|
+
// `URLSearchParams`, which decodes exactly once. Do NOT decode it a second time or
|
|
69
|
+
// terms containing a literal `%` (e.g. "50%") would throw a URIError.
|
|
70
|
+
if (searchString) {
|
|
71
|
+
entries["search"] = encodeURIComponent(searchString);
|
|
72
|
+
}
|
|
73
|
+
if (!Object.keys(entries).length) {
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
return Object.entries(entries).map(([key, value]) => `${key}=${value}`).join("&");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Restore the state of a collection table from a URL query string.
|
|
81
|
+
* The inverse of {@link encodeFilterAndSort}.
|
|
82
|
+
*/
|
|
83
|
+
export function parseFilterAndSort<M>(search: string): {
|
|
84
|
+
filterValues: FilterValues<string> | undefined,
|
|
85
|
+
sortBy?: [Extract<keyof M, string>, "asc" | "desc"],
|
|
86
|
+
searchString?: string
|
|
87
|
+
} {
|
|
88
|
+
const entries = new URLSearchParams(search);
|
|
89
|
+
const filterValues: FilterValues<string> = {};
|
|
90
|
+
let sortBy: [string, "asc" | "desc"] | undefined = undefined;
|
|
91
|
+
entries.forEach((value, key) => {
|
|
92
|
+
if (key === "__sort") {
|
|
93
|
+
sortBy = [decodeURIComponent(value), entries.get("__sort_order") as "asc" | "desc"];
|
|
94
|
+
} else if (key.endsWith("_op")) {
|
|
95
|
+
const field = key.replace("_op", "");
|
|
96
|
+
const filterOp = decodeURIComponent(value) as WhereFilterOp;
|
|
97
|
+
const filterValStr = entries.get(`${field}_value`);
|
|
98
|
+
if (filterValStr !== null) {
|
|
99
|
+
filterValues[field] = [filterOp, decodeString(filterValStr)];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// `URLSearchParams` has already percent-decoded the value, so it is used as is.
|
|
105
|
+
const searchString = entries.get("search") ?? undefined;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
filterValues: Object.keys(filterValues).length ? filterValues : undefined,
|
|
109
|
+
sortBy,
|
|
110
|
+
searchString: searchString ? searchString : undefined
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isDate(dateString: string): boolean {
|
|
115
|
+
// Define a regex pattern that matches the exact date format: 2025-01-07T23:00:00.000Z
|
|
116
|
+
const regexPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
117
|
+
|
|
118
|
+
// Test the dateString against the regex pattern
|
|
119
|
+
if (!regexPattern.test(dateString)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// If the regex matches, further validate if it is a valid UTC date
|
|
124
|
+
const date = new Date(dateString);
|
|
125
|
+
return date.toISOString() === dateString;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function encodeRef(val: EntityReference) {
|
|
129
|
+
return `ref::${val.path}/${encodeEntityId(val.id)}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Split a "path/id" reference on its LAST separator: the path may itself be a
|
|
134
|
+
* subcollection path with several segments, and the id is escaped so it is always exactly
|
|
135
|
+
* one segment.
|
|
136
|
+
*/
|
|
137
|
+
function decodeRef(encoded: string): EntityReference {
|
|
138
|
+
const separatorIndex = encoded.lastIndexOf("/");
|
|
139
|
+
if (separatorIndex < 0) return new EntityReference(encoded, "");
|
|
140
|
+
return new EntityReference(
|
|
141
|
+
decodeEntityId(encoded.substring(separatorIndex + 1)),
|
|
142
|
+
encoded.substring(0, separatorIndex)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function decodeString(val: string): EntityReference | Date | string {
|
|
147
|
+
let parsedFilterVal: any = val;
|
|
148
|
+
if (isDate(val)) {
|
|
149
|
+
try {
|
|
150
|
+
parsedFilterVal = new Date(val);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// ignore
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (typeof parsedFilterVal === "string") {
|
|
156
|
+
try {
|
|
157
|
+
parsedFilterVal = JSON.parse(parsedFilterVal, (key, value) => {
|
|
158
|
+
if (typeof value === "string" && value.startsWith("ref::")) {
|
|
159
|
+
return decodeRef(value.substring(5));
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
});
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// ignore
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (typeof parsedFilterVal === "string" && parsedFilterVal.startsWith("ref::")) {
|
|
169
|
+
return decodeRef(parsedFilterVal.substring(5));
|
|
170
|
+
}
|
|
171
|
+
return parsedFilterVal;
|
|
172
|
+
}
|