frontend: trim document formats, simplify type getters, use FILE_TYPES consistently

- Trim document extension list to common office formats only
  (doc/docx/xls/xlsx/ppt/pptx/odt/ods/odp/rtf), removing txt/md/csv/html/xml/etc.
- Simplify Doc getters: merge guard clauses into single returns
  (img, previewable, previewurl, ext).
- Simplify MediaPreview.preview() into single boolean expression.
- Use global FILE_TYPES instead of inline extension lists.
This commit is contained in:
Leo Vasanko
2026-05-01 23:17:40 +00:00
parent b6f4172514
commit dff95c414b
3 changed files with 66 additions and 167 deletions
+10 -70
View File
@@ -126,80 +126,20 @@ defineExpose({
media media
}) })
const video = () => ['mkv', 'mp4', 'webm', 'mov', 'avi'].includes(props.doc.ext) const video = () => props.doc.video
const audio = () => ['mp3', 'flac', 'ogg', 'aac'].includes(props.doc.ext) const audio = () => props.doc.audio
const archive = () => const archive = () => props.doc.archive
['zip', 'tar', 'gz', 'bz2', 'xz', '7z', 'rar'].includes(props.doc.ext) const docs = () => props.doc.document
// image = requires server-side preview (browsers cannot display it natively)
// img = browser-viewable image that can be used directly in an <img> tag
const image = () => props.doc.image
const print = () => props.doc.print
const showProgress = () => !props.doc.complete && (preview() || props.doc.img) const showProgress = () => !props.doc.complete && (preview() || props.doc.img)
const preview = () => { const preview = () => {
const store = useMainStore() const store = useMainStore()
const ext = props.doc.ext
// Office document previews may be optionally disabled server-side
if (store.server.office_previews === false) {
const officeExts = [
'doc', 'dot', 'docx', 'docm', 'dotx', 'dotm', 'rtf',
'odt', 'ott', 'txt', 'md', 'mhtml', 'mht', 'html',
'htm', 'xml', 'wps', 'wri',
'xls', 'xlsx', 'xlsm', 'xlsb', 'xltx', 'xltm',
'ods', 'ots', 'csv',
'ppt', 'pptx', 'pptm', 'pps', 'ppsx',
'pot', 'potx', 'odp', 'otp'
]
if (officeExts.includes(ext)) return false
}
return ( return (
[ !(store.server.office_previews === false && docs()) &&
'bmp', (image() || print() || (props.doc.img && props.doc.size > 500000))
'ico',
'tif',
'tiff',
'heic',
'heif',
'pdf',
'epub',
'mobi',
// Documents
'doc',
'dot',
'docx',
'docm',
'dotx',
'dotm',
'rtf',
'odt',
'ott',
'txt',
'md',
'mhtml',
'mht',
'html',
'htm',
'xml',
'wps',
'wri',
// Spreadsheets
'xls',
'xlsx',
'xlsm',
'xlsb',
'xltx',
'xltm',
'ods',
'ots',
'csv',
// Presentations
'ppt',
'pptx',
'pptm',
'pps',
'ppsx',
'pot',
'potx',
'odp',
'otp'
].includes(ext) ||
(props.doc.size > 500000 &&
['avif', 'webp', 'png', 'jpg', 'jpeg'].includes(ext))
) )
} }
</script> </script>
+38 -85
View File
@@ -1,4 +1,5 @@
import { formatSize, formatUnixDate } from '@/utils' import { useMainStore } from '@/stores/main'
import { FILE_TYPES, formatSize, formatUnixDate } from '@/utils'
export type FUID = string export type FUID = string
@@ -63,101 +64,53 @@ export class Doc {
return this.url.replace(/^\/#/, '') return this.url.replace(/^\/#/, '')
} }
get img(): boolean { get img(): boolean {
// Folders cannot be images return (
if (this.dir) return false !this.dir && (FILE_TYPES.imageBrowser as readonly string[]).includes(this.ext)
return [ )
'jpg', }
'jpeg', get video(): boolean {
'png', return (FILE_TYPES.video as readonly string[]).includes(this.ext)
'gif', }
'webp', get audio(): boolean {
'avif', return (FILE_TYPES.audio as readonly string[]).includes(this.ext)
'heic', }
'heif', get archive(): boolean {
'svg' return (FILE_TYPES.archive as readonly string[]).includes(this.ext)
].includes(this.ext) }
get document(): boolean {
return (FILE_TYPES.document as readonly string[]).includes(this.ext)
}
// Images that require server-side preview (browsers cannot display them natively)
get image(): boolean {
return (FILE_TYPES.image as readonly string[]).includes(this.ext)
}
get print(): boolean {
return (FILE_TYPES.print as readonly string[]).includes(this.ext)
} }
get complete(): boolean { get complete(): boolean {
return !this.ghost && (this.dir || this.size <= this.allocated) return !this.ghost && (this.dir || this.size <= this.allocated)
} }
get previewable(): boolean { get previewable(): boolean {
// Folders cannot be previewable
if (this.dir) return false if (this.dir) return false
if (this.img) return true return (
const store = useMainStore() this.img ||
const ext = this.ext this.video ||
// Office document previews may be optionally disabled server-side this.audio ||
if (store.server.office_previews === false) { this.image ||
const officeExts = [ this.print ||
'doc', 'dot', 'docx', 'docm', 'dotx', 'dotm', 'rtf', (this.document && useMainStore().server.office_previews !== false)
'odt', 'ott', 'txt', 'md', 'mhtml', 'mht', 'html', )
'htm', 'xml', 'wps', 'wri',
'xls', 'xlsx', 'xlsm', 'xlsb', 'xltx', 'xltm',
'ods', 'ots', 'csv',
'ppt', 'pptx', 'pptm', 'pps', 'ppsx',
'pot', 'potx', 'odp', 'otp'
]
if (officeExts.includes(ext)) return false
}
// Not a comprehensive list, but good enough for now
return [
'mp4',
'mkv',
'webm',
'ogg',
'mp3',
'flac',
'aac',
'pdf',
// Documents
'doc',
'dot',
'docx',
'docm',
'dotx',
'dotm',
'rtf',
'odt',
'ott',
'txt',
'md',
'mhtml',
'mht',
'html',
'htm',
'xml',
'wps',
'wri',
// Spreadsheets
'xls',
'xlsx',
'xlsm',
'xlsb',
'xltx',
'xltm',
'ods',
'ots',
'csv',
// Presentations
'ppt',
'pptx',
'pptm',
'pps',
'ppsx',
'pot',
'potx',
'odp',
'otp'
].includes(ext)
} }
get previewurl(): string { get previewurl(): string {
if (!this.complete || !this.previewable) return '' return !this.complete || !this.previewable
return this.url.replace(/^\/files/, '/preview') ? ''
: this.url.replace(/^\/files/, '/preview')
} }
get ext(): string { get ext(): string {
const dotIndex = this.name.lastIndexOf('.') const dotIndex = this.name.lastIndexOf('.')
if (dotIndex === -1 || dotIndex === this.name.length - 1) return '' return dotIndex === -1 || dotIndex === this.name.length - 1
return this.name.slice(dotIndex + 1).toLowerCase() ? ''
: this.name.slice(dotIndex + 1).toLowerCase()
} }
} }
export type errorEvent = { export type errorEvent = {
+18 -12
View File
@@ -69,23 +69,29 @@ export function getFileExtension(filename: string) {
} }
return filename.slice(dotIndex + 1) return filename.slice(dotIndex + 1)
} }
interface FileTypes { export const FILE_TYPES = {
[key: string]: string[]
}
const filetypes: FileTypes = {
video: ['avi', 'mkv', 'mov', 'mp4', 'webm'], video: ['avi', 'mkv', 'mov', 'mp4', 'webm'],
image: ['avif', 'gif', 'jpg', 'jpeg', 'png', 'webp', 'svg'], audio: ['mp3', 'flac', 'ogg', 'aac'],
pdf: ['pdf'] archive: ['zip', 'tar', 'gz', 'bz2', 'xz', '7z', 'rar'],
} document: ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'odt', 'ods', 'odp', 'rtf'],
imageBrowser: ['avif', 'gif', 'jpg', 'jpeg', 'png', 'webp', 'svg'],
// Images that require server-side preview (browsers cannot display them natively)
image: ['bmp', 'heic', 'heif', 'ico', 'tif', 'tiff'],
print: ['epub', 'mobi', 'pdf']
} as const
export function getFileType(name: string): string { export type FileCategory = keyof typeof FILE_TYPES
export function getFileType(name: string): FileCategory | 'unknown' {
const dotIndex = name.lastIndexOf('.') const dotIndex = name.lastIndexOf('.')
if (dotIndex === -1 || dotIndex === name.length - 1) return 'unknown' if (dotIndex === -1 || dotIndex === name.length - 1) return 'unknown'
const ext = name.slice(dotIndex + 1).toLowerCase() const ext = name.slice(dotIndex + 1).toLowerCase()
return ( for (const category of Object.keys(FILE_TYPES) as FileCategory[]) {
Object.keys(filetypes).find(type => filetypes[type]!.includes(ext)) || 'unknown' if ((FILE_TYPES[category] as readonly string[]).includes(ext)) {
) return category
}
}
return 'unknown'
} }
// Prebuilt for fast & consistent sorting // Prebuilt for fast & consistent sorting