Skip to content
This repository has been archived by the owner on Jul 26, 2023. It is now read-only.

Search #283

Merged
merged 4 commits into from
Jan 22, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions components/FileListing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import VideoPreview from './previews/VideoPreview'
import PDFPreview from './previews/PDFPreview'
import URLPreview from './previews/URLPreview'
import DefaultPreview from './previews/DefaultPreview'
import { PreviewContainer } from './previews/Containers'
import { DownloadBtnContainer, PreviewContainer } from './previews/Containers'
import DownloadButtonGroup from './DownloadBtnGtoup'

// Disabling SSR for some previews (image gallery view, and PDF view)
const ReactViewer = dynamic(() => import('react-viewer'), { ssr: false })
Expand Down Expand Up @@ -545,10 +546,15 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
switch (extensions[fileExtension]) {
case preview.image:
return (
<PreviewContainer>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img className="mx-auto" src={downloadUrl} alt={fileName} />
</PreviewContainer>
<>
<PreviewContainer>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img className="mx-auto" src={downloadUrl} alt={fileName} />
</PreviewContainer>
<DownloadBtnContainer>
<DownloadButtonGroup downloadUrl={file['@microsoft.graph.downloadUrl']} />
</DownloadBtnContainer>
</>
)

case preview.text:
Expand Down
20 changes: 17 additions & 3 deletions components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import { useRouter } from 'next/router'
import { Fragment, useEffect, useState } from 'react'

import siteConfig from '../config/site.json'
import SearchModal from './SearchModal'

const Navbar = () => {
const router = useRouter()
const [tokenPresent, setTokenPresent] = useState(false)
const [isOpen, setIsOpen] = useState(false)

const [searchOpen, setSearchOpen] = useState(false)
const openSearchBox = () => setSearchOpen(true)

useEffect(() => {
const storedToken = () => {
for (const r of siteConfig.protectedRoutes) {
Expand Down Expand Up @@ -44,15 +48,25 @@ const Navbar = () => {
<div className="bg-white dark:bg-gray-900 dark:border-gray-500/30 sticky top-0 bg-opacity-80 border-b border-gray-900/10 backdrop-blur-md z-[100]">
<Toaster />

<div className="flex items-center justify-between w-full mx-auto px-4 py-1">
<SearchModal searchOpen={searchOpen} setSearchOpen={setSearchOpen} />

<div className="flex items-center space-x-4 justify-between w-full mx-auto px-4 py-1">
<Link href="/" passHref>
<a className="dark:text-white hover:opacity-80 flex items-center p-2 space-x-2">
<a className="dark:text-white hover:opacity-80 flex items-center py-2 md:p-2 space-x-2">
<Image src={siteConfig.icon} alt="icon" width="25" height="25" priority />
<span className="sm:block hidden font-bold">{siteConfig.title}</span>
</a>
</Link>

<div className="flex items-center space-x-4 text-gray-700">
<div className="flex items-center flex-1 md:flex-initial space-x-4 text-gray-700">
<button
className="flex-1 flex items-center space-x-2 rounded-lg bg-gray-100 dark:bg-gray-800 md:w-40 px-2.5 py-1.5 dark:text-white hover:opacity-80"
onClick={openSearchBox}
>
<FontAwesomeIcon icon="search" />
<span className="text-sm font-medium">Search ...</span>
</button>

{siteConfig.links.length !== 0 &&
siteConfig.links.map((l: { name: string; link: string }) => (
<a
Expand Down
148 changes: 148 additions & 0 deletions components/SearchModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import axios from 'axios'
import AwesomeDebouncePromise from 'awesome-debounce-promise'
import { useAsync } from 'react-async-hook'
import useConstant from 'use-constant'

import { Dispatch, FC, Fragment, SetStateAction, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'

import Link from 'next/link'
import Image from 'next/image'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'

import { OdSearchResult } from '../types'
import { getFileIcon } from '../utils/getFileIcon'
import siteConfig from '../config/site.json'
import { LoadingIcon } from './Loading'

function useDriveItemSearch() {
const [query, setQuery] = useState('')
const searchDriveItem = async (q: string) => {
const { data } = await axios.get<OdSearchResult>(`/api/search?q=${q}`)

// Extract the searched item's path and convert it to the absolute path in onedrive-vercel-index
function mapAbsolutePath(path: string): string {
return siteConfig.baseDirectory === '/' ? path.split('root:')[1] : path.split(siteConfig.baseDirectory)[1]
}

// Map parentReference to the absolute path of the search result
data.map(item => {
item['path'] = `${mapAbsolutePath(item.parentReference.path)}/${encodeURIComponent(item.name)}`
})

return data
}

const debouncedNotionSearch = useConstant(() => AwesomeDebouncePromise(searchDriveItem, 1000))
const results = useAsync(async () => {
if (query.length === 0) {
return []
} else {
return debouncedNotionSearch(query)
}
}, [query])

return {
query,
setQuery,
results,
}
}

function SearchModal({
searchOpen,
setSearchOpen,
}: {
searchOpen: boolean
setSearchOpen: Dispatch<SetStateAction<boolean>>
}) {
const closeSearchBox = () => setSearchOpen(false)

const { query, setQuery, results } = useDriveItemSearch()

return (
<Transition appear show={searchOpen} as={Fragment}>
<Dialog as="div" className="inset-0 z-[200] fixed overflow-y-auto" onClose={closeSearchBox}>
<div className="min-h-screen text-center px-4">
<Transition.Child
as={Fragment}
enter="ease-out duration-100"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="bg-white/80 inset-0 fixed dark:bg-gray-800/80" />
</Transition.Child>

<Transition.Child
as={Fragment}
enter="ease-out duration-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-100"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="border rounded border-gray-400/30 shadow-xl my-12 text-left w-full max-w-3xl transform transition-all inline-block overflow-hidden">
<Dialog.Title
as="h3"
className="flex items-center space-x-4 dark:text-white border-b bg-gray-50 border-gray-400/30 p-4 dark:bg-gray-800"
>
<FontAwesomeIcon icon="search" className="w-4 h-4" />
<input
type="text"
id="search-box"
className="w-full bg-transparent focus:outline-none focus-visible:outline-none"
placeholder="Search ..."
value={query}
onChange={e => setQuery(e.target.value)}
/>
<div className="px-2 py-1 rounded-lg bg-gray-100 dark:bg-gray-700 font-medium text-xs">ESC</div>
</Dialog.Title>

<div className="bg-white dark:text-white dark:bg-gray-900 max-h-[80vh] overflow-x-hidden overflow-y-scroll">
{results.loading && (
<div className="text-center px-4 py-12 text-sm font-medium">
<LoadingIcon className="animate-spin w-4 h-4 mr-2 inline-block svg-inline--fa" />
<span>Loading ...</span>
</div>
)}
{results.error && (
<div className="text-center px-4 py-12 text-sm font-medium">Error: {results.error.message}</div>
)}
{results.result && (
<>
{results.result.length === 0 ? (
<div className="text-center px-4 py-12 text-sm font-medium">Nothing here.</div>
) : (
results.result.map(result => (
<Link href={result.path} key={result.id} passHref>
<div
className="flex items-center space-x-4 border-b cursor-pointer border-gray-400/30 px-4 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-850"
onClick={closeSearchBox}
>
<FontAwesomeIcon icon={result.file ? getFileIcon(result.name) : ['far', 'folder']} />
<div>
<div className="text-sm font-medium leading-8">{result.name}</div>
<div className="text-xs font-mono opacity-60 truncate overflow-hidden">
{decodeURIComponent(result.path)}
</div>
</div>
</div>
</Link>
))
)}
</>
)}
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
)
}

export default SearchModal
Loading