11import Anser , { AnserJsonEntry } from "anser" ;
22import { escapeCarriageReturn } from "escape-carriage" ;
3+ import linkifyit from "linkify-it" ;
34import * as React from "react" ;
45
56/**
@@ -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,22 @@ 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+ const linkRegex =
139+ / ( \s | ^ ) ( h t t p s ? : \/ \/ (?: w w w \. | (? ! w w w ) ) [ ^ \s . ] + \. [ ^ \s ] { 2 , } | w w w \. [ ^ \s ] + \. [ ^ \s ] { 2 , } ) / g;
124140
125141 let index = 0 ;
126142 let match : RegExpExecArray | null ;
@@ -157,20 +173,87 @@ function convertBundleIntoReact(
157173 return React . createElement ( "span" , { style, key, className } , content ) ;
158174}
159175
176+ function linkWithLinkify (
177+ bundle : AnserJsonEntry ,
178+ key : number ,
179+ style : React . CSSProperties | null ,
180+ className : string | null
181+ ) : JSX . Element {
182+ const linker = linkifyit ( { fuzzyEmail : false } ) . tlds ( [ "io" ] , true ) ;
183+
184+ if ( ! linker . pretest ( bundle . content ) ) {
185+ return React . createElement (
186+ "span" ,
187+ { style, key, className } ,
188+ bundle . content
189+ ) ;
190+ }
191+
192+ const matches = linker . match ( bundle . content ) ;
193+
194+ if ( ! matches ) {
195+ return React . createElement (
196+ "span" ,
197+ { style, key, className } ,
198+ bundle . content
199+ ) ;
200+ }
201+
202+ const content : React . ReactNode [ ] = [
203+ bundle . content . substring ( 0 , matches [ 0 ] ?. index ) ,
204+ ] ;
205+
206+ matches . forEach ( ( match , i ) => {
207+ content . push (
208+ React . createElement (
209+ "a" ,
210+ {
211+ href : match . url ,
212+ target : "_blank" ,
213+ key : i ,
214+ } ,
215+ bundle . content . substring ( match . index , match . lastIndex )
216+ )
217+ ) ;
218+
219+ if ( matches [ i + 1 ] ) {
220+ content . push (
221+ bundle . content . substring ( matches [ i ] . lastIndex , matches [ i + 1 ] ?. index )
222+ ) ;
223+ }
224+ } ) ;
225+
226+ if ( matches [ matches . length - 1 ] . lastIndex !== bundle . content . length ) {
227+ content . push (
228+ bundle . content . substring (
229+ matches [ matches . length - 1 ] . lastIndex ,
230+ bundle . content . length
231+ )
232+ ) ;
233+ }
234+ return React . createElement ( "span" , { style, key, className } , content ) ;
235+ }
236+
160237declare interface Props {
161238 children ?: string ;
162239 linkify ?: boolean ;
240+ fuzzyLinks ?: boolean ;
163241 className ?: string ;
164242 useClasses ?: boolean ;
165243}
166244
167245export default function Ansi ( props : Props ) : JSX . Element {
168- const { className, useClasses, children, linkify } = props ;
246+ const { className, useClasses, children, linkify, fuzzyLinks } = props ;
169247 return React . createElement (
170248 "code" ,
171249 { className } ,
172250 ansiToJSON ( children ?? "" , useClasses ?? false ) . map (
173- convertBundleIntoReact . bind ( null , linkify ?? false , useClasses ?? false )
251+ convertBundleIntoReact . bind (
252+ null ,
253+ linkify ?? false ,
254+ fuzzyLinks ?? false ,
255+ useClasses ?? false
256+ )
174257 )
175258 ) ;
176259}
0 commit comments