Files
bookorbit/client/src/features/settings/libraries/lib/library-formats.ts
T
neon 97cd03325f feat(reader): open DjVu documents in the web reader
Scanned books are commonly distributed as DjVu, which the scanner only stored as a supplement.
Decode pages on the server with DjVuLibre so DjVu imports with a cover and page count, opens in
the image reader with its embedded OCR text, and can be the preferred format of an ordinary email
recipient. Migration 0113 widens the recipient format check to allow it.

Decoding runs on validated private snapshots behind a bounded, reader-first queue, and
revalidation and cached pages are answered without a decoder. Released iOS builds cannot open
DjVu, so series continuation offers it only to callers sending includeDjvu=true, and the
dashboard and notebook read file never name it. The first full scan after upgrading promotes
stored .djvu supplements to content.
2026-10-14 21:37:41 +00:00

57 lines
1.6 KiB
TypeScript

/**
* Format colouring for the composition bar. Grouping by family rather than by extension keeps the
* palette bounded at six tokens however many extensions a library holds, and means the same family
* is always the same colour across rows. The legend still names the exact format.
*/
export type FormatFamily = 'ebook' | 'kindle' | 'document' | 'comic' | 'audio' | 'other'
const FAMILY_BY_FORMAT: Record<string, FormatFamily> = {
epub: 'ebook',
kepub: 'ebook',
fb2: 'ebook',
mobi: 'kindle',
azw3: 'kindle',
azw: 'kindle',
pdf: 'document',
djvu: 'document',
cbz: 'comic',
cbr: 'comic',
cb7: 'comic',
m4b: 'audio',
mp3: 'audio',
m4a: 'audio',
opus: 'audio',
ogg: 'audio',
flac: 'audio',
}
export function formatFamily(format: string): FormatFamily {
return FAMILY_BY_FORMAT[format.toLowerCase()] ?? 'other'
}
export function formatFamilyColor(format: string): string {
return `var(--format-${formatFamily(format)})`
}
export interface FormatSegment {
format: string
count: number
color: string
percent: number
}
/** Largest format first, ties broken alphabetically so the order never jitters between refreshes. */
export function toFormatSegments(formatCounts: Record<string, number>): FormatSegment[] {
const entries = Object.entries(formatCounts).filter(([, count]) => count > 0)
const total = entries.reduce((sum, [, count]) => sum + count, 0)
if (total === 0) return []
return entries
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.map(([format, count]) => ({
format,
count,
color: formatFamilyColor(format),
percent: (count / total) * 100,
}))
}