93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
export interface BoundedTextPreviewOptions {
|
|
maxCharacters: number;
|
|
maxLines: number;
|
|
}
|
|
|
|
export interface BoundedTextPreview {
|
|
head: string;
|
|
tail: string;
|
|
truncated: boolean;
|
|
omittedCharacters: number;
|
|
omittedLines: number;
|
|
totalCharacters: number;
|
|
totalLines: number;
|
|
}
|
|
|
|
function countLines(text: string): number {
|
|
let lines = 1;
|
|
for (let index = 0; index < text.length; index += 1) {
|
|
if (text.charCodeAt(index) === 10) lines += 1;
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
function clampCodePointEnd(text: string, end: number): number {
|
|
if (end <= 0 || end >= text.length) return end;
|
|
const previous = text.charCodeAt(end - 1);
|
|
const current = text.charCodeAt(end);
|
|
return previous >= 0xd800 && previous <= 0xdbff && current >= 0xdc00 && current <= 0xdfff ? end - 1 : end;
|
|
}
|
|
|
|
function clampCodePointStart(text: string, start: number): number {
|
|
if (start <= 0 || start >= text.length) return start;
|
|
const previous = text.charCodeAt(start - 1);
|
|
const current = text.charCodeAt(start);
|
|
return previous >= 0xd800 && previous <= 0xdbff && current >= 0xdc00 && current <= 0xdfff ? start + 1 : start;
|
|
}
|
|
|
|
function headBoundary(text: string, maxCharacters: number, maxLines: number): number {
|
|
const characterBoundary = Math.min(text.length, maxCharacters);
|
|
let lines = 1;
|
|
for (let index = 0; index < characterBoundary; index += 1) {
|
|
if (text.charCodeAt(index) !== 10) continue;
|
|
lines += 1;
|
|
if (lines > maxLines) return index;
|
|
}
|
|
return clampCodePointEnd(text, characterBoundary);
|
|
}
|
|
|
|
function tailBoundary(text: string, maxCharacters: number, maxLines: number): number {
|
|
const characterBoundary = Math.max(0, text.length - maxCharacters);
|
|
let lines = 1;
|
|
for (let index = text.length - 1; index >= characterBoundary; index -= 1) {
|
|
if (text.charCodeAt(index) !== 10) continue;
|
|
lines += 1;
|
|
if (lines > maxLines) return index + 1;
|
|
}
|
|
return clampCodePointStart(text, characterBoundary);
|
|
}
|
|
|
|
export function createBoundedTextPreview(text: string, options: BoundedTextPreviewOptions): BoundedTextPreview {
|
|
const maxCharacters = Math.max(2, Math.floor(options.maxCharacters));
|
|
const maxLines = Math.max(2, Math.floor(options.maxLines));
|
|
const totalLines = countLines(text);
|
|
|
|
if (text.length <= maxCharacters && totalLines <= maxLines) {
|
|
return {
|
|
head: text,
|
|
tail: "",
|
|
truncated: false,
|
|
omittedCharacters: 0,
|
|
omittedLines: 0,
|
|
totalCharacters: text.length,
|
|
totalLines,
|
|
};
|
|
}
|
|
|
|
const headEnd = headBoundary(text, Math.ceil(maxCharacters / 2), Math.ceil(maxLines / 2));
|
|
const tailStart = Math.max(headEnd, tailBoundary(text, Math.floor(maxCharacters / 2), Math.floor(maxLines / 2)));
|
|
const head = text.slice(0, headEnd);
|
|
const tail = text.slice(tailStart);
|
|
const visibleLines = countLines(head) + (tail ? countLines(tail) : 0);
|
|
|
|
return {
|
|
head,
|
|
tail,
|
|
truncated: true,
|
|
omittedCharacters: tailStart - headEnd,
|
|
omittedLines: Math.max(0, totalLines - visibleLines),
|
|
totalCharacters: text.length,
|
|
totalLines,
|
|
};
|
|
}
|