@@ -4,6 +4,18 @@ function escapeRegExp(s: string): string {
44 return s . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' )
55}
66
7+ // 출력 가능한 ASCII(공백~틸드)만으로 이뤄진 term — 영문 키워드 판별용(제어문자 제외).
8+ const ASCII_ONLY = / ^ [ \x20 - \x7E ] + $ /
9+
10+ // 단일 term → 정규식 조각. 순수 ASCII(영문 키워드 'AI'·'Go' 등)는 단어 중간 매칭을
11+ // 막기 위해 ASCII 경계 lookaround 로 감싼다(예: 'AI' 가 "detail"·"main" 내부를 강조하지 않게).
12+ // 한글/혼용 구절은 경계 개념이 모호하고 긴 구절이라 오매칭이 드물어 그대로 둔다.
13+ // lookaround 는 zero-width 라 split 의 '캡처 그룹 1개' 가정을 깨지 않는다.
14+ function termToPattern ( t : string ) : string {
15+ const esc = escapeRegExp ( t )
16+ return ASCII_ONLY . test ( t ) ? `(?<![A-Za-z0-9])${ esc } (?![A-Za-z0-9])` : esc
17+ }
18+
719// 피드백 본문에서 주어진 구절/키워드를 <mark> 로 강조한다.
820// terms = AI 가 고른 핵심 구절(highlights) ∪ 다음에 채울 키워드(improvementKeywords).
921// 긴 구절을 먼저 매칭(부분 겹침 방지), 대소문자 무시. mark 는 html2canvas(PDF)에서도 렌더된다.
@@ -19,7 +31,7 @@ export function HighlightedText({
1931 new Set ( terms . map ( ( t ) => ( t ?? '' ) . trim ( ) ) . filter ( ( t ) => t . length >= 2 ) ) ,
2032 ) . sort ( ( a , b ) => b . length - a . length )
2133 if ( cleaned . length === 0 ) return null
22- return new RegExp ( `(${ cleaned . map ( escapeRegExp ) . join ( '|' ) } )` , 'gi' )
34+ return new RegExp ( `(${ cleaned . map ( termToPattern ) . join ( '|' ) } )` , 'gi' )
2335 } , [ terms ] )
2436
2537 if ( ! regex || ! text ) return < > { text } </ >
0 commit comments