@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.
- package/README.md +1 -1
- package/dist/hooks/useProjectLog.d.ts +1 -1
- package/dist/index.es.js +37 -20
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +37 -20
- package/dist/index.umd.js.map +1 -1
- package/dist/util/entity_cache.d.ts +0 -1
- package/dist/util/strings.d.ts +1 -0
- package/dist/util/strings.test.d.ts +1 -0
- package/package.json +5 -5
- package/src/app/Scaffold.tsx +1 -1
- package/src/components/ArrayContainer.tsx +1 -1
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +3 -3
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/SelectableTable.tsx +1 -1
- package/src/components/VirtualTable/VirtualTableHeader.tsx +1 -1
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/core/DefaultDrawer.tsx +1 -1
- package/src/core/DrawerNavigationItem.tsx +3 -4
- package/src/core/SideDialogs.tsx +19 -8
- package/src/hooks/useProjectLog.tsx +1 -1
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/util/entity_cache.ts +0 -5
- package/src/util/strings.test.ts +101 -0
- package/src/util/strings.ts +21 -0
|
@@ -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;
|
package/dist/util/strings.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
57
|
-
"@firecms/formex": "^3.0.0
|
|
58
|
-
"@firecms/ui": "^3.0.0
|
|
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": "
|
|
111
|
+
"gitHead": "d106a7fde537c4330ae4ba5471c74d4536dea710",
|
|
112
112
|
"publishConfig": {
|
|
113
113
|
"access": "public"
|
|
114
114
|
},
|
package/src/app/Scaffold.tsx
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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>
|
package/src/core/SideDialogs.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import 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
|
-
|
|
71
|
-
|
|
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) =>
|
|
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
|
|
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
|
|
9
|
+
className="rounded-full bg-surface-200 bg-opacity-30 dark:bg-opacity-20 w-5 h-2 inline-block"/>;
|
|
10
10
|
}
|
package/src/util/entity_cache.ts
CHANGED
|
@@ -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
|
+
|
package/src/util/strings.ts
CHANGED
|
@@ -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
|
+
}
|