@firecms/core 3.0.0-canary.40 → 3.0.0-canary.41

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.
@@ -47,7 +47,7 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
47
47
  * Get the collection configuration for a given path.
48
48
  * The collection is resolved from the given path or alias.
49
49
  */
50
- getCollection: (pathOrAlias: string, entityId?: string, includeUserOverride?: boolean) => EC | undefined;
50
+ getCollection: (pathOrId: string, entityId?: string, includeUserOverride?: boolean) => EC | undefined;
51
51
  /**
52
52
  * Get the collection configuration from its parent path segments.
53
53
  */
@@ -8,10 +8,10 @@ export declare function resolveCollectionPathIds(path: string, allCollections: E
8
8
  /**
9
9
  * Find the corresponding view at any depth for a given path.
10
10
  * Note that path or segments of the paths can be collection aliases.
11
- * @param pathOrAlias
11
+ * @param pathOrId
12
12
  * @param collections
13
13
  */
14
- export declare function getCollectionByPathOrId(pathOrAlias: string, collections: EntityCollection[]): EntityCollection | undefined;
14
+ export declare function getCollectionByPathOrId(pathOrId: string, collections: EntityCollection[]): EntityCollection | undefined;
15
15
  /**
16
16
  * Get the subcollection combinations from a path:
17
17
  * "sites/es/locales" => ["sites/es/locales", "sites"]
@@ -2,11 +2,11 @@ import { ArrayProperty, CMSType, EntityCollection, EntityCustomView, EntityValue
2
2
  export declare const resolveCollection: <M extends Record<string, any>>({ collection, path, entityId, values, previousValues, userConfigPersistence, fields }: {
3
3
  collection: EntityCollection<M> | ResolvedEntityCollection<M>;
4
4
  path: string;
5
- entityId?: string | undefined;
6
- values?: Partial<M> | undefined;
7
- previousValues?: Partial<M> | undefined;
8
- userConfigPersistence?: UserConfigurationPersistence | undefined;
9
- fields?: Record<string, PropertyConfig> | undefined;
5
+ entityId?: string;
6
+ values?: Partial<EntityValues<M>>;
7
+ previousValues?: Partial<EntityValues<M>>;
8
+ userConfigPersistence?: UserConfigurationPersistence;
9
+ fields?: Record<string, PropertyConfig>;
10
10
  }) => ResolvedEntityCollection<M>;
11
11
  /**
12
12
  * Resolve property builders, enums and arrays.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.40",
4
+ "version": "3.0.0-canary.41",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -46,17 +46,20 @@
46
46
  "./package.json": "./package.json"
47
47
  },
48
48
  "dependencies": {
49
- "@firecms/formex": "^3.0.0-canary.40",
50
- "@firecms/ui": "^3.0.0-canary.40",
49
+ "@firecms/formex": "^3.0.0-canary.41",
50
+ "@firecms/ui": "^3.0.0-canary.41",
51
51
  "@fontsource/jetbrains-mono": "^5.0.19",
52
- "@fontsource/roboto": "^5.0.12",
53
52
  "@hello-pangea/dnd": "^16.5.0",
53
+ "@radix-ui/react-portal": "^1.0.4",
54
+ "clsx": "^2.1.0",
54
55
  "date-fns": "^3.6.0",
55
56
  "history": "^5.3.0",
56
57
  "js-search": "^2.0.1",
57
58
  "markdown-it": "^14.1.0",
58
59
  "notistack": "^3.0.1",
59
60
  "object-hash": "^3.0.0",
61
+ "react-dropzone": "^14.2.3",
62
+ "react-fast-compare": "^3.2.2",
60
63
  "react-image-file-resizer": "^0.4.8",
61
64
  "react-markdown-editor-lite": "^1.3.4",
62
65
  "react-transition-group": "^4.4.5",
@@ -115,7 +118,7 @@
115
118
  "dist",
116
119
  "src"
117
120
  ],
118
- "gitHead": "40b01721ac260b6cce7f5d8a3a1d768410e7f20d",
121
+ "gitHead": "d1ffa185f8930bab4e7b9941ba551c53f947aa1f",
119
122
  "publishConfig": {
120
123
  "access": "public"
121
124
  }
@@ -74,7 +74,6 @@ import { DeleteEntityDialog } from "../DeleteEntityDialog";
74
74
  import { useAnalyticsController } from "../../hooks/useAnalyticsController";
75
75
  import { useSelectionController } from "./useSelectionController";
76
76
  import { EntityCollectionViewStartActions } from "./EntityCollectionViewStartActions";
77
- import { ClearFilterSortButton } from "../ClearFilterSortButton";
78
77
 
79
78
  const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
80
79
 
@@ -82,8 +81,18 @@ const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
82
81
  * @group Components
83
82
  */
84
83
  export type EntityCollectionViewProps<M extends Record<string, any>> = {
85
- fullPath: string;
84
+ /**
85
+ * Complete path where this collection is located.
86
+ * It defaults to the collection path if not provided.
87
+ */
88
+ fullPath?: string;
89
+ /**
90
+ * If this is a subcollection, specify the parent collection ids.
91
+ */
86
92
  parentCollectionIds?: string[];
93
+ /**
94
+ * Whether this is a subcollection or not.
95
+ */
87
96
  isSubCollection?: boolean;
88
97
  className?: string;
89
98
  } & EntityCollection<M>;
@@ -114,7 +123,7 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
114
123
  */
115
124
  export const EntityCollectionView = React.memo(
116
125
  function EntityCollectionView<M extends Record<string, any>>({
117
- fullPath,
126
+ fullPath: fullPathProp,
118
127
  parentCollectionIds,
119
128
  isSubCollection,
120
129
  className,
@@ -122,6 +131,7 @@ export const EntityCollectionView = React.memo(
122
131
  }: EntityCollectionViewProps<M>
123
132
  ) {
124
133
 
134
+ const fullPath = fullPathProp ?? collectionProp.path;
125
135
  const dataSource = useDataSource(collectionProp);
126
136
  const navigation = useNavigationController();
127
137
  const sideEntityController = useSideEntityController();
@@ -176,7 +186,6 @@ export const EntityCollectionView = React.memo(
176
186
  const usedSelectionController = collection.selectionController ?? selectionController;
177
187
  const {
178
188
  selectedEntities,
179
- toggleEntitySelection,
180
189
  isEntitySelected,
181
190
  setSelectedEntities
182
191
  } = usedSelectionController;
@@ -58,7 +58,7 @@ export function EntityView<M extends Record<string, any>>(
58
58
  </div>
59
59
  {Object.entries(properties)
60
60
  .map(([key, property]) => {
61
- const value = (entity.values)[key];
61
+ const value = entity.values?.[key];
62
62
  return (
63
63
  <div
64
64
  key={`reference_previews_${key}`}
@@ -20,7 +20,7 @@ import { VirtualTableContextProps } from "./types";
20
20
  import { VirtualTableHeaderRow } from "./VirtualTableHeaderRow";
21
21
  import { VirtualTableRow } from "./VirtualTableRow";
22
22
  import { VirtualTableCell } from "./VirtualTableCell";
23
- import { AssignmentIcon, cn, Markdown, Typography } from "@firecms/ui";
23
+ import { AssignmentIcon, cn, Typography } from "@firecms/ui";
24
24
 
25
25
  const VirtualListContext = createContext<VirtualTableContextProps<any>>({} as any);
26
26
  VirtualListContext.displayName = "VirtualListContext";
@@ -225,11 +225,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
225
225
  }
226
226
 
227
227
  if (onFilterUpdate) onFilterUpdate(newFilterValue);
228
-
229
- if (column.key !== sortByProperty) {
230
- resetSort();
231
- }
232
- }, [checkFilterCombination, currentSort, onFilterUpdate, resetSort, sortByProperty]);
228
+ }, [checkFilterCombination, currentSort, onFilterUpdate, sortByProperty]);
233
229
 
234
230
  const buildErrorView = useCallback(() => (
235
231
  <div
@@ -239,7 +235,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
239
235
  {"Error fetching data from the data source"}
240
236
  </Typography>
241
237
 
242
- {error?.message && <Markdown className={"px-4 break-all"} source={error.message}/>}
238
+ {error?.message && <SafeLinkRenderer text={error.message}/>}
243
239
 
244
240
  </div>
245
241
  ), [error?.message]);
@@ -403,3 +399,17 @@ function MemoizedList({
403
399
  {Row}
404
400
  </List>;
405
401
  }
402
+
403
+ const SafeLinkRenderer: React.FC<{
404
+ text: string;
405
+ }> = ({ text }) => {
406
+ const urlRegex = /https?:\/\/[^\s]+/g;
407
+ const htmlContent = text.replace(urlRegex, (url) => {
408
+ // For each URL found, replace it with an HTML <a> tag
409
+ return `<a href="${url}" target="_blank">Link to your console</a>`;
410
+ });
411
+
412
+ return (
413
+ <div className={"px-4 break-all"} dangerouslySetInnerHTML={{ __html: htmlContent }}/>
414
+ );
415
+ };
@@ -12,8 +12,7 @@ import { DataSourceContext } from "../contexts/DataSourceContext";
12
12
  import { SideEntityControllerContext } from "../contexts/SideEntityControllerContext";
13
13
  import { NavigationContext } from "../contexts/NavigationContext";
14
14
  import { SideDialogsControllerContext } from "../contexts/SideDialogsControllerContext";
15
- import { useLocaleConfig } from "../internal/useLocaleConfig";
16
- import { CenteredView, Typography } from "@firecms/ui";
15
+ import { CenteredView, Typography, useLocaleConfig } from "@firecms/ui";
17
16
  import { DialogsProvider } from "../contexts/DialogsProvider";
18
17
  import { useBuildDataSource } from "../internal/useBuildDataSource";
19
18
  import { useBuildCustomizationController } from "../internal/useBuildCustomizationController";
@@ -276,9 +276,9 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
276
276
  const getCollectionFromPaths = useCallback(<EC extends EntityCollection>(pathSegments: string[]): EC | undefined => {
277
277
 
278
278
  const collections = collectionsRef.current;
279
+ if (collections === undefined)
280
+ throw Error("getCollectionFromPaths: Collections have not been initialised yet");
279
281
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
280
- if (!currentCollections)
281
- throw Error("Collections have not been initialised yet");
282
282
 
283
283
  for (let i = 0; i < pathSegments.length; i++) {
284
284
  const pathSegment = pathSegments[i];
@@ -297,9 +297,9 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
297
297
  const getCollectionFromIds = useCallback(<EC extends EntityCollection>(ids: string[]): EC | undefined => {
298
298
 
299
299
  const collections = collectionsRef.current;
300
+ if (collections === undefined)
301
+ throw Error("getCollectionFromIds: Collections have not been initialised yet");
300
302
  let currentCollections: EntityCollection[] | undefined = [...(collections ?? [])];
301
- if (!currentCollections)
302
- throw Error("Collections have not been initialised yet");
303
303
 
304
304
  for (let i = 0; i < ids.length; i++) {
305
305
  const id = ids[i];
@@ -335,9 +335,7 @@ export function useBuildNavigationController<EC extends EntityCollection, UserTy
335
335
  []);
336
336
 
337
337
  const resolveAliasesFrom = useCallback((path: string): string => {
338
- const collections = collectionsRef.current;
339
- if (!collections)
340
- throw Error("Collections have not been initialised yet");
338
+ const collections = collectionsRef.current ?? [];
341
339
  return resolveCollectionPathIds(path, collections);
342
340
  }, []);
343
341
 
@@ -440,7 +438,7 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
440
438
  collectionPermissions: PermissionsBuilder | undefined,
441
439
  authController: AuthController,
442
440
  dataSource: DataSourceDelegate,
443
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]) {
441
+ injectCollections?: (collections: EntityCollection[]) => EntityCollection[]): Promise<EntityCollection[]> {
444
442
  let resolvedCollections: EntityCollection[] = [];
445
443
  if (typeof collections === "function") {
446
444
  resolvedCollections = await collections({
@@ -489,8 +487,10 @@ function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
489
487
  if (a.length !== b.length) {
490
488
  return false;
491
489
  }
492
- const aSorted = a.sort((a, b) => a.id.localeCompare(b.id));
493
- const bSorted = b.sort((a, b) => a.id.localeCompare(b.id));
490
+ const aCopy = [...a];
491
+ const bCopy = [...b];
492
+ const aSorted = aCopy.sort((x, y) => x.id.localeCompare(y.id));
493
+ const bSorted = bCopy.sort((x, y) => x.id.localeCompare(y.id));
494
494
  return aSorted.every((value, index) => areCollectionsEqual(value, bSorted[index]));
495
495
  }
496
496
 
@@ -9,7 +9,7 @@ export type AccessResponse = {
9
9
  message?: string;
10
10
  }
11
11
 
12
- async function makeRequest(authController: AuthController, pluginKeys: string | undefined) {
12
+ async function makeRequest(authController: AuthController, pluginKeys: string[] | undefined) {
13
13
  const firebaseToken = await authController.getAuthToken();
14
14
  return fetch(DEFAULT_SERVER + "/access_log",
15
15
  {
@@ -30,12 +30,12 @@ export function useProjectLog(authController: AuthController,
30
30
  plugins?: FireCMSPlugin<any, any, any>[]): AccessResponse | null {
31
31
  const [accessResponse, setAccessResponse] = useState<AccessResponse | null>(null);
32
32
  const accessedUserRef = useRef<string | null>(null);
33
- const pluginKeys = plugins?.map(plugin => plugin.key).join(",");
33
+ const pluginKeys = plugins?.map(plugin => plugin.key);
34
34
  useEffect(() => {
35
35
  if (authController.user && authController.user.uid !== accessedUserRef.current && !authController.initialLoading) {
36
36
  makeRequest(authController, pluginKeys).then(setAccessResponse);
37
37
  accessedUserRef.current = authController.user.uid;
38
38
  }
39
- }, [authController]);
39
+ }, [authController, pluginKeys]);
40
40
  return accessResponse;
41
41
  }
@@ -49,7 +49,7 @@ export const useBuildSideEntityController = (navigation: NavigationController,
49
49
  }, 1);
50
50
  }
51
51
  } else {
52
- console.warn("Location path is not a collection path");
52
+ // console.warn("Location path is not a collection path");
53
53
  }
54
54
  initialised.current = true;
55
55
  }
@@ -56,7 +56,7 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
56
56
  * Get the collection configuration for a given path.
57
57
  * The collection is resolved from the given path or alias.
58
58
  */
59
- getCollection: (pathOrAlias: string,
59
+ getCollection: (pathOrId: string,
60
60
  entityId?: string,
61
61
  includeUserOverride?: boolean) => EC | undefined;
62
62
  /**
@@ -60,14 +60,14 @@ export function resolveCollectionPathIds(path: string, allCollections: EntityCol
60
60
  /**
61
61
  * Find the corresponding view at any depth for a given path.
62
62
  * Note that path or segments of the paths can be collection aliases.
63
- * @param pathOrAlias
63
+ * @param pathOrId
64
64
  * @param collections
65
65
  */
66
- export function getCollectionByPathOrId(pathOrAlias: string, collections: EntityCollection[]): EntityCollection | undefined {
66
+ export function getCollectionByPathOrId(pathOrId: string, collections: EntityCollection[]): EntityCollection | undefined {
67
67
 
68
- const subpaths = removeInitialAndTrailingSlashes(pathOrAlias).split("/");
68
+ const subpaths = removeInitialAndTrailingSlashes(pathOrId).split("/");
69
69
  if (subpaths.length % 2 === 0) {
70
- throw Error(`getCollectionByPathOrAlias: Collection paths must have an odd number of segments: ${pathOrAlias}`);
70
+ throw Error(`getCollectionByPathOrId: Collection paths must have an odd number of segments: ${pathOrId}`);
71
71
  }
72
72
 
73
73
  const subpathCombinations = getCollectionPathsCombinations(subpaths);
@@ -80,10 +80,10 @@ export function getCollectionByPathOrId(pathOrAlias: string, collections: Entity
80
80
 
81
81
  if (navigationEntry) {
82
82
 
83
- if (subpathCombination === pathOrAlias) {
83
+ if (subpathCombination === pathOrId) {
84
84
  result = navigationEntry;
85
85
  } else if (navigationEntry.subcollections) {
86
- const newPath = pathOrAlias.replace(subpathCombination, "").split("/").slice(2).join("/");
86
+ const newPath = pathOrId.replace(subpathCombination, "").split("/").slice(2).join("/");
87
87
  if (newPath.length > 0)
88
88
  result = getCollectionByPathOrId(newPath, navigationEntry.subcollections);
89
89
  }
@@ -1 +0,0 @@
1
- export declare function useLocaleConfig(locale?: string): void;
@@ -1,18 +0,0 @@
1
- import * as locales from "date-fns/locale";
2
- // @ts-ignore
3
- import { registerLocale, setDefaultLocale } from "react-datepicker";
4
- import { useEffect } from "react";
5
-
6
- export function useLocaleConfig(locale?: string) {
7
- useEffect(() => {
8
- if (!locale) {
9
- return;
10
- }
11
- // @ts-ignore
12
- const dateFnsLocale = locales[locale];
13
- if (dateFnsLocale) {
14
- registerLocale(locale, dateFnsLocale);
15
- setDefaultLocale(locale);
16
- }
17
- }, [locale])
18
- }