Merge pull request #283 from spencerwooo/native-search

This commit is contained in:
Spencer Woo
2022-01-22 19:37:41 +08:00
committed by GitHub
10 changed files with 431 additions and 12 deletions
+11 -5
View File
@@ -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 })
@@ -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:
+32 -3
View File
@@ -2,6 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconName } from '@fortawesome/fontawesome-svg-core'
import { Dialog, Transition } from '@headlessui/react'
import toast, { Toaster } from 'react-hot-toast'
import { useHotkeys } from 'react-hotkeys-hook'
import Link from 'next/link'
import Image from 'next/image'
@@ -9,12 +10,21 @@ import { useRouter } from 'next/router'
import { Fragment, useEffect, useState } from 'react'
import siteConfig from '../config/site.json'
import SearchModal from './SearchModal'
import useDeviceOS from '../utils/useDeviceOS'
const Navbar = () => {
const router = useRouter()
const os = useDeviceOS()
const [tokenPresent, setTokenPresent] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const [searchOpen, setSearchOpen] = useState(false)
const openSearchBox = () => setSearchOpen(true)
useHotkeys(`${os === 'mac' ? 'cmd' : 'ctrl'}+k`, openSearchBox)
useEffect(() => {
const storedToken = () => {
for (const r of siteConfig.protectedRoutes) {
@@ -44,15 +54,34 @@ 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 justify-between rounded-lg bg-gray-100 dark:bg-gray-800 md:w-48 px-2.5 py-1.5 dark:text-white hover:opacity-80"
onClick={openSearchBox}
>
<div className="flex items-center space-x-2">
<FontAwesomeIcon icon="search" />
<span className="text-sm font-medium">Search ...</span>
</div>
<div className="flex items-center space-x-1">
<div className="px-2 py-1 rounded-lg bg-gray-100 dark:bg-gray-700 font-medium text-xs">
{os === 'mac' ? '⌘' : 'Ctrl'}
</div>
<div className="px-2 py-1 rounded-lg bg-gray-100 dark:bg-gray-700 font-medium text-xs">K</div>
</div>
</button>
{siteConfig.links.length !== 0 &&
siteConfig.links.map((l: { name: string; link: string }) => (
<a
+146
View File
@@ -0,0 +1,146 @@
import axios from 'axios'
import AwesomeDebouncePromise from 'awesome-debounce-promise'
import { useAsync } from 'react-async-hook'
import useConstant from 'use-constant'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Dispatch, FC, Fragment, SetStateAction, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import Link from 'next/link'
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
+147 -1
View File
@@ -14,6 +14,7 @@
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@headlessui/react": "^1.4.0",
"awesome-debounce-promise": "^2.1.0",
"axios": "^0.21.1",
"crypto-js": "^4.1.1",
"emoji-regex": "^9.2.2",
@@ -24,9 +25,11 @@
"preview-office-docs": "^1.0.2",
"prismjs": "^1.23.0",
"react": "^17.0.2",
"react-async-hook": "^4.0.0",
"react-copy-to-clipboard": "^5.0.3",
"react-dom": "^17.0.2",
"react-hot-toast": "^2.0.0",
"react-hotkeys-hook": "^3.4.4",
"react-markdown": "^6.0.2",
"react-player": "^2.9.0",
"react-reader": "^0.20.4",
@@ -36,7 +39,8 @@
"remark-gfm": "^1.0.0",
"remark-math": "^4.0.0",
"swr": "^0.5.6",
"use-clipboard-copy": "^0.2.0"
"use-clipboard-copy": "^0.2.0",
"use-constant": "^1.1.0"
},
"devDependencies": {
"@types/crypto-js": "^4.0.2",
@@ -619,6 +623,11 @@
"integrity": "sha512-sCVniU+h3GcGqxOmng11BRvf9TfN9yIs8KKjB8C8d75W69cpTfZG80gau9yTx5SxF3gvHGbJhdESzzvnjtf3Og==",
"dev": true
},
"node_modules/@types/debounce-promise": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/debounce-promise/-/debounce-promise-3.1.4.tgz",
"integrity": "sha512-9SEVY3nsz+uMN2DwDocftB5TAgZe7D0cOzxxRhpotWs6T4QFqRaTXpXbOSzbk31/7iYcfCkJJPwWGzTxyuGhCg=="
},
"node_modules/@types/hast": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
@@ -1139,6 +1148,42 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/awesome-debounce-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/awesome-debounce-promise/-/awesome-debounce-promise-2.1.0.tgz",
"integrity": "sha512-0Dv4j2wKk5BrNZh4jgV2HUdznaeVgEK/WTvcHhZWUElhmQ1RR+iURRoLEwICFyR0S/5VtxfcvY6gR+qSe95jNg==",
"dependencies": {
"@types/debounce-promise": "^3.1.1",
"awesome-imperative-promise": "^1.0.1",
"awesome-only-resolves-last-promise": "^1.0.3",
"debounce-promise": "^3.1.0"
},
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/awesome-imperative-promise": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/awesome-imperative-promise/-/awesome-imperative-promise-1.0.1.tgz",
"integrity": "sha512-EmPr3FqbQGqlNh+WxMNcF9pO9uDQJnOC4/3rLBQNH9m4E9qI+8lbfHCmHpVAsmGqPJPKhCjJLHUQzQW/RBHRdQ==",
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/awesome-only-resolves-last-promise": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/awesome-only-resolves-last-promise/-/awesome-only-resolves-last-promise-1.0.3.tgz",
"integrity": "sha512-7q4WPsYiD8Omvi/yHL314DkvsD/lM//Z2/KcU1vWk0xJotiV0GMJTgHTpWl3n90HJqpXKg7qX+VVNs5YbQyPRQ==",
"dependencies": {
"awesome-imperative-promise": "^1.0.1"
},
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/axe-core": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz",
@@ -1804,6 +1849,11 @@
"node": ">= 6"
}
},
"node_modules/debounce-promise": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz",
"integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg=="
},
"node_modules/debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
@@ -3397,6 +3447,11 @@
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"node_modules/hotkeys-js": {
"version": "3.8.7",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.8.7.tgz",
"integrity": "sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg=="
},
"node_modules/html-void-elements": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.0.tgz",
@@ -5697,6 +5752,18 @@
"node": ">=0.10.0"
}
},
"node_modules/react-async-hook": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-async-hook/-/react-async-hook-4.0.0.tgz",
"integrity": "sha512-97lgjFkOcHCTYSrsKBpsXg3iVWM0LnzedB749iP76sb3/8Ouu4nHIkCLEOrQWHVYqrYxjF05NN6GHoXWFkB3Kw==",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-copy-to-clipboard": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
@@ -5742,6 +5809,18 @@
"react-dom": ">=16"
}
},
"node_modules/react-hotkeys-hook": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz",
"integrity": "sha512-vaORq07rWgmuF3owWRhgFV/3VL8/l2q9lz0WyVEddJnWTtKW+AOgU5YgYKuwN6h6h7bCcLG3MFsJIjCrM/5DvQ==",
"dependencies": {
"hotkeys-js": "3.8.7"
},
"peerDependencies": {
"react": ">=16.8.1",
"react-dom": ">=16.8.1"
}
},
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -7202,6 +7281,14 @@
"react": ">=16.8.0"
}
},
"node_modules/use-constant": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/use-constant/-/use-constant-1.1.0.tgz",
"integrity": "sha512-yrflEfv7Xv/W8WlYV6nwRH01K+2BpR4cWxuzY03yPRjYZuHixhGlvnJN5O2bRYrXGpJ4zy8QjFABGIQ2QXeBOA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0"
}
},
"node_modules/use-subscription": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",
@@ -7798,6 +7885,11 @@
"integrity": "sha512-sCVniU+h3GcGqxOmng11BRvf9TfN9yIs8KKjB8C8d75W69cpTfZG80gau9yTx5SxF3gvHGbJhdESzzvnjtf3Og==",
"dev": true
},
"@types/debounce-promise": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/debounce-promise/-/debounce-promise-3.1.4.tgz",
"integrity": "sha512-9SEVY3nsz+uMN2DwDocftB5TAgZe7D0cOzxxRhpotWs6T4QFqRaTXpXbOSzbk31/7iYcfCkJJPwWGzTxyuGhCg=="
},
"@types/hast": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
@@ -8192,6 +8284,30 @@
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
},
"awesome-debounce-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/awesome-debounce-promise/-/awesome-debounce-promise-2.1.0.tgz",
"integrity": "sha512-0Dv4j2wKk5BrNZh4jgV2HUdznaeVgEK/WTvcHhZWUElhmQ1RR+iURRoLEwICFyR0S/5VtxfcvY6gR+qSe95jNg==",
"requires": {
"@types/debounce-promise": "^3.1.1",
"awesome-imperative-promise": "^1.0.1",
"awesome-only-resolves-last-promise": "^1.0.3",
"debounce-promise": "^3.1.0"
}
},
"awesome-imperative-promise": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/awesome-imperative-promise/-/awesome-imperative-promise-1.0.1.tgz",
"integrity": "sha512-EmPr3FqbQGqlNh+WxMNcF9pO9uDQJnOC4/3rLBQNH9m4E9qI+8lbfHCmHpVAsmGqPJPKhCjJLHUQzQW/RBHRdQ=="
},
"awesome-only-resolves-last-promise": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/awesome-only-resolves-last-promise/-/awesome-only-resolves-last-promise-1.0.3.tgz",
"integrity": "sha512-7q4WPsYiD8Omvi/yHL314DkvsD/lM//Z2/KcU1vWk0xJotiV0GMJTgHTpWl3n90HJqpXKg7qX+VVNs5YbQyPRQ==",
"requires": {
"awesome-imperative-promise": "^1.0.1"
}
},
"axe-core": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.3.tgz",
@@ -8710,6 +8826,11 @@
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og=="
},
"debounce-promise": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz",
"integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg=="
},
"debug": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
@@ -9931,6 +10052,11 @@
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"hotkeys-js": {
"version": "3.8.7",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.8.7.tgz",
"integrity": "sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg=="
},
"html-void-elements": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.0.tgz",
@@ -11620,6 +11746,12 @@
"object-assign": "^4.1.1"
}
},
"react-async-hook": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-async-hook/-/react-async-hook-4.0.0.tgz",
"integrity": "sha512-97lgjFkOcHCTYSrsKBpsXg3iVWM0LnzedB749iP76sb3/8Ouu4nHIkCLEOrQWHVYqrYxjF05NN6GHoXWFkB3Kw==",
"requires": {}
},
"react-copy-to-clipboard": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
@@ -11652,6 +11784,14 @@
"goober": "^2.0.35"
}
},
"react-hotkeys-hook": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz",
"integrity": "sha512-vaORq07rWgmuF3owWRhgFV/3VL8/l2q9lz0WyVEddJnWTtKW+AOgU5YgYKuwN6h6h7bCcLG3MFsJIjCrM/5DvQ==",
"requires": {
"hotkeys-js": "3.8.7"
}
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -12741,6 +12881,12 @@
"clipboard-copy": "^3.0.0"
}
},
"use-constant": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/use-constant/-/use-constant-1.1.0.tgz",
"integrity": "sha512-yrflEfv7Xv/W8WlYV6nwRH01K+2BpR4cWxuzY03yPRjYZuHixhGlvnJN5O2bRYrXGpJ4zy8QjFABGIQ2QXeBOA==",
"requires": {}
},
"use-subscription": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",
+5 -1
View File
@@ -15,6 +15,7 @@
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/react-fontawesome": "^0.1.14",
"@headlessui/react": "^1.4.0",
"awesome-debounce-promise": "^2.1.0",
"axios": "^0.21.1",
"crypto-js": "^4.1.1",
"emoji-regex": "^9.2.2",
@@ -25,9 +26,11 @@
"preview-office-docs": "^1.0.2",
"prismjs": "^1.23.0",
"react": "^17.0.2",
"react-async-hook": "^4.0.0",
"react-copy-to-clipboard": "^5.0.3",
"react-dom": "^17.0.2",
"react-hot-toast": "^2.0.0",
"react-hotkeys-hook": "^3.4.4",
"react-markdown": "^6.0.2",
"react-player": "^2.9.0",
"react-reader": "^0.20.4",
@@ -37,7 +40,8 @@
"remark-gfm": "^1.0.0",
"remark-math": "^4.0.0",
"swr": "^0.5.6",
"use-clipboard-copy": "^0.2.0"
"use-clipboard-copy": "^0.2.0",
"use-constant": "^1.1.0"
},
"devDependencies": {
"@types/crypto-js": "^4.0.2",
+2
View File
@@ -23,6 +23,7 @@ import {
faCheckCircle,
} from '@fortawesome/free-regular-svg-icons'
import {
faSearch,
faCheck,
faPlus,
faMinus,
@@ -92,6 +93,7 @@ library.add(
faHome,
faCheck,
faCheckCircle,
faSearch,
...iconList
)
+2 -2
View File
@@ -12,7 +12,7 @@ import { getOdAuthTokens, storeOdAuthTokens } from '../../utils/odAuthTokenStore
const basePath = pathPosix.resolve('/', siteConfig.baseDirectory)
const clientSecret = revealObfuscatedToken(apiConfig.obfuscatedClientSecret)
const encodePath = (path: string) => {
export function encodePath(path: string): string {
let encodedPath = pathPosix.join(basePath, pathPosix.resolve('/', path))
if (encodedPath === '/' || encodedPath === '') {
return ''
@@ -21,7 +21,7 @@ const encodePath = (path: string) => {
return `:${encodeURIComponent(encodedPath)}`
}
async function getAccessToken(): Promise<any> {
export async function getAccessToken(): Promise<string> {
const { accessToken, refreshToken } = await getOdAuthTokens()
// Return in storage access token if it is still valid
+34
View File
@@ -0,0 +1,34 @@
import axios from 'axios'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encodePath, getAccessToken } from '.'
import apiConfig from '../../config/api.json'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Get access token from storage
const accessToken = await getAccessToken()
// Query parameter from request
const { q: searchQuery = '' } = req.query
if (typeof searchQuery === 'string') {
// Construct Microsoft Graph Search API URL, and perform search only under the base dir
const encodedPath = encodePath('/') === '' ? encodePath('/') : encodePath('/') + ':'
const searchApi = `${apiConfig.driveApi}/root${encodedPath}/search(q='${encodeURIComponent(searchQuery)}')`
try {
const { data } = await axios.get(searchApi, {
headers: { Authorization: `Bearer ${accessToken}` },
params: {
select: 'id,name,file,folder,parentReference',
},
})
res.status(200).json(data.value)
} catch (error: any) {
res.status(error.response.status).json({ error: error.response.data })
}
} else {
res.status(200).json([])
}
return
}
+31
View File
@@ -13,3 +13,34 @@ export type OdFileObject = {
}
}
}
export type OdFolderObject = {
'@odata.count': number
value: Array<{
id: string
name: string
lastModifiedDateTime: string
size: number
folder: {
childCount: number
view: {
sortBy: 'name'
sortOrder: 'ascending'
viewType: 'thumbnails'
}
}
}>
}
export type OdSearchResult = Array<{
id: string
name: string
file?: OdFileObject
folder?: OdFolderObject
path: string
parentReference: {
id: string
name: string
path: string
}
}>
+21
View File
@@ -0,0 +1,21 @@
import { useEffect, useState } from 'react'
export default function useDeviceOS(): string {
const [os, setOS] = useState('')
useEffect(() => {
const userAgent = window.navigator.userAgent
if (userAgent.indexOf('Windows') > -1) {
setOS('windows')
} else if (userAgent.indexOf('Mac OS') > -1) {
setOS('mac')
} else if (userAgent.indexOf('Linux') > -1) {
setOS('linux')
} else {
setOS('other')
}
}, [])
return os
}