Mirror of https://github.com/roostorg/osprey github.com/roostorg/osprey
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 139 lines 4.6 kB view raw
1export function pluralize(word: string, val: number, pluralization: string = 's'): string { 2 if (val === 1) return word; 3 4 return `${word}${pluralization}`; 5} 6 7/** 8 * Splits a string into an array of objects containing substrings and their match status. 9 * Supports both exact string matching and regex patterns. Matched substrings are included in the result. 10 * Useful for tokenizing a string so that substrings matching a search query can be highlighted. 11 * 12 * @param str - The string to split 13 * @param pattern - The substring or regex pattern to match against 14 * @param useRegex - Whether to treat the pattern as a regex (default: false) 15 * @returns Array of objects with substring and matched properties 16 * @throws {Error} When useRegex is true and pattern is an invalid regex 17 * 18 * @example 19 * ```ts 20 * // Exact string matching 21 * const result1 = splitStringIncludingMatches("foo boor", "oo"); 22 * // returns [ 23 * // { substring: "f", matched: false }, 24 * // { substring: "oo", matched: true }, 25 * // { substring: " b", matched: false }, 26 * // { substring: "oo", matched: true }, 27 * // { substring: "r", matched: false } 28 * // ] 29 * 30 * // Regex matching 31 * const result2 = splitStringIncludingMatches("foo123bar456", "\\d+", true); 32 * // returns [ 33 * // { substring: "foo", matched: false }, 34 * // { substring: "123", matched: true }, 35 * // { substring: "bar", matched: false }, 36 * // { substring: "456", matched: true } 37 * // ] 38 * ``` 39 */ 40export function splitStringIncludingMatches( 41 str: string, 42 pattern: string, 43 useRegex: boolean = false 44): Array<{ substring: string; matched: boolean }> { 45 // Early returns for edge cases 46 if (!str) return []; 47 if (!pattern) return [{ substring: str, matched: false }]; 48 49 const result = useRegex ? splitStringRegexMatch(str, pattern) : splitStringExactMatch(str, pattern); 50 51 // Filter out empty substrings (except for edge cases where they might be meaningful) 52 return result.filter((item) => item.substring.length > 0); 53} 54 55export function highlightMatchedText(text: Array<{ substring: string; matched: boolean }>, highlightClass?: string) { 56 const highlightText = (content: string) => { 57 return ( 58 <span className={highlightClass ?? ''} style={{ backgroundColor: highlightClass ? 'inherit' : 'yellow' }}> 59 {content} 60 </span> 61 ); 62 }; 63 const highlighted = text.map((item) => (item.matched ? highlightText(item.substring) : item.substring)); 64 return <>{highlighted}</>; 65} 66 67/** 68 * Helper function to split string using exact text matching 69 */ 70function splitStringExactMatch(str: string, pattern: string): Array<{ substring: string; matched: boolean }> { 71 const result: Array<{ substring: string; matched: boolean }> = []; 72 let remaining = str; 73 let index = remaining.indexOf(pattern); 74 75 while (index !== -1) { 76 // Add non-matching substring before the match 77 const beforeMatch = remaining.substring(0, index); 78 if (beforeMatch) { 79 result.push({ substring: beforeMatch, matched: false }); 80 } 81 82 // Add the matched substring 83 result.push({ substring: pattern, matched: true }); 84 85 remaining = remaining.substring(index + pattern.length); 86 index = remaining.indexOf(pattern); 87 } 88 89 // Add any remaining non-matching substring 90 if (remaining) { 91 result.push({ substring: remaining, matched: false }); 92 } 93 94 return result; 95} 96 97/** 98 * Helper function to split string using regex pattern matching 99 */ 100function splitStringRegexMatch(str: string, pattern: string): Array<{ substring: string; matched: boolean }> { 101 const result: Array<{ substring: string; matched: boolean }> = []; 102 let regex: RegExp; 103 104 try { 105 regex = new RegExp(pattern, 'g'); 106 } catch (error) { 107 // invalid regex can't match in the string, so no need to force error handling upstream. 108 return [{ substring: str, matched: false }]; 109 } 110 111 let lastIndex = 0; 112 let match: RegExpExecArray | null; 113 114 while ((match = regex.exec(str)) !== null) { 115 // Add non-matching substring before the match 116 if (match.index > lastIndex) { 117 const nonMatchSubstring = str.substring(lastIndex, match.index); 118 if (nonMatchSubstring) { 119 result.push({ substring: nonMatchSubstring, matched: false }); 120 } 121 } 122 123 // Add the matched substring 124 result.push({ substring: match[0], matched: true }); 125 lastIndex = regex.lastIndex; 126 127 // Prevent infinite loop for zero-length matches 128 if (match[0].length === 0) { 129 regex.lastIndex++; 130 } 131 } 132 133 // Add any remaining non-matching substring 134 if (lastIndex < str.length) { 135 result.push({ substring: str.substring(lastIndex), matched: false }); 136 } 137 138 return result; 139}