Merge pull request #835 from spencerwooo/swo/housekeeping

This commit is contained in:
Spencer (Shangbo Wu) 2023-01-26 14:47:15 +08:00 committed by GitHub
commit b194a2c07d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1296 additions and 667 deletions

View file

@ -1,7 +1,7 @@
<div align="center"> <div align="center">
<img src="./public/header.png" alt="onedrive-vercel-index" /> <img src="./public/header.png" alt="onedrive-vercel-index" />
<h3><a href="https://drive.swo.moe">onedrive-vercel-index</a></h3> <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> <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" /> <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 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 ## 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! 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) [🧸 Please donate - 微信/支付宝](https://ovi.swo.moe/sponsor/ways) · [Patreon](https://www.patreon.com/spencerwoo) · [爱发电](https://afdian.net/@spencerwoo)
### Sponsors
*Your name will appear here if you sponsor or donate 😀*
## License ## License

View file

@ -8,11 +8,9 @@ const HomeCrumb = () => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<Link href="/"> <Link href="/" className="flex items-center">
<a className="flex items-center"> <FontAwesomeIcon className="h-3 w-3" icon={['far', 'flag']} />
<FontAwesomeIcon className="h-3 w-3" icon={['far', 'flag']} /> <span className="ml-2 font-medium">{t('Home')}</span>
<span className="ml-2 font-medium">{t('Home')}</span>
</a>
</Link> </Link>
) )
} }
@ -37,14 +35,11 @@ const Breadcrumb: React.FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
.map(p => encodeURIComponent(p)) .map(p => encodeURIComponent(p))
.join('/')}`} .join('/')}`}
passHref passHref
className={`ml-1 transition-all duration-75 hover:opacity-70 md:ml-3 ${
i == 0 && 'pointer-events-none opacity-80'
}`}
> >
<a {p}
className={`ml-1 transition-all duration-75 hover:opacity-70 md:ml-3 ${
i == 0 && 'pointer-events-none opacity-80'
}`}
>
{p}
</a>
</Link> </Link>
</li> </li>
))} ))}

View file

@ -370,7 +370,7 @@ const FileListing: FC<{ query?: ParsedUrlQuery }> = ({ query }) => {
{readmeFile && ( {readmeFile && (
<div className="mt-4"> <div className="mt-4">
<MarkdownPreview file={readmeFile} path={path} standalone={false} proxy={true} /> <MarkdownPreview file={readmeFile} path={path} standalone={false} />
</div> </div>
)} )}
</> </>

View file

@ -176,9 +176,7 @@ const FolderGridLayout = ({
</div> </div>
<Link href={getItemPath(c.name)} passHref> <Link href={getItemPath(c.name)} passHref>
<a> <GridItem c={c} path={getItemPath(c.name)} />
<GridItem c={c} path={getItemPath(c.name)} />
</a>
</Link> </Link>
</div> </div>
))} ))}

View file

@ -96,10 +96,12 @@ const FolderListLayout = ({
className="grid grid-cols-12 transition-all duration-100 hover:bg-gray-100 dark:hover:bg-gray-850" className="grid grid-cols-12 transition-all duration-100 hover:bg-gray-100 dark:hover:bg-gray-850"
key={c.id} key={c.id}
> >
<Link href={`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`} passHref> <Link
<a className="col-span-12 md:col-span-10"> href={`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`}
<FileListItem fileContent={c} /> passHref
</a> className="col-span-12 md:col-span-10"
>
<FileListItem fileContent={c} />
</Link> </Link>
{c.folder ? ( {c.folder ? (

View file

@ -188,10 +188,10 @@ export async function* traverseFolder(path: string): AsyncGenerator<TraverseItem
return { return {
i, i,
path, path,
data: await fetcher( data: await fetcher([
next ? `/api/?path=${path}&next=${next}` : `/api?path=${path}`, next ? `/api/?path=${path}&next=${next}` : `/api?path=${path}`,
hashedToken ?? undefined hashedToken ?? undefined,
).catch(error => ({ i, path, error })), ]).catch(error => ({ i, path, error })),
} }
} }

View file

@ -25,7 +25,7 @@ const Navbar = () => {
const [searchOpen, setSearchOpen] = useState(false) const [searchOpen, setSearchOpen] = useState(false)
const openSearchBox = () => setSearchOpen(true) const openSearchBox = () => setSearchOpen(true)
useHotkeys(`${os === 'mac' ? 'cmd' : 'ctrl'}+k`, e => { useHotkeys(`${os === 'mac' ? 'meta' : 'ctrl'}+k`, e => {
openSearchBox() openSearchBox()
e.preventDefault() e.preventDefault()
}) })
@ -64,11 +64,9 @@ const Navbar = () => {
<SearchModal searchOpen={searchOpen} setSearchOpen={setSearchOpen} /> <SearchModal searchOpen={searchOpen} setSearchOpen={setSearchOpen} />
<div className="mx-auto flex w-full items-center justify-between space-x-4 px-4 py-1"> <div className="mx-auto flex w-full items-center justify-between space-x-4 px-4 py-1">
<Link href="/" passHref> <Link href="/" passHref className="flex items-center space-x-2 py-2 hover:opacity-80 dark:text-white md:p-2">
<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 />
<Image src={siteConfig.icon} alt="icon" width="25" height="25" priority /> <span className="hidden font-bold sm:block">{siteConfig.title}</span>
<span className="hidden font-bold sm:block">{siteConfig.title}</span>
</a>
</Link> </Link>
<div className="flex flex-1 items-center space-x-4 text-gray-700 md:flex-initial"> <div className="flex flex-1 items-center space-x-4 text-gray-700 md:flex-initial">

View file

@ -90,31 +90,31 @@ function SearchResultItemTemplate({
disabled: boolean disabled: boolean
}) { }) {
return ( return (
<Link href={driveItemPath} passHref> <Link
<a href={driveItemPath}
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 ${ passHref
disabled ? 'pointer-events-none cursor-not-allowed' : 'cursor-pointer' 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> <FontAwesomeIcon icon={driveItem.file ? getFileIcon(driveItem.name) : ['far', 'folder']} />
<div className="text-sm font-medium leading-8">{driveItem.name}</div> <div>
<div <div className="text-sm font-medium leading-8">{driveItem.name}</div>
className={`overflow-hidden truncate font-mono text-xs opacity-60 ${ <div
itemDescription === 'Loading ...' && 'animate-pulse' className={`overflow-hidden truncate font-mono text-xs opacity-60 ${
}`} itemDescription === 'Loading ...' && 'animate-pulse'
> }`}
{itemDescription} >
</div> {itemDescription}
</div> </div>
</a> </div>
</Link> </Link>
) )
} }
function SearchResultItemLoadRemote({ result }: { result: OdSearchResult[number] }) { function SearchResultItemLoadRemote({ result }: { result: OdSearchResult[number] }) {
const { data, error }: SWRResponse<OdDriveItem, { status: number; message: any }> = useSWR( const { data, error }: SWRResponse<OdDriveItem, { status: number; message: any }> = useSWR(
`/api/item/?id=${result.id}`, [`/api/item/?id=${result.id}`],
fetcher fetcher
) )

View file

@ -9,8 +9,8 @@ import { useCookies, withCookies } from 'react-cookie'
// https://headlessui.dev/react/menu#integrating-with-next-js // https://headlessui.dev/react/menu#integrating-with-next-js
const CustomLink = ({ href, children, as, locale, ...props }): JSX.Element => { const CustomLink = ({ href, children, as, locale, ...props }): JSX.Element => {
return ( return (
<Link href={href} as={as} locale={locale}> <Link href={href} as={as} locale={locale} {...props}>
<a {...props}>{children}</a> {children}
</Link> </Link>
) )
} }

View file

@ -62,7 +62,7 @@ const EPUBPreview: FC<{ file: OdFileObject }> = ({ file }) => {
location={location} location={location}
locationChanged={onLocationChange} locationChanged={onLocationChange}
epubInitOptions={{ openAs: 'epub' }} epubInitOptions={{ openAs: 'epub' }}
epubOptions={{ flow: 'scrolled' }} epubOptions={{ flow: 'scrolled', allowPopups: true }}
/> />
</div> </div>
</div> </div>

View file

@ -20,16 +20,11 @@ const MarkdownPreview: FC<{
file: any file: any
path: string path: string
standalone?: boolean standalone?: boolean
proxy?: boolean }> = ({ file, path, standalone = true }) => {
}> = ({ file, path, standalone = true, proxy = false }) => {
// The parent folder of the markdown file, which is also the relative image folder // The parent folder of the markdown file, which is also the relative image folder
const parentPath = standalone ? path.substring(0, path.lastIndexOf('/')) : path const parentPath = standalone ? path.substring(0, path.lastIndexOf('/')) : path
const { const { response: content, error, validating } = useFileContent(`/api/raw/?path=${parentPath}/${file.name}`, path)
response: content,
error,
validating,
} = useFileContent(`/api/raw/?path=${parentPath}/${file.name}${proxy ? `&proxy=true` : ''}`, path)
const { t } = useTranslation() const { t } = useTranslation()
// Check if the image is relative path instead of a absolute url // Check if the image is relative path instead of a absolute url

9
i18next.d.ts vendored Normal file
View 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
}
}

View file

@ -11,27 +11,28 @@
"extract": "i18next" "extract": "i18next"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.35", "@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-regular-svg-icons": "^5.15.3", "@fortawesome/free-regular-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/free-solid-svg-icons": "^6.2.1",
"@fortawesome/react-fontawesome": "^0.1.14", "@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.3", "@headlessui/react": "^1.7.7",
"@tailwindcss/line-clamp": "^0.4.2", "@tailwindcss/line-clamp": "^0.4.2",
"awesome-debounce-promise": "^2.1.0", "awesome-debounce-promise": "^2.1.0",
"axios": "^1.1.2", "axios": "^1.2.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"csstype": "^3.1.1", "csstype": "^3.1.1",
"dayjs": "^1.11.5", "dayjs": "^1.11.7",
"emoji-regex": "^10.2.1", "emoji-regex": "^10.2.1",
"ioredis": "^5.2.3", "i18next": "^22.4.9",
"ioredis": "^5.3.0",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"mpegts.js": "^1.6.10", "mpegts.js": "^1.7.2",
"next": "^12.3.1", "next": "^13.1.5",
"next-i18next": "^12.1.0", "next-i18next": "^13.0.3",
"nextjs-progressbar": "^0.0.14", "nextjs-progressbar": "^0.0.16",
"plyr-react": "^5.1.0", "plyr-react": "^5.1.2",
"preview-office-docs": "^1.0.2", "preview-office-docs": "^1.0.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-async-hook": "^4.0.0", "react-async-hook": "^4.0.0",
@ -40,37 +41,38 @@
"react-copy-to-clipboard": "^5.0.3", "react-copy-to-clipboard": "^5.0.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hot-toast": "^2.4.0", "react-hot-toast": "^2.4.0",
"react-hotkeys-hook": "^3.4.7", "react-hotkeys-hook": "^4.3.2",
"react-markdown": "^8.0.3", "react-i18next": "^12.1.4",
"react-reader": "^0.21.3", "react-markdown": "^8.0.5",
"react-reader": "^1.0.2",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"react-use-system-theme": "^1.1.1", "react-use-system-theme": "^1.1.1",
"rehype-katex": "^6.0.2", "rehype-katex": "^6.0.2",
"rehype-raw": "^6.0.0", "rehype-raw": "^6.0.0",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"swr": "^1.3.0", "swr": "^2.0.1",
"use-clipboard-copy": "^0.2.0", "use-clipboard-copy": "^0.2.0",
"use-constant": "^1.1.1" "use-constant": "^1.1.1"
}, },
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.12", "@types/cors": "^2.8.13",
"@types/crypto-js": "^4.0.2", "@types/crypto-js": "^4.0.2",
"@types/node": "18.8.3", "@types/node": "18.11.18",
"@types/react": "18.0.21", "@types/react": "18.0.27",
"@types/react-copy-to-clipboard": "^5.0.4", "@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.10",
"@types/react-pdf": "^5.0.4", "@types/react-pdf": "^6.2.0",
"@types/react-syntax-highlighter": "^15.5.5", "@types/react-syntax-highlighter": "^15.5.6",
"autoprefixer": "^10.4.12", "autoprefixer": "^10.4.13",
"eslint": "8.25.0", "eslint": "8.32.0",
"eslint-config-next": "12.3.1", "eslint-config-next": "13.1.5",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.6.0",
"i18next-parser": "^6.6.0", "i18next-parser": "^7.6.0",
"postcss": "^8.4.17", "postcss": "^8.4.21",
"prettier": "^2.7.1", "prettier": "^2.8.3",
"prettier-plugin-tailwindcss": "^0.1.13", "prettier-plugin-tailwindcss": "^0.2.2",
"tailwindcss": "^3.1.8", "tailwindcss": "^3.2.4",
"typescript": "4.8.4" "typescript": "4.9.4"
} }
} }

View file

@ -1,12 +1,13 @@
import { config } from '@fortawesome/fontawesome-svg-core'
import '@fortawesome/fontawesome-svg-core/styles.css' import '@fortawesome/fontawesome-svg-core/styles.css'
import '../styles/globals.css' import '../styles/globals.css'
import '../styles/markdown-github.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 config.autoAddCss = false
import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faFileImage, faFileImage,
faFilePdf, faFilePdf,

View file

@ -9,7 +9,7 @@ class MyDocument extends Document {
<meta name="description" content="OneDrive Vercel Index" /> <meta name="description" content="OneDrive Vercel Index" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <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 => ( {siteConfig.googleFontLinks.map(link => (
<link key={link} rel="stylesheet" href={link} /> <link key={link} rel="stylesheet" href={link} />
))} ))}

View file

@ -1,7 +1,7 @@
import { posix as pathPosix } from 'path' import { posix as pathPosix } from 'path'
import type { NextApiRequest, NextApiResponse } from 'next' import type { NextApiRequest, NextApiResponse } from 'next'
import axios from 'axios' import axios, { AxiosResponseHeaders } from 'axios'
import Cors from 'cors' import Cors from 'cors'
import { driveApi, cacheControlHeader } from '../../config/api.config' import { driveApi, cacheControlHeader } from '../../config/api.config'
@ -77,7 +77,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}) })
headers['Cache-Control'] = cacheControlHeader headers['Cache-Control'] = cacheControlHeader
// Send data stream as response // Send data stream as response
res.writeHead(200, headers) res.writeHead(200, headers as AxiosResponseHeaders)
stream.pipe(res) stream.pipe(res)
} else { } else {
res.redirect(data['@microsoft.graph.downloadUrl']) res.redirect(data['@microsoft.graph.downloadUrl'])

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ import type { OdAPIResponse } from '../types'
import { getStoredToken } from './protectedRouteHandler' import { getStoredToken } from './protectedRouteHandler'
// Common axios fetch function for use with useSWR // 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 { try {
return ( return (
await (token await (token

View file

@ -97,11 +97,13 @@ const extensions = {
* To stop TypeScript complaining about indexing the object with a non-existent key * To stop TypeScript complaining about indexing the object with a non-existent key
* https://dev.to/mapleleaf/indexing-objects-in-typescript-1cgi * 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 obj Object with keys to index
* @param key The index key * @param key The index key
* @returns Whether or not the key exists inside the object * @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 return key in obj
} }