import axios from 'axios' import useSWR, { SWRResponse } from 'swr' import { Dispatch, Fragment, SetStateAction, useState } from 'react' import AwesomeDebouncePromise from 'awesome-debounce-promise' import { useAsync } from 'react-async-hook' import useConstant from 'use-constant' import Link from 'next/link' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Dialog, Transition } from '@headlessui/react' import type { OdDriveItem, OdSearchResult } from '../types' import { LoadingIcon } from './Loading' import { getFileIcon } from '../utils/getFileIcon' import { fetcher } from '../utils/fetchWithSWR' import siteConfig from '../config/site.config' /** * Extract the searched item's path in field 'parentReference' and convert it to the * absolute path represented in onedrive-vercel-index * * @param path Path returned from the parentReference field of the driveItem * @returns The absolute path of the driveItem in the search result */ function mapAbsolutePath(path: string): string { // path is in the format of '/drive/root:/path/to/file', if baseDirectory is '/' then we split on 'root:', // otherwise we split on the user defined 'baseDirectory' const absolutePath = path.split(siteConfig.baseDirectory === '/' ? 'root:' : siteConfig.baseDirectory)[1] // path returned by the API may contain #, by doing a decodeURIComponent and then encodeURIComponent we can // replace URL sensitive characters such as the # with %23 return absolutePath .split('/') .map(p => encodeURIComponent(decodeURIComponent(p))) .join('/') } /** * Implements a debounced search function that returns a promise that resolves to an array of * search results. * * @returns A react hook for a debounced async search of the drive */ function useDriveItemSearch() { const [query, setQuery] = useState('') const searchDriveItem = async (q: string) => { const { data } = await axios.get(`/api/search?q=${q}`) // Map parentReference to the absolute path of the search result data.map(item => { item['path'] = 'path' in item.parentReference ? // OneDrive International have the path returned in the parentReference field `${mapAbsolutePath(item.parentReference.path)}/${encodeURIComponent(item.name)}` : // OneDrive for Business/Education does not, so we need extra steps here '' }) return data } const debouncedDriveItemSearch = useConstant(() => AwesomeDebouncePromise(searchDriveItem, 1000)) const results = useAsync(async () => { if (query.length === 0) { return [] } else { return debouncedDriveItemSearch(query) } }, [query]) return { query, setQuery, results, } } function SearchResultItemTemplate({ driveItem, driveItemPath, itemDescription, disabled, }: { driveItem: OdSearchResult[number] driveItemPath: string itemDescription: string disabled: boolean }) { return (
{driveItem.name}
{itemDescription}
) } function SearchResultItemLoadRemote({ result }: { result: OdSearchResult[number] }) { const { data, error }: SWRResponse = useSWR(`/api/item?id=${result.id}`, fetcher) if (error) { return } if (!data) { return ( ) } const driveItemPath = `${mapAbsolutePath(data.parentReference.path)}/${encodeURIComponent(data.name)}` return ( ) } function SearchResultItem({ result }: { result: OdSearchResult[number] }) { if (result.path === '') { // path is empty, which means we need to fetch the parentReference to get the path return } else { // path is not an empty string in the search result, such that we can directly render the component as is const driveItemPath = decodeURIComponent(result.path) return ( ) } } export default function SearchModal({ searchOpen, setSearchOpen, }: { searchOpen: boolean setSearchOpen: Dispatch> }) { const { query, setQuery, results } = useDriveItemSearch() const closeSearchBox = () => { setSearchOpen(false) setQuery('') } return (
setQuery(e.target.value)} />
ESC
{results.loading && (
Loading ...
)} {results.error && (
Error: {results.error.message}
)} {results.result && ( <> {results.result.length === 0 ? (
Nothing here.
) : ( results.result.map(result => ) )} )}
) }