@firecms/core 3.0.0-tw4.2 → 3.0.0

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.
@@ -12,7 +12,6 @@ export declare function hasEntityInCache(path: string): boolean;
12
12
  * Retrieves an entity from the in-memory cache or `localStorage`.
13
13
  * If the entity is not in the cache but exists in `localStorage`, it loads it into the cache.
14
14
  * @param path - The unique path/key for the entity.
15
- * @param useLocalStorage
16
15
  * @returns The cached entity or `undefined` if not found.
17
16
  */
18
17
  export declare function getEntityFromCache(path: string): object | undefined;
@@ -4,3 +4,4 @@ export declare function randomString(strLength?: number): string;
4
4
  export declare function randomColor(): string;
5
5
  export declare function slugify(text?: string, separator?: string, lowercase?: boolean): string;
6
6
  export declare function unslugify(slug?: string): string;
7
+ export declare function prettifyIdentifier(input: string): string;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-tw4.2",
4
+ "version": "3.0.0",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -53,9 +53,9 @@
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/editor": "^3.0.0-tw4.2",
57
- "@firecms/formex": "^3.0.0-tw4.2",
58
- "@firecms/ui": "^3.0.0-tw4.2",
56
+ "@firecms/editor": "^3.0.0",
57
+ "@firecms/formex": "^3.0.0",
58
+ "@firecms/ui": "^3.0.0",
59
59
  "@radix-ui/react-portal": "^1.1.10",
60
60
  "clsx": "^2.1.1",
61
61
  "compressorjs": "^1.2.1",
@@ -108,7 +108,7 @@
108
108
  "dist",
109
109
  "src"
110
110
  ],
111
- "gitHead": "1292c52536d1485a6dd0803de81e05448cb0a36e",
111
+ "gitHead": "d106a7fde537c4330ae4ba5471c74d4536dea710",
112
112
  "publishConfig": {
113
113
  "access": "public"
114
114
  },
@@ -228,7 +228,7 @@ function DrawerWrapper(props: {
228
228
  open={props.open}
229
229
  onOpenChange={props.setDrawerOpen}
230
230
  title={"Navigation drawer"}
231
- overlayClassName={"bg-white bg-opacity-80 bg-white/80"}
231
+ overlayClassName={"bg-white bg-opacity-80"}
232
232
  >
233
233
  {innerDrawer}
234
234
  </Sheet>
@@ -186,7 +186,7 @@ export function ArrayContainerItem({
186
186
  <div
187
187
  ref={nodeRef}
188
188
  style={style}
189
- className={`relative ${!isDragging ? "hover\\:bg-surface-accent-50 dark\\:hover\\:bg-surface-800 dark\\:hover\\:bg-opacity-20 dark\\:hover\\:bg-surface-800/20" : ""} rounded-md opacity-100`}
189
+ className={`relative ${!isDragging ? "hover\\:bg-surface-accent-50 dark\\:hover\\:bg-surface-800 dark\\:hover\\:bg-opacity-20" : ""} rounded-md opacity-100`}
190
190
  >
191
191
  <div className="flex items-start">
192
192
  <div className="flex-grow w-[calc(100%-48px)] text-text-primary dark:text-text-primary-dark">
@@ -75,7 +75,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
75
75
  return (
76
76
  <div
77
77
  className={cls(
78
- "h-full flex items-center justify-center flex-col bg-surface-50 dark:bg-surface-900 bg-opacity-90 bg-surface-50/90 dark:bg-opacity-90 dark:bg-surface-900/90 z-10",
78
+ "h-full flex items-center justify-center flex-col bg-surface-50 dark:bg-surface-900 bg-opacity-90 dark:bg-opacity-90 z-10",
79
79
  frozen ? "sticky left-0" : ""
80
80
  )}
81
81
  onClick={useCallback((event: any) => {
@@ -188,9 +188,9 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
188
188
  <div
189
189
  className={cls(
190
190
  "transition-colors duration-100 ease-in-out",
191
- `flex relative h-full rounded-md p-${p} border border-4 border-opacity-75`,
191
+ `flex relative h-full rounded-md p-${p} border border-4 border-opacity-75`,
192
192
  onHover && !disabled ? "bg-surface-50 dark:bg-surface-900" : "",
193
- saved ? "bg-surface-100/75 dark:bg-surface-800/75" : "",
193
+ saved ? "bg-surface-100 bg-opacity-75 dark:bg-surface-800 dark:bg-opacity-75" : "",
194
194
  hideOverflow ? "overflow-hidden" : "",
195
195
  isSelected ? "bg-surface-50 dark:bg-surface-900" : "",
196
196
  borderClass
@@ -237,7 +237,7 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
237
237
  {disabled && onHover && disabledTooltip &&
238
238
  <div className="absolute top-1 right-1 text-xs">
239
239
  <Tooltip title={disabledTooltip}>
240
- <DoNotDisturbOnIcon size={"smallest"} color={"disabled"} className={"text-text-disabled"}/>
240
+ <DoNotDisturbOnIcon size={"smallest"} color={"disabled"} className={"text-surface-500"}/>
241
241
  </Tooltip>
242
242
  </div>}
243
243
 
@@ -136,7 +136,7 @@ export function ReferenceWidget<M extends Record<string, any>>({
136
136
  "min-w-80 flex flex-col gap-4",
137
137
  "relative transition-colors duration-200 ease-in rounded font-medium",
138
138
  disabled ? "bg-opacity-50" : "hover:bg-opacity-75",
139
- "text-opacity-50 text-text-primary/50 dark:text-white dark:text-opacity-50 dark:text-white/50",
139
+ "text-opacity-50 dark:text-white dark:text-opacity-50",
140
140
  className
141
141
  )}
142
142
  >
@@ -247,7 +247,7 @@ export const SelectableTable = function SelectableTable<M extends Record<string,
247
247
  checkFilterCombination={checkFilterCombination}
248
248
  createFilterField={filterable ? createFilterField : undefined}
249
249
  rowClassName={useCallback((entity: Entity<M>) => {
250
- return highlightedRow?.(entity) ? "bg-surface-100 bg-opacity-75 bg-surface-100/75 dark:bg-surface-800 dark:bg-opacity-75 dark:bg-surface-800/75" : "";
250
+ return highlightedRow?.(entity) ? "bg-surface-100 bg-opacity-75 dark:bg-surface-800 dark:bg-opacity-75" : "";
251
251
  }, [highlightedRow])}
252
252
  className="flex-grow"
253
253
  emptyComponent={emptyComponent}
@@ -87,7 +87,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
87
87
  <div
88
88
  className={cls("flex py-0 px-3 h-full text-xs uppercase font-semibold relative select-none items-center bg-surface-50 dark:bg-surface-900",
89
89
  "text-text-secondary hover:text-text-primary dark:text-text-secondary-dark dark:hover:text-text-primary-dark",
90
- "hover:bg-surface-100 dark:hover:bg-surface-800 hover:bg-opacity-50 hover:bg-surface-100/50 dark:hover:bg-opacity-50 dark:hover:bg-surface-800/50",
90
+ "hover:bg-surface-100 dark:hover:bg-surface-800 hover:bg-opacity-50 dark:hover:bg-opacity-50",
91
91
  column.frozen ? "sticky left-0 z-10" : "relative z-0"
92
92
  )}
93
93
  style={{
@@ -29,7 +29,7 @@ export const VirtualTableRow = React.memo<VirtualTableRowProps<any>>(
29
29
  return (
30
30
  <div
31
31
  className={cls(
32
- "flex min-w-full text-sm border-b border-surface-200 dark:border-surface-800 border-opacity-40 border-surface-200/40 dark:border-opacity-40 dark:border-surface-800/40",
32
+ "flex min-w-full text-sm border-b border-surface-200 dark:border-surface-800 border-opacity-40 dark:border-opacity-40",
33
33
  rowClassName ? rowClassName(rowData) : "",
34
34
  {
35
35
  "hover:bg-opacity-95": hoverRow,
@@ -85,7 +85,7 @@ export function DefaultDrawer({
85
85
 
86
86
  {groupsWithoutAdmin.map((group) => (
87
87
  <div
88
- className={"bg-surface-50 dark:bg-surface-800 dark:bg-opacity-30 dark:bg-surface-800/30 bg-surface-50 dark:bg-surface-800/30 my-4 rounded-lg ml-3 mr-1"}
88
+ className={"bg-surface-50 dark:bg-surface-800 dark:bg-opacity-30 my-4 rounded-lg ml-3 mr-1"}
89
89
  key={`drawer_group_${group}`}>
90
90
  {buildGroupHeader(group)}
91
91
  {Object.values(navigationEntries)
@@ -34,13 +34,13 @@ export function DrawerNavigationItem({
34
34
  transition: drawerOpen ? "width 150ms ease-in" : undefined
35
35
  }}
36
36
  className={({ isActive }: any) => cls("rounded-lg truncate",
37
- "hover:bg-surface-accent-300 hover:bg-opacity-75 hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800 dark:hover:bg-opacity-75 dark:hover:bg-surface-accent-800/75 text-text-primary dark:text-surface-200 hover:text-surface-900 hover:dark:text-white hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800/75",
37
+ "hover:bg-surface-accent-300 hover:bg-opacity-75 dark:hover:bg-surface-accent-800 dark:hover:bg-opacity-75 text-text-primary dark:text-surface-200 hover:text-surface-900 hover:dark:text-white",
38
38
  "flex flex-row items-center mr-8",
39
39
  // "transition-all ease-in-out delay-100 duration-300",
40
40
  // drawerOpen ? "w-full" : "w-18",
41
41
  drawerOpen ? "pl-4 h-10" : "pl-4 h-9",
42
42
  "font-semibold text-xs",
43
- isActive ? "bg-surface-accent-200 bg-opacity-60 dark:bg-surface-800 dark:bg-opacity-50 bg-surface-accent-200/60 dark:bg-surface-800/50" : ""
43
+ isActive ? "bg-surface-accent-200 bg-opacity-60 dark:bg-surface-800 dark:bg-opacity-50" : ""
44
44
  )}
45
45
  to={url}
46
46
  >
@@ -49,9 +49,8 @@ export function DrawerNavigationItem({
49
49
 
50
50
  <div
51
51
  className={cls(
52
- "text-text-primary dark:text-surface-200",
53
52
  drawerOpen ? "opacity-100" : "opacity-0 hidden",
54
- "ml-4 font-inherit"
53
+ "ml-4 font-inherit text-inherit"
55
54
  )}>
56
55
  {name.toUpperCase()}
57
56
  </div>
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
1
+ import React, { useContext, useEffect, useState } from "react";
2
2
  import { useSideDialogsController } from "../hooks";
3
3
  import { SideDialogPanelProps } from "../types";
4
4
  import { Sheet } from "@firecms/ui";
@@ -61,15 +61,15 @@ export function SideDialogs() {
61
61
  <SideDialogView
62
62
  key={`side_dialog_${index}`}
63
63
  panel={panel}
64
- offsetPosition={sidePanels.length - index - 1}/>)
64
+ offsetPosition={sidePanels.length - index - 1} />)
65
65
  }
66
66
  </>;
67
67
  }
68
68
 
69
69
  function SideDialogView({
70
- offsetPosition,
71
- panel
72
- }: {
70
+ offsetPosition,
71
+ panel
72
+ }: {
73
73
  offsetPosition: number,
74
74
  panel?: SideDialogPanelProps
75
75
  }) {
@@ -134,7 +134,18 @@ function SideDialogView({
134
134
 
135
135
  <Sheet
136
136
  open={Boolean(panel)}
137
- onOpenChange={(open) => !open && onCloseRequest()}
137
+ onOpenChange={(open) => {
138
+ if (!open) {
139
+ // Check if any suggestion menu is visible in DOM
140
+ const suggestionMenu = document.querySelector("[data-suggestion-menu=\"true\"]");
141
+ if (suggestionMenu && window.getComputedStyle(suggestionMenu).visibility !== "hidden") {
142
+ // Don't close the sheet if a suggestion menu is visible
143
+ // Let Tiptap handle closing the menu first
144
+ return;
145
+ }
146
+ onCloseRequest();
147
+ }
148
+ }}
138
149
  title={"Side dialog " + panel?.key}
139
150
  >
140
151
  {panel &&
@@ -150,7 +161,7 @@ function SideDialogView({
150
161
  </ErrorBoundary>
151
162
  </div>}
152
163
 
153
- {!panel && <div style={{ width }}/>}
164
+ {!panel && <div style={{ width }} />}
154
165
 
155
166
  </Sheet>
156
167
 
@@ -158,7 +169,7 @@ function SideDialogView({
158
169
  open={drawerCloseRequested}
159
170
  handleOk={drawerCloseRequested ? handleDrawerCloseOk : handleNavigationOk}
160
171
  handleCancel={drawerCloseRequested ? handleDrawerCloseCancel : handleNavigationCancel}
161
- body={blockedNavigationMessage}/>
172
+ body={blockedNavigationMessage} />
162
173
 
163
174
  </SideDialogContext.Provider>
164
175
 
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
2
2
  import { AuthController, DataSourceDelegate, FireCMSPlugin } from "../types";
3
3
 
4
4
  export const DEFAULT_SERVER_DEV = "https://api-kdoe6pj3qq-ey.a.run.app";
5
- export const DEFAULT_SERVER = "https://api-drplyi3b6q-ey.a.run.app";
5
+ export const DEFAULT_SERVER = "https://api.firecms.co";
6
6
 
7
7
  export type AccessResponse = {
8
8
  blocked?: boolean;
@@ -6,5 +6,5 @@ import React from "react";
6
6
  export function EmptyValue() {
7
7
 
8
8
  return <div
9
- className="rounded-full bg-surface-200 bg-opacity-30 bg-surface-200/30 dark:bg-opacity-20 dark:bg-surface-200/20 w-5 h-2 inline-block"/>;
9
+ className="rounded-full bg-surface-200 bg-opacity-30 dark:bg-opacity-20 w-5 h-2 inline-block"/>;
10
10
  }
@@ -122,7 +122,6 @@ export function hasEntityInCache(path: string): boolean {
122
122
  * Retrieves an entity from the in-memory cache or `localStorage`.
123
123
  * If the entity is not in the cache but exists in `localStorage`, it loads it into the cache.
124
124
  * @param path - The unique path/key for the entity.
125
- * @param useLocalStorage
126
125
  * @returns The cached entity or `undefined` if not found.
127
126
  */
128
127
  export function getEntityFromCache(path: string): object | undefined {
@@ -134,10 +133,6 @@ export function getEntityFromCache(path: string): object | undefined {
134
133
  const entityString = localStorage.getItem(key);
135
134
  if (entityString) {
136
135
  const entity: object = JSON.parse(entityString, customReviver);
137
- console.log("Loaded entity from localStorage:", {
138
- key,
139
- entity
140
- });
141
136
  return entity;
142
137
  }
143
138
  } catch (error) {
@@ -0,0 +1,101 @@
1
+ import { prettifyIdentifier } from "./strings";
2
+
3
+ describe("prettifyIdentifier", () => {
4
+ it("should return empty string for empty input", () => {
5
+ expect(prettifyIdentifier("")).toBe("");
6
+ });
7
+
8
+ it("should handle camelCase", () => {
9
+ expect(prettifyIdentifier("displayName")).toBe("Display Name");
10
+ expect(prettifyIdentifier("firstName")).toBe("First Name");
11
+ expect(prettifyIdentifier("lastName")).toBe("Last Name");
12
+ expect(prettifyIdentifier("emailAddress")).toBe("Email Address");
13
+ });
14
+
15
+ it("should handle PascalCase", () => {
16
+ expect(prettifyIdentifier("DisplayName")).toBe("Display Name");
17
+ expect(prettifyIdentifier("FirstName")).toBe("First Name");
18
+ expect(prettifyIdentifier("UserProfile")).toBe("User Profile");
19
+ });
20
+
21
+ it("should handle snake_case", () => {
22
+ expect(prettifyIdentifier("display_name")).toBe("Display Name");
23
+ expect(prettifyIdentifier("first_name")).toBe("First Name");
24
+ expect(prettifyIdentifier("user_profile")).toBe("User Profile");
25
+ });
26
+
27
+ it("should handle kebab-case", () => {
28
+ expect(prettifyIdentifier("display-name")).toBe("Display Name");
29
+ expect(prettifyIdentifier("first-name")).toBe("First Name");
30
+ expect(prettifyIdentifier("user-profile")).toBe("User Profile");
31
+ });
32
+
33
+ it("should handle mixed separators", () => {
34
+ expect(prettifyIdentifier("display_name-test")).toBe("Display Name Test");
35
+ expect(prettifyIdentifier("first-name_last")).toBe("First Name Last");
36
+ });
37
+
38
+ it("should handle acronyms correctly", () => {
39
+ expect(prettifyIdentifier("imageURL")).toBe("Image URL");
40
+ expect(prettifyIdentifier("XMLParser")).toBe("XML Parser");
41
+ expect(prettifyIdentifier("HTTPSConnection")).toBe("HTTPS Connection");
42
+ expect(prettifyIdentifier("parseHTML")).toBe("Parse HTML");
43
+ });
44
+
45
+ it("should handle consecutive uppercase letters", () => {
46
+ expect(prettifyIdentifier("URLParser")).toBe("URL Parser");
47
+ expect(prettifyIdentifier("HTMLElement")).toBe("HTML Element");
48
+ expect(prettifyIdentifier("APIKey")).toBe("API Key");
49
+ });
50
+
51
+ it("should handle single words", () => {
52
+ expect(prettifyIdentifier("name")).toBe("Name");
53
+ expect(prettifyIdentifier("title")).toBe("Title");
54
+ expect(prettifyIdentifier("description")).toBe("Description");
55
+ });
56
+
57
+ it("should handle all uppercase", () => {
58
+ expect(prettifyIdentifier("NAME")).toBe("NAME");
59
+ expect(prettifyIdentifier("TITLE")).toBe("TITLE");
60
+ });
61
+
62
+ it("should handle all lowercase", () => {
63
+ expect(prettifyIdentifier("name")).toBe("Name");
64
+ expect(prettifyIdentifier("title")).toBe("Title");
65
+ });
66
+
67
+ it("should handle multiple consecutive separators", () => {
68
+ expect(prettifyIdentifier("display__name")).toBe("Display Name");
69
+ expect(prettifyIdentifier("first--name")).toBe("First Name");
70
+ expect(prettifyIdentifier("user___profile")).toBe("User Profile");
71
+ });
72
+
73
+ it("should trim whitespace", () => {
74
+ expect(prettifyIdentifier(" displayName ")).toBe("Display Name");
75
+ expect(prettifyIdentifier(" first_name ")).toBe("First Name");
76
+ });
77
+
78
+ it("should handle numbers", () => {
79
+ expect(prettifyIdentifier("user123")).toBe("User123");
80
+ expect(prettifyIdentifier("item1Name")).toBe("Item1Name");
81
+ expect(prettifyIdentifier("version2Point0")).toBe("Version2Point0");
82
+ });
83
+
84
+ it("should handle complex combinations", () => {
85
+ expect(prettifyIdentifier("userProfileURLParser")).toBe("User Profile URL Parser");
86
+ expect(prettifyIdentifier("parse_HTML_document")).toBe("Parse HTML Document");
87
+ expect(prettifyIdentifier("API-key-validator")).toBe("API Key Validator");
88
+ });
89
+
90
+ it("should handle edge cases with underscores and hyphens at boundaries", () => {
91
+ expect(prettifyIdentifier("_displayName")).toBe("Display Name");
92
+ expect(prettifyIdentifier("displayName_")).toBe("Display Name");
93
+ expect(prettifyIdentifier("-displayName-")).toBe("Display Name");
94
+ });
95
+
96
+ it("should handle already formatted strings", () => {
97
+ expect(prettifyIdentifier("Display Name")).toBe("Display Name");
98
+ expect(prettifyIdentifier("First Name")).toBe("First Name");
99
+ });
100
+ });
101
+
@@ -61,3 +61,24 @@ export function unslugify(slug?: string): string {
61
61
  return slug.trim();
62
62
  }
63
63
  }
64
+
65
+ export function prettifyIdentifier(input: string) {
66
+ if (!input) return "";
67
+
68
+ let text = input;
69
+
70
+ // 1. Handle camelCase and Acronyms
71
+ // Group 1 ($1 $2): Lowercase followed by Uppercase (e.g., imageURL -> image URL)
72
+ // Group 2 ($3 $4): Uppercase followed by Uppercase+lowercase (e.g., XMLParser -> XML Parser)
73
+ text = text.replace(/([a-z])([A-Z])|([A-Z])([A-Z][a-z])/g, "$1$3 $2$4");
74
+
75
+ // 2. Replace hyphens/underscores with spaces
76
+ text = text.replace(/[_-]+/g, " ");
77
+
78
+ // 3. Capitalize first letter of each word (Title Case)
79
+ const s = text
80
+ .trim()
81
+ .replace(/\b\w/g, (char) => char.toUpperCase());
82
+ console.log("Prettified identifier:", { input,s });
83
+ return s;
84
+ }