ultisuite-client/lib/drive/drive-url.ts
R3D347HR4Y 6ec95262af Add OnlyOffice integration and update project configurations
- 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.
2026-06-07 15:49:21 +02:00

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))
}