| 1 | /** |
| 2 | * truncate.ts — code-point-safe truncation for text the repository owns |
| 3 | * rather than the site. |
| 4 | * |
| 5 | * `String.prototype.slice` counts UTF-16 code units, so a cut can land |
| 6 | * between the two halves of a surrogate pair. GitHub issue, pull request, |
| 7 | * and release titles routinely carry emoji, and the resulting lone surrogate |
| 8 | * is not a character: it renders as U+FFFD (the black-diamond question |
| 9 | * mark) in every browser, immediately before the ellipsis that says the text |
| 10 | * was shortened. |
| 11 | */ |
| 12 | |
| 13 | /** |
| 14 | * `value` unchanged when it is at most `limit` characters long; otherwise its |
| 15 | * first `keep` characters (default: `limit`) followed by `ellipsis`. |
| 16 | * |
| 17 | * Characters are Unicode code points, so an astral character is either kept |
| 18 | * whole or dropped whole. |
| 19 | */ |
| 20 | export function truncateChars( |
| 21 | value: string, |
| 22 | limit: number, |
| 23 | keep: number = limit, |
| 24 | ellipsis = "…", |
| 25 | ): string { |
| 26 | const chars = Array.from(value); |
| 27 | if (chars.length <= limit) return value; |
| 28 | return chars.slice(0, Math.max(0, keep)).join("") + ellipsis; |
| 29 | } |
| 30 |