xref: /expo/docs/ui/components/CommandMenu/utils.ts (revision e31fcd00)
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 EASPathChunks = [
103  '/app-signing/',
104  '/build/',
105  '/build-reference/',
106  '/development/',
107  '/eas/',
108  '/eas/metadata/',
109  '/eas-update/',
110  '/submit/',
111] as const;
112
113export const isEASPath = (url: string) => {
114  for (const pathChunk of EASPathChunks) {
115    if (url.includes(pathChunk)) {
116      return true;
117    }
118  }
119  return false;
120};
121
122export const isAppleDevice = () => {
123  return /(Mac|iPhone|iPod|iPad)/i.test(
124    navigator?.platform ?? navigator?.userAgentData?.platform ?? ''
125  );
126};
127
128export const addHighlight = (content: string, query: string) => {
129  const highlightStart = content.toLowerCase().indexOf(query.toLowerCase());
130
131  if (highlightStart === -1) return content;
132
133  const highlightEnd = highlightStart + query.length;
134  return (
135    content.substring(0, highlightStart) +
136    '<mark>' +
137    content.substring(highlightStart, highlightEnd) +
138    '</mark>' +
139    content.substring(highlightEnd)
140  );
141};
142