@firecms/core 3.3.0-canary.3ea2cd2 → 3.3.0-canary.42ba35b

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.
Files changed (70) hide show
  1. package/dist/app/useApp.d.ts +1 -0
  2. package/dist/components/EntityCollectionTable/column_utils.d.ts +4 -3
  3. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
  4. package/dist/components/EntityPreview.d.ts +3 -1
  5. package/dist/core/DefaultDrawer.d.ts +10 -3
  6. package/dist/core/field_configs.d.ts +1 -1
  7. package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
  8. package/dist/form/index.d.ts +1 -0
  9. package/dist/index.es.js +2090 -743
  10. package/dist/index.es.js.map +1 -1
  11. package/dist/index.umd.js +2089 -742
  12. package/dist/index.umd.js.map +1 -1
  13. package/dist/locales/pl.d.ts +2 -0
  14. package/dist/preview/index.d.ts +1 -0
  15. package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
  16. package/dist/types/collections.d.ts +17 -0
  17. package/dist/types/translations.d.ts +9 -1
  18. package/dist/util/entities.d.ts +1 -0
  19. package/dist/util/geopoint.d.ts +22 -0
  20. package/dist/util/index.d.ts +1 -0
  21. package/dist/util/navigation_blocking.d.ts +22 -0
  22. package/dist/util/navigation_from_path.d.ts +10 -0
  23. package/dist/util/navigation_utils.d.ts +20 -0
  24. package/package.json +16 -9
  25. package/src/app/Scaffold.tsx +47 -45
  26. package/src/app/useApp.tsx +1 -0
  27. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +2 -1
  28. package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
  29. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
  30. package/src/components/EntityCollectionView/EntityCollectionView.tsx +5 -2
  31. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -1
  32. package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
  33. package/src/components/EntityPreview.tsx +41 -40
  34. package/src/components/ReferenceWidget.tsx +1 -1
  35. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
  36. package/src/components/VirtualTable/VirtualTable.tsx +19 -8
  37. package/src/components/common/useDataSourceTableController.tsx +66 -10
  38. package/src/core/DefaultDrawer.tsx +150 -64
  39. package/src/core/DrawerNavigationGroup.tsx +27 -29
  40. package/src/core/DrawerNavigationItem.tsx +10 -12
  41. package/src/core/EntityEditView.tsx +57 -32
  42. package/src/core/field_configs.tsx +15 -0
  43. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
  44. package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
  45. package/src/form/index.tsx +1 -0
  46. package/src/hooks/useBuildNavigationController.tsx +5 -1
  47. package/src/i18n/FireCMSi18nProvider.tsx +2 -0
  48. package/src/internal/useBuildSideEntityController.tsx +2 -1
  49. package/src/locales/de.ts +11 -3
  50. package/src/locales/en.ts +11 -3
  51. package/src/locales/es.ts +11 -3
  52. package/src/locales/fr.ts +11 -3
  53. package/src/locales/hi.ts +11 -3
  54. package/src/locales/it.ts +11 -3
  55. package/src/locales/pl.ts +730 -0
  56. package/src/locales/pt.ts +11 -3
  57. package/src/preview/PropertyPreview.tsx +12 -0
  58. package/src/preview/index.ts +1 -0
  59. package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
  60. package/src/routes/FireCMSRoute.tsx +44 -25
  61. package/src/types/collections.ts +18 -0
  62. package/src/types/translations.ts +9 -1
  63. package/src/util/entities.ts +13 -0
  64. package/src/util/geopoint.ts +81 -0
  65. package/src/util/index.ts +1 -0
  66. package/src/util/navigation_blocking.ts +45 -0
  67. package/src/util/navigation_from_path.ts +23 -6
  68. package/src/util/navigation_utils.ts +36 -2
  69. package/src/util/parent_references_from_path.ts +4 -2
  70. package/src/util/resolutions.ts +3 -0
@@ -12,7 +12,6 @@ import {
12
12
  DialogActions,
13
13
  DialogContent,
14
14
  DialogTitle,
15
- FilterListIcon,
16
15
  Typography
17
16
  } from "@firecms/ui";
18
17
  import { useTranslation } from "../../hooks/useTranslation";
@@ -30,6 +29,7 @@ export interface FiltersDialogProps {
30
29
  filterValues: FilterValues<any> | undefined;
31
30
  setFilterValues: (filterValues?: FilterValues<any>) => void;
32
31
  forceFilter?: FilterValues<any>;
32
+ allowedFilters?: string[];
33
33
  }
34
34
 
35
35
  /**
@@ -42,7 +42,8 @@ export function FiltersDialog({
42
42
  properties,
43
43
  filterValues,
44
44
  setFilterValues,
45
- forceFilter
45
+ forceFilter,
46
+ allowedFilters
46
47
  }: FiltersDialogProps) {
47
48
  const { t } = useTranslation();
48
49
 
@@ -59,18 +60,18 @@ export function FiltersDialog({
59
60
  }
60
61
  }, [open, filterValues]);
61
62
 
62
- // Get list of filterable properties
63
- const filterableProperties = useMemo(() => {
64
- return Object.entries(properties).filter(([key, property]) => {
65
- if (!property) return false;
66
- // Force filter properties should not be editable
67
- if (forceFilter && key in forceFilter) return false;
68
- // Check if property type is filterable
69
- const baseProperty = property.dataType === "array" ? property.of : property;
70
- if (!baseProperty) return false;
71
- return ["string", "number", "boolean", "date", "reference"].includes(baseProperty.dataType);
63
+ // Get list of editable filter properties
64
+ const editableFilterProperties = useMemo(() => {
65
+ return Object.entries(properties).filter(([key]) => {
66
+ const isFilterAllowed = !allowedFilters || allowedFilters.includes(key);
67
+
68
+ const isFilterForced = Boolean(forceFilter && Object.keys(forceFilter).includes(key));
69
+
70
+ return isFilterAllowed && !isFilterForced;
72
71
  });
73
- }, [properties, forceFilter]);
72
+ }, [properties, allowedFilters, forceFilter]);
73
+
74
+ const hasEditableFilterProperties = editableFilterProperties.length > 0;
74
75
 
75
76
  const handleFilterChange = useCallback((propertyKey: string, value?: [VirtualTableWhereFilterOp, any]) => {
76
77
  setLocalFilters(prev => {
@@ -103,7 +104,13 @@ export function FiltersDialog({
103
104
 
104
105
  // Check if any reference field's dialog is currently open (should hide this dialog)
105
106
  const isAnyFieldHidden = Object.values(hiddenFields).some(hidden => hidden);
106
- const activeFilterCount = Object.keys(localFilters).length;
107
+
108
+ const getActiveFilterCount = () => {
109
+ const editableLocalFilters = Object.keys(localFilters).filter((key) => !forceFilter || !(key in forceFilter));
110
+ return editableLocalFilters.length;
111
+ }
112
+
113
+ const activeFilterCount = getActiveFilterCount();
107
114
 
108
115
  const renderFilterField = useCallback((propertyKey: string, property: ResolvedProperty) => {
109
116
  const isArray = property.dataType === "array";
@@ -185,14 +192,14 @@ export function FiltersDialog({
185
192
  </DialogTitle>
186
193
 
187
194
  <DialogContent >
188
- {filterableProperties.length === 0 ? (
195
+ {!hasEditableFilterProperties ? (
189
196
  <Typography color="secondary" className="py-8 text-center">
190
197
  {t("no_filterable_properties")}
191
198
  </Typography>
192
199
  ) : (
193
200
  <table className="w-full border-collapse">
194
201
  <tbody>
195
- {filterableProperties.map(([propertyKey, property], index) => {
202
+ {editableFilterProperties.map(([propertyKey, property], index) => {
196
203
  const hasFilter = propertyKey in localFilters;
197
204
 
198
205
  return (
@@ -234,18 +241,22 @@ export function FiltersDialog({
234
241
  {t("clear")}
235
242
  </Button>
236
243
  <div className="flex-grow" />
237
- <Button
238
- variant="text"
239
- onClick={() => onOpenChange(false)}
240
- >
241
- {t("cancel")}
242
- </Button>
243
- <Button
244
- variant="filled"
245
- onClick={handleApply}
246
- >
247
- {t("apply_filters")}
248
- </Button>
244
+ {hasEditableFilterProperties && (
245
+ <>
246
+ <Button
247
+ variant="text"
248
+ onClick={() => onOpenChange(false)}
249
+ >
250
+ {t("cancel")}
251
+ </Button>
252
+ <Button
253
+ variant="filled"
254
+ onClick={handleApply}
255
+ >
256
+ {t("apply_filters")}
257
+ </Button>
258
+ </>
259
+ )}
249
260
  </DialogActions>
250
261
  </Dialog>
251
262
  );
@@ -200,43 +200,44 @@ export type EntityPreviewContainerProps = {
200
200
  onClick?: (e: React.SyntheticEvent) => void;
201
201
  };
202
202
 
203
- export const EntityPreviewContainer = React.forwardRef<HTMLDivElement, EntityPreviewContainerProps>(({
204
- children,
205
- hover,
206
- onClick,
207
- size = "medium",
208
- style,
209
- className,
210
- fullwidth = true,
211
- ...props
212
- }, ref) => {
213
- return <div
214
- ref={ref}
215
- style={{
216
- ...style,
217
- // @ts-ignore
218
- tabindex: 0
219
- }}
220
- className={cls(
221
- "bg-white dark:bg-surface-900",
222
- "min-h-[44px]",
223
- fullwidth ? "w-full" : "",
224
- "items-center",
225
- hover ? "hover:bg-surface-accent-50 dark:hover:bg-surface-800 group-hover:bg-surface-accent-50 dark:group-hover:bg-surface-800" : "",
226
- size === "small" ? "p-1" : "px-2 py-1",
227
- "flex border rounded-lg",
228
- onClick ? "cursor-pointer" : "",
229
- defaultBorderMixin,
230
- className)}
231
- onClick={(event) => {
232
- if (onClick) {
233
- event.preventDefault();
234
- onClick(event);
235
- }
236
- }}
237
- {...props}>
238
- {children}
239
- </div>;
240
- });
241
-
242
- EntityPreviewContainer.displayName = "EntityPreviewContainer";
203
+ export function EntityPreviewContainer({
204
+ children,
205
+ hover,
206
+ onClick,
207
+ size = "medium",
208
+ style,
209
+ className,
210
+ fullwidth = true,
211
+ ref
212
+ }: EntityPreviewContainerProps & { ref?: React.Ref<HTMLDivElement> }) {
213
+ const divClassName = cls(
214
+ "bg-white dark:bg-surface-900",
215
+ "min-h-[44px]",
216
+ fullwidth ? "w-full" : "",
217
+ "items-center",
218
+ hover ? "hover:bg-surface-accent-50 dark:hover:bg-surface-800 group-hover:bg-surface-accent-50 dark:group-hover:bg-surface-800" : "",
219
+ size === "small" ? "p-1" : "px-2 py-1",
220
+ "flex border rounded-lg",
221
+ onClick ? "cursor-pointer" : "",
222
+ defaultBorderMixin,
223
+ className
224
+ );
225
+
226
+ const handleClick = onClick
227
+ ? (event: React.MouseEvent<HTMLDivElement>) => {
228
+ event.preventDefault();
229
+ onClick(event);
230
+ }
231
+ : undefined;
232
+
233
+ const divProps: React.HTMLAttributes<HTMLDivElement> & { ref?: React.Ref<HTMLDivElement> } = {
234
+ ref,
235
+ tabIndex: 0,
236
+ style,
237
+ className: divClassName,
238
+ onClick: handleClick,
239
+ };
240
+
241
+ return <div {...divProps}>{children}</div>;
242
+ }
243
+
@@ -73,7 +73,7 @@ export function ReferenceWidget<M extends Record<string, any>>({
73
73
  if (disabled)
74
74
  return;
75
75
  if (onMultipleReferenceSelected) {
76
- const references = entities ? entities.map(e => getReferenceFrom(e)) : null;
76
+ const references = entities ? entities.filter(Boolean).map(e => getReferenceFrom(e)) : null;
77
77
  onMultipleReferenceSelected({
78
78
  references,
79
79
  entities
@@ -113,11 +113,11 @@ export function ReferenceFilterField({
113
113
  }, [path]);
114
114
 
115
115
  const onSingleEntitySelected = (entity: Entity<any>) => {
116
- updateFilter(operation, getReferenceFrom(entity));
116
+ if (entity) updateFilter(operation, getReferenceFrom(entity));
117
117
  };
118
118
 
119
119
  const onMultipleEntitiesSelected = (entities: Entity<any>[]) => {
120
- updateFilter(operation, entities.map(e => getReferenceFrom(e)));
120
+ updateFilter(operation, entities.filter(Boolean).map(e => getReferenceFrom(e)));
121
121
  };
122
122
 
123
123
  const multiple = multipleSelectOperations.includes(operation);
@@ -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 endReachCallbackThreshold = useRef<number>(0);
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
- endReachCallbackThreshold.current = 0;
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((scrollOffset: number) => {
282
- if (onEndReached && (data?.length ?? 0) > 0 && scrollOffset > endReachCallbackThreshold.current + endOffset) {
283
- endReachCallbackThreshold.current = scrollOffset;
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(scrollOffset);
306
- }, [maxScroll, onEndReachedInternal]);
316
+ onEndReachedInternal();
317
+ }, [endOffset, maxScroll, onEndReachedInternal]);
307
318
 
308
319
  const onFilterUpdateInternal = useCallback((column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, any]) => {
309
320
 
310
- endReachCallbackThreshold.current = 0;
321
+ lastEndReachedDataLength.current = undefined;
311
322
  const filter = filterRef.current;
312
323
  let newFilterValue: VirtualTableFilterValues<any> = filter ? { ...filter } : {};
313
324
 
@@ -4,6 +4,7 @@ import { useLocation } from "react-router-dom";
4
4
  import { useDataSource, useFireCMSContext, useNavigationController } from "../../hooks";
5
5
  import { useDataOrder } from "../../hooks/data/useDataOrder";
6
6
  import {
7
+ DataType,
7
8
  Entity,
8
9
  EntityCollection,
9
10
  EntityReference,
@@ -16,6 +17,8 @@ import {
16
17
  } from "../../types";
17
18
  import { useDebouncedData } from "./useDebouncedData";
18
19
  import { ScrollRestorationController } from "./useScrollRestoration";
20
+ import { isDataTypeFilterable } from "../../util";
21
+ import { decodeEntityId, encodeEntityId } from "../../util/navigation_utils";
19
22
 
20
23
  const DEFAULT_PAGE_SIZE = 50;
21
24
 
@@ -135,8 +138,41 @@ export function useDataSourceTableController<M extends Record<string, any> = any
135
138
  filterValues: initialFilterUrl,
136
139
  sortBy: initialSortUrl,
137
140
  } = parseFilterAndSort(location.search);
141
+
142
+ const availableFilterKeys = collection.allowedFilters ?? Object.keys(collection.properties);
143
+ const forcedFilterKeys = collection.forceFilter ? Object.keys(collection.forceFilter) : [];
138
144
 
139
- const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? (updateUrl ? initialFilterUrl : undefined) ?? initialFilter ?? undefined);
145
+ const allowedFilterKeys = useMemo(() => {
146
+ const availableKeys = availableFilterKeys.filter((key) => {
147
+ const property = collection.properties[key];
148
+
149
+ if (!property) return false;
150
+
151
+ if (typeof property === "function") return false;
152
+
153
+ const dataType = property.dataType;
154
+ const filterable = dataType === "array"
155
+ ? isDataTypeFilterable((property as { of?: { dataType: DataType } }).of?.dataType as DataType, true)
156
+ : isDataTypeFilterable(dataType);
157
+
158
+ return filterable;
159
+ });
160
+
161
+ const forcedKeys = forcedFilterKeys.filter((key) => !availableKeys.includes(key));
162
+
163
+ return [...availableKeys, ...forcedKeys];
164
+
165
+ }, [collection.properties, availableFilterKeys, forcedFilterKeys]);
166
+
167
+ const removeUnallowedFilters = useCallback((filters?: FilterValues<Extract<keyof M, string>>) => {
168
+ if (!filters) return;
169
+
170
+ return Object.fromEntries(Object.entries(filters).filter(([key]) => allowedFilterKeys.includes(key as keyof M))) as FilterValues<Extract<keyof M, string>>;
171
+ }, [allowedFilterKeys]);
172
+
173
+ const initFilters = forceFilter ?? (updateUrl ? initialFilterUrl : undefined) ?? initialFilter ?? undefined
174
+
175
+ const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(removeUnallowedFilters(initFilters));
140
176
  const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>((updateUrl ? initialSortUrl : undefined) ?? initialSortInternal);
141
177
 
142
178
  useUpdateUrl(filterValues, sortBy, searchString, updateUrl);
@@ -168,7 +204,7 @@ export function useDataSourceTableController<M extends Record<string, any> = any
168
204
  const [dataLoadingError, setDataLoadingError] = useState<Error | undefined>();
169
205
  const [noMoreToLoad, setNoMoreToLoad] = useState<boolean>(false);
170
206
 
171
- const clearFilter = useCallback(() => setFilterValues(forceFilter ?? undefined), [forceFilter]);
207
+ const clearFilter = useCallback(() => setFilterValues(removeUnallowedFilters(forceFilter)), [forceFilter, removeUnallowedFilters]);
172
208
 
173
209
  const updateFilterValues = useCallback((updatedFilter: FilterValues<Extract<keyof M, string>> | undefined) => {
174
210
  if (forceFilter) {
@@ -178,9 +214,9 @@ export function useDataSourceTableController<M extends Record<string, any> = any
178
214
  if (updatedFilter && Object.keys(updatedFilter).length === 0) {
179
215
  setFilterValues(undefined);
180
216
  } else {
181
- setFilterValues(updatedFilter);
217
+ setFilterValues(removeUnallowedFilters(updatedFilter));
182
218
  }
183
- }, [forceFilter]);
219
+ }, [forceFilter, removeUnallowedFilters]);
184
220
 
185
221
  useEffect(() => {
186
222
 
@@ -268,6 +304,8 @@ export function useDataSourceTableController<M extends Record<string, any> = any
268
304
  dataLoadingError,
269
305
  filterValues,
270
306
  setFilterValues: updateFilterValues,
307
+ allowedFilters: allowedFilterKeys,
308
+ forcedFilters: forcedFilterKeys,
271
309
  sortBy,
272
310
  setSortBy,
273
311
  searchString,
@@ -342,7 +380,13 @@ function encodeFilterAndSort(filterValues?: FilterValues<string>, sortBy?: [stri
342
380
  }
343
381
  if (encodedValue !== undefined) {
344
382
  entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(op);
345
- entries[encodeURIComponent(`${key}_value`)] = encodedValue ? encodeURIComponent(encodedValue.toString()) : "null";
383
+ // Note: check for null/undefined explicitly instead of truthiness,
384
+ // otherwise falsy-but-valid values (boolean `false`, number `0`)
385
+ // would be serialized as "null" and round-trip back as `null`,
386
+ // breaking filters like `archived == false`.
387
+ entries[encodeURIComponent(`${key}_value`)] = encodedValue !== null && encodedValue !== undefined
388
+ ? encodeURIComponent(encodedValue.toString())
389
+ : "null";
346
390
  }
347
391
  }
348
392
  });
@@ -394,7 +438,21 @@ function isDate(dateString: string): boolean {
394
438
  }
395
439
 
396
440
  function encodeRef(val: EntityReference) {
397
- return `ref::${val.path}/${val.id}`;
441
+ return `ref::${val.path}/${encodeEntityId(val.id)}`;
442
+ }
443
+
444
+ /**
445
+ * Split a "path/id" reference on its LAST separator: the path may itself be a
446
+ * subcollection path with several segments, and the id is escaped so it is always exactly
447
+ * one segment.
448
+ */
449
+ function decodeRef(encoded: string): EntityReference {
450
+ const separatorIndex = encoded.lastIndexOf("/");
451
+ if (separatorIndex < 0) return new EntityReference(encoded, "");
452
+ return new EntityReference(
453
+ decodeEntityId(encoded.substring(separatorIndex + 1)),
454
+ encoded.substring(0, separatorIndex)
455
+ );
398
456
  }
399
457
 
400
458
  function decodeString(val: string): EntityReference | Date | string {
@@ -410,8 +468,7 @@ function decodeString(val: string): EntityReference | Date | string {
410
468
  try {
411
469
  parsedFilterVal = JSON.parse(parsedFilterVal, (key, value) => {
412
470
  if (typeof value === "string" && value.startsWith("ref::")) {
413
- const [path, id] = value.substring(5).split("/");
414
- return new EntityReference(id, path);
471
+ return decodeRef(value.substring(5));
415
472
  }
416
473
  return value;
417
474
  });
@@ -421,8 +478,7 @@ function decodeString(val: string): EntityReference | Date | string {
421
478
  }
422
479
 
423
480
  if (typeof parsedFilterVal === "string" && parsedFilterVal.startsWith("ref::")) {
424
- const [path, id] = parsedFilterVal.substring(5).split("/");
425
- return new EntityReference(id, path);
481
+ return decodeRef(parsedFilterVal.substring(5));
426
482
  }
427
483
  return parsedFilterVal;
428
484
  }