From 038b4288cb7829cf22dae88df2ad6010fc207506 Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Mon, 17 Jan 2022 00:08:04 +0800 Subject: [PATCH 1/4] search modal with mock data --- components/Navbar.tsx | 10 +++ components/SearchModal.tsx | 130 +++++++++++++++++++++++++++++++++++++ package-lock.json | 117 ++++++++++++++++++++++++++++++++- package.json | 5 +- pages/_app.tsx | 2 + 5 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 components/SearchModal.tsx diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 955e7cf..5b24c9e 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -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) { @@ -44,6 +48,8 @@ const Navbar = () => {
+ +
@@ -53,6 +59,10 @@ const Navbar = () => {
+ + {siteConfig.links.length !== 0 && siteConfig.links.map((l: { name: string; link: string }) => ( { + const [query, setQuery] = useState('') + const searchDriveItem = async (q: string) => { + // TODO: currently using mock data, change this to a query to OneDrive API + const result = await axios.get('https://jsonplaceholder.typicode.com/posts') + console.log(q, result.data) + + return result.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, + } +} + +const SearchModal: FC<{ + searchOpen: boolean + setSearchOpen: Dispatch> +}> = ({ searchOpen, setSearchOpen }) => { + const closeSearchBox = () => setSearchOpen(false) + + const { query, setQuery, results } = useDriveItemSearch() + + return ( + + +
+ + + + + +
+ +
+ +
+ setQuery(e.target.value)} + /> +
+ +
+ {results.loading && ( +
+
+ purr loading +
+
Loading ...
+
+ )} + {results.error && ( +
+ errored out +
Error: {results.error.message}
+
+ )} + {results.result && ( + <> + {results.result.length === 0 ? ( +
+ empty list +
Nothing here...
+
+ ) : ( + results.result.map((result: any, i: number) => ( + +
+
{result.title}
+
{result.body}
+
+ + )) + )} + + )} +
+
+
+
+
+
+ ) +} + +export default SearchModal diff --git a/package-lock.json b/package-lock.json index e32c36f..66f8251 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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,6 +25,7 @@ "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", @@ -36,7 +38,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 +622,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 +1147,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 +1848,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", @@ -5697,6 +5746,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", @@ -7202,6 +7263,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 +7867,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 +8266,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 +8808,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", @@ -11620,6 +11723,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", @@ -12741,6 +12850,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", diff --git a/package.json b/package.json index cb93127..eadb520 100644 --- a/package.json +++ b/package.json @@ -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,6 +26,7 @@ "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", @@ -37,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", diff --git a/pages/_app.tsx b/pages/_app.tsx index a707042..3795919 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -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 ) From 1b658b6a987b26007a0a6f69aed4162784aab0eb Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Fri, 21 Jan 2022 21:42:21 +0800 Subject: [PATCH 2/4] search with onedrive graph api --- components/SearchModal.tsx | 78 +++++++++++++++++++++++--------------- pages/api/index.ts | 4 +- pages/api/search.ts | 36 ++++++++++++++++++ types/index.d.ts | 31 +++++++++++++++ 4 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 pages/api/search.ts diff --git a/components/SearchModal.tsx b/components/SearchModal.tsx index b0bd5db..c04a7cc 100644 --- a/components/SearchModal.tsx +++ b/components/SearchModal.tsx @@ -10,14 +10,27 @@ import Link from 'next/link' import Image from 'next/image' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -const useDriveItemSearch = () => { +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) => { - // TODO: currently using mock data, change this to a query to OneDrive API - const result = await axios.get('https://jsonplaceholder.typicode.com/posts') - console.log(q, result.data) + const { data } = await axios.get(`/api/search?q=${q}`) - return result.data + // 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)) @@ -36,10 +49,13 @@ const useDriveItemSearch = () => { } } -const SearchModal: FC<{ +function SearchModal({ + searchOpen, + setSearchOpen, +}: { searchOpen: boolean setSearchOpen: Dispatch> -}> = ({ searchOpen, setSearchOpen }) => { +}) { const closeSearchBox = () => setSearchOpen(false) const { query, setQuery, results } = useDriveItemSearch() @@ -70,48 +86,50 @@ const SearchModal: FC<{ leaveTo="opacity-0 scale-95" >
- -
- -
+ + setQuery(e.target.value)} /> +
ESC
{results.loading && ( -
-
- purr loading -
-
Loading ...
+
+ + Loading ...
)} {results.error && ( -
- errored out -
Error: {results.error.message}
-
+
Error: {results.error.message}
)} {results.result && ( <> {results.result.length === 0 ? ( -
- empty list -
Nothing here...
-
+
Nothing here.
) : ( - results.result.map((result: any, i: number) => ( - -
-
{result.title}
-
{result.body}
+ results.result.map(result => ( + +
+ +
+
{result.name}
+
+ {decodeURIComponent(result.path)} +
+
)) diff --git a/pages/api/index.ts b/pages/api/index.ts index 025bc83..5547def 100644 --- a/pages/api/index.ts +++ b/pages/api/index.ts @@ -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 { +export async function getAccessToken(): Promise { const { accessToken, refreshToken } = await getOdAuthTokens() // Return in storage access token if it is still valid diff --git a/pages/api/search.ts b/pages/api/search.ts new file mode 100644 index 0000000..61e1853 --- /dev/null +++ b/pages/api/search.ts @@ -0,0 +1,36 @@ +import axios from 'axios' +import type { NextApiRequest, NextApiResponse } from 'next' + +import { encodePath, getAccessToken } from '.' +import apiConfig from '../../config/api.json' +import siteConfig from '../../config/site.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', + top: '10', + }, + }) + 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 +} diff --git a/types/index.d.ts b/types/index.d.ts index 827a55e..f93f54d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -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 + } +}> From c3daf370ecce490da880beb21bfcd439b568a436 Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Fri, 21 Jan 2022 22:12:07 +0800 Subject: [PATCH 3/4] layout stuff for mobile and desktop --- components/FileListing.tsx | 16 +++++++++++----- components/Navbar.tsx | 12 ++++++++---- components/SearchModal.tsx | 6 +++--- pages/api/search.ts | 2 -- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/components/FileListing.tsx b/components/FileListing.tsx index f0f82ba..8043f76 100644 --- a/components/FileListing.tsx +++ b/components/FileListing.tsx @@ -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 ( - - {/* eslint-disable-next-line @next/next/no-img-element */} - {fileName} - + <> + + {/* eslint-disable-next-line @next/next/no-img-element */} + {fileName} + + + + + ) case preview.text: diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 5b24c9e..8dbedd5 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -50,17 +50,21 @@ const Navbar = () => { -
+
- + icon {siteConfig.title} -
- {siteConfig.links.length !== 0 && diff --git a/components/SearchModal.tsx b/components/SearchModal.tsx index c04a7cc..dcf6196 100644 --- a/components/SearchModal.tsx +++ b/components/SearchModal.tsx @@ -62,7 +62,7 @@ function SearchModal({ return ( - +
-
+
ESC
-
+
{results.loading && (
diff --git a/pages/api/search.ts b/pages/api/search.ts index 61e1853..1a9cff6 100644 --- a/pages/api/search.ts +++ b/pages/api/search.ts @@ -3,7 +3,6 @@ import type { NextApiRequest, NextApiResponse } from 'next' import { encodePath, getAccessToken } from '.' import apiConfig from '../../config/api.json' -import siteConfig from '../../config/site.json' export default async function handler(req: NextApiRequest, res: NextApiResponse) { // Get access token from storage @@ -22,7 +21,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) headers: { Authorization: `Bearer ${accessToken}` }, params: { select: 'id,name,file,folder,parentReference', - top: '10', }, }) res.status(200).json(data.value) From 3d918fbd90e2d4c3d9d51d9e0039322cdeb236a1 Mon Sep 17 00:00:00 2001 From: spencerwooo Date: Sat, 22 Jan 2022 19:35:23 +0800 Subject: [PATCH 4/4] hot keys support for opening search modal --- components/Navbar.tsx | 21 ++++++++++++++++++--- components/SearchModal.tsx | 4 +--- package-lock.json | 31 +++++++++++++++++++++++++++++++ package.json | 1 + utils/useDeviceOS.ts | 21 +++++++++++++++++++++ 5 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 utils/useDeviceOS.ts diff --git a/components/Navbar.tsx b/components/Navbar.tsx index 8dbedd5..3c24540 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -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' @@ -10,15 +11,20 @@ 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) { @@ -60,11 +66,20 @@ const Navbar = () => {
{siteConfig.links.length !== 0 && diff --git a/components/SearchModal.tsx b/components/SearchModal.tsx index dcf6196..5b9e7ae 100644 --- a/components/SearchModal.tsx +++ b/components/SearchModal.tsx @@ -2,13 +2,11 @@ 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 Image from 'next/image' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { OdSearchResult } from '../types' import { getFileIcon } from '../utils/getFileIcon' diff --git a/package-lock.json b/package-lock.json index 66f8251..0797d6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "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", @@ -3446,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", @@ -5803,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", @@ -10034,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", @@ -11761,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", diff --git a/package.json b/package.json index eadb520..376f2de 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "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", diff --git a/utils/useDeviceOS.ts b/utils/useDeviceOS.ts new file mode 100644 index 0000000..232e03f --- /dev/null +++ b/utils/useDeviceOS.ts @@ -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 +}