xref: /expo/docs/ui/components/CommandMenu/utils.ts (revision da751fd5)
1import type { Dispatch, SetStateAction } from 'react';
2
3import type { AlgoliaItemHierarchy, AlgoliaItemType } from './types';
4
5export const getItemsAsync = async <T>(
6  query: string,
7  fetcher: (query: string, version?: string) => Promise<Response>,
8  setter: Dispatch<SetStateAction<T[]>>,
9  version?: string
10) => {
11  const { hits, libraries } = await fetcher(query, version).then(response => response.json());
12  setter(hits || libraries || []);
13};
14
15const getAlgoliaFetchParams = (
16  query: string,
17  appId: string,
18  apiKey: string,
19  indexName: string,
20  hits: number,
21  additionalParams: object = {}
22): [string, RequestInit] => [
23  `https://${appId}-dsn.algolia.net/1/indexes/${indexName}/query`,
24  {
25    method: 'POST',
26    headers: {
27      'X-Algolia-Application-Id': appId,
28      'X-Algolia-API-Key': apiKey,
29    },
30    body: JSON.stringify({
31      params: `query=${query}&hitsPerPage=${hits}`,
32      highlightPreTag: '<mark>',
33      highlightPostTag: '</mark>',
34      ...additionalParams,
35    }),
36  },
37];
38
39export const getExpoDocsResults = (query: string, version?: string) => {
40  return fetch(
41    ...getAlgoliaFetchParams(query, 'QEX7PB7D46', '6652d26570e8628af4601e1d78ad456b', 'expo', 20, {
42      facetFilters: [['version:none', `version:${version}`]],
43    })
44  );
45};
46
47export const getRNDocsResults = (query: string) => {
48  return fetch(
49    ...getAlgoliaFetchParams(
50      query,
51      '8TDSE0OHGQ',
52      'c9c791d9d5fd7f315d7f3859b32c1f3b',
53      'react-native-v2',
54      5,
55      { facetFilters: [['version:current']] }
56    )
57  );
58};
59
60export const getDirectoryResults = (query: string) => {
61  return fetch(`https://reactnative.directory/api/libraries?search=${encodeURI(query)}&limit=5`);
62};
63
64export const getHighlightHTML = (
65  item: AlgoliaItemType,
66  tag: keyof AlgoliaItemHierarchy<string>
67) => ({
68  dangerouslySetInnerHTML: {
69    __html: item._highlightResult.hierarchy[`${tag}`]?.value || '',
70  },
71});
72
73const trimContent = (content: string, length = 36) => {
74  if (!content || !content.length) return '';
75
76  const trimStart = Math.max(content.indexOf('<mark>') - length, 0);
77  const trimEnd = Math.min(content.indexOf('</mark>') + length + 6, content.length);
78
79  return `${trimStart !== 0 ? '…' : ''}${content.substring(trimStart, trimEnd).trim()}${
80    trimEnd !== content.length ? '…' : ''
81  }`;
82};
83
84export const getContentHighlightHTML = (item: AlgoliaItemType, skipDescription = false) =>
85  skipDescription
86    ? {}
87    : {
88        dangerouslySetInnerHTML: {
89          __html: item._highlightResult.content?.value
90            ? trimContent(item._highlightResult.content?.value)
91            : trimContent(item._highlightResult.hierarchy.lvl1?.value || '', 82),
92        },
93      };
94
95// note(simek): this code make sure that browser popup blocker
96// do not prevent opening links via key press (when it fires windows.open)
97export const openLink = (url: string, isExternal: boolean = false) => {
98  const link = document.createElement('a');
99  if (isExternal) {
100    link.target = '_blank';
101    link.rel = 'noopener noreferrer';
102  }
103  link.href = url;
104  link.click();
105};
106
107const ReferencePathChunks = ['/versions/', '/more/'] as const;
108
109export const isReferencePath = (url: string) => {
110  return ReferencePathChunks.some(pathChunk => url.includes(pathChunk));
111};
112
113const EASPathChunks = [
114  '/app-signing/',
115  '/build/',
116  '/build-reference/',
117  '/development/',
118  '/eas/',
119  '/eas/metadata/',
120  '/eas-update/',
121  '/submit/',
122] as const;
123
124export const isEASPath = (url: string) => {
125  return EASPathChunks.some(pathChunk => url.includes(pathChunk));
126};
127
128const HomePathChunks = [
129  '/get-started/',
130  '/develop/',
131  '/deploy/',
132  '/faq/',
133  '/core-concepts/',
134  '/debugging/',
135  '/config-plugins/',
136] as const;
137
138export const isHomePath = (url: string) => {
139  return HomePathChunks.some(pathChunk => url.includes(pathChunk));
140};
141
142const LearnPathChunks = ['/tutorial', '/ui-programming/', '/additional-resources/'] as const;
143
144export const isLearnPath = (url: string) => {
145  return LearnPathChunks.some(pathChunk => url.includes(pathChunk));
146};
147
148export const isAppleDevice = () => {
149  return /(Mac|iPhone|iPod|iPad)/i.test(
150    navigator?.platform ?? navigator?.userAgentData?.platform ?? ''
151  );
152};
153
154export const addHighlight = (content: string, query: string) => {
155  const highlightStart = content.toLowerCase().indexOf(query.toLowerCase());
156
157  if (highlightStart === -1) return content;
158
159  const highlightEnd = highlightStart + query.length;
160  return (
161    content.substring(0, highlightStart) +
162    '<mark>' +
163    content.substring(highlightStart, highlightEnd) +
164    '</mark>' +
165    content.substring(highlightEnd)
166  );
167};
168