mirror of
https://github.com/Nezumi-2711/onedrive-vercel-index.git
synced 2026-09-22 13:38:45 +00:00
add thumbnail api and update grid view thumbnail fetching
This commit is contained in:
@@ -11,9 +11,20 @@ import { formatModifiedDateTime } from '../utils/fileDetails'
|
||||
import { getReadablePath } from '../utils/getReadablePath'
|
||||
import { Checkbox, ChildIcon, Downloading, formatChildName } from './FileListing'
|
||||
|
||||
const GridItem = ({ c }: { c: OdFolderChildren }) => {
|
||||
const GridItem = ({ c, path }: { c: OdFolderChildren; path: string }) => {
|
||||
// We use the generated medium thumbnail for rendering preview images
|
||||
const thumbnail = c.thumbnails && c.thumbnails.length > 0 ? c.thumbnails[0].medium : null
|
||||
const thumbnailUrl =
|
||||
'folder' in c
|
||||
? // Folders don't have thumbnails
|
||||
null
|
||||
: c.thumbnails
|
||||
? // Most OneDrive versions, including E5 developer, should have thumbnails returned
|
||||
c.thumbnails.length > 0
|
||||
? c.thumbnails[0].medium.url
|
||||
: null
|
||||
: // According to OneDrive docs, OneDrive for Business and SharePoint does not
|
||||
// (can not retrieve thumbnails via expand). But currently we only see OneDrive 世纪互联 really does not.
|
||||
`/api/thumbnail?path=${path}`
|
||||
|
||||
// Some thumbnails are broken, so we check for onerror event in the image component
|
||||
const [brokenThumbnail, setBrokenThumbnail] = useState(false)
|
||||
@@ -21,11 +32,11 @@ const GridItem = ({ c }: { c: OdFolderChildren }) => {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="h-32 overflow-hidden rounded border border-gray-900/10 dark:border-gray-500/30">
|
||||
{thumbnail && !brokenThumbnail ? (
|
||||
{thumbnailUrl && !brokenThumbnail ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
className="h-full w-full object-cover object-top"
|
||||
src={thumbnail.url}
|
||||
src={thumbnailUrl}
|
||||
alt={c.name}
|
||||
onError={() => setBrokenThumbnail(true)}
|
||||
/>
|
||||
@@ -69,6 +80,9 @@ const FolderGridLayout = ({
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
// Get item path from item name
|
||||
const getItemPath = (name: string) => `${path === '/' ? '' : path}/${encodeURIComponent(name)}`
|
||||
|
||||
return (
|
||||
<div className="rounded bg-white dark:bg-gray-900 dark:text-gray-100">
|
||||
<div className="flex items-center border-b border-gray-900/10 px-3 text-xs font-bold uppercase tracking-widest text-gray-600 dark:border-gray-500/30 dark:text-gray-400">
|
||||
@@ -108,9 +122,7 @@ const FolderGridLayout = ({
|
||||
title={t('Copy folder permalink')}
|
||||
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
clipboard.copy(
|
||||
`${getBaseUrl()}${getReadablePath(`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`)}`
|
||||
)
|
||||
clipboard.copy(`${getBaseUrl()}${getReadablePath(getItemPath(c.name))}`)
|
||||
toast(t('Copied folder permalink.'), { icon: '👌' })
|
||||
}}
|
||||
>
|
||||
@@ -122,10 +134,7 @@ const FolderGridLayout = ({
|
||||
<span
|
||||
title={t('Download folder')}
|
||||
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
const p = `${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`
|
||||
handleFolderDownload(p, c.id, c.name)()
|
||||
}}
|
||||
onClick={handleFolderDownload(getItemPath(c.name), c.id, c.name)}
|
||||
>
|
||||
<FontAwesomeIcon icon={['far', 'arrow-alt-circle-down']} />
|
||||
</span>
|
||||
@@ -137,11 +146,7 @@ const FolderGridLayout = ({
|
||||
title={t('Copy raw file permalink')}
|
||||
className="cursor-pointer rounded px-1.5 py-1 hover:bg-gray-300 dark:hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
clipboard.copy(
|
||||
`${getBaseUrl()}/api?path=${getReadablePath(
|
||||
`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`
|
||||
)}&raw=true`
|
||||
)
|
||||
clipboard.copy(`${getBaseUrl()}/api?path=${getReadablePath(getItemPath(c.name))}&raw=true`)
|
||||
toast.success(t('Copied raw file permalink.'))
|
||||
}}
|
||||
>
|
||||
@@ -172,9 +177,9 @@ const FolderGridLayout = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Link href={`${path === '/' ? '' : path}/${encodeURIComponent(c.name)}`} passHref>
|
||||
<Link href={getItemPath(c.name)} passHref>
|
||||
<a>
|
||||
<GridItem c={c} />
|
||||
<GridItem c={c} path={getItemPath(c.name)} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
+18
-8
@@ -90,6 +90,23 @@ export async function getAccessToken(): Promise<string> {
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Match protected routes in site config to get path to required auth token
|
||||
* @param path Path cleaned in advance
|
||||
* @returns Path to required auth token. If not required, return empty string.
|
||||
*/
|
||||
export function getAuthTokenPath(path: string) {
|
||||
const protectedRoutes = siteConfig.protectedRoutes
|
||||
let authTokenPath = ''
|
||||
for (const r of protectedRoutes) {
|
||||
if (path.startsWith(r)) {
|
||||
authTokenPath = `${r}/.password`
|
||||
break
|
||||
}
|
||||
}
|
||||
return authTokenPath
|
||||
}
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// If method is POST, then the API is called by the client to store acquired tokens
|
||||
if (req.method === 'POST') {
|
||||
@@ -135,14 +152,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
|
||||
// Handle authentication through .password
|
||||
const protectedRoutes = siteConfig.protectedRoutes
|
||||
let authTokenPath = ''
|
||||
for (const r of protectedRoutes) {
|
||||
if (cleanPath.startsWith(r)) {
|
||||
authTokenPath = `${r}/.password`
|
||||
break
|
||||
}
|
||||
}
|
||||
const authTokenPath = getAuthTokenPath(cleanPath)
|
||||
|
||||
// Fetch password from remote file content
|
||||
if (authTokenPath !== '') {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { OdThumbnail } from '../../types'
|
||||
|
||||
import { posix as pathPosix } from 'path'
|
||||
|
||||
import axios from 'axios'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { encodePath, getAccessToken, getAuthTokenPath } from '.'
|
||||
import apiConfig from '../../config/api.config'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Get access token from storage
|
||||
const accessToken = await getAccessToken()
|
||||
|
||||
// Get item thumbnails by its path since we will later check if it is protected
|
||||
const { path = '' } = req.query
|
||||
|
||||
// Sometimes the path parameter is defaulted to '[...path]' which we need to handle
|
||||
if (path === '[...path]') {
|
||||
res.status(400).json({ error: 'No path specified.' })
|
||||
return
|
||||
}
|
||||
// If the path is not a valid path, return 400
|
||||
if (typeof path !== 'string') {
|
||||
res.status(400).json({ error: 'Path query invalid.' })
|
||||
return
|
||||
}
|
||||
const cleanPath = pathPosix.resolve('/', pathPosix.normalize(path))
|
||||
|
||||
// Check if the path is protected
|
||||
const authTokenPath = getAuthTokenPath(cleanPath)
|
||||
|
||||
// Currently protected paths are rejected to avoid file content leak
|
||||
if (authTokenPath) {
|
||||
res.status(404).json({ error: 'Protected pathes are not allowed.' })
|
||||
return
|
||||
}
|
||||
|
||||
const requestPath = encodePath(cleanPath)
|
||||
// Handle response from OneDrive API
|
||||
const requestUrl = `${apiConfig.driveApi}/root${requestPath}`
|
||||
// Whether path is root, which requires some special treatment
|
||||
const isRoot = requestPath === ''
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(`${requestUrl}${isRoot ? '' : ':'}/thumbnails`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
})
|
||||
|
||||
const thumbnailUrl = data.value && data.value.length > 0 ? (data.value[0] as OdThumbnail).medium.url : null
|
||||
if (thumbnailUrl) {
|
||||
res.redirect(thumbnailUrl)
|
||||
} else {
|
||||
res.status(400).json({ error: "The item doesn't have a valid thumbnail." })
|
||||
}
|
||||
} catch (error: any) {
|
||||
res.status(error.response.status).json({ error: error.response.data })
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user