ultisuite-client/lib/drive/richtext-formats.ts
R3D347HR4Y cdff12490a
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
hocuspocus
2026-06-09 14:31:07 +02:00

106 lines
3.0 KiB
TypeScript

/**
* Word-processor formats routed to the TipTap rich-text editor.
* Cell/slide/diagram formats stay on OnlyOffice.
*/
export const RICHTEXT_EXTENSIONS = new Set([
"doc",
"docm",
"docx",
"dot",
"dotm",
"dotx",
"epub",
"fb2",
"fodt",
"htm",
"html",
"hwp",
"hwpx",
"md",
"mht",
"mhtml",
"odt",
"ott",
"rtf",
"stw",
"sxw",
"txt",
"wps",
"wpt",
"xml",
"ultidoc",
] as const)
export const ULTIDOC_EXTENSION = "ultidoc.json"
const RICHTEXT_MIME_HINTS = [
"wordprocessingml",
"msword",
"opendocument.text",
"text/html",
"text/plain",
"text/markdown",
"application/rtf",
"application/epub",
] as const
export function fileExtension(name: string): string {
const base = name.split("/").pop() ?? name
const lower = base.toLowerCase()
if (lower.endsWith(`.${ULTIDOC_EXTENSION}`)) return ULTIDOC_EXTENSION
const i = base.lastIndexOf(".")
if (i <= 0) return ""
return base.slice(i + 1).toLowerCase()
}
export function isRichTextExtension(ext: string): boolean {
const normalized = ext.toLowerCase()
if (normalized === ULTIDOC_EXTENSION) return true
return RICHTEXT_EXTENSIONS.has(normalized as (typeof RICHTEXT_EXTENSIONS extends Set<infer T> ? T : never))
}
export function isRichTextMime(mime: string): boolean {
const m = mime.toLowerCase()
return RICHTEXT_MIME_HINTS.some((hint) => m.includes(hint))
}
export function isRichTextFile(file: { name: string; mime_type?: string }): boolean {
const ext = fileExtension(file.name)
if (ext && isRichTextExtension(ext)) return true
const mime = (file.mime_type ?? "").toLowerCase()
if (mime && isRichTextMime(mime)) return true
return false
}
export function isUltidocPath(path: string): boolean {
return path.toLowerCase().endsWith(`.${ULTIDOC_EXTENSION}`)
}
/** Sidecar path for a source document, e.g. /docs/report.docx → /docs/report.ultidoc.json */
export function sidecarPathForSource(sourcePath: string): string {
const normalized = sourcePath.replace(/\/+/g, "/")
const slash = normalized.lastIndexOf("/")
const dir = slash >= 0 ? normalized.slice(0, slash) : ""
const fileName = slash >= 0 ? normalized.slice(slash + 1) : normalized
const dot = fileName.lastIndexOf(".")
const base = dot > 0 ? fileName.slice(0, dot) : fileName
const sidecarName = `${base}.${ULTIDOC_EXTENSION}`
if (!dir) return `/${sidecarName}`
return `${dir}/${sidecarName}`.replace(/\/+/g, "/")
}
/** Source path from an ultidoc sidecar name (best-effort). */
export function guessSourcePathFromSidecar(sidecarPath: string, knownExtensions: string[]): string | null {
if (!isUltidocPath(sidecarPath)) return null
const normalized = sidecarPath.replace(/\/+/g, "/")
const slash = normalized.lastIndexOf("/")
const dir = slash >= 0 ? normalized.slice(0, slash) : ""
const fileName = slash >= 0 ? normalized.slice(slash + 1) : normalized
const base = fileName.slice(0, -(ULTIDOC_EXTENSION.length + 1))
for (const ext of knownExtensions) {
const candidate = `${base}.${ext}`
return dir ? `${dir}/${candidate}` : `/${candidate}`
}
return null
}