react-datocms 3.0.13 → 3.1.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.
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ declare type SearchResultInstancesHrefSchema = {
3
+ page?: {
4
+ offset?: number;
5
+ limit?: number;
6
+ [k: string]: unknown;
7
+ };
8
+ filter: {
9
+ query: string;
10
+ build_trigger_id?: string;
11
+ locale?: string;
12
+ [k: string]: unknown;
13
+ };
14
+ [k: string]: unknown;
15
+ };
16
+ declare type SearchResultInstancesTargetSchema = {
17
+ data: RawSearchResult[];
18
+ meta: {
19
+ total_count: number;
20
+ };
21
+ };
22
+ export declare type RawSearchResult = {
23
+ type: 'search_result';
24
+ id: string;
25
+ attributes: {
26
+ title: string;
27
+ body_excerpt: string;
28
+ url: string;
29
+ score: number;
30
+ highlight: {
31
+ title?: string[] | null;
32
+ body?: string[] | null;
33
+ };
34
+ };
35
+ };
36
+ declare class GenericClient {
37
+ config: {
38
+ apiToken: string | null;
39
+ };
40
+ searchResults: {
41
+ rawList(queryParams: SearchResultInstancesHrefSchema): Promise<SearchResultInstancesTargetSchema>;
42
+ };
43
+ }
44
+ declare type Highlighter = (match: string, key: string, context: 'title' | 'bodyExcerpt') => React.ReactNode;
45
+ export declare type UseSiteSearchConfig<Client extends GenericClient> = {
46
+ client: Client;
47
+ buildTriggerId: string;
48
+ resultsPerPage?: number;
49
+ highlightMatch?: Highlighter;
50
+ initialState?: {
51
+ locale?: string;
52
+ page?: number;
53
+ query?: string;
54
+ };
55
+ };
56
+ declare type SearchResult = {
57
+ id: string;
58
+ title: React.ReactNode;
59
+ bodyExcerpt: React.ReactNode;
60
+ url: string;
61
+ raw: RawSearchResult;
62
+ };
63
+ export declare type UseSiteSearchData = {
64
+ pageResults: SearchResult[];
65
+ totalResults: number;
66
+ totalPages: number;
67
+ };
68
+ export declare type UseSiteSearchResult = {
69
+ state: {
70
+ query: string;
71
+ setQuery: (newQuery: string) => void;
72
+ locale: string | undefined;
73
+ setLocale: (newLocale: string) => void;
74
+ page: number;
75
+ setPage: (newPage: number) => void;
76
+ };
77
+ data?: UseSiteSearchData;
78
+ error?: string;
79
+ };
80
+ export declare function useSiteSearch<Client extends GenericClient>(config: UseSiteSearchConfig<Client>): UseSiteSearchResult;
81
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-datocms",
3
- "version": "3.0.13",
3
+ "version": "3.1.0",
4
4
  "types": "dist/types/index.d.ts",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -43,7 +43,7 @@
43
43
  "watch": "rimraf dist && tsc --watch",
44
44
  "prepare": "npm run test && npm run build",
45
45
  "test": "jest --coverage",
46
- "toc": "doctoc README.md"
46
+ "toc": "doctoc --github docs"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "react": ">= 16.12.0"
@@ -76,10 +76,11 @@
76
76
  ]
77
77
  },
78
78
  "dependencies": {
79
- "datocms-listen": "^0.1.7",
79
+ "datocms-listen": "^0.1.9",
80
80
  "datocms-structured-text-generic-html-renderer": "^2.0.1",
81
81
  "datocms-structured-text-utils": "^2.0.1",
82
82
  "react-intersection-observer": "^8.33.1",
83
+ "react-string-replace": "^1.1.0",
83
84
  "universal-base64": "^2.1.0",
84
85
  "use-deep-compare-effect": "^1.6.1"
85
86
  }
package/src/Seo/types.tsx CHANGED
@@ -2,9 +2,9 @@ export interface TitleMetaLinkTag {
2
2
  /** the tag for the meta information */
3
3
  tag: string;
4
4
  /** the inner content of the meta tag */
5
- content: string | null | undefined;
5
+ content?: string | null | undefined;
6
6
  /** the HTML attributes to attach to the meta tag */
7
- attributes: Record<string, string> | null | undefined;
7
+ attributes?: Record<string, string> | null | undefined;
8
8
  }
9
9
 
10
10
  export interface SeoTitleTag {
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './Image/index';
2
2
  export * from './Seo/index';
3
3
  export * from './useQuerySubscription/index';
4
- export * from './StructuredText/index';
4
+ export * from './StructuredText/index';
5
+ export * from './useSiteSearch/index';
@@ -1,16 +1,16 @@
1
- import { useState } from "react";
1
+ import { useState } from 'react';
2
2
  import {
3
3
  subscribeToQuery,
4
4
  UnsubscribeFn,
5
5
  ChannelErrorData,
6
6
  ConnectionStatus,
7
7
  Options,
8
- } from "datocms-listen";
9
- import { useDeepCompareEffectNoCheck as useDeepCompareEffect } from "use-deep-compare-effect";
8
+ } from 'datocms-listen';
9
+ import { useDeepCompareEffectNoCheck as useDeepCompareEffect } from 'use-deep-compare-effect';
10
10
 
11
11
  export type SubscribeToQueryOptions<QueryResult, QueryVariables> = Omit<
12
12
  Options<QueryResult, QueryVariables>,
13
- "onStatusChange" | "onUpdate" | "onChannelError"
13
+ 'onStatusChange' | 'onUpdate' | 'onChannelError'
14
14
  >;
15
15
 
16
16
  export type EnabledQueryListenerOptions<QueryResult, QueryVariables> = {
@@ -33,14 +33,14 @@ export type QueryListenerOptions<QueryResult, QueryVariables> =
33
33
 
34
34
  export function useQuerySubscription<
35
35
  QueryResult = any,
36
- QueryVariables = Record<string, any>
36
+ QueryVariables = Record<string, any>,
37
37
  >(options: QueryListenerOptions<QueryResult, QueryVariables>) {
38
38
  const { enabled, initialData, ...other } = options;
39
39
 
40
40
  const [error, setError] = useState<ChannelErrorData | null>(null);
41
41
  const [data, setData] = useState<QueryResult | null>(null);
42
42
  const [status, setStatus] = useState<ConnectionStatus>(
43
- enabled ? "connecting" : "closed"
43
+ enabled ? 'connecting' : 'closed',
44
44
  );
45
45
 
46
46
  const subscribeToQueryOptions = other as EnabledQueryListenerOptions<
@@ -50,7 +50,7 @@ export function useQuerySubscription<
50
50
 
51
51
  useDeepCompareEffect(() => {
52
52
  if (enabled === false) {
53
- setStatus("closed");
53
+ setStatus('closed');
54
54
 
55
55
  return () => {
56
56
  // we don't have to perform any uninstall
@@ -0,0 +1,263 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import reactStringReplace from 'react-string-replace';
3
+
4
+ type SearchResultInstancesHrefSchema = {
5
+ page?: {
6
+ offset?: number;
7
+ limit?: number;
8
+ [k: string]: unknown;
9
+ };
10
+ filter: {
11
+ query: string;
12
+ build_trigger_id?: string;
13
+ locale?: string;
14
+ [k: string]: unknown;
15
+ };
16
+ [k: string]: unknown;
17
+ };
18
+
19
+ type SearchResultInstancesTargetSchema = {
20
+ data: RawSearchResult[];
21
+ meta: {
22
+ total_count: number;
23
+ };
24
+ };
25
+
26
+ export type RawSearchResult = {
27
+ type: 'search_result';
28
+ id: string;
29
+ attributes: {
30
+ title: string;
31
+ body_excerpt: string;
32
+ url: string;
33
+ score: number;
34
+ highlight: {
35
+ title?: string[] | null;
36
+ body?: string[] | null;
37
+ };
38
+ };
39
+ };
40
+
41
+ declare class GenericClient {
42
+ config: {
43
+ apiToken: string | null;
44
+ };
45
+ searchResults: {
46
+ rawList(
47
+ queryParams: SearchResultInstancesHrefSchema,
48
+ ): Promise<SearchResultInstancesTargetSchema>;
49
+ };
50
+ }
51
+
52
+ type Highlighter = (
53
+ match: string,
54
+ key: string,
55
+ context: 'title' | 'bodyExcerpt',
56
+ ) => React.ReactNode;
57
+
58
+ export type UseSiteSearchConfig<Client extends GenericClient> = {
59
+ client: Client;
60
+ buildTriggerId: string;
61
+ resultsPerPage?: number;
62
+ highlightMatch?: Highlighter;
63
+ initialState?: {
64
+ locale?: string;
65
+ page?: number;
66
+ query?: string;
67
+ };
68
+ };
69
+
70
+ type SearchResult = {
71
+ id: string;
72
+ title: React.ReactNode;
73
+ bodyExcerpt: React.ReactNode;
74
+ url: string;
75
+ raw: RawSearchResult;
76
+ };
77
+
78
+ export type UseSiteSearchData = {
79
+ pageResults: SearchResult[];
80
+ totalResults: number;
81
+ totalPages: number;
82
+ };
83
+
84
+ export type UseSiteSearchResult = {
85
+ state: {
86
+ query: string;
87
+ setQuery: (newQuery: string) => void;
88
+ locale: string | undefined;
89
+ setLocale: (newLocale: string) => void;
90
+ page: number;
91
+ setPage: (newPage: number) => void;
92
+ };
93
+ data?: UseSiteSearchData;
94
+ error?: string;
95
+ };
96
+
97
+ const defaultHighlighter: Highlighter = (text, key) => (
98
+ <mark key={key}>{text}</mark>
99
+ );
100
+
101
+ function MatchHighlighter({
102
+ children,
103
+ highlighter,
104
+ context,
105
+ }: {
106
+ children: string;
107
+ highlighter: Highlighter;
108
+ context: 'title' | 'bodyExcerpt';
109
+ }) {
110
+ return (
111
+ <>
112
+ {reactStringReplace(children, /\[h\](.+?)\[\/h\]/g, (match, index) =>
113
+ highlighter(match, index.toString(), context),
114
+ )}
115
+ </>
116
+ );
117
+ }
118
+
119
+ export function useSiteSearch<Client extends GenericClient>(
120
+ config: UseSiteSearchConfig<Client>,
121
+ ): UseSiteSearchResult {
122
+ const [state, setState] = useState<{
123
+ query: string;
124
+ page: number;
125
+ locale: string | undefined;
126
+ }>({
127
+ query: config.initialState?.query || '',
128
+ page: config.initialState?.page || 0,
129
+ locale: config.initialState?.locale,
130
+ });
131
+
132
+ const [error, setError] = useState<string | undefined>();
133
+ const [response, setResponse] = useState<
134
+ SearchResultInstancesTargetSchema | undefined
135
+ >();
136
+
137
+ const resultsPerPage = config.resultsPerPage || 8;
138
+
139
+ useEffect(() => {
140
+ let isCancelled = false;
141
+
142
+ const run = async () => {
143
+ try {
144
+ setError(undefined);
145
+
146
+ if (!state.query) {
147
+ setResponse({ data: [], meta: { total_count: 0 } });
148
+ return;
149
+ }
150
+
151
+ setResponse(undefined);
152
+
153
+ const response = await config.client.searchResults.rawList({
154
+ filter: {
155
+ query: state.query,
156
+ locale: state.locale,
157
+ build_trigger_id: config.buildTriggerId,
158
+ },
159
+ page: {
160
+ limit: resultsPerPage,
161
+ offset: resultsPerPage * state.page,
162
+ },
163
+ });
164
+
165
+ if (!isCancelled) {
166
+ setResponse(response);
167
+ }
168
+ } catch (e) {
169
+ if (isCancelled) {
170
+ return;
171
+ }
172
+
173
+ if (e instanceof Error) {
174
+ setError(e.message);
175
+ } else {
176
+ setError('Unknown error!');
177
+ }
178
+ }
179
+ };
180
+
181
+ run();
182
+
183
+ return () => {
184
+ isCancelled = true;
185
+ };
186
+ }, [
187
+ resultsPerPage,
188
+ state,
189
+ config.buildTriggerId,
190
+ config.client.config.apiToken,
191
+ ]);
192
+
193
+ const publicSetQuery = useCallback(
194
+ (newQuery: string) => {
195
+ setState((oldState) => ({ ...oldState, query: newQuery, page: 0 }));
196
+ },
197
+ [setState],
198
+ );
199
+
200
+ const publicSetPage = useCallback(
201
+ (newPage: number) => {
202
+ setState((oldState) => ({ ...oldState, page: newPage }));
203
+ },
204
+ [setState],
205
+ );
206
+
207
+ const publicSetLocale = useCallback(
208
+ (newLocale: string | undefined) => {
209
+ setState((oldState) => ({ ...oldState, locale: newLocale, page: 0 }));
210
+ },
211
+ [setState],
212
+ );
213
+
214
+ const highlighter = config.highlightMatch || defaultHighlighter;
215
+
216
+ return {
217
+ state: {
218
+ query: state.query,
219
+ setQuery: publicSetQuery,
220
+ page: state.page,
221
+ setPage: publicSetPage,
222
+ locale: state.locale,
223
+ setLocale: publicSetLocale,
224
+ },
225
+
226
+ error,
227
+ data:
228
+ state.query === ''
229
+ ? {
230
+ pageResults: [],
231
+ totalResults: 0,
232
+ totalPages: 0,
233
+ }
234
+ : response
235
+ ? {
236
+ pageResults: response.data.map((rawSearchResult) => ({
237
+ id: rawSearchResult.id,
238
+ url: rawSearchResult.attributes.url,
239
+ title: rawSearchResult.attributes.highlight.title ? (
240
+ <MatchHighlighter highlighter={highlighter} context="title">
241
+ {rawSearchResult.attributes.highlight.title[0]}
242
+ </MatchHighlighter>
243
+ ) : (
244
+ rawSearchResult.attributes.title
245
+ ),
246
+ bodyExcerpt: rawSearchResult.attributes.highlight.body ? (
247
+ <MatchHighlighter
248
+ highlighter={highlighter}
249
+ context="bodyExcerpt"
250
+ >
251
+ {rawSearchResult.attributes.highlight.body[0]}
252
+ </MatchHighlighter>
253
+ ) : (
254
+ rawSearchResult.attributes.body_excerpt
255
+ ),
256
+ raw: rawSearchResult,
257
+ })),
258
+ totalResults: response.meta.total_count,
259
+ totalPages: Math.ceil(response.meta.total_count / resultsPerPage),
260
+ }
261
+ : undefined,
262
+ };
263
+ }