1
1
import Anser , { AnserJsonEntry } from "anser" ;
2
2
import { escapeCarriageReturn } from "escape-carriage" ;
3
+ import linkifyit from "linkify-it" ;
3
4
import * as React from "react" ;
4
5
5
6
/**
@@ -67,29 +68,29 @@ function createStyle(bundle: AnserJsonEntry): React.CSSProperties {
67
68
style . color = `rgb(${ bundle . fg } )` ;
68
69
}
69
70
switch ( bundle . decoration ) {
70
- case ' bold' :
71
- style . fontWeight = ' bold' ;
72
- break ;
73
- case ' dim' :
74
- style . opacity = ' 0.5' ;
75
- break ;
76
- case ' italic' :
77
- style . fontStyle = ' italic' ;
78
- break ;
79
- case ' hidden' :
80
- style . visibility = ' hidden' ;
81
- break ;
82
- case ' strikethrough' :
83
- style . textDecoration = ' line-through' ;
84
- break ;
85
- case ' underline' :
86
- style . textDecoration = ' underline' ;
87
- break ;
88
- case ' blink' :
89
- style . textDecoration = ' blink' ;
90
- break ;
71
+ case " bold" :
72
+ style . fontWeight = " bold" ;
73
+ break ;
74
+ case " dim" :
75
+ style . opacity = " 0.5" ;
76
+ break ;
77
+ case " italic" :
78
+ style . fontStyle = " italic" ;
79
+ break ;
80
+ case " hidden" :
81
+ style . visibility = " hidden" ;
82
+ break ;
83
+ case " strikethrough" :
84
+ style . textDecoration = " line-through" ;
85
+ break ;
86
+ case " underline" :
87
+ style . textDecoration = " underline" ;
88
+ break ;
89
+ case " blink" :
90
+ style . textDecoration = " blink" ;
91
+ break ;
91
92
default :
92
- break ;
93
+ break ;
93
94
}
94
95
return style ;
95
96
}
@@ -104,6 +105,7 @@ function createStyle(bundle: AnserJsonEntry): React.CSSProperties {
104
105
105
106
function convertBundleIntoReact (
106
107
linkify : boolean ,
108
+ fuzzyLinks : boolean ,
107
109
useClasses : boolean ,
108
110
bundle : AnserJsonEntry ,
109
111
key : number
@@ -119,8 +121,23 @@ function convertBundleIntoReact(
119
121
) ;
120
122
}
121
123
124
+ if ( fuzzyLinks ) {
125
+ return linkWithLinkify ( bundle , key , style , className ) ;
126
+ }
127
+
128
+ return linkWithClassicMode ( bundle , key , style , className ) ;
129
+ }
130
+
131
+ function linkWithClassicMode (
132
+ bundle : AnserJsonEntry ,
133
+ key : number ,
134
+ style : React . CSSProperties | null ,
135
+ className : string | null
136
+ ) {
122
137
const content : React . ReactNode [ ] = [ ] ;
123
- const linkRegex = / ( \s | ^ ) ( h t t p s ? : \/ \/ (?: w w w \. | (? ! w w w ) ) [ ^ \s . ] + \. [ ^ \s ] { 2 , } | w w w \. [ ^ \s ] + \. [ ^ \s ] { 2 , } ) / g;
138
+
139
+ const linkRegex =
140
+ / ( \s | ^ ) ( h t t p s ? : \/ \/ (?: w w w \. | (? ! w w w ) ) [ ^ \s . ] + \. [ ^ \s ] { 2 , } | w w w \. [ ^ \s ] + \. [ ^ \s ] { 2 , } ) / g;
124
141
125
142
let index = 0 ;
126
143
let match : RegExpExecArray | null ;
@@ -157,20 +174,87 @@ function convertBundleIntoReact(
157
174
return React . createElement ( "span" , { style, key, className } , content ) ;
158
175
}
159
176
177
+ function linkWithLinkify (
178
+ bundle : AnserJsonEntry ,
179
+ key : number ,
180
+ style : React . CSSProperties | null ,
181
+ className : string | null
182
+ ) : JSX . Element {
183
+ const linker = linkifyit ( { fuzzyEmail : false } ) . tlds ( [ "io" ] , true ) ;
184
+
185
+ if ( ! linker . pretest ( bundle . content ) ) {
186
+ return React . createElement (
187
+ "span" ,
188
+ { style, key, className } ,
189
+ bundle . content
190
+ ) ;
191
+ }
192
+
193
+ const matches = linker . match ( bundle . content ) ;
194
+
195
+ if ( ! matches ) {
196
+ return React . createElement (
197
+ "span" ,
198
+ { style, key, className } ,
199
+ bundle . content
200
+ ) ;
201
+ }
202
+
203
+ const content : React . ReactNode [ ] = [
204
+ bundle . content . substring ( 0 , matches [ 0 ] ?. index ) ,
205
+ ] ;
206
+
207
+ matches . forEach ( ( match , i ) => {
208
+ content . push (
209
+ React . createElement (
210
+ "a" ,
211
+ {
212
+ href : match . url ,
213
+ target : "_blank" ,
214
+ key : i ,
215
+ } ,
216
+ bundle . content . substring ( match . index , match . lastIndex )
217
+ )
218
+ ) ;
219
+
220
+ if ( matches [ i + 1 ] ) {
221
+ content . push (
222
+ bundle . content . substring ( matches [ i ] . lastIndex , matches [ i + 1 ] ?. index )
223
+ ) ;
224
+ }
225
+ } ) ;
226
+
227
+ if ( matches [ matches . length - 1 ] . lastIndex !== bundle . content . length ) {
228
+ content . push (
229
+ bundle . content . substring (
230
+ matches [ matches . length - 1 ] . lastIndex ,
231
+ bundle . content . length
232
+ )
233
+ ) ;
234
+ }
235
+ return React . createElement ( "span" , { style, key, className } , content ) ;
236
+ }
237
+
160
238
declare interface Props {
161
239
children ?: string ;
162
240
linkify ?: boolean ;
241
+ fuzzyLinks ?: boolean ;
163
242
className ?: string ;
164
243
useClasses ?: boolean ;
165
244
}
166
245
167
246
export default function Ansi ( props : Props ) : JSX . Element {
168
- const { className, useClasses, children, linkify } = props ;
247
+ const { className, useClasses, children, linkify, fuzzyLinks } = props ;
169
248
return React . createElement (
170
249
"code" ,
171
250
{ className } ,
172
251
ansiToJSON ( children ?? "" , useClasses ?? false ) . map (
173
- convertBundleIntoReact . bind ( null , linkify ?? false , useClasses ?? false )
252
+ convertBundleIntoReact . bind (
253
+ null ,
254
+ linkify ?? false ,
255
+ fuzzyLinks ?? false ,
256
+ useClasses ?? false
257
+ )
174
258
)
175
259
) ;
176
260
}
0 commit comments