@rebasepro/common 0.10.0 → 0.10.1-canary.0a881d4
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/collections/default-collections.d.ts +6 -90
- package/dist/index.es.js +19 -488
- package/dist/index.es.js.map +1 -1
- package/dist/util/collections.d.ts +1 -9
- package/dist/util/conditions.d.ts +2 -3
- package/dist/util/entities.d.ts +0 -2
- package/dist/util/index.d.ts +0 -5
- package/dist/util/permissions.d.ts +7 -4
- package/dist/util/resolutions.d.ts +5 -5
- package/package.json +4 -6
- package/src/collections/default-collections.ts +16 -32
- package/src/util/collections.ts +0 -28
- package/src/util/conditions.ts +2 -190
- package/src/util/entities.ts +0 -17
- package/src/util/index.ts +0 -5
- package/src/util/permissions.test.ts +9 -14
- package/src/util/permissions.ts +7 -4
- package/src/util/resolutions.ts +15 -7
- package/dist/util/filter-operator-resolution.d.ts +0 -32
- package/dist/util/navigation_from_path.d.ts +0 -34
- package/dist/util/navigation_utils.d.ts +0 -20
- package/dist/util/parent_references_from_path.d.ts +0 -6
- package/dist/util/references.d.ts +0 -2
- package/src/util/filter-operator-resolution.ts +0 -81
- package/src/util/navigation_from_path.ts +0 -122
- package/src/util/navigation_utils.ts +0 -170
- package/src/util/parent_references_from_path.ts +0 -58
- package/src/util/references.ts +0 -48
package/src/util/resolutions.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ArrayProperty,
|
|
3
|
-
|
|
3
|
+
AuthState,
|
|
4
4
|
CollectionConfig,
|
|
5
5
|
EnumValueConfig,
|
|
6
6
|
EnumValues,
|
|
@@ -36,7 +36,7 @@ export type ResolvePropertyProps<M extends Record<string, unknown> = Record<stri
|
|
|
36
36
|
index?: number,
|
|
37
37
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
38
38
|
ignoreMissingFields?: boolean;
|
|
39
|
-
authController:
|
|
39
|
+
authController: AuthState;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function resolveProperty<M extends Record<string, unknown> = Record<string, unknown>>(props: ResolvePropertyProps<M>): Property | null {
|
|
@@ -191,7 +191,7 @@ export function resolveProperties<M extends Record<string, unknown>>({
|
|
|
191
191
|
index?: number,
|
|
192
192
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
193
193
|
ignoreMissingFields?: boolean;
|
|
194
|
-
authController:
|
|
194
|
+
authController: AuthState;
|
|
195
195
|
}): Properties {
|
|
196
196
|
return Object.entries<Property>(properties as Record<string, Property>)
|
|
197
197
|
.map(([key, property]) => {
|
|
@@ -226,7 +226,7 @@ export function resolveArrayProperties<M>({
|
|
|
226
226
|
index?: number,
|
|
227
227
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
228
228
|
ignoreMissingFields?: boolean;
|
|
229
|
-
authController:
|
|
229
|
+
authController: AuthState;
|
|
230
230
|
}): Property[] {
|
|
231
231
|
const propertyValue = propertyKey ? getIn(props.values, propertyKey) : undefined;
|
|
232
232
|
|
|
@@ -280,8 +280,16 @@ export function resolveArrayProperties<M>({
|
|
|
280
280
|
}).filter(e => Boolean(e)) as Property[]
|
|
281
281
|
: [];
|
|
282
282
|
return resolvedProperties;
|
|
283
|
-
} else if (!
|
|
284
|
-
|
|
283
|
+
} else if (!property.columnType) {
|
|
284
|
+
// An array with neither `of`/`oneOf` nor a `columnType` describes no element
|
|
285
|
+
// type, so nothing can be generated or rendered from it.
|
|
286
|
+
//
|
|
287
|
+
// The escape hatch used to be `ui.Field` — "a custom component can render
|
|
288
|
+
// anything" — which made a *presentation* field decide whether a schema was
|
|
289
|
+
// valid, in code the Postgres generator runs. `columnType` is the same escape
|
|
290
|
+
// hatch stated as data: `columnType: "text[]"` says what the column holds,
|
|
291
|
+
// which is what both the generator and the form actually need.
|
|
292
|
+
throw Error(`The array property (${propertyKey}) needs to declare an 'of' or a 'oneOf' property, or a \`columnType\` such as "text[]"`);
|
|
285
293
|
} else {
|
|
286
294
|
return [];
|
|
287
295
|
}
|
|
@@ -304,7 +312,7 @@ export function getArrayResolvedProperties({
|
|
|
304
312
|
entityId?: string | number;
|
|
305
313
|
index?: number;
|
|
306
314
|
propertyConfigs?: Record<string, PropertyConfig>;
|
|
307
|
-
authController:
|
|
315
|
+
authController: AuthState;
|
|
308
316
|
}) {
|
|
309
317
|
|
|
310
318
|
const of = property.of;
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Property, WhereFilterOp } from "@rebasepro/types";
|
|
2
|
-
export interface ResolveFilterOperatorsParams {
|
|
3
|
-
/**
|
|
4
|
-
* The property to filter on. For array properties, pass the **item**
|
|
5
|
-
* property (`property.of`) together with `isArray: true` — the same
|
|
6
|
-
* convention the filter field dispatchers use.
|
|
7
|
-
*/
|
|
8
|
-
property: Property;
|
|
9
|
-
/** True when filtering an array of `property`. */
|
|
10
|
-
isArray?: boolean;
|
|
11
|
-
/**
|
|
12
|
-
* The engine backing the collection (`collection.engine`, e.g.
|
|
13
|
-
* `"postgres"`, `"firestore"`). Falls back to the default engine's
|
|
14
|
-
* capabilities when omitted.
|
|
15
|
-
*/
|
|
16
|
-
engine?: string;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Resolve which filter operators the UI should offer for a property.
|
|
20
|
-
*
|
|
21
|
-
* The result is the **intersection** of three sets:
|
|
22
|
-
* 1. what the engine can execute — {@link DataSourceCapabilities.filterOperators}
|
|
23
|
-
* (e.g. Firestore cannot run the LIKE family);
|
|
24
|
-
* 2. what makes sense for the property type (e.g. no `>` on booleans);
|
|
25
|
-
* 3. the developer's optional narrowing — `property.ui.filterOperators`.
|
|
26
|
-
*
|
|
27
|
-
* Returns an empty array when the property is not filterable (either by
|
|
28
|
-
* type, or because the developer disabled it with `filterOperators: []`).
|
|
29
|
-
*
|
|
30
|
-
* @group Models
|
|
31
|
-
*/
|
|
32
|
-
export declare function resolveFilterOperators({ property, isArray, engine }: ResolveFilterOperatorsParams): WhereFilterOp[];
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
-
type EntityCustomView<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
3
|
-
key: string;
|
|
4
|
-
[key: string]: unknown;
|
|
5
|
-
};
|
|
6
|
-
export type NavigationViewInternal<M extends Record<string, unknown> = Record<string, unknown>> = NavigationViewEntityInternal<M> | NavigationViewCollectionInternal<M> | NavigationViewEntityCustomInternal<M>;
|
|
7
|
-
export interface NavigationViewEntityInternal<M extends Record<string, unknown>> {
|
|
8
|
-
type: "entity";
|
|
9
|
-
entityId: string | number;
|
|
10
|
-
slug: string;
|
|
11
|
-
path: string;
|
|
12
|
-
parentCollection: CollectionConfig<M>;
|
|
13
|
-
}
|
|
14
|
-
export interface NavigationViewCollectionInternal<M extends Record<string, unknown>> {
|
|
15
|
-
type: "collection";
|
|
16
|
-
id: string;
|
|
17
|
-
slug: string;
|
|
18
|
-
path: string;
|
|
19
|
-
collection: CollectionConfig<M>;
|
|
20
|
-
}
|
|
21
|
-
export interface NavigationViewEntityCustomInternal<M extends Record<string, unknown>> {
|
|
22
|
-
type: "custom_view";
|
|
23
|
-
slug: string;
|
|
24
|
-
path: string;
|
|
25
|
-
entityId: string | number;
|
|
26
|
-
view: EntityCustomView<M>;
|
|
27
|
-
}
|
|
28
|
-
export declare function getNavigationEntriesFromPath(props: {
|
|
29
|
-
path: string;
|
|
30
|
-
collections: CollectionConfig[] | undefined;
|
|
31
|
-
currentFullPath?: string;
|
|
32
|
-
contextEntityViews?: EntityCustomView[];
|
|
33
|
-
}): NavigationViewInternal[];
|
|
34
|
-
export {};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
-
export declare function removeInitialAndTrailingSlashes(s: string): string;
|
|
3
|
-
export declare function removeInitialSlash(s: string): string;
|
|
4
|
-
export declare function removeTrailingSlash(s: string): string;
|
|
5
|
-
export declare function addInitialSlash(s: string): string;
|
|
6
|
-
export declare function getLastSegment(path: string): string;
|
|
7
|
-
export declare function resolveCollectionPathIds(path: string, allCollections: CollectionConfig[]): string;
|
|
8
|
-
/**
|
|
9
|
-
* Find the corresponding view at any depth for a given path.
|
|
10
|
-
* Note that path or segments of the paths can be collection aliases.
|
|
11
|
-
* @param slugOrPath
|
|
12
|
-
* @param collections
|
|
13
|
-
*/
|
|
14
|
-
export declare function getCollectionBySlugWithin(slugOrPath: string, collections: CollectionConfig[]): CollectionConfig | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* Get the subcollection combinations from a path:
|
|
17
|
-
* "sites/es/locales" => ["sites/es/locales", "sites"]
|
|
18
|
-
* @param subpaths
|
|
19
|
-
*/
|
|
20
|
-
export declare function getCollectionPathsCombinations(subpaths: string[]): string[];
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ALL_WHERE_FILTER_OPS,
|
|
3
|
-
DataType,
|
|
4
|
-
getDataSourceCapabilities,
|
|
5
|
-
Property,
|
|
6
|
-
WhereFilterOp
|
|
7
|
-
} from "@rebasepro/types";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Default operators offered per property type, before engine capabilities and
|
|
11
|
-
* per-property narrowing are applied. These mirror what the built-in filter
|
|
12
|
-
* fields can render.
|
|
13
|
-
*/
|
|
14
|
-
const COMPARISON_OPS: readonly WhereFilterOp[] = ["==", "!=", ">", ">=", "<", "<="];
|
|
15
|
-
const NULL_CHECK_OPS: readonly WhereFilterOp[] = ["is-null", "is-not-null"];
|
|
16
|
-
const MEMBERSHIP_OPS: readonly WhereFilterOp[] = ["in", "not-in"];
|
|
17
|
-
const PATTERN_OPS: readonly WhereFilterOp[] = ["like", "ilike", "not-like", "not-ilike"];
|
|
18
|
-
|
|
19
|
-
const DEFAULT_OPS_BY_TYPE: Partial<Record<DataType, readonly WhereFilterOp[]>> = {
|
|
20
|
-
string: [...COMPARISON_OPS, ...MEMBERSHIP_OPS, ...PATTERN_OPS, ...NULL_CHECK_OPS],
|
|
21
|
-
number: [...COMPARISON_OPS, ...MEMBERSHIP_OPS, ...NULL_CHECK_OPS],
|
|
22
|
-
date: [...COMPARISON_OPS, ...NULL_CHECK_OPS],
|
|
23
|
-
boolean: ["==", "!=", ...NULL_CHECK_OPS],
|
|
24
|
-
reference: ["==", "!=", ...MEMBERSHIP_OPS, ...NULL_CHECK_OPS],
|
|
25
|
-
relation: ["==", "!=", ...MEMBERSHIP_OPS, ...NULL_CHECK_OPS]
|
|
26
|
-
// geopoint, map, vector, binary, array (as a container): not filterable
|
|
27
|
-
// through the generic filter UI.
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/** Operators offered when the property is an *array of* a filterable type. */
|
|
31
|
-
const ARRAY_OPS: readonly WhereFilterOp[] = ["array-contains", "array-contains-any"];
|
|
32
|
-
|
|
33
|
-
export interface ResolveFilterOperatorsParams {
|
|
34
|
-
/**
|
|
35
|
-
* The property to filter on. For array properties, pass the **item**
|
|
36
|
-
* property (`property.of`) together with `isArray: true` — the same
|
|
37
|
-
* convention the filter field dispatchers use.
|
|
38
|
-
*/
|
|
39
|
-
property: Property;
|
|
40
|
-
/** True when filtering an array of `property`. */
|
|
41
|
-
isArray?: boolean;
|
|
42
|
-
/**
|
|
43
|
-
* The engine backing the collection (`collection.engine`, e.g.
|
|
44
|
-
* `"postgres"`, `"firestore"`). Falls back to the default engine's
|
|
45
|
-
* capabilities when omitted.
|
|
46
|
-
*/
|
|
47
|
-
engine?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Resolve which filter operators the UI should offer for a property.
|
|
52
|
-
*
|
|
53
|
-
* The result is the **intersection** of three sets:
|
|
54
|
-
* 1. what the engine can execute — {@link DataSourceCapabilities.filterOperators}
|
|
55
|
-
* (e.g. Firestore cannot run the LIKE family);
|
|
56
|
-
* 2. what makes sense for the property type (e.g. no `>` on booleans);
|
|
57
|
-
* 3. the developer's optional narrowing — `property.ui.filterOperators`.
|
|
58
|
-
*
|
|
59
|
-
* Returns an empty array when the property is not filterable (either by
|
|
60
|
-
* type, or because the developer disabled it with `filterOperators: []`).
|
|
61
|
-
*
|
|
62
|
-
* @group Models
|
|
63
|
-
*/
|
|
64
|
-
export function resolveFilterOperators({
|
|
65
|
-
property,
|
|
66
|
-
isArray,
|
|
67
|
-
engine
|
|
68
|
-
}: ResolveFilterOperatorsParams): WhereFilterOp[] {
|
|
69
|
-
const typeDefaults: readonly WhereFilterOp[] = isArray
|
|
70
|
-
? ARRAY_OPS
|
|
71
|
-
: DEFAULT_OPS_BY_TYPE[property.type] ?? [];
|
|
72
|
-
if (typeDefaults.length === 0) return [];
|
|
73
|
-
|
|
74
|
-
const engineOps = new Set(getDataSourceCapabilities(engine).filterOperators ?? ALL_WHERE_FILTER_OPS);
|
|
75
|
-
|
|
76
|
-
const narrowing = property.ui?.filterOperators;
|
|
77
|
-
const narrowingSet = narrowing !== undefined ? new Set(narrowing) : undefined;
|
|
78
|
-
|
|
79
|
-
return typeDefaults.filter(op =>
|
|
80
|
-
engineOps.has(op) && (narrowingSet === undefined || narrowingSet.has(op)));
|
|
81
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
-
type EntityCustomView<M extends Record<string, unknown> = Record<string, unknown>> = { key: string; [key: string]: unknown };
|
|
3
|
-
import { getCollectionPathsCombinations, removeInitialAndTrailingSlashes } from "./navigation_utils";
|
|
4
|
-
import { getSubcollections } from "./resolutions";
|
|
5
|
-
|
|
6
|
-
export type NavigationViewInternal<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
7
|
-
| NavigationViewEntityInternal<M>
|
|
8
|
-
| NavigationViewCollectionInternal<M>
|
|
9
|
-
| NavigationViewEntityCustomInternal<M>;
|
|
10
|
-
|
|
11
|
-
export interface NavigationViewEntityInternal<M extends Record<string, unknown>> {
|
|
12
|
-
type: "entity";
|
|
13
|
-
entityId: string | number;
|
|
14
|
-
slug: string;
|
|
15
|
-
path: string;
|
|
16
|
-
parentCollection: CollectionConfig<M>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface NavigationViewCollectionInternal<M extends Record<string, unknown>> {
|
|
20
|
-
type: "collection";
|
|
21
|
-
id: string;
|
|
22
|
-
slug: string;
|
|
23
|
-
path: string;
|
|
24
|
-
collection: CollectionConfig<M>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface NavigationViewEntityCustomInternal<M extends Record<string, unknown>> {
|
|
28
|
-
type: "custom_view";
|
|
29
|
-
slug: string;
|
|
30
|
-
path: string;
|
|
31
|
-
entityId: string | number;
|
|
32
|
-
view: EntityCustomView<M>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function getNavigationEntriesFromPath(props: {
|
|
36
|
-
path: string,
|
|
37
|
-
collections: CollectionConfig[] | undefined,
|
|
38
|
-
currentFullPath?: string,
|
|
39
|
-
contextEntityViews?: EntityCustomView[]
|
|
40
|
-
}): NavigationViewInternal[] {
|
|
41
|
-
|
|
42
|
-
const {
|
|
43
|
-
path,
|
|
44
|
-
collections = [],
|
|
45
|
-
currentFullPath
|
|
46
|
-
} = props;
|
|
47
|
-
|
|
48
|
-
const subpaths = removeInitialAndTrailingSlashes(path).split("/");
|
|
49
|
-
const subpathCombinations = getCollectionPathsCombinations(subpaths);
|
|
50
|
-
|
|
51
|
-
const result: NavigationViewInternal[] = [];
|
|
52
|
-
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
53
|
-
const subpathCombination = subpathCombinations[i];
|
|
54
|
-
|
|
55
|
-
const collection = collections && collections.find((entry) => entry.slug === subpathCombination);
|
|
56
|
-
|
|
57
|
-
if (collection) {
|
|
58
|
-
const collectionPath = currentFullPath && currentFullPath.length > 0
|
|
59
|
-
? (currentFullPath + "/" + collection.slug)
|
|
60
|
-
: collection.slug;
|
|
61
|
-
result.push({
|
|
62
|
-
type: "collection",
|
|
63
|
-
id: collection.slug,
|
|
64
|
-
slug: collectionPath,
|
|
65
|
-
path: collectionPath,
|
|
66
|
-
collection
|
|
67
|
-
});
|
|
68
|
-
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
69
|
-
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
70
|
-
if (nextSegments.length > 0) {
|
|
71
|
-
const entityId = nextSegments[0];
|
|
72
|
-
const path = collectionPath + "/" + entityId;
|
|
73
|
-
result.push({
|
|
74
|
-
type: "entity",
|
|
75
|
-
entityId,
|
|
76
|
-
slug: collectionPath,
|
|
77
|
-
path,
|
|
78
|
-
parentCollection: collection
|
|
79
|
-
});
|
|
80
|
-
if (nextSegments.length > 1) {
|
|
81
|
-
const newPath = nextSegments.slice(1).join("/");
|
|
82
|
-
if (!collection) {
|
|
83
|
-
throw Error("collection not found resolving path: " + collection);
|
|
84
|
-
}
|
|
85
|
-
const entityViews = collection.entityViews;
|
|
86
|
-
const customView = entityViews && entityViews
|
|
87
|
-
.map((entry) => resolveEntityView(entry, props.contextEntityViews))
|
|
88
|
-
.filter((v): v is EntityCustomView => v != null)
|
|
89
|
-
.find((entry) => entry.key === newPath);
|
|
90
|
-
const subcollections = getSubcollections(collection);
|
|
91
|
-
if (customView) {
|
|
92
|
-
result.push({
|
|
93
|
-
type: "custom_view",
|
|
94
|
-
slug: collectionPath,
|
|
95
|
-
entityId: entityId,
|
|
96
|
-
path: path + "/" + customView.key,
|
|
97
|
-
view: customView
|
|
98
|
-
});
|
|
99
|
-
} else if (subcollections) {
|
|
100
|
-
result.push(...getNavigationEntriesFromPath({
|
|
101
|
-
path: newPath,
|
|
102
|
-
collections: subcollections,
|
|
103
|
-
currentFullPath: path,
|
|
104
|
-
contextEntityViews: props.contextEntityViews
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
return result;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function resolveEntityView(entityView: string | EntityCustomView, contextEntityViews?: EntityCustomView[]): EntityCustomView | undefined {
|
|
117
|
-
if (typeof entityView === "string") {
|
|
118
|
-
return contextEntityViews?.find((entry) => entry.key === entityView);
|
|
119
|
-
} else {
|
|
120
|
-
return entityView;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
-
|
|
3
|
-
import { getSubcollections } from "./resolutions";
|
|
4
|
-
|
|
5
|
-
export function removeInitialAndTrailingSlashes(s: string): string {
|
|
6
|
-
return removeInitialSlash(removeTrailingSlash(s));
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function removeInitialSlash(s: string) {
|
|
10
|
-
if (s.startsWith("/"))
|
|
11
|
-
return s.slice(1);
|
|
12
|
-
else return s;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function removeTrailingSlash(s: string) {
|
|
16
|
-
if (s.endsWith("/"))
|
|
17
|
-
return s.slice(0, -1);
|
|
18
|
-
else return s;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function addInitialSlash(s: string) {
|
|
22
|
-
if (s.startsWith("/"))
|
|
23
|
-
return s;
|
|
24
|
-
else return `/${s}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function getLastSegment(path: string) {
|
|
28
|
-
const cleanPath = removeInitialAndTrailingSlashes(path);
|
|
29
|
-
if (cleanPath.includes("/")) {
|
|
30
|
-
const segments = cleanPath.split("/");
|
|
31
|
-
return segments[segments.length - 1];
|
|
32
|
-
}
|
|
33
|
-
return cleanPath;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function resolveCollectionPathIds(path: string, allCollections: CollectionConfig[]): string {
|
|
37
|
-
let remainingPath = removeInitialAndTrailingSlashes(path);
|
|
38
|
-
if (!remainingPath) {
|
|
39
|
-
return "";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let currentCollections: CollectionConfig[] | undefined = allCollections;
|
|
43
|
-
const resolvedPathParts: string[] = [];
|
|
44
|
-
|
|
45
|
-
while (remainingPath.length > 0) {
|
|
46
|
-
if (!currentCollections || currentCollections.length === 0) {
|
|
47
|
-
// We have remaining path segments but no more collections to match against
|
|
48
|
-
console.warn(`resolveCollectionPathIds: Path structure implies subcollections, but none found before segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
|
|
49
|
-
resolvedPathParts.push(remainingPath);
|
|
50
|
-
remainingPath = ""; // Stop processing
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
let foundMatch = false;
|
|
55
|
-
// Sort potential matches by length descending to prioritize longer matches (e.g., "a/b" over "a")
|
|
56
|
-
const potentialMatches: { col: CollectionConfig; match: string; }[] = currentCollections
|
|
57
|
-
.flatMap(col => [{
|
|
58
|
-
col,
|
|
59
|
-
match: col.slug
|
|
60
|
-
}])
|
|
61
|
-
.filter(p => p.match && remainingPath.startsWith(p.match))
|
|
62
|
-
.sort((a, b) => b.match.length - a.match.length);
|
|
63
|
-
|
|
64
|
-
if (potentialMatches.length > 0) {
|
|
65
|
-
const {
|
|
66
|
-
col: foundCollection,
|
|
67
|
-
match: matchString
|
|
68
|
-
} = potentialMatches[0];
|
|
69
|
-
|
|
70
|
-
resolvedPathParts.push(foundCollection.slug); // Use the defined path
|
|
71
|
-
remainingPath = removeInitialSlash(remainingPath.substring(matchString.length));
|
|
72
|
-
|
|
73
|
-
// Check if we are at the end of the path
|
|
74
|
-
if (remainingPath.length === 0) {
|
|
75
|
-
foundMatch = true;
|
|
76
|
-
break; // Path ends with a collection segment
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// The next segment must be a entity ID
|
|
80
|
-
const idSeparatorIndex = remainingPath.indexOf("/");
|
|
81
|
-
let entityId: string | number;
|
|
82
|
-
if (idSeparatorIndex > -1) {
|
|
83
|
-
entityId = remainingPath.substring(0, idSeparatorIndex);
|
|
84
|
-
remainingPath = remainingPath.substring(idSeparatorIndex + 1);
|
|
85
|
-
} else {
|
|
86
|
-
// This should not happen if the original path is valid (odd segments)
|
|
87
|
-
// but handle it defensively: assume the rest is the ID
|
|
88
|
-
entityId = remainingPath;
|
|
89
|
-
remainingPath = "";
|
|
90
|
-
console.warn(`resolveCollectionPathIds: Path seems to end with a entity ID "${entityId}" instead of a collection segment in original path "${path}". This might indicate an invalid input path.`);
|
|
91
|
-
// Even if it ends here, we still need to push the ID
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
resolvedPathParts.push(entityId); // Append entity ID
|
|
95
|
-
currentCollections = getSubcollections(foundCollection); // Move to subcollections
|
|
96
|
-
foundMatch = true;
|
|
97
|
-
|
|
98
|
-
if (!currentCollections && remainingPath.length > 0) {
|
|
99
|
-
// Warn if the path continues but no subcollections were defined
|
|
100
|
-
console.warn(`resolveCollectionPathIds: Path continues after entity ID "${entityId}", but no subcollections are defined for the preceding collection "${foundCollection.slug}" in path "${path}". Appending remaining original path.`);
|
|
101
|
-
resolvedPathParts.push(remainingPath); // Append the rest
|
|
102
|
-
remainingPath = ""; // Stop processing
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!foundMatch) {
|
|
109
|
-
// Collection definition not found for the start of the remaining path
|
|
110
|
-
console.warn(`resolveCollectionPathIds: Collection definition not found for segment starting with "${remainingPath}" in original path "${path}". Appending remaining original path.`);
|
|
111
|
-
resolvedPathParts.push(remainingPath); // Append the rest
|
|
112
|
-
remainingPath = ""; // Stop processing
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return resolvedPathParts.join("/");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Find the corresponding view at any depth for a given path.
|
|
122
|
-
* Note that path or segments of the paths can be collection aliases.
|
|
123
|
-
* @param slugOrPath
|
|
124
|
-
* @param collections
|
|
125
|
-
*/
|
|
126
|
-
export function getCollectionBySlugWithin(slugOrPath: string, collections: CollectionConfig[]): CollectionConfig | undefined {
|
|
127
|
-
|
|
128
|
-
const subpaths = removeInitialAndTrailingSlashes(slugOrPath).split("/");
|
|
129
|
-
if (subpaths.length % 2 === 0) {
|
|
130
|
-
throw Error(`getCollectionBySlug: Collection paths must have an odd number of segments: ${slugOrPath}`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const subpathCombinations = getCollectionPathsCombinations(subpaths);
|
|
134
|
-
let result: CollectionConfig | undefined;
|
|
135
|
-
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
136
|
-
const subpathCombination = subpathCombinations[i];
|
|
137
|
-
const navigationEntry = collections && collections
|
|
138
|
-
.sort((a, b) => (a.slug ?? "").localeCompare(b.slug ?? ""))
|
|
139
|
-
.find((entry) => entry.slug === subpathCombination);
|
|
140
|
-
|
|
141
|
-
if (navigationEntry) {
|
|
142
|
-
|
|
143
|
-
if (subpathCombination === slugOrPath) {
|
|
144
|
-
result = navigationEntry;
|
|
145
|
-
} else if (getSubcollections(navigationEntry).length > 0) {
|
|
146
|
-
const newPath = slugOrPath.replace(subpathCombination, "").split("/").slice(2).join("/");
|
|
147
|
-
if (newPath.length > 0)
|
|
148
|
-
result = getCollectionBySlugWithin(newPath, getSubcollections(navigationEntry));
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (result) break;
|
|
152
|
-
}
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get the subcollection combinations from a path:
|
|
158
|
-
* "sites/es/locales" => ["sites/es/locales", "sites"]
|
|
159
|
-
* @param subpaths
|
|
160
|
-
*/
|
|
161
|
-
export function getCollectionPathsCombinations(subpaths: string[]): string[] {
|
|
162
|
-
const entries = subpaths.length > 0 && subpaths.length % 2 === 0 ? subpaths.splice(0, subpaths.length - 1) : subpaths;
|
|
163
|
-
|
|
164
|
-
const length = entries.length;
|
|
165
|
-
const result: string[] = [];
|
|
166
|
-
for (let i = length; i > 0; i = i - 2) {
|
|
167
|
-
result.push(entries.slice(0, i).join("/"));
|
|
168
|
-
}
|
|
169
|
-
return result;
|
|
170
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { CollectionConfig, EntityReference } from "@rebasepro/types";
|
|
2
|
-
import { getCollectionPathsCombinations, removeInitialAndTrailingSlashes } from "./navigation_utils";
|
|
3
|
-
import { getSubcollections } from "./resolutions";
|
|
4
|
-
|
|
5
|
-
export function getParentReferencesFromPath(props: {
|
|
6
|
-
path: string,
|
|
7
|
-
collections: CollectionConfig[] | undefined,
|
|
8
|
-
currentFullPath?: string,
|
|
9
|
-
}): EntityReference[] {
|
|
10
|
-
|
|
11
|
-
const {
|
|
12
|
-
path,
|
|
13
|
-
collections = [],
|
|
14
|
-
currentFullPath
|
|
15
|
-
} = props;
|
|
16
|
-
|
|
17
|
-
const subpaths = removeInitialAndTrailingSlashes(path).split("/");
|
|
18
|
-
const subpathCombinations = getCollectionPathsCombinations(subpaths);
|
|
19
|
-
|
|
20
|
-
const result: EntityReference[] = [];
|
|
21
|
-
for (let i = 0; i < subpathCombinations.length; i++) {
|
|
22
|
-
const subpathCombination = subpathCombinations[i];
|
|
23
|
-
|
|
24
|
-
const collection: CollectionConfig | undefined = collections && collections.find((entry) => entry.slug === subpathCombination);
|
|
25
|
-
|
|
26
|
-
// If we find a collection, we add the reference and continue
|
|
27
|
-
if (collection) {
|
|
28
|
-
const collectionPath = currentFullPath && currentFullPath.length > 0
|
|
29
|
-
? (currentFullPath + "/" + collection.slug) // Use the current full path if provided
|
|
30
|
-
: collection.slug;
|
|
31
|
-
|
|
32
|
-
const restOfThePath = removeInitialAndTrailingSlashes(removeInitialAndTrailingSlashes(path).replace(subpathCombination, ""));
|
|
33
|
-
const nextSegments = restOfThePath.length > 0 ? restOfThePath.split("/") : [];
|
|
34
|
-
if (nextSegments.length > 0) {
|
|
35
|
-
const entityId = nextSegments[0];
|
|
36
|
-
const path = collectionPath + "/" + entityId;
|
|
37
|
-
result.push(new EntityReference({ id: entityId,
|
|
38
|
-
path: collectionPath }));
|
|
39
|
-
if (nextSegments.length > 1) {
|
|
40
|
-
const newPath = nextSegments.slice(1).join("/");
|
|
41
|
-
if (!collection) {
|
|
42
|
-
throw Error("collection not found resolving path: " + collection);
|
|
43
|
-
}
|
|
44
|
-
if (getSubcollections(collection).length > 0) {
|
|
45
|
-
result.push(...getParentReferencesFromPath({
|
|
46
|
-
path: newPath,
|
|
47
|
-
collections: getSubcollections(collection),
|
|
48
|
-
currentFullPath: path
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
return result;
|
|
58
|
-
}
|
package/src/util/references.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
-
|
|
3
|
-
export function getEntityImagePreviewPropertyKey<M extends Record<string, unknown>>(collection: CollectionConfig<M>): string | undefined {
|
|
4
|
-
|
|
5
|
-
// find first storage property of type image
|
|
6
|
-
for (const key in collection.properties) {
|
|
7
|
-
const property = collection.properties[key];
|
|
8
|
-
if (property.type === "string" && property.storage?.acceptedFiles?.includes("image/*")) {
|
|
9
|
-
return key;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
// alternatively, look for the first array of images
|
|
13
|
-
for (const key in collection.properties) {
|
|
14
|
-
const property = collection.properties[key];
|
|
15
|
-
if (property.type === "array" && !Array.isArray(property.of) && property.of?.type === "string" && property.of.storage?.acceptedFiles?.includes("image/*")) {
|
|
16
|
-
return key;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
// also check for URL properties with image preview type
|
|
20
|
-
for (const key in collection.properties) {
|
|
21
|
-
const property = collection.properties[key];
|
|
22
|
-
if (property.type === "string" && property.ui?.url === "image") {
|
|
23
|
-
return key;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// and arrays of URL properties with image preview type
|
|
27
|
-
for (const key in collection.properties) {
|
|
28
|
-
const property = collection.properties[key];
|
|
29
|
-
if (property.type === "array" && property.of && !Array.isArray(property.of) && property.of.type === "string" && property.of.ui?.url === "image") {
|
|
30
|
-
return key;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
// fallback: any storage property without explicit acceptedFiles (e.g. a generic "picture" field)
|
|
34
|
-
for (const key in collection.properties) {
|
|
35
|
-
const property = collection.properties[key];
|
|
36
|
-
if (property.type === "string" && property.storage && !property.storage.acceptedFiles) {
|
|
37
|
-
return key;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// fallback: any array of storage properties without explicit acceptedFiles
|
|
41
|
-
for (const key in collection.properties) {
|
|
42
|
-
const property = collection.properties[key];
|
|
43
|
-
if (property.type === "array" && !Array.isArray(property.of) && property.of?.type === "string" && property.of.storage && !property.of.storage.acceptedFiles) {
|
|
44
|
-
return key;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|