my website at ewancroft.uk
1/**
2 * Validation and text processing utilities
3 */
4
5/**
6 * Validates AT Protocol TID (Timestamp Identifier) format
7 * @param tid - The TID to validate
8 * @returns True if valid TID format
9 */
10export function isValidTid(tid: string): boolean {
11 // TIDs are base32-encoded timestamps, 12-16 alphanumeric characters
12 const tidPattern = /^[a-zA-Z0-9]{12,16}$/;
13 return tidPattern.test(tid);
14}
15
16/**
17 * Validates AT Protocol DID format
18 * @param did - The DID to validate
19 * @returns True if valid DID format
20 */
21export function isValidDid(did: string): boolean {
22 // DID format: did:method:identifier
23 const didPattern = /^did:[a-z]+:[a-zA-Z0-9._:-]+$/;
24 return didPattern.test(did);
25}
26
27/**
28 * Truncates text to a specified length with ellipsis
29 * @param text - The text to truncate
30 * @param maxLength - Maximum length before truncation
31 * @param ellipsis - String to append when truncated (default: "...")
32 * @returns Truncated text
33 */
34export function truncateText(text: string, maxLength: number, ellipsis = '...'): string {
35 if (text.length <= maxLength) return text;
36 return text.slice(0, maxLength - ellipsis.length).trim() + ellipsis;
37}
38
39/**
40 * Safely escapes HTML to prevent XSS attacks
41 * @param text - The text to escape
42 * @returns HTML-safe text
43 */
44export function escapeHtml(text: string): string {
45 const div = typeof document !== 'undefined' ? document.createElement('div') : null;
46 if (div) {
47 div.textContent = text;
48 return div.innerHTML;
49 }
50 // Server-side fallback
51 return text
52 .replace(/&/g, '&')
53 .replace(/</g, '<')
54 .replace(/>/g, '>')
55 .replace(/"/g, '"')
56 .replace(/'/g, ''');
57}
58
59/**
60 * Generates initials from a name (max 2 characters)
61 * @param name - The name to generate initials from
62 * @returns Uppercase initials
63 */
64export function getInitials(name: string): string {
65 const words = name.trim().split(/\s+/);
66 if (words.length === 1) {
67 return words[0].charAt(0).toUpperCase();
68 }
69 return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase();
70}
71
72/**
73 * Debounces a function call
74 * @param func - The function to debounce
75 * @param delay - Delay in milliseconds
76 * @returns Debounced function
77 */
78export function debounce<T extends (...args: any[]) => any>(
79 func: T,
80 delay: number
81): (...args: Parameters<T>) => void {
82 let timeoutId: ReturnType<typeof setTimeout>;
83 return (...args: Parameters<T>) => {
84 clearTimeout(timeoutId);
85 timeoutId = setTimeout(() => func(...args), delay);
86 };
87}
88
89/**
90 * Throttles a function call
91 * @param func - The function to throttle
92 * @param limit - Time limit in milliseconds
93 * @returns Throttled function
94 */
95export function throttle<T extends (...args: any[]) => any>(
96 func: T,
97 limit: number
98): (...args: Parameters<T>) => void {
99 let inThrottle: boolean;
100 return (...args: Parameters<T>) => {
101 if (!inThrottle) {
102 func(...args);
103 inThrottle = true;
104 setTimeout(() => (inThrottle = false), limit);
105 }
106 };
107}