fimo-react 0.21.0-experimental.1
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 +21 -0
- package/dist/runtime/content/DataSourcesProvider.d.ts +4 -0
- package/dist/runtime/content/DataSourcesProvider.js +63 -0
- package/dist/runtime/content/RefreshContentProvider.d.ts +19 -0
- package/dist/runtime/content/RefreshContentProvider.js +71 -0
- package/dist/runtime/content/createContentClient.d.ts +7 -0
- package/dist/runtime/content/createContentClient.js +7 -0
- package/dist/runtime/content/createSingletonClient.d.ts +5 -0
- package/dist/runtime/content/createSingletonClient.js +6 -0
- package/dist/runtime/content/fixtures/Author.d.ts +48 -0
- package/dist/runtime/content/fixtures/Author.js +22 -0
- package/dist/runtime/content/fixtures/Author.types.d.ts +38 -0
- package/dist/runtime/content/fixtures/Author.types.js +1 -0
- package/dist/runtime/content/fixtures/Post.d.ts +48 -0
- package/dist/runtime/content/fixtures/Post.js +37 -0
- package/dist/runtime/content/fixtures/Post.types.d.ts +50 -0
- package/dist/runtime/content/fixtures/Post.types.js +1 -0
- package/dist/runtime/content/fixtures/SiteSettings.d.ts +42 -0
- package/dist/runtime/content/fixtures/SiteSettings.js +15 -0
- package/dist/runtime/content/fixtures/usage-contract.d.ts +3 -0
- package/dist/runtime/content/fixtures/usage-contract.js +57 -0
- package/dist/runtime/content/fixtures/virtual-fimo-config.d.ts +3 -0
- package/dist/runtime/content/fixtures/virtual-fimo-config.js +3 -0
- package/dist/runtime/content/index.d.ts +5 -0
- package/dist/runtime/content/index.js +2 -0
- package/dist/runtime/content/react-content-client.d.ts +10 -0
- package/dist/runtime/content/react-content-client.js +164 -0
- package/dist/runtime/content/react-singleton-client.d.ts +7 -0
- package/dist/runtime/content/react-singleton-client.js +37 -0
- package/dist/runtime/content/retrieve-data-sources.d.ts +29 -0
- package/dist/runtime/content/retrieve-data-sources.js +107 -0
- package/dist/runtime/content/types.d.ts +42 -0
- package/dist/runtime/content/types.js +1 -0
- package/dist/runtime/content/useQueryLocale.d.ts +2 -0
- package/dist/runtime/content/useQueryLocale.js +5 -0
- package/dist/runtime/content/vite-runtime.d.ts +3 -0
- package/dist/runtime/content/vite-runtime.js +23 -0
- package/dist/runtime/core/AppErrorBoundary.d.ts +10 -0
- package/dist/runtime/core/AppErrorBoundary.js +27 -0
- package/dist/runtime/core/ViteLifecycles.d.ts +5 -0
- package/dist/runtime/core/ViteLifecycles.js +35 -0
- package/dist/runtime/i18n-context.d.ts +9 -0
- package/dist/runtime/i18n-context.js +17 -0
- package/dist/runtime/iframe/ConsoleErrorProvider.d.ts +3 -0
- package/dist/runtime/iframe/ConsoleErrorProvider.js +103 -0
- package/dist/runtime/iframe/ScreenshotProvider.d.ts +3 -0
- package/dist/runtime/iframe/ScreenshotProvider.js +60 -0
- package/dist/runtime/index.d.ts +24 -0
- package/dist/runtime/index.js +24 -0
- package/dist/runtime/internal.d.ts +3 -0
- package/dist/runtime/internal.js +6 -0
- package/dist/runtime/primitives-server.d.ts +7 -0
- package/dist/runtime/primitives-server.js +5 -0
- package/dist/runtime/primitives.d.ts +17 -0
- package/dist/runtime/primitives.js +18 -0
- package/dist/runtime/react/primitives/contracts.d.ts +100 -0
- package/dist/runtime/react/primitives/contracts.js +1 -0
- package/dist/runtime/react/primitives/editable/Boolean.d.ts +5 -0
- package/dist/runtime/react/primitives/editable/Boolean.js +11 -0
- package/dist/runtime/react/primitives/editable/Date.d.ts +4 -0
- package/dist/runtime/react/primitives/editable/Date.js +12 -0
- package/dist/runtime/react/primitives/editable/DateTime.d.ts +4 -0
- package/dist/runtime/react/primitives/editable/DateTime.js +13 -0
- package/dist/runtime/react/primitives/editable/Image.d.ts +4 -0
- package/dist/runtime/react/primitives/editable/Image.js +10 -0
- package/dist/runtime/react/primitives/editable/Json.d.ts +5 -0
- package/dist/runtime/react/primitives/editable/Json.js +11 -0
- package/dist/runtime/react/primitives/editable/RichText.d.ts +4 -0
- package/dist/runtime/react/primitives/editable/RichText.js +11 -0
- package/dist/runtime/react/primitives/editable/Text.d.ts +20 -0
- package/dist/runtime/react/primitives/editable/Text.js +36 -0
- package/dist/runtime/react/primitives/editable/Video.d.ts +4 -0
- package/dist/runtime/react/primitives/editable/Video.js +10 -0
- package/dist/runtime/react/primitives/renderers/DateRenderer.d.ts +17 -0
- package/dist/runtime/react/primitives/renderers/DateRenderer.js +17 -0
- package/dist/runtime/react/primitives/renderers/ImageRenderer.d.ts +8 -0
- package/dist/runtime/react/primitives/renderers/ImageRenderer.js +30 -0
- package/dist/runtime/react/primitives/renderers/RichTextRenderer.d.ts +12 -0
- package/dist/runtime/react/primitives/renderers/RichTextRenderer.js +135 -0
- package/dist/runtime/react/primitives/renderers/StaticImage.d.ts +35 -0
- package/dist/runtime/react/primitives/renderers/StaticImage.js +58 -0
- package/dist/runtime/react/primitives/renderers/VideoRenderer.d.ts +8 -0
- package/dist/runtime/react/primitives/renderers/VideoRenderer.js +10 -0
- package/dist/runtime/react/useFimoLiveValue.d.ts +20 -0
- package/dist/runtime/react/useFimoLiveValue.js +43 -0
- package/dist/runtime/vite/Label.d.ts +13 -0
- package/dist/runtime/vite/Label.js +16 -0
- package/dist/runtime/vite/__fixtures__/virtual-translations.d.ts +5 -0
- package/dist/runtime/vite/__fixtures__/virtual-translations.js +6 -0
- package/dist/runtime/vite/primitives/Images.d.ts +4 -0
- package/dist/runtime/vite/primitives/Images.js +12 -0
- package/dist/runtime/vite/translations.d.ts +28 -0
- package/dist/runtime/vite/translations.js +103 -0
- package/package.json +72 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ContentRuntime } from 'fimo/content';
|
|
2
|
+
import type { CollectionContentClient, ContentClientConfig } from './types.js';
|
|
3
|
+
export declare const SOURCE_TRACKED_QUERY_OPTIONS: {
|
|
4
|
+
readonly structuralSharing: false;
|
|
5
|
+
};
|
|
6
|
+
export type UseContentLocale = (requestedLocale: string | undefined, defaultLocale: string) => string;
|
|
7
|
+
export declare function createReactContentClient<T extends {
|
|
8
|
+
id: string;
|
|
9
|
+
}, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime, useContentLocale: UseContentLocale): CollectionContentClient<T, TInput>;
|
|
10
|
+
//# sourceMappingURL=react-content-client.d.ts.map
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { useInfiniteQuery, useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
+
import { createCollectionContentCore, readCollectionLocale, withMutationLocale, } from 'fimo/content';
|
|
3
|
+
export const SOURCE_TRACKED_QUERY_OPTIONS = { structuralSharing: false };
|
|
4
|
+
export function createReactContentClient(config, runtime, useContentLocale) {
|
|
5
|
+
const { client: core, defaultLocale, queryKeys } = createCollectionContentCore(config, runtime);
|
|
6
|
+
const { getById, getBySlug, getByField, list, create, update, remove } = core;
|
|
7
|
+
function activeLocale(requested) {
|
|
8
|
+
return useContentLocale(requested, defaultLocale);
|
|
9
|
+
}
|
|
10
|
+
function useGetById(id, options = {}) {
|
|
11
|
+
const localizedOptions = { ...options, locale: activeLocale(options.locale) };
|
|
12
|
+
return useQuery({
|
|
13
|
+
queryKey: queryKeys.byId(id, localizedOptions),
|
|
14
|
+
queryFn: () => getById(id, localizedOptions),
|
|
15
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function useGetBySlug(slug, options = {}) {
|
|
19
|
+
const locale = readCollectionLocale(options, activeLocale(options.locale));
|
|
20
|
+
const localizedOptions = { ...options, locale };
|
|
21
|
+
return useQuery({
|
|
22
|
+
queryKey: queryKeys.bySlug(slug, localizedOptions),
|
|
23
|
+
queryFn: () => getBySlug(slug, localizedOptions),
|
|
24
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
25
|
+
enabled: !!slug,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function useGetByField(fieldName, value, options = {}) {
|
|
29
|
+
const locale = readCollectionLocale(options, activeLocale(options.locale));
|
|
30
|
+
const localizedOptions = { ...options, locale };
|
|
31
|
+
return useQuery({
|
|
32
|
+
queryKey: queryKeys.byField(fieldName, value, localizedOptions),
|
|
33
|
+
queryFn: () => getByField(fieldName, value, localizedOptions),
|
|
34
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
35
|
+
enabled: !!value,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function useGet(params = {}) {
|
|
39
|
+
const locale = readCollectionLocale(params, activeLocale(params.locale));
|
|
40
|
+
const localizedParams = { ...params, locale };
|
|
41
|
+
return useQuery({
|
|
42
|
+
queryKey: queryKeys.list(localizedParams),
|
|
43
|
+
queryFn: () => list(localizedParams),
|
|
44
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
45
|
+
select: (page) => page.items,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function useList(params = {}) {
|
|
49
|
+
const locale = readCollectionLocale(params, activeLocale(params.locale));
|
|
50
|
+
const localizedParams = { ...params, locale };
|
|
51
|
+
return useQuery({
|
|
52
|
+
queryKey: queryKeys.list(localizedParams),
|
|
53
|
+
queryFn: () => list(localizedParams),
|
|
54
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function useInfiniteList(params = {}) {
|
|
58
|
+
const locale = readCollectionLocale(params, activeLocale(params.locale));
|
|
59
|
+
const localizedParams = { ...params, locale };
|
|
60
|
+
const query = useInfiniteQuery({
|
|
61
|
+
queryKey: queryKeys.infiniteList(localizedParams),
|
|
62
|
+
queryFn: ({ pageParam }) => list({ ...localizedParams, offset: pageParam }),
|
|
63
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
64
|
+
initialPageParam: 0,
|
|
65
|
+
getNextPageParam: (lastPage) => lastPage.pageInfo.nextOffset ?? undefined,
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
...query,
|
|
69
|
+
items: query.data?.pages.flatMap((page) => page.items) ?? [],
|
|
70
|
+
total: query.data?.pages[0]?.pageInfo.total ?? 0,
|
|
71
|
+
hasMore: query.hasNextPage,
|
|
72
|
+
loadMore: () => query.fetchNextPage(),
|
|
73
|
+
isLoadingMore: query.isFetchingNextPage,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function useSuspenseGetById(id, options = {}) {
|
|
77
|
+
const localizedOptions = { ...options, locale: activeLocale(options.locale) };
|
|
78
|
+
return useSuspenseQuery({
|
|
79
|
+
queryKey: queryKeys.byId(id, localizedOptions),
|
|
80
|
+
queryFn: () => getById(id, localizedOptions),
|
|
81
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function useSuspenseGetBySlug(slug, options = {}) {
|
|
85
|
+
const locale = readCollectionLocale(options, activeLocale(options.locale));
|
|
86
|
+
const localizedOptions = { ...options, locale };
|
|
87
|
+
return useSuspenseQuery({
|
|
88
|
+
queryKey: queryKeys.bySlug(slug, localizedOptions),
|
|
89
|
+
queryFn: () => getBySlug(slug, localizedOptions),
|
|
90
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function useSuspenseGetByField(fieldName, value, options = {}) {
|
|
94
|
+
const locale = readCollectionLocale(options, activeLocale(options.locale));
|
|
95
|
+
const localizedOptions = { ...options, locale };
|
|
96
|
+
return useSuspenseQuery({
|
|
97
|
+
queryKey: queryKeys.byField(fieldName, value, localizedOptions),
|
|
98
|
+
queryFn: () => getByField(fieldName, value, localizedOptions),
|
|
99
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function useSuspenseGet(params = {}) {
|
|
103
|
+
const locale = readCollectionLocale(params, activeLocale(params.locale));
|
|
104
|
+
const localizedParams = { ...params, locale };
|
|
105
|
+
return useSuspenseQuery({
|
|
106
|
+
queryKey: queryKeys.list(localizedParams),
|
|
107
|
+
queryFn: () => list(localizedParams),
|
|
108
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
109
|
+
select: (page) => page.items,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function useCreate() {
|
|
113
|
+
const queryClient = useQueryClient();
|
|
114
|
+
const locale = activeLocale();
|
|
115
|
+
return useMutation({
|
|
116
|
+
mutationFn: (payload) => create(withMutationLocale(payload, locale)),
|
|
117
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.root }),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function useUpdate() {
|
|
121
|
+
const queryClient = useQueryClient();
|
|
122
|
+
const locale = activeLocale();
|
|
123
|
+
return useMutation({
|
|
124
|
+
mutationFn: ({ id, ...patch }) => update(id, withMutationLocale(patch, locale)),
|
|
125
|
+
onSuccess: (_data, variables) => {
|
|
126
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.root });
|
|
127
|
+
queryClient.invalidateQueries({
|
|
128
|
+
queryKey: queryKeys.byId(variables.id, { locale: variables.locale ?? locale }),
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function useRemove() {
|
|
134
|
+
const queryClient = useQueryClient();
|
|
135
|
+
const active = activeLocale();
|
|
136
|
+
return useMutation({
|
|
137
|
+
mutationFn: (value) => {
|
|
138
|
+
const { id, locale } = typeof value === 'string' ? { id: value, locale: active } : { ...value, locale: value.locale ?? active };
|
|
139
|
+
return remove(id, { locale });
|
|
140
|
+
},
|
|
141
|
+
onSuccess: (_data, value) => {
|
|
142
|
+
const { id, locale } = typeof value === 'string' ? { id: value, locale: active } : { ...value, locale: value.locale ?? active };
|
|
143
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.root });
|
|
144
|
+
queryClient.removeQueries({ queryKey: queryKeys.byId(id, { locale }), exact: false });
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...core,
|
|
150
|
+
useGetById,
|
|
151
|
+
useGetBySlug,
|
|
152
|
+
useGetByField,
|
|
153
|
+
useGet,
|
|
154
|
+
useList,
|
|
155
|
+
useInfiniteList,
|
|
156
|
+
useSuspenseGetById,
|
|
157
|
+
useSuspenseGetBySlug,
|
|
158
|
+
useSuspenseGetByField,
|
|
159
|
+
useSuspenseGet,
|
|
160
|
+
useCreate,
|
|
161
|
+
useUpdate,
|
|
162
|
+
useRemove,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ContentRuntime } from 'fimo/content';
|
|
2
|
+
import { type UseContentLocale } from './react-content-client.js';
|
|
3
|
+
import type { ContentClientConfig, SingletonContentClient } from './types.js';
|
|
4
|
+
export declare function createReactSingletonClient<T extends {
|
|
5
|
+
id: string;
|
|
6
|
+
}, TInput extends object>(config: ContentClientConfig, runtime: ContentRuntime, useContentLocale: UseContentLocale): SingletonContentClient<T, TInput>;
|
|
7
|
+
//# sourceMappingURL=react-singleton-client.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
|
|
2
|
+
import { createSingletonContentCore, withMutationLocale } from 'fimo/content';
|
|
3
|
+
import { SOURCE_TRACKED_QUERY_OPTIONS } from './react-content-client.js';
|
|
4
|
+
export function createReactSingletonClient(config, runtime, useContentLocale) {
|
|
5
|
+
const { client: core, defaultLocale, localize, queryKeys } = createSingletonContentCore(config, runtime);
|
|
6
|
+
const { get, update } = core;
|
|
7
|
+
function activeLocale(requested) {
|
|
8
|
+
return useContentLocale(requested, defaultLocale);
|
|
9
|
+
}
|
|
10
|
+
function useGet(options = {}) {
|
|
11
|
+
const locale = localize(options, activeLocale(options.locale));
|
|
12
|
+
const localizedOptions = { ...options, locale };
|
|
13
|
+
return useQuery({
|
|
14
|
+
queryKey: queryKeys.document(localizedOptions),
|
|
15
|
+
queryFn: () => get(localizedOptions),
|
|
16
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function useSuspenseGet(options = {}) {
|
|
20
|
+
const locale = localize(options, activeLocale(options.locale));
|
|
21
|
+
const localizedOptions = { ...options, locale };
|
|
22
|
+
return useSuspenseQuery({
|
|
23
|
+
queryKey: queryKeys.document(localizedOptions),
|
|
24
|
+
queryFn: () => get(localizedOptions),
|
|
25
|
+
...SOURCE_TRACKED_QUERY_OPTIONS,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function useUpdate() {
|
|
29
|
+
const queryClient = useQueryClient();
|
|
30
|
+
const locale = activeLocale();
|
|
31
|
+
return useMutation({
|
|
32
|
+
mutationFn: (payload) => update(withMutationLocale(payload, locale)),
|
|
33
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: queryKeys.root }),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return { ...core, useGet, useSuspenseGet, useUpdate };
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type FieldData = Record<string, string>;
|
|
2
|
+
interface RecordData {
|
|
3
|
+
id: string;
|
|
4
|
+
locale?: string;
|
|
5
|
+
data: FieldData;
|
|
6
|
+
visible: boolean;
|
|
7
|
+
}
|
|
8
|
+
export type GroupedDataSources = Record<string, RecordData[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Parses a `data-fimo-source` attribute into its schema, identity, and field
|
|
11
|
+
* parts. The field itself may contain dots (e.g. a blocks leaf such as
|
|
12
|
+
* "body[0].title"), so only the first two segments are fixed positions.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseDataSource(source: string): {
|
|
15
|
+
schemaName: string;
|
|
16
|
+
identity: {
|
|
17
|
+
id: string;
|
|
18
|
+
locale?: string;
|
|
19
|
+
};
|
|
20
|
+
fieldName: string;
|
|
21
|
+
} | null;
|
|
22
|
+
/**
|
|
23
|
+
* Finds all visible elements with a `data-fimo-source` attribute and groups them
|
|
24
|
+
* by schema and ID into a structured object.
|
|
25
|
+
* @returns An object where keys are schema names and values are lists of records.
|
|
26
|
+
*/
|
|
27
|
+
export declare function retrieveVisibleDataSources(): GroupedDataSources;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=retrieve-data-sources.d.ts.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
function parseRecordIdentity(token) {
|
|
2
|
+
if (!token.startsWith('id:')) {
|
|
3
|
+
return token.includes(':') ? null : { id: token };
|
|
4
|
+
}
|
|
5
|
+
const [format, id, encodedLocale, ...rest] = token.split(':');
|
|
6
|
+
if (format !== 'id' || !id || !encodedLocale || rest.length > 0) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const locale = decodeURIComponent(encodedLocale);
|
|
11
|
+
return locale ? { id, locale } : null;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parses a `data-fimo-source` attribute into its schema, identity, and field
|
|
19
|
+
* parts. The field itself may contain dots (e.g. a blocks leaf such as
|
|
20
|
+
* "body[0].title"), so only the first two segments are fixed positions.
|
|
21
|
+
*/
|
|
22
|
+
export function parseDataSource(source) {
|
|
23
|
+
const parts = source.split('.');
|
|
24
|
+
if (parts.length < 3) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const [schemaName, identityToken] = parts;
|
|
28
|
+
const fieldName = parts.slice(2).join('.');
|
|
29
|
+
const identity = parseRecordIdentity(identityToken);
|
|
30
|
+
if (!identity) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return { schemaName, identity, fieldName };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if an element is genuinely visible to the user within the viewport.
|
|
37
|
+
* @param el The element to check.
|
|
38
|
+
* @returns True if the element is visible, otherwise false.
|
|
39
|
+
*/
|
|
40
|
+
function isElementVisible(el) {
|
|
41
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const rect = el.getBoundingClientRect();
|
|
45
|
+
if (rect.top >= window.innerHeight || rect.left >= window.innerWidth || rect.bottom <= 0 || rect.right <= 0) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const style = window.getComputedStyle(el);
|
|
49
|
+
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Check if the element is occluded (covered by another element)
|
|
53
|
+
const pointX = rect.left + rect.width / 2;
|
|
54
|
+
const pointY = rect.top + rect.height / 2;
|
|
55
|
+
const elementAtPoint = document.elementFromPoint(pointX, pointY);
|
|
56
|
+
return elementAtPoint === el || el.contains(elementAtPoint);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Finds all visible elements with a `data-fimo-source` attribute and groups them
|
|
60
|
+
* by schema and ID into a structured object.
|
|
61
|
+
* @returns An object where keys are schema names and values are lists of records.
|
|
62
|
+
*/
|
|
63
|
+
export function retrieveVisibleDataSources() {
|
|
64
|
+
// Use an intermediate object for easier data assembly.
|
|
65
|
+
const tempGroupedData = {};
|
|
66
|
+
const elementsWithSource = document.querySelectorAll('[data-fimo-source]');
|
|
67
|
+
elementsWithSource.forEach((el) => {
|
|
68
|
+
const source = el.getAttribute('data-fimo-source');
|
|
69
|
+
if (!source) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const parsed = parseDataSource(source);
|
|
73
|
+
if (!parsed) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const { schemaName, identity, fieldName } = parsed;
|
|
77
|
+
const { id, locale } = identity;
|
|
78
|
+
const recordKey = JSON.stringify([id, locale ?? null]);
|
|
79
|
+
// Skip __translations
|
|
80
|
+
if (schemaName === '__translations') {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Ensure the schema key exists
|
|
84
|
+
if (!tempGroupedData[schemaName]) {
|
|
85
|
+
tempGroupedData[schemaName] = {};
|
|
86
|
+
}
|
|
87
|
+
// Ensure the record (by ID) exists for this schema
|
|
88
|
+
if (!tempGroupedData[schemaName][recordKey]) {
|
|
89
|
+
tempGroupedData[schemaName][recordKey] = {
|
|
90
|
+
id: id,
|
|
91
|
+
...(locale ? { locale } : {}),
|
|
92
|
+
data: {},
|
|
93
|
+
visible: isElementVisible(el),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// Add the field and its text content to the record
|
|
97
|
+
if (el.textContent) {
|
|
98
|
+
tempGroupedData[schemaName][recordKey].data[fieldName] = el.textContent.trim();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// Convert the temporary object into the final desired array structure.
|
|
102
|
+
const finalResult = {};
|
|
103
|
+
for (const schemaName in tempGroupedData) {
|
|
104
|
+
finalResult[schemaName] = Object.values(tempGroupedData[schemaName]);
|
|
105
|
+
}
|
|
106
|
+
return finalResult;
|
|
107
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { InfiniteData, UseInfiniteQueryResult, UseMutationResult, UseQueryResult, UseSuspenseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
import type { CollectionContentCore, CollectionPage, ContentReadOptions, ContentResult, SingletonContentCore } from 'fimo/content';
|
|
3
|
+
import type { Fields, Populate, Query } from 'fimo/primitives';
|
|
4
|
+
export type { CollectionContentCore, CollectionPage, CollectionPageInfo, ContentClientConfig, ContentClientHandle, ContentModuleMeta, ContentModuleRef, ContentReadOptions, ContentResult, ContentSchema, ContentSchemaField, SingletonContentCore, } from 'fimo/content';
|
|
5
|
+
export type InfiniteListResult<TItem> = UseInfiniteQueryResult<InfiniteData<CollectionPage<TItem>>, Error> & {
|
|
6
|
+
readonly items: TItem[];
|
|
7
|
+
readonly total: number;
|
|
8
|
+
readonly hasMore: boolean;
|
|
9
|
+
readonly loadMore: () => ReturnType<UseInfiniteQueryResult<InfiniteData<CollectionPage<TItem>>, Error>['fetchNextPage']>;
|
|
10
|
+
readonly isLoadingMore: boolean;
|
|
11
|
+
};
|
|
12
|
+
export interface CollectionContentClient<T extends {
|
|
13
|
+
id: string;
|
|
14
|
+
}, TInput extends object> extends CollectionContentCore<T, TInput> {
|
|
15
|
+
useGetById<TPopulate extends Populate<T> | undefined = undefined>(id: string, options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
|
|
16
|
+
useGetBySlug<TPopulate extends Populate<T> | undefined = undefined>(slug: string, options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
|
|
17
|
+
useGetByField<TPopulate extends Populate<T> | undefined = undefined>(fieldName: string, value: string, options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
|
|
18
|
+
useGet<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): UseQueryResult<ContentResult<T, TFields, TPopulate>[], Error>;
|
|
19
|
+
useList<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): UseQueryResult<CollectionPage<ContentResult<T, TFields, TPopulate>>, Error>;
|
|
20
|
+
useInfiniteList<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Omit<Query<T, TFields, TPopulate>, 'offset'>): InfiniteListResult<ContentResult<T, TFields, TPopulate>>;
|
|
21
|
+
useSuspenseGetById<TPopulate extends Populate<T> | undefined = undefined>(id: string, options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
|
|
22
|
+
useSuspenseGetBySlug<TPopulate extends Populate<T> | undefined = undefined>(slug: string, options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
|
|
23
|
+
useSuspenseGetByField<TPopulate extends Populate<T> | undefined = undefined>(fieldName: string, value: string, options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate> | null, Error>;
|
|
24
|
+
useSuspenseGet<TFields extends Fields<T> | undefined = undefined, TPopulate extends Populate<T> | undefined = undefined>(params?: Query<T, TFields, TPopulate>): UseSuspenseQueryResult<ContentResult<T, TFields, TPopulate>[], Error>;
|
|
25
|
+
useCreate(): UseMutationResult<T, Error, TInput>;
|
|
26
|
+
useUpdate(): UseMutationResult<T, Error, {
|
|
27
|
+
id: string;
|
|
28
|
+
locale?: string;
|
|
29
|
+
} & Partial<TInput>>;
|
|
30
|
+
useRemove(): UseMutationResult<void, Error, string | {
|
|
31
|
+
id: string;
|
|
32
|
+
locale?: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface SingletonContentClient<T extends {
|
|
36
|
+
id: string;
|
|
37
|
+
}, TInput extends object> extends SingletonContentCore<T, TInput> {
|
|
38
|
+
useGet<TPopulate extends Populate<T> | undefined = undefined>(options?: ContentReadOptions<TPopulate>): UseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
|
|
39
|
+
useSuspenseGet<TPopulate extends Populate<T> | undefined = undefined>(options?: ContentReadOptions<TPopulate>): UseSuspenseQueryResult<ContentResult<T, undefined, TPopulate>, Error>;
|
|
40
|
+
useUpdate(): UseMutationResult<T, Error, Partial<TInput>>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @ts-ignore - virtual module provided by fimoConfigPlugin in fimo/vite
|
|
2
|
+
import fimoConfig from 'virtual:fimo-config';
|
|
3
|
+
export const viteContentRuntime = {
|
|
4
|
+
apiBase() {
|
|
5
|
+
// @ts-ignore - import.meta.env is a Vite feature
|
|
6
|
+
return import.meta.env.VITE_API_URL;
|
|
7
|
+
},
|
|
8
|
+
defaultLocale() {
|
|
9
|
+
return fimoConfig.i18n?.defaultLocale ?? 'en';
|
|
10
|
+
},
|
|
11
|
+
headers(extra) {
|
|
12
|
+
const headers = { ...extra };
|
|
13
|
+
// @ts-ignore - import.meta.env is a Vite feature
|
|
14
|
+
const env = import.meta.env.FIMO_ENV;
|
|
15
|
+
if (env) {
|
|
16
|
+
headers['X-Fimo-Env'] = env;
|
|
17
|
+
}
|
|
18
|
+
return headers;
|
|
19
|
+
},
|
|
20
|
+
fetch(input, init) {
|
|
21
|
+
return globalThis.fetch(input, init);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NOTE: Preferable over using a useEffect in App Error Boundary, it sometimes results in errors
|
|
3
|
+
*
|
|
4
|
+
* When a component using hooks is rendered under a different React instance (or an alias mis-resolves),
|
|
5
|
+
* React's exports can be null in that module graph.
|
|
6
|
+
* When rendering AppErrorBoundary fallback; useEffect inside it gets called with a "React" export that's null, yielding "Cannot read properties of null (reading 'useEffect')".
|
|
7
|
+
*/
|
|
8
|
+
export declare function onError(error: unknown): void;
|
|
9
|
+
export declare function AppErrorBoundary(): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=AppErrorBoundary.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* NOTE: Preferable over using a useEffect in App Error Boundary, it sometimes results in errors
|
|
4
|
+
*
|
|
5
|
+
* When a component using hooks is rendered under a different React instance (or an alias mis-resolves),
|
|
6
|
+
* React's exports can be null in that module graph.
|
|
7
|
+
* When rendering AppErrorBoundary fallback; useEffect inside it gets called with a "React" export that's null, yielding "Cannot read properties of null (reading 'useEffect')".
|
|
8
|
+
*/
|
|
9
|
+
export function onError(error) {
|
|
10
|
+
let heading = 'Something went wrong!';
|
|
11
|
+
let message = 'An unexpected error occurred.';
|
|
12
|
+
if (error instanceof Error) {
|
|
13
|
+
message = error.message;
|
|
14
|
+
}
|
|
15
|
+
window.parent.postMessage({
|
|
16
|
+
type: 'app/error',
|
|
17
|
+
payload: {
|
|
18
|
+
heading,
|
|
19
|
+
message,
|
|
20
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
21
|
+
},
|
|
22
|
+
}, '*');
|
|
23
|
+
}
|
|
24
|
+
// This component now receives props from react-error-boundary and serves as the fallback UI.
|
|
25
|
+
export function AppErrorBoundary() {
|
|
26
|
+
return (_jsx("div", { role: "alert", style: { padding: '20px' } }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
// Dev-only provider to forward Vite HMR lifecycle events to the parent window
|
|
4
|
+
export function ViteLifecycles({ children }) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
7
|
+
// @ts-ignore - Vite injects `import.meta.hot` in dev
|
|
8
|
+
const hot = import.meta?.hot;
|
|
9
|
+
if (!hot) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const beforeUpdate = (payload) => {
|
|
13
|
+
window.parent.postMessage({ type: 'hmr/beforeUpdate', payload: { ts: Date.now(), payload } }, '*');
|
|
14
|
+
};
|
|
15
|
+
const afterUpdate = () => {
|
|
16
|
+
window.parent.postMessage({ type: 'hmr/afterUpdate', payload: { ts: Date.now() } }, '*');
|
|
17
|
+
};
|
|
18
|
+
// Vite names the module that forced the reload (`path`/`triggeredBy`).
|
|
19
|
+
// Forward it: a full reload costs seconds and resets client state, and
|
|
20
|
+
// without the culprit there is no way to tell an unavoidable reload from
|
|
21
|
+
// one our own plugins caused by regenerating a module nobody can accept.
|
|
22
|
+
const beforeFullReload = (payload) => {
|
|
23
|
+
window.parent.postMessage({ type: 'hmr/beforeFullReload', payload: { ts: Date.now(), payload } }, '*');
|
|
24
|
+
};
|
|
25
|
+
const onError = (err) => {
|
|
26
|
+
window.parent.postMessage({ type: 'hmr/error', payload: { ts: Date.now(), err } }, '*');
|
|
27
|
+
};
|
|
28
|
+
hot.on('vite:beforeUpdate', beforeUpdate);
|
|
29
|
+
hot.on('vite:afterUpdate', afterUpdate);
|
|
30
|
+
hot.on('vite:beforeFullReload', beforeFullReload);
|
|
31
|
+
hot.on('vite:error', onError);
|
|
32
|
+
// No cleanup API for Vite HMR event bus; listeners are ephemeral in dev
|
|
33
|
+
}, []);
|
|
34
|
+
return _jsx(_Fragment, { children: children });
|
|
35
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FimoLocaleResolution } from 'fimo/config';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export type FimoLocaleContextValue = Pick<FimoLocaleResolution, 'locale' | 'defaultLocale' | 'locales' | 'routePath' | 'pathname' | 'dir'>;
|
|
4
|
+
export declare function FimoLocaleProvider({ value, children }: {
|
|
5
|
+
value: FimoLocaleContextValue;
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function useLocale(): FimoLocaleContextValue;
|
|
9
|
+
//# sourceMappingURL=i18n-context.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
const fallbackLocale = {
|
|
4
|
+
locale: 'en',
|
|
5
|
+
defaultLocale: 'en',
|
|
6
|
+
locales: ['en'],
|
|
7
|
+
pathname: '/',
|
|
8
|
+
routePath: '/',
|
|
9
|
+
dir: 'ltr',
|
|
10
|
+
};
|
|
11
|
+
const FimoLocaleContext = createContext(fallbackLocale);
|
|
12
|
+
export function FimoLocaleProvider({ value, children }) {
|
|
13
|
+
return _jsx(FimoLocaleContext.Provider, { value: value, children: children });
|
|
14
|
+
}
|
|
15
|
+
export function useLocale() {
|
|
16
|
+
return useContext(FimoLocaleContext);
|
|
17
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
function toMessage(args) {
|
|
4
|
+
try {
|
|
5
|
+
if (!args || args.length === 0) {
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
const first = args[0];
|
|
9
|
+
if (first instanceof Error) {
|
|
10
|
+
return first.message || String(first);
|
|
11
|
+
}
|
|
12
|
+
if (typeof first === 'string') {
|
|
13
|
+
return first;
|
|
14
|
+
}
|
|
15
|
+
return String(first);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function toStack(args) {
|
|
22
|
+
try {
|
|
23
|
+
const errArg = args.find((a) => a instanceof Error);
|
|
24
|
+
if (errArg && errArg.stack) {
|
|
25
|
+
return errArg.stack;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
throw new Error(toMessage(args));
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return e?.stack || '';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function firstAppFrame(stack) {
|
|
39
|
+
try {
|
|
40
|
+
const origin = window.location.origin;
|
|
41
|
+
const lines = (stack || '').split('\n');
|
|
42
|
+
for (const raw of lines) {
|
|
43
|
+
const line = raw.trim();
|
|
44
|
+
if (!line) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const isApp = line.includes(origin) && !/\/node_modules\//.test(line) && !/\/@/.test(line) && !/vite/i.test(line);
|
|
48
|
+
if (isApp) {
|
|
49
|
+
return line;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function hashString(input) {
|
|
59
|
+
let h = 2166136261;
|
|
60
|
+
for (let i = 0; i < input.length; i++) {
|
|
61
|
+
h ^= input.charCodeAt(i);
|
|
62
|
+
h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
|
|
63
|
+
}
|
|
64
|
+
return (h >>> 0).toString(16);
|
|
65
|
+
}
|
|
66
|
+
export const ConsoleErrorProvider = ({ children }) => {
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const original = console.error.bind(console);
|
|
69
|
+
const counts = new Map();
|
|
70
|
+
console.error = (...args) => {
|
|
71
|
+
try {
|
|
72
|
+
original(...args);
|
|
73
|
+
const message = toMessage(args);
|
|
74
|
+
const stack = toStack(args);
|
|
75
|
+
const top = firstAppFrame(stack);
|
|
76
|
+
if (!top) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const hash = hashString(`${message}|${top}`);
|
|
80
|
+
const next = (counts.get(hash) || 0) + 1;
|
|
81
|
+
counts.set(hash, next);
|
|
82
|
+
window.parent.postMessage({
|
|
83
|
+
type: 'app/console/error',
|
|
84
|
+
payload: {
|
|
85
|
+
heading: 'Error',
|
|
86
|
+
message,
|
|
87
|
+
stack,
|
|
88
|
+
hash,
|
|
89
|
+
count: next,
|
|
90
|
+
},
|
|
91
|
+
}, '*');
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
};
|
|
95
|
+
return () => {
|
|
96
|
+
try {
|
|
97
|
+
console.error = original;
|
|
98
|
+
}
|
|
99
|
+
catch { }
|
|
100
|
+
};
|
|
101
|
+
}, []);
|
|
102
|
+
return _jsx(_Fragment, { children: children });
|
|
103
|
+
};
|