- Updated .env.example to include configuration for OnlyOffice Document Server. - Modified the workspace configuration to remove the drive-suite path. - Adjusted TypeScript environment imports for consistency. - Enhanced Next.js configuration to disable canvas in Webpack. - Updated package.json to include new dependencies for OnlyOffice and PDF.js. - Added global styles for OnlyOffice theme integration in the CSS. - Created new layout and page components for the Drive feature, including public sharing and editing functionalities. - Updated metadata handling across various layouts to reflect the new app structure.
164 lines
5.1 KiB
TypeScript
164 lines
5.1 KiB
TypeScript
export type DriveView = "files" | "recent" | "starred" | "trash" | "search" | "shared"
|
|
|
|
/** Decode one URL path segment (safe for names with literal `%`). */
|
|
export function decodePathSegment(segment: string): string {
|
|
try {
|
|
return decodeURIComponent(segment.replace(/\+/g, " "))
|
|
} catch {
|
|
return segment
|
|
}
|
|
}
|
|
|
|
function decodePathSegments(segments: string[]): string[] {
|
|
return segments.map(decodePathSegment)
|
|
}
|
|
|
|
export function encodePathSegments(segments: string[]): string {
|
|
return segments.map((s) => encodeURIComponent(decodePathSegment(s))).join("/")
|
|
}
|
|
|
|
export function buildDriveFolderHref(
|
|
view: Extract<DriveView, "files" | "shared">,
|
|
segments: string[]
|
|
): string {
|
|
const decoded = decodePathSegments(segments)
|
|
if (decoded.length === 0) {
|
|
return view === "shared" ? "/drive/shared" : "/drive"
|
|
}
|
|
const prefix = view === "shared" ? "/drive/shared/folders" : "/drive/folders"
|
|
return `${prefix}/${encodePathSegments(decoded)}`
|
|
}
|
|
|
|
export interface DriveRouteState {
|
|
view: DriveView
|
|
pathSegments: string[]
|
|
page: number
|
|
fileId: string | null
|
|
query: string
|
|
}
|
|
|
|
export function parseDriveSegments(segments: string[] | undefined): DriveRouteState {
|
|
const parts = segments ?? []
|
|
if (parts.length === 0) {
|
|
return { view: "files", pathSegments: [], page: 1, fileId: null, query: "" }
|
|
}
|
|
const head = parts[0]
|
|
if (head === "recent" || head === "starred" || head === "trash") {
|
|
return { view: head, pathSegments: [], page: 1, fileId: null, query: "" }
|
|
}
|
|
if (head === "shared") {
|
|
const folderParts = parts.slice(1)
|
|
let page = 1
|
|
if (folderParts[0] === "folders") {
|
|
folderParts.shift()
|
|
const pageIdx = folderParts.indexOf("page")
|
|
if (pageIdx >= 0 && folderParts[pageIdx + 1]) {
|
|
page = Math.max(1, parseInt(folderParts[pageIdx + 1], 10) || 1)
|
|
folderParts.splice(pageIdx, 2)
|
|
}
|
|
return {
|
|
view: "shared",
|
|
pathSegments: decodePathSegments(folderParts),
|
|
page,
|
|
fileId: null,
|
|
query: "",
|
|
}
|
|
}
|
|
return { view: "shared", pathSegments: [], page: 1, fileId: null, query: "" }
|
|
}
|
|
if (head === "search") {
|
|
return { view: "search", pathSegments: [], page: 1, fileId: null, query: "" }
|
|
}
|
|
if (head === "edit" && parts[1]) {
|
|
return {
|
|
view: "files",
|
|
pathSegments: [],
|
|
page: 1,
|
|
fileId: decodeURIComponent(parts[1]),
|
|
query: "",
|
|
}
|
|
}
|
|
if (head === "folders") {
|
|
const folderParts = parts.slice(1)
|
|
let page = 1
|
|
const pageIdx = folderParts.indexOf("page")
|
|
if (pageIdx >= 0 && folderParts[pageIdx + 1]) {
|
|
page = Math.max(1, parseInt(folderParts[pageIdx + 1], 10) || 1)
|
|
folderParts.splice(pageIdx, 2)
|
|
}
|
|
return {
|
|
view: "files",
|
|
pathSegments: decodePathSegments(folderParts),
|
|
page,
|
|
fileId: null,
|
|
query: "",
|
|
}
|
|
}
|
|
return {
|
|
view: "files",
|
|
pathSegments: decodePathSegments(parts),
|
|
page: 1,
|
|
fileId: null,
|
|
query: "",
|
|
}
|
|
}
|
|
|
|
export function buildDrivePath(state: DriveRouteState): string {
|
|
if (state.view === "recent") return "/drive/recent"
|
|
if (state.view === "starred") return "/drive/starred"
|
|
if (state.view === "trash") return "/drive/trash"
|
|
if (state.view === "search") return "/drive/search"
|
|
if (state.view === "shared") {
|
|
const folder = state.pathSegments.length
|
|
? `/drive/shared/folders/${encodePathSegments(state.pathSegments)}`
|
|
: "/drive/shared"
|
|
const pageSuffix = state.page > 1 ? `/page/${state.page}` : ""
|
|
return `${folder}${pageSuffix}`
|
|
}
|
|
if (state.fileId && state.view === "files") {
|
|
return `/drive/edit/${encodeURIComponent(state.fileId)}`
|
|
}
|
|
const folder = state.pathSegments.length
|
|
? `/drive/folders/${encodePathSegments(state.pathSegments)}`
|
|
: "/drive"
|
|
const pageSuffix = state.page > 1 ? `/page/${state.page}` : ""
|
|
return `${folder}${pageSuffix}`
|
|
}
|
|
|
|
export function folderPathFromSegments(segments: string[]): string {
|
|
const decoded = decodePathSegments(segments)
|
|
if (decoded.length === 0) return "/"
|
|
return "/" + decoded.join("/")
|
|
}
|
|
|
|
export function parentFolderPathFromFilePath(filePath: string): string {
|
|
const normalized = filePath.replace(/^\/+/, "").replace(/\/+$/, "")
|
|
if (!normalized) return "/"
|
|
const parts = normalized.split("/")
|
|
if (parts.length <= 1) return "/"
|
|
return "/" + parts.slice(0, -1).join("/")
|
|
}
|
|
|
|
function isSafeDriveReturnPath(path: string): boolean {
|
|
if (!path.startsWith("/drive")) return false
|
|
if (path.startsWith("//")) return false
|
|
if (path.includes("://")) return false
|
|
return true
|
|
}
|
|
|
|
export function buildDriveEditHref(filePath: string, returnTo?: string): string {
|
|
const base = `/drive/edit/${encodeURIComponent(filePath)}`
|
|
if (!returnTo || !isSafeDriveReturnPath(returnTo)) return base
|
|
return `${base}?returnTo=${encodeURIComponent(returnTo)}`
|
|
}
|
|
|
|
/** Resolve back link from editor: prefer explicit returnTo, else parent folder. */
|
|
export function resolveDriveEditReturnTo(
|
|
returnTo: string | null | undefined,
|
|
filePath: string,
|
|
folderHref: (folderPath: string) => string
|
|
): string {
|
|
if (returnTo && isSafeDriveReturnPath(returnTo)) return returnTo
|
|
return folderHref(parentFolderPathFromFilePath(filePath))
|
|
}
|