openreader/src/lib/text-snippets.ts
Richard R 4a5f3060f2 refactor(documents): migrate from IndexedDB to server-backed storage with caching
- Replace client-side IndexedDB with server-first document storage
- Add document caching layer for offline capability and performance
- Implement document transfer during anonymous-to-authenticated account linking
- Add database indexes for improved query performance
- Update document contexts to use new caching system
- Refactor document upload and deletion APIs
- Add audiobook pruning for missing files
- Improve test isolation with namespace support
- Add unit tests for document cache and transfer functions
2026-02-08 11:15:57 -07:00

40 lines
1.3 KiB
TypeScript

export function extractTextSnippet(source: string, maxChars = 220): string {
const strippedHtml = source
.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, ' ')
.replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, ' ')
.replace(/<[^>]+>/g, ' ');
const normalizedMarkdown = strippedHtml
.replace(/```[\s\S]*?```/g, ' ')
.replace(/`[^`]+`/g, ' ')
.replace(/!\[[^\]]*?\]\([^)]+\)/g, ' ')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
.replace(/[#>*_~]/g, ' ');
const normalized = normalizedMarkdown
.replace(/\r\n/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.replace(/[ \t]{2,}/g, ' ')
.trim();
const paragraphs = normalized
.split(/\n\s*\n/)
.map((p) => p.trim())
.filter(Boolean);
const first = paragraphs[0] ?? normalized;
if (first.length <= maxChars) return first;
return `${first.slice(0, Math.max(0, maxChars - 1)).trimEnd()}`;
}
export function extractRawTextSnippet(source: string, maxChars = 1600): string {
const strippedHtml = source
.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, '');
const normalized = strippedHtml.replace(/\r\n/g, '\n').trim();
if (normalized.length <= maxChars) return normalized;
return `${normalized.slice(0, Math.max(0, maxChars - 1)).trimEnd()}`;
}