const DEFAULT_MAX_LENGTH = 15; const MIN_MAX_LENGTH = 2; const ELLIPSIS = "…"; /** * Maximum total length (including the leading dot) of a trailing token that * we are willing to treat as a file extension. 10 covers every real-world * extension this package routes to a recognisable icon (`.numbers`, `.fodt`, * `.docx`, `.xlsx`, etc.) while rejecting accidental long suffixes from * URL-shaped filenames. Chained pseudo-extensions like `.tar.gz` collapse * to the trailing portion (`.gz`), which is the common-sense user * expectation. */ const MAX_EXTENSION_LENGTH = 10; /** * Truncate a filename so it fits a single line in the small file-type tile. * * Why this exists: on Android, RN's `Text` with `numberOfLines={1}` does not * always enforce single-line layout for URL-shaped strings with no * whitespace (e.g. `vertopal.com/converter`). They wrap to a second line * which then bleeds outside the rounded tile border. Pre-truncating in JS * guarantees the rendered string is short enough that single-line wrapping * is never required. * * The truncation preserves the trailing file extension when one is * detectable so the file type stays visible at a glance (examples below * use the default 15-character budget): * `vertopal.com/converter` → `vertopal.com/c…` * `file-sample_2_dx.docx` → `file-samp….docx` * `quarterly-budget.numbers` → `quarte….numbers` * `report.pdf` → `report.pdf` (unchanged) * `embedded\nname.txt` → caller should pass through * `sanitizeFileName` first * * `maxLength` is clamped to a sensible minimum of 2 so a caller passing * `0` or a negative value cannot violate the single-line guarantee. */ // eslint-disable-next-line max-statements export function truncateFileNameForLine( name?: string, maxLength = DEFAULT_MAX_LENGTH, ): string { if (!name) return ""; const budget = Math.max(MIN_MAX_LENGTH, Math.floor(maxLength)); if (name.length <= budget) return name; const lastDot = name.lastIndexOf("."); const extensionLength = name.length - lastDot; const hasExtension = lastDot > 0 && lastDot < name.length - 1 && extensionLength <= MAX_EXTENSION_LENGTH; if (!hasExtension) { return name.slice(0, budget - 1) + ELLIPSIS; } const extension = name.slice(lastDot); const stemBudget = budget - extension.length - 1; if (stemBudget <= 0) { return name.slice(0, budget - 1) + ELLIPSIS; } return name.slice(0, stemBudget) + ELLIPSIS + extension; }