xref: /expo/docs/ui/components/CommandMenu/utils.ts (revision d4613734)
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', 10, {
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) => {
74  if (!content || !content.length) return '';
75
76  const trimStart = Math.max(content.indexOf('<mark>') - 36, 0);
77  const trimEnd = Math.min(content.indexOf('</mark>') + 36 + 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) => ({
85  dangerouslySetInnerHTML: {
86    __html: trimContent(item._highlightResult.content?.value || ''),
87  },
88});
89
90// note(simek): this code make sure that browser popup blocker
91// do not prevent opening links via key press (when it fires windows.open)
92export const openLink = (url: string, isExternal: boolean = false) => {
93  const link = document.createElement('a');
94  if (isExternal) {
95    link.target = '_blank';
96    link.rel = 'noopener noreferrer';
97  }
98  link.href = url;
99  link.click();
100};
101
102const ReferencePathChunks = ['/versions/', '/modules/', '/more/'] as const;
103
104export const isReferencePath = (url: string) => {
105  return ReferencePathChunks.some(pathChunk => url.includes(pathChunk));
106};
107
108const EASPathChunks = [
109  '/app-signing/',
110  '/build/',
111  '/build-reference/',
112  '/development/',
113  '/eas/',
114  '/eas/metadata/',
115  '/eas-update/',
116  '/submit/',
117] as const;
118
119export const isEASPath = (url: string) => {
120  return EASPathChunks.some(pathChunk => url.includes(pathChunk));
121};
122
123const HomePathChunks = [
124  '/get-started/',
125  '/develop/',
126  '/deploy/',
127  '/faq/',
128  '/core-concepts/',
129  '/debugging/',
130  '/config-plugins/',
131] as const;
132
133export const isHomePath = (url: string) => {
134  return HomePathChunks.some(pathChunk => url.includes(pathChunk));
135};
136
137const LearnPathChunks = ['/tutorial', '/ui-programming/', '/additional-resources/'] as const;
138
139export const isLearnPath = (url: string) => {
140  return LearnPathChunks.some(pathChunk => url.includes(pathChunk));
141};
142
143export const isAppleDevice = () => {
144  return /(Mac|iPhone|iPod|iPad)/i.test(
145    navigator?.platform ?? navigator?.userAgentData?.platform ?? ''
146  );
147};
148
149export const addHighlight = (content: string, query: string) => {
150  const highlightStart = content.toLowerCase().indexOf(query.toLowerCase());
151
152  if (highlightStart === -1) return content;
153
154  const highlightEnd = highlightStart + query.length;
155  return (
156    content.substring(0, highlightStart) +
157    '<mark>' +
158    content.substring(highlightStart, highlightEnd) +
159    '</mark>' +
160    content.substring(highlightEnd)
161  );
162};
163