11import Anser , { AnserJsonEntry } from "anser" ;
22import { escapeCarriageReturn } from "escape-carriage" ;
3+ import linkifyit from "linkify-it" ;
34import * as React from "react" ;
45
56/**
@@ -67,29 +68,29 @@ function createStyle(bundle: AnserJsonEntry): React.CSSProperties {
6768 style . color = `rgb(${ bundle . fg } )` ;
6869 }
6970 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 ;
9192 default :
92- break ;
93+ break ;
9394 }
9495 return style ;
9596}
@@ -104,6 +105,7 @@ function createStyle(bundle: AnserJsonEntry): React.CSSProperties {
104105
105106function convertBundleIntoReact (
106107 linkify : boolean ,
108+ fuzzyLinks : boolean ,
107109 useClasses : boolean ,
108110 bundle : AnserJsonEntry ,
109111 key : number
@@ -119,8 +121,23 @@ function convertBundleIntoReact(
119121 ) ;
120122 }
121123
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+ ) {
122137 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;
124141
125142 let index = 0 ;
126143 let match : RegExpExecArray | null ;
@@ -157,20 +174,87 @@ function convertBundleIntoReact(
157174 return React . createElement ( "span" , { style, key, className } , content ) ;
158175}
159176
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+
160238declare interface Props {
161239 children ?: string ;
162240 linkify ?: boolean ;
241+ fuzzyLinks ?: boolean ;
163242 className ?: string ;
164243 useClasses ?: boolean ;
165244}
166245
167246export default function Ansi ( props : Props ) : JSX . Element {
168- const { className, useClasses, children, linkify } = props ;
247+ const { className, useClasses, children, linkify, fuzzyLinks } = props ;
169248 return React . createElement (
170249 "code" ,
171250 { className } ,
172251 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+ )
174258 )
175259 ) ;
176260}
0 commit comments