Files
cista-storage/frontend/src/utils/index.ts
T

190 lines
4.6 KiB
TypeScript

export function determineFileType(inputString: string): 'file' | 'folder' {
if (inputString.includes('.') && !inputString.endsWith('.')) {
return 'file'
} else {
return 'folder'
}
}
export function formatSize(size: number) {
if (size === 0) return 'empty'
for (const unit of [null, 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']) {
if (size < 1e4)
return (
size.toLocaleString().replace(',', '\u202F') + (unit ? `\u202F${unit}` : '')
)
size = Math.round(size / 1000)
}
return 'huge'
}
export function formatUnixDate(t: number) {
const date = new Date(t * 1000)
const now = new Date()
const diff = date.getTime() - now.getTime()
const adiff = Math.abs(diff)
const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
if (adiff <= 5000) return 'now'
if (adiff <= 60000) {
return formatter
.format(Math.round(diff / 1000), 'second')
.replace(' ago', '')
.replaceAll(' ', '\u202F')
}
if (adiff <= 3600000) {
return formatter
.format(Math.round(diff / 60000), 'minute')
.replace('utes', '')
.replace('ute', '')
.replaceAll(' ', '\u202F')
}
if (adiff <= 86400000) {
return formatter
.format(Math.round(diff / 3600000), 'hour')
.replaceAll(' ', '\u202F')
}
if (adiff <= 604800000) {
return formatter
.format(Math.round(diff / 86400000), 'day')
.replaceAll(' ', '\u202F')
}
let d = date
.toLocaleDateString('en-ie', {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric'
})
.replace('Sept', 'Sep')
if (d.length === 14) d = d.replace(' ', ' \u2007') // dom < 10 alignment (add figure space)
d = d.replaceAll(' ', '\u202F').replace('\u202F', '\u00A0') // nobr spaces, thin w/ date but not weekday
d = d.slice(0, -4) + d.slice(-2) // Two digit year is enough
return d
}
export function getFileExtension(filename: string) {
const dotIndex = filename.lastIndexOf('.')
if (dotIndex === -1 || dotIndex === filename.length - 1) {
return '' // No extension
}
return filename.slice(dotIndex + 1)
}
export const FILE_TYPES = {
video: ['avi', 'mkv', 'mov', 'mp4', 'webm'],
audio: ['mp3', 'flac', 'ogg', 'aac'],
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'],
text: [
'txt',
'md',
'json',
'xml',
'yaml',
'yml',
'toml',
'ini',
'conf',
'config',
'cfg',
'log',
'csv',
'tsv',
'py',
'js',
'ts',
'jsx',
'tsx',
'html',
'htm',
'css',
'scss',
'sass',
'less',
'vue',
'php',
'rb',
'go',
'rs',
'java',
'c',
'cpp',
'h',
'hpp',
'cs',
'swift',
'kt',
'sh',
'bash',
'zsh',
'fish',
'ps1',
'bat',
'cmd',
'sql',
'lua',
'r',
'pl',
'dockerfile',
'makefile',
'gitignore',
'gitattributes',
'env',
'diff',
'patch'
]
} as const
export type FileCategory = keyof typeof FILE_TYPES
export function getFileType(name: string): FileCategory | 'unknown' {
const dotIndex = name.lastIndexOf('.')
if (dotIndex === -1 || dotIndex === name.length - 1) return 'unknown'
const ext = name.slice(dotIndex + 1).toLowerCase()
for (const category of Object.keys(FILE_TYPES) as FileCategory[]) {
if ((FILE_TYPES[category] as readonly string[]).includes(ext)) {
return category
}
}
return 'unknown'
}
// Prebuilt for fast & consistent sorting
export const collator = new Intl.Collator('en', {
sensitivity: 'base',
numeric: true,
usage: 'search'
})
// Preformat document names for faster search
export function haystackFormat(str: string) {
const based = str
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
return '^' + based + '$'
}
// Preformat search string for faster search
export function needleFormat(query: string) {
const based = query
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
return { based, words: based.split(/\s+/) }
}
// Test if haystack includes needle
export function localeIncludes(
haystack: string,
filter: { based: string; words: string[] }
) {
const { based, words } = filter
return (
haystack.includes(based) || (words && words.every(word => haystack.includes(word)))
)
}