react-datocms 3.0.11 → 3.0.14

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.
@@ -0,0 +1,244 @@
1
+ import {
2
+ defaultMetaTransformer,
3
+ render,
4
+ renderNodeRule,
5
+ renderMarkRule,
6
+ TransformedMeta,
7
+ TransformMetaFn,
8
+ RenderMarkRule,
9
+ } from 'datocms-structured-text-generic-html-renderer';
10
+ import {
11
+ isBlock,
12
+ isInlineItem,
13
+ isItemLink,
14
+ Record as StructuredTextGraphQlResponseRecord,
15
+ Document as StructuredTextDocument,
16
+ RenderError,
17
+ RenderResult,
18
+ RenderRule,
19
+ Node,
20
+ StructuredText as StructuredTextGraphQlResponse,
21
+ isStructuredText,
22
+ } from 'datocms-structured-text-utils';
23
+ import React, { cloneElement, isValidElement, ReactElement } from 'react';
24
+
25
+ export { renderNodeRule, renderMarkRule, RenderError };
26
+
27
+ // deprecated
28
+ export { renderNodeRule as renderRule };
29
+
30
+ export type {
31
+ StructuredTextGraphQlResponse,
32
+ StructuredTextDocument,
33
+ StructuredTextGraphQlResponseRecord,
34
+ };
35
+
36
+ type AdapterReturn = ReactElement | string | null;
37
+
38
+ export const defaultAdapter = {
39
+ renderNode: React.createElement as (...args: any) => AdapterReturn,
40
+ renderFragment: (
41
+ children: ReactElement | null[],
42
+ key: string,
43
+ ): AdapterReturn => <React.Fragment key={key}>{children}</React.Fragment>,
44
+ renderText: (text: string, key: string): AdapterReturn => text,
45
+ };
46
+
47
+ export function appendKeyToValidElement(
48
+ element: ReactElement | null,
49
+ key: string,
50
+ ): ReactElement | null {
51
+ if (isValidElement(element) && element.key === null) {
52
+ return cloneElement(element, { key });
53
+ }
54
+ return element;
55
+ }
56
+
57
+ type H = typeof defaultAdapter.renderNode;
58
+ type T = typeof defaultAdapter.renderText;
59
+ type F = typeof defaultAdapter.renderFragment;
60
+
61
+ export type RenderInlineRecordContext<
62
+ R extends StructuredTextGraphQlResponseRecord,
63
+ > = {
64
+ record: R;
65
+ };
66
+
67
+ export type RenderRecordLinkContext<
68
+ R extends StructuredTextGraphQlResponseRecord,
69
+ > = {
70
+ record: R;
71
+ children: RenderResult<H, T, F>;
72
+ transformedMeta: TransformedMeta;
73
+ };
74
+
75
+ export type RenderBlockContext<R extends StructuredTextGraphQlResponseRecord> =
76
+ {
77
+ record: R;
78
+ };
79
+
80
+ export type StructuredTextPropTypes<
81
+ R1 extends StructuredTextGraphQlResponseRecord,
82
+ R2 extends StructuredTextGraphQlResponseRecord = R1,
83
+ > = {
84
+ /** The actual field value you get from DatoCMS **/
85
+ data:
86
+ | StructuredTextGraphQlResponse<R1, R2>
87
+ | StructuredTextDocument
88
+ | Node
89
+ | null
90
+ | undefined;
91
+ /** A set of additional rules to convert nodes to JSX **/
92
+ customNodeRules?: RenderRule<H, T, F>[];
93
+ /** A set of additional rules to convert marks to JSX **/
94
+ customMarkRules?: RenderMarkRule<H, T, F>[];
95
+ /** Fuction that converts an 'inlineItem' node into React **/
96
+ renderInlineRecord?: (
97
+ context: RenderInlineRecordContext<R2>,
98
+ ) => ReactElement | null;
99
+ /** Fuction that converts an 'itemLink' node into React **/
100
+ renderLinkToRecord?: (
101
+ context: RenderRecordLinkContext<R2>,
102
+ ) => ReactElement | null;
103
+ /** Fuction that converts a 'block' node into React **/
104
+ renderBlock?: (context: RenderBlockContext<R1>) => ReactElement | null;
105
+ /** Function that converts 'link' and 'itemLink' `meta` into HTML props */
106
+ metaTransformer?: TransformMetaFn;
107
+ /** Fuction that converts a simple string text into React **/
108
+ renderText?: T;
109
+ /** React.createElement-like function to use to convert a node into React **/
110
+ renderNode?: H;
111
+ /** Function to use to generate a React.Fragment **/
112
+ renderFragment?: F;
113
+ /** @deprecated use customNodeRules **/
114
+ customRules?: RenderRule<H, T, F>[];
115
+ };
116
+
117
+ export function StructuredText<
118
+ R1 extends StructuredTextGraphQlResponseRecord,
119
+ R2 extends StructuredTextGraphQlResponseRecord = R1,
120
+ >({
121
+ data,
122
+ renderInlineRecord,
123
+ renderLinkToRecord,
124
+ renderBlock,
125
+ renderText,
126
+ renderNode,
127
+ renderFragment,
128
+ customMarkRules,
129
+ customRules,
130
+ customNodeRules,
131
+ metaTransformer,
132
+ }: StructuredTextPropTypes<R1, R2>): ReactElement | null {
133
+ const result = render(data, {
134
+ adapter: {
135
+ renderText: renderText || defaultAdapter.renderText,
136
+ renderNode: renderNode || defaultAdapter.renderNode,
137
+ renderFragment: renderFragment || defaultAdapter.renderFragment,
138
+ },
139
+ metaTransformer,
140
+ customMarkRules,
141
+ customNodeRules: [
142
+ renderNodeRule(isInlineItem, ({ node, key }) => {
143
+ if (!renderInlineRecord) {
144
+ throw new RenderError(
145
+ `The Structured Text document contains an 'inlineItem' node, but no 'renderInlineRecord' prop is specified!`,
146
+ node,
147
+ );
148
+ }
149
+
150
+ if (!isStructuredText(data) || !data.links) {
151
+ throw new RenderError(
152
+ `The document contains an 'itemLink' node, but the passed data prop is not a Structured Text GraphQL response, or data.links is not present!`,
153
+ node,
154
+ );
155
+ }
156
+
157
+ const item = data.links.find((item) => item.id === node.item);
158
+
159
+ if (!item) {
160
+ throw new RenderError(
161
+ `The Structured Text document contains an 'inlineItem' node, but cannot find a record with ID ${node.item} inside data.links!`,
162
+ node,
163
+ );
164
+ }
165
+
166
+ return appendKeyToValidElement(
167
+ renderInlineRecord({ record: item }),
168
+ key,
169
+ );
170
+ }),
171
+ renderNodeRule(isItemLink, ({ node, key, children }) => {
172
+ if (!renderLinkToRecord) {
173
+ throw new RenderError(
174
+ `The Structured Text document contains an 'itemLink' node, but no 'renderLinkToRecord' prop is specified!`,
175
+ node,
176
+ );
177
+ }
178
+
179
+ if (!isStructuredText(data) || !data.links) {
180
+ throw new RenderError(
181
+ `The document contains an 'itemLink' node, but the passed data prop is not a Structured Text GraphQL response, or data.links is not present!`,
182
+ node,
183
+ );
184
+ }
185
+
186
+ const item = data.links.find((item) => item.id === node.item);
187
+
188
+ if (!item) {
189
+ throw new RenderError(
190
+ `The Structured Text document contains an 'itemLink' node, but cannot find a record with ID ${node.item} inside data.links!`,
191
+ node,
192
+ );
193
+ }
194
+
195
+ return appendKeyToValidElement(
196
+ renderLinkToRecord({
197
+ record: item,
198
+ children: children as any as ReturnType<F>,
199
+ transformedMeta: node.meta
200
+ ? (metaTransformer || defaultMetaTransformer)({
201
+ node,
202
+ meta: node.meta,
203
+ })
204
+ : null,
205
+ }),
206
+ key,
207
+ );
208
+ }),
209
+ renderNodeRule(isBlock, ({ node, key }) => {
210
+ if (!renderBlock) {
211
+ throw new RenderError(
212
+ `The Structured Text document contains a 'block' node, but no 'renderBlock' prop is specified!`,
213
+ node,
214
+ );
215
+ }
216
+
217
+ if (!isStructuredText(data) || !data.blocks) {
218
+ throw new RenderError(
219
+ `The document contains an 'block' node, but the passed data prop is not a Structured Text GraphQL response, or data.blocks is not present!`,
220
+ node,
221
+ );
222
+ }
223
+
224
+ const item = data.blocks.find((item) => item.id === node.item);
225
+
226
+ if (!item) {
227
+ throw new RenderError(
228
+ `The Structured Text document contains a 'block' node, but cannot find a record with ID ${node.item} inside data.blocks!`,
229
+ node,
230
+ );
231
+ }
232
+
233
+ return appendKeyToValidElement(renderBlock({ record: item }), key);
234
+ }),
235
+ ...(customNodeRules || customRules || []),
236
+ ],
237
+ });
238
+
239
+ if (typeof result === 'string') {
240
+ return <>{result}</>;
241
+ }
242
+
243
+ return result || null;
244
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './Image/index';
2
+ export * from './Seo/index';
3
+ export * from './useQuerySubscription/index';
4
+ export * from './StructuredText/index';
@@ -0,0 +1,6 @@
1
+ import { configure } from 'enzyme'
2
+ import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
3
+
4
+ configure({
5
+ adapter: new Adapter(),
6
+ })
@@ -0,0 +1,89 @@
1
+ import { useState } from "react";
2
+ import {
3
+ subscribeToQuery,
4
+ UnsubscribeFn,
5
+ ChannelErrorData,
6
+ ConnectionStatus,
7
+ Options,
8
+ } from "datocms-listen";
9
+ import { useDeepCompareEffectNoCheck as useDeepCompareEffect } from "use-deep-compare-effect";
10
+
11
+ export type SubscribeToQueryOptions<QueryResult, QueryVariables> = Omit<
12
+ Options<QueryResult, QueryVariables>,
13
+ "onStatusChange" | "onUpdate" | "onChannelError"
14
+ >;
15
+
16
+ export type EnabledQueryListenerOptions<QueryResult, QueryVariables> = {
17
+ /** Whether the subscription has to be performed or not */
18
+ enabled?: true;
19
+ /** The initial data to use while the initial request is being performed */
20
+ initialData?: QueryResult;
21
+ } & SubscribeToQueryOptions<QueryResult, QueryVariables>;
22
+
23
+ export type DisabledQueryListenerOptions<QueryResult, QueryVariables> = {
24
+ /** Whether the subscription has to be performed or not */
25
+ enabled: false;
26
+ /** The initial data to use while the initial request is being performed */
27
+ initialData?: QueryResult;
28
+ } & Partial<SubscribeToQueryOptions<QueryResult, QueryVariables>>;
29
+
30
+ export type QueryListenerOptions<QueryResult, QueryVariables> =
31
+ | EnabledQueryListenerOptions<QueryResult, QueryVariables>
32
+ | DisabledQueryListenerOptions<QueryResult, QueryVariables>;
33
+
34
+ export function useQuerySubscription<
35
+ QueryResult = any,
36
+ QueryVariables = Record<string, any>
37
+ >(options: QueryListenerOptions<QueryResult, QueryVariables>) {
38
+ const { enabled, initialData, ...other } = options;
39
+
40
+ const [error, setError] = useState<ChannelErrorData | null>(null);
41
+ const [data, setData] = useState<QueryResult | null>(null);
42
+ const [status, setStatus] = useState<ConnectionStatus>(
43
+ enabled ? "connecting" : "closed"
44
+ );
45
+
46
+ const subscribeToQueryOptions = other as EnabledQueryListenerOptions<
47
+ QueryResult,
48
+ QueryVariables
49
+ >;
50
+
51
+ useDeepCompareEffect(() => {
52
+ if (enabled === false) {
53
+ setStatus("closed");
54
+
55
+ return () => {
56
+ // we don't have to perform any uninstall
57
+ };
58
+ }
59
+
60
+ let unsubscribe: UnsubscribeFn | null;
61
+
62
+ async function subscribe() {
63
+ unsubscribe = await subscribeToQuery<QueryResult, QueryVariables>({
64
+ ...subscribeToQueryOptions,
65
+ onStatusChange: (status) => {
66
+ setStatus(status);
67
+ },
68
+ onUpdate: (updateData) => {
69
+ setError(null);
70
+ setData(updateData.response.data);
71
+ },
72
+ onChannelError: (errorData) => {
73
+ setData(null);
74
+ setError(errorData);
75
+ },
76
+ });
77
+ }
78
+
79
+ subscribe();
80
+
81
+ return () => {
82
+ if (unsubscribe) {
83
+ unsubscribe();
84
+ }
85
+ };
86
+ }, [subscribeToQueryOptions]);
87
+
88
+ return { error, status, data: data || initialData };
89
+ }