Merge pull request #835 from spencerwooo/swo/housekeeping
This commit is contained in:
commit
b194a2c07d
10
README.md
10
README.md
|
@ -1,7 +1,7 @@
|
|||
<div align="center">
|
||||
<img src="./public/header.png" alt="onedrive-vercel-index" />
|
||||
<h3><a href="https://drive.swo.moe">onedrive-vercel-index</a></h3>
|
||||
<p><a href="https://ovi.swo.moe/docs/getting-started">Get started</a> · <a href="https://ovi.swo.moe/blog/whats-new">What's new?</a> · <a href="https://ovi.swo.moe/sponsor">Sponsoring</a></p>
|
||||
<p><a href="https://ovi.swo.moe/docs/getting-started">Get started</a> · <a href="https://ovi.swo.moe/blog/whats-new">What's new?</a> · <a href="https://ovi.swo.moe/sponsor/ways">Sponsoring</a></p>
|
||||
<p><em>OneDrive public directory listing, powered by Vercel and Next.js</em></p>
|
||||
|
||||
<img src="https://img.shields.io/badge/OneDrive-2C68C3?style=flat&logo=microsoft-onedrive&logoColor=white" alt="OneDrive" />
|
||||
|
@ -32,7 +32,7 @@ Please go to our [discussion forum](https://github.com/spencerwooo/onedrive-verc
|
|||
|
||||
*If you happen to like this project, please give it a star!* :3
|
||||
|
||||
*If you really, really like this project, please send money! -> [Sponsors 🤑 and donations 💰](https://ovi.swo.moe/sponsor)*
|
||||
*If you really, really like this project, please send money! -> [Sponsors 🤑 and donations 💰](https://ovi.swo.moe/sponsor/ways)*
|
||||
|
||||
## Demo
|
||||
|
||||
|
@ -134,11 +134,7 @@ Yes! Completely free with no backend server what-so-ever. (Well, we use Redis, b
|
|||
|
||||
Open-source is hard! If you happen to like this project and want me to keep going, please consider sponsoring me or providing a single donation! Thanks for all the love and support!
|
||||
|
||||
[🧸 Please donate - 微信/支付宝](https://ovi.swo.moe/sponsor) · [Patreon](https://www.patreon.com/spencerwoo) · [爱发电](https://afdian.net/@spencerwoo)
|
||||
|
||||
### Sponsors
|
||||
|
||||
*Your name will appear here if you sponsor or donate 😀*
|
||||
[🧸 Please donate - 微信/支付宝](https://ovi.swo.moe/sponsor/ways) · [Patreon](https://www.patreon.com/spencerwoo) · [爱发电](https://afdian.net/@spencerwoo)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -8,11 +8,9 @@ const HomeCrumb = () => {
|
|||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Link href="/">
|
||||
<a className="flex items-center">
|
||||
<FontAwesomeIcon className="h-3 w-3" icon={['far', 'flag']} />
|
||||
<span className="ml-2 font-medium">{t('Home')}</span>
|
||||
</a>
|
||||
<Link href="/" className="flex items-center">
|
||||
<FontAwesomeIcon className="h-3 w-3" icon={['far', 'flag']} />
|
||||
<span className="ml-2 font-medium">{t('Home')}</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
@ -37,14 +35,11 @@ const Breadcrumb: React.FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
|
|||
.map(p => encodeURIComponent(p))
|
||||
.join('/')}`}
|
||||
passHref
|
||||
className={`ml-1 transition-all duration-75 hover:opacity-70 md:ml-3 ${
|
||||
i == 0 && 'pointer-events-none opacity-80'
|
||||
}`}
|
||||
>
|
||||
<a
|
||||
className={`ml-1 transition-all duration-75 hover:opacity-70 md:ml-3 ${
|
||||
i == 0 && 'pointer-events-none opacity-80'
|
||||
}`}
|
||||
>
|
||||
{p}
|
||||
</a>
|
||||
{p}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
|
|
|
@ -370,7 +370,7 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
|
|||
|
||||
{readmeFile && (
|
||||
<div className="mt-4">
|
||||
<MarkdownPreview file={readmeFile} path={path} standalone={false} proxy={true} />
|
||||
<MarkdownPreview file={readmeFile} path={path} standalone={false} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -176,9 +176,7 @@ const FolderGridLayout = ({
|
|||
</div>
|
||||
|
||||
<Link href={getItemPath(c.name)} passHref>
|
||||
<a>
|
||||
<GridItem c={c} path={getItemPath(c.name)} />
|
||||
</a>
|
||||
<GridItem c={c} path={getItemPath(c.name)} />
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -96,10 +96,12 @@ const FolderListLayout = ({
|
|||
className="grid grid-cols-12 transition-all duration-100 hover:bg-gray-100 dark:hover:bg-gray-850"
|
||||
key={c.id}
|
||||
>
|
||||
<Link href={`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`} passHref>
|
||||
<a className="col-span-12 md:col-span-10">
|
||||
<FileListItem fileContent={c} />
|
||||
</a>
|
||||
<Link
|
||||
href={`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`}
|
||||
passHref
|
||||
className="col-span-12 md:col-span-10"
|
||||
>
|
||||
<FileListItem fileContent={c} />
|
||||
</Link>
|
||||
|
||||
{c.folder ? (
|
||||
|
|
|
@ -188,10 +188,10 @@ export async function* traverseFolder(path: string): AsyncGenerator<TraverseItem
|
|||
return {
|
||||
i,
|
||||
path,
|
||||
data: await fetcher(
|
||||
data: await fetcher([
|
||||
next ? `/api/?path=${path}&next=${next}` : `/api?path=${path}`,
|
||||
hashedToken ?? undefined
|
||||
).catch(error => ({ i, path, error })),
|
||||
hashedToken ?? undefined,
|
||||
]).catch(error => ({ i, path, error })),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ const Navbar = () => {
|
|||
const [searchOpen, setSearchOpen] = useState(false)
|
||||
const openSearchBox = () => setSearchOpen(true)
|
||||
|
||||
useHotkeys(`${os === 'mac' ? 'cmd' : 'ctrl'}+k`, e => {
|
||||
useHotkeys(`${os === 'mac' ? 'meta' : 'ctrl'}+k`, e => {
|
||||
openSearchBox()
|
||||
e.preventDefault()
|
||||
})
|
||||
|
@ -64,11 +64,9 @@ const Navbar = () => {
|
|||
<SearchModal searchOpen={searchOpen} setSearchOpen={setSearchOpen} />
|
||||
|
||||
<div className="mx-auto flex w-full items-center justify-between space-x-4 px-4 py-1">
|
||||
<Link href="/" passHref>
|
||||
<a className="flex items-center space-x-2 py-2 hover:opacity-80 dark:text-white md:p-2">
|
||||
<Image src={siteConfig.icon} alt="icon" width="25" height="25" priority />
|
||||
<span className="hidden font-bold sm:block">{siteConfig.title}</span>
|
||||
</a>
|
||||
<Link href="/" passHref className="flex items-center space-x-2 py-2 hover:opacity-80 dark:text-white md:p-2">
|
||||
<Image src={siteConfig.icon} alt="icon" width="25" height="25" priority />
|
||||
<span className="hidden font-bold sm:block">{siteConfig.title}</span>
|
||||
</Link>
|
||||
|
||||
<div className="flex flex-1 items-center space-x-4 text-gray-700 md:flex-initial">
|
||||
|
|
|
@ -90,31 +90,31 @@ function SearchResultItemTemplate({
|
|||
disabled: boolean
|
||||
}) {
|
||||
return (
|
||||
<Link href={driveItemPath} passHref>
|
||||
<a
|
||||
className={`flex items-center space-x-4 border-b border-gray-400/30 px-4 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-850 ${
|
||||
disabled ? 'pointer-events-none cursor-not-allowed' : 'cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={driveItem.file ? getFileIcon(driveItem.name) : ['far', 'folder']} />
|
||||
<div>
|
||||
<div className="text-sm font-medium leading-8">{driveItem.name}</div>
|
||||
<div
|
||||
className={`overflow-hidden truncate font-mono text-xs opacity-60 ${
|
||||
itemDescription === 'Loading ...' && 'animate-pulse'
|
||||
}`}
|
||||
>
|
||||
{itemDescription}
|
||||
</div>
|
||||
<Link
|
||||
href={driveItemPath}
|
||||
passHref
|
||||
className={`flex items-center space-x-4 border-b border-gray-400/30 px-4 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-850 ${
|
||||
disabled ? 'pointer-events-none cursor-not-allowed' : 'cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={driveItem.file ? getFileIcon(driveItem.name) : ['far', 'folder']} />
|
||||
<div>
|
||||
<div className="text-sm font-medium leading-8">{driveItem.name}</div>
|
||||
<div
|
||||
className={`overflow-hidden truncate font-mono text-xs opacity-60 ${
|
||||
itemDescription === 'Loading ...' && 'animate-pulse'
|
||||
}`}
|
||||
>
|
||||
{itemDescription}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchResultItemLoadRemote({ result }: { result: OdSearchResult[number] }) {
|
||||
const { data, error }: SWRResponse<OdDriveItem, { status: number; message: any }> = useSWR(
|
||||
`/api/item/?id=${result.id}`,
|
||||
[`/api/item/?id=${result.id}`],
|
||||
fetcher
|
||||
)
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ import { useCookies, withCookies } from 'react-cookie'
|
|||
// https://headlessui.dev/react/menu#integrating-with-next-js
|
||||
const CustomLink = ({ href, children, as, locale, ...props }): JSX.Element => {
|
||||
return (
|
||||
<Link href={href} as={as} locale={locale}>
|
||||
<a {...props}>{children}</a>
|
||||
<Link href={href} as={as} locale={locale} {...props}>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ const EPUBPreview: FC<{ file: OdFileObject }> = ({ file }) => {
|
|||
location={location}
|
||||
locationChanged={onLocationChange}
|
||||
epubInitOptions={{ openAs: 'epub' }}
|
||||
epubOptions={{ flow: 'scrolled' }}
|
||||
epubOptions={{ flow: 'scrolled', allowPopups: true }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,16 +20,11 @@ const MarkdownPreview: FC<{
|
|||
file: any
|
||||
path: string
|
||||
standalone?: boolean
|
||||
proxy?: boolean
|
||||
}> = ({ file, path, standalone = true, proxy = false }) => {
|
||||
}> = ({ file, path, standalone = true }) => {
|
||||
// The parent folder of the markdown file, which is also the relative image folder
|
||||
const parentPath = standalone ? path.substring(0, path.lastIndexOf('/')) : path
|
||||
|
||||
const {
|
||||
response: content,
|
||||
error,
|
||||
validating,
|
||||
} = useFileContent(`/api/raw/?path=${parentPath}/${file.name}${proxy ? `&proxy=true` : ''}`, path)
|
||||
const { response: content, error, validating } = useFileContent(`/api/raw/?path=${parentPath}/${file.name}`, path)
|
||||
const { t } = useTranslation()
|
||||
|
||||
// Check if the image is relative path instead of a absolute url
|
||||
|
|
9
i18next.d.ts
vendored
Normal file
9
i18next.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'i18next'
|
||||
|
||||
declare module 'i18next' {
|
||||
interface CustomTypeOptions {
|
||||
// This is set to prevent i18next's t function to return null
|
||||
// https://github.com/i18next/next-i18next/issues/2038
|
||||
returnNull: false
|
||||
}
|
||||
}
|
70
package.json
70
package.json
|
@ -11,27 +11,28 @@
|
|||
"extract": "i18next"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||
"@headlessui/react": "^1.7.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.7",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"awesome-debounce-promise": "^2.1.0",
|
||||
"axios": "^1.1.2",
|
||||
"axios": "^1.2.4",
|
||||
"cors": "^2.8.5",
|
||||
"crypto-js": "^4.1.1",
|
||||
"csstype": "^3.1.1",
|
||||
"dayjs": "^1.11.5",
|
||||
"dayjs": "^1.11.7",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"ioredis": "^5.2.3",
|
||||
"i18next": "^22.4.9",
|
||||
"ioredis": "^5.3.0",
|
||||
"jszip": "^3.10.1",
|
||||
"mpegts.js": "^1.6.10",
|
||||
"next": "^12.3.1",
|
||||
"next-i18next": "^12.1.0",
|
||||
"nextjs-progressbar": "^0.0.14",
|
||||
"plyr-react": "^5.1.0",
|
||||
"mpegts.js": "^1.7.2",
|
||||
"next": "^13.1.5",
|
||||
"next-i18next": "^13.0.3",
|
||||
"nextjs-progressbar": "^0.0.16",
|
||||
"plyr-react": "^5.1.2",
|
||||
"preview-office-docs": "^1.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-async-hook": "^4.0.0",
|
||||
|
@ -40,37 +41,38 @@
|
|||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.0",
|
||||
"react-hotkeys-hook": "^3.4.7",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-reader": "^0.21.3",
|
||||
"react-hotkeys-hook": "^4.3.2",
|
||||
"react-i18next": "^12.1.4",
|
||||
"react-markdown": "^8.0.5",
|
||||
"react-reader": "^1.0.2",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-use-system-theme": "^1.1.1",
|
||||
"rehype-katex": "^6.0.2",
|
||||
"rehype-raw": "^6.0.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"swr": "^1.3.0",
|
||||
"swr": "^2.0.1",
|
||||
"use-clipboard-copy": "^0.2.0",
|
||||
"use-constant": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/crypto-js": "^4.0.2",
|
||||
"@types/node": "18.8.3",
|
||||
"@types/react": "18.0.21",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/react": "18.0.27",
|
||||
"@types/react-copy-to-clipboard": "^5.0.4",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-pdf": "^5.0.4",
|
||||
"@types/react-syntax-highlighter": "^15.5.5",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"i18next-parser": "^6.6.0",
|
||||
"postcss": "^8.4.17",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-tailwindcss": "^0.1.13",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"typescript": "4.8.4"
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/react-pdf": "^6.2.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "8.32.0",
|
||||
"eslint-config-next": "13.1.5",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"i18next-parser": "^7.6.0",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-tailwindcss": "^0.2.2",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "4.9.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { config } from '@fortawesome/fontawesome-svg-core'
|
||||
import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||
|
||||
import '../styles/globals.css'
|
||||
import '../styles/markdown-github.css'
|
||||
|
||||
// Require had to be used to prevent SSR failure in Next.js
|
||||
// Related discussion: https://github.com/FortAwesome/Font-Awesome/issues/19348
|
||||
const { library, config } = require('@fortawesome/fontawesome-svg-core')
|
||||
config.autoAddCss = false
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faFileImage,
|
||||
faFilePdf,
|
||||
|
|
|
@ -9,7 +9,7 @@ class MyDocument extends Document {
|
|||
<meta name="description" content="OneDrive Vercel Index" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
||||
{siteConfig.googleFontLinks.map(link => (
|
||||
<link key={link} rel="stylesheet" href={link} />
|
||||
))}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { posix as pathPosix } from 'path'
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import axios from 'axios'
|
||||
import axios, { AxiosResponseHeaders } from 'axios'
|
||||
import Cors from 'cors'
|
||||
|
||||
import { driveApi, cacheControlHeader } from '../../config/api.config'
|
||||
|
@ -77,7 +77,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
})
|
||||
headers['Cache-Control'] = cacheControlHeader
|
||||
// Send data stream as response
|
||||
res.writeHead(200, headers)
|
||||
res.writeHead(200, headers as AxiosResponseHeaders)
|
||||
stream.pipe(res)
|
||||
} else {
|
||||
res.redirect(data['@microsoft.graph.downloadUrl'])
|
||||
|
|
1755
pnpm-lock.yaml
1755
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,7 @@ import type { OdAPIResponse } from '../types'
|
|||
import { getStoredToken } from './protectedRouteHandler'
|
||||
|
||||
// Common axios fetch function for use with useSWR
|
||||
export async function fetcher(url: string, token?: string): Promise<any> {
|
||||
export async function fetcher([url, token]: [url: string, token?: string]): Promise<any> {
|
||||
try {
|
||||
return (
|
||||
await (token
|
||||
|
|
|
@ -97,11 +97,13 @@ const extensions = {
|
|||
* To stop TypeScript complaining about indexing the object with a non-existent key
|
||||
* https://dev.to/mapleleaf/indexing-objects-in-typescript-1cgi
|
||||
*
|
||||
* Fixed by ChatGPT with the upgrade of TypeScript 4.9
|
||||
*
|
||||
* @param obj Object with keys to index
|
||||
* @param key The index key
|
||||
* @returns Whether or not the key exists inside the object
|
||||
*/
|
||||
export function hasKey<O>(obj: O, key: PropertyKey): key is keyof O {
|
||||
export function hasKey(obj: Record<string, any>, key: string): boolean {
|
||||
return key in obj
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue