@@ -84,36 +84,139 @@ const UpdateNotification: React.FC = () => {
8484
8585 const formatReleaseNotes = ( releaseNotes : string ) : React . ReactNode => {
8686 try {
87- // HTML 태그가 있는지 확인
88- const hasHtmlTags = / < [ ^ > ] * > / g. test ( releaseNotes ) ;
89-
90- if ( hasHtmlTags ) {
91- // HTML 태그 제거하고 마크다운 스타일로 변환
92- let formatted = releaseNotes
93- . replace ( / < h [ 1 - 6 ] [ ^ > ] * > ( .* ?) < \/ h [ 1 - 6 ] > / gi, '## $1\n' )
94- . replace ( / < s t r o n g [ ^ > ] * > ( .* ?) < \/ s t r o n g > / gi, '**$1**' )
95- . replace ( / < b [ ^ > ] * > ( .* ?) < \/ b > / gi, '**$1**' )
96- . replace ( / < e m [ ^ > ] * > ( .* ?) < \/ e m > / gi, '*$1*' )
97- . replace ( / < i [ ^ > ] * > ( .* ?) < \/ i > / gi, '*$1*' )
98- . replace ( / < u l [ ^ > ] * > / gi, '' )
99- . replace ( / < \/ u l > / gi, '' )
100- . replace ( / < l i [ ^ > ] * > / gi, '• ' )
101- . replace ( / < \/ l i > / gi, '\n' )
102- . replace ( / < b r \s * \/ ? > / gi, '\n' )
103- . replace ( / < p [ ^ > ] * > / gi, '' )
104- . replace ( / < \/ p > / gi, '\n\n' )
105- . replace ( / < [ ^ > ] * > / g, '' ) // 남은 모든 HTML 태그 제거
106- . replace ( / & n b s p ; / gi, ' ' )
107- . replace ( / & l t ; / gi, '<' )
108- . replace ( / & g t ; / gi, '>' )
109- . replace ( / & a m p ; / gi, '&' )
110- . trim ( ) ;
87+ const lines = releaseNotes . split ( '\n' ) ;
88+ const elements : React . ReactNode [ ] = [ ] ;
89+ let listItems : string [ ] = [ ] ;
90+ let key = 0 ;
91+
92+ const flushList = ( ) => {
93+ if ( listItems . length > 0 ) {
94+ elements . push (
95+ < ul key = { key ++ } style = { { margin : '8px 0' , paddingLeft : '16px' } } >
96+ { listItems . map ( ( item , index ) => (
97+ < li key = { index } style = { { marginBottom : '4px' } } >
98+ < Text style = { { fontSize : 13 } } > { parseInlineMarkdown ( item ) } </ Text >
99+ </ li >
100+ ) ) }
101+ </ ul >
102+ ) ;
103+ listItems = [ ] ;
104+ }
105+ } ;
106+
107+ const parseInlineMarkdown = ( text : string ) : React . ReactNode => {
108+ const parts : React . ReactNode [ ] = [ ] ;
109+ let currentText = text ;
110+ let partKey = 0 ;
111+
112+ // Bold (**text**)
113+ currentText = currentText . replace ( / \* \* ( .+ ?) \* \* / g, ( _ , content ) => {
114+ const placeholder = `__BOLD_${ partKey } __` ;
115+ parts [ partKey ] = < Text key = { partKey } strong style = { { fontSize : 13 } } > { content } </ Text > ;
116+ partKey ++ ;
117+ return placeholder ;
118+ } ) ;
119+
120+ // Italic (*text* or _text_)
121+ currentText = currentText . replace ( / (?< ! \* ) \* ( [ ^ * ] + ?) \* (? ! \* ) / g, ( _ , content ) => {
122+ const placeholder = `__ITALIC_${ partKey } __` ;
123+ parts [ partKey ] = < Text key = { partKey } italic style = { { fontSize : 13 } } > { content } </ Text > ;
124+ partKey ++ ;
125+ return placeholder ;
126+ } ) ;
127+
128+ currentText = currentText . replace ( / _ ( [ ^ _ ] + ?) _ / g, ( _ , content ) => {
129+ const placeholder = `__ITALIC_${ partKey } __` ;
130+ parts [ partKey ] = < Text key = { partKey } italic style = { { fontSize : 13 } } > { content } </ Text > ;
131+ partKey ++ ;
132+ return placeholder ;
133+ } ) ;
134+
135+ // Split by placeholders and combine
136+ const finalParts : React . ReactNode [ ] = [ ] ;
137+ const segments = currentText . split ( / ( _ _ (?: B O L D | I T A L I C ) _ \d + _ _ ) / ) ;
138+
139+ segments . forEach ( ( segment , index ) => {
140+ const match = segment . match ( / ^ _ _ (?: B O L D | I T A L I C ) _ ( \d + ) _ _ $ / ) ;
141+ if ( match ) {
142+ const partIndex = parseInt ( match [ 1 ] ) ;
143+ finalParts . push ( parts [ partIndex ] ) ;
144+ } else if ( segment ) {
145+ finalParts . push ( < span key = { `text_${ index } ` } > { segment } </ span > ) ;
146+ }
147+ } ) ;
148+
149+ return finalParts . length > 1 ? < > { finalParts } </ > : finalParts [ 0 ] || text ;
150+ } ;
151+
152+ for ( const line of lines ) {
153+ const trimmedLine = line . trim ( ) ;
111154
112- return < Text style = { { fontSize : 13 , fontFamily : 'inherit' } } > { formatted } </ Text > ;
155+ if ( ! trimmedLine ) {
156+ flushList ( ) ;
157+ continue ;
158+ }
159+
160+ // Headers
161+ if ( trimmedLine . startsWith ( '# ' ) ) {
162+ flushList ( ) ;
163+ elements . push (
164+ < Title key = { key ++ } level = { 1 } style = { { fontSize : 18 , margin : '16px 0 8px 0' , color : '#1890ff' } } >
165+ { trimmedLine . slice ( 2 ) }
166+ </ Title >
167+ ) ;
168+ } else if ( trimmedLine . startsWith ( '## ' ) ) {
169+ flushList ( ) ;
170+ elements . push (
171+ < Title key = { key ++ } level = { 2 } style = { { fontSize : 16 , margin : '12px 0 6px 0' , color : '#1890ff' } } >
172+ { trimmedLine . slice ( 3 ) }
173+ </ Title >
174+ ) ;
175+ } else if ( trimmedLine . startsWith ( '### ' ) ) {
176+ flushList ( ) ;
177+ elements . push (
178+ < Title key = { key ++ } level = { 3 } style = { { fontSize : 14 , margin : '10px 0 4px 0' , color : '#1890ff' } } >
179+ { trimmedLine . slice ( 4 ) }
180+ </ Title >
181+ ) ;
182+ } else if ( trimmedLine . startsWith ( '#### ' ) ) {
183+ flushList ( ) ;
184+ elements . push (
185+ < Title key = { key ++ } level = { 4 } style = { { fontSize : 13 , margin : '8px 0 4px 0' , color : '#1890ff' } } >
186+ { trimmedLine . slice ( 5 ) }
187+ </ Title >
188+ ) ;
189+ }
190+ // Horizontal rule
191+ else if ( trimmedLine === '---' || trimmedLine === '***' ) {
192+ flushList ( ) ;
193+ elements . push (
194+ < hr key = { key ++ } style = { {
195+ border : 'none' ,
196+ borderTop : '1px solid #f0f0f0' ,
197+ margin : '16px 0'
198+ } } />
199+ ) ;
200+ }
201+ // List items
202+ else if ( trimmedLine . startsWith ( '- ' ) || trimmedLine . startsWith ( '* ' ) ) {
203+ listItems . push ( trimmedLine . slice ( 2 ) ) ;
204+ }
205+ // Regular text
206+ else {
207+ flushList ( ) ;
208+ elements . push (
209+ < Text key = { key ++ } style = { { fontSize : 13 , display : 'block' , marginBottom : '8px' } } >
210+ { parseInlineMarkdown ( trimmedLine ) }
211+ </ Text >
212+ ) ;
213+ }
113214 }
114-
115- // 일반 텍스트인 경우 그대로 표시
116- return < Text style = { { fontSize : 13 , fontFamily : 'inherit' } } > { releaseNotes } </ Text > ;
215+
216+ // Flush any remaining list items
217+ flushList ( ) ;
218+
219+ return < div style = { { lineHeight : 1.6 } } > { elements } </ div > ;
117220 } catch ( error ) {
118221 console . error ( '릴리즈 노트 포맷팅 오류:' , error ) ;
119222 return < Text style = { { fontSize : 13 , fontFamily : 'inherit' } } > { releaseNotes } </ Text > ;
0 commit comments