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,6 +121,19 @@ 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 [ ] = [ ] ;
123138 const linkRegex = / ( \s | ^ ) ( h t t p s ? : \/ \/ (?: w w w \. | (? ! w w w ) ) [ ^ \s . ] + \. [ ^ \s ] { 2 , } | w w w \. [ ^ \s ] + \. [ ^ \s ] { 2 , } ) / g;
124139
@@ -157,20 +172,87 @@ function convertBundleIntoReact(
157172 return React . createElement ( "span" , { style, key, className } , content ) ;
158173}
159174
175+ function linkWithLinkify (
176+ bundle : AnserJsonEntry ,
177+ key : number ,
178+ style : React . CSSProperties | null ,
179+ className : string | null
180+ ) : JSX . Element {
181+ const linker = linkifyit ( { fuzzyEmail : false } ) . tlds ( [ "io" ] , true ) ;
182+
183+ if ( ! linker . pretest ( bundle . content ) ) {
184+ return React . createElement (
185+ "span" ,
186+ { style, key, className } ,
187+ bundle . content
188+ ) ;
189+ }
190+
191+ const matches = linker . match ( bundle . content ) ;
192+
193+ if ( ! matches ) {
194+ return React . createElement (
195+ "span" ,
196+ { style, key, className } ,
197+ bundle . content
198+ ) ;
199+ }
200+
201+ const content : React . ReactNode [ ] = [
202+ bundle . content . substring ( 0 , matches [ 0 ] ?. index ) ,
203+ ] ;
204+
205+ matches . forEach ( ( match , i ) => {
206+ content . push (
207+ React . createElement (
208+ "a" ,
209+ {
210+ href : match . url ,
211+ target : "_blank" ,
212+ key : i ,
213+ } ,
214+ bundle . content . substring ( match . index , match . lastIndex )
215+ )
216+ ) ;
217+
218+ if ( matches [ i + 1 ] ) {
219+ content . push (
220+ bundle . content . substring ( matches [ i ] . lastIndex , matches [ i + 1 ] ?. index )
221+ ) ;
222+ }
223+ } ) ;
224+
225+ if ( matches [ matches . length - 1 ] . lastIndex !== bundle . content . length ) {
226+ content . push (
227+ bundle . content . substring (
228+ matches [ matches . length - 1 ] . lastIndex ,
229+ bundle . content . length
230+ )
231+ ) ;
232+ }
233+ return React . createElement ( "span" , { style, key, className } , content ) ;
234+ }
235+
160236declare interface Props {
161237 children ?: string ;
162238 linkify ?: boolean ;
239+ fuzzyLinks ?: boolean ;
163240 className ?: string ;
164241 useClasses ?: boolean ;
165242}
166243
167244export default function Ansi ( props : Props ) : JSX . Element {
168- const { className, useClasses, children, linkify } = props ;
245+ const { className, useClasses, children, linkify, fuzzyLinks } = props ;
169246 return React . createElement (
170247 "code" ,
171248 { className } ,
172249 ansiToJSON ( children ?? "" , useClasses ?? false ) . map (
173- convertBundleIntoReact . bind ( null , linkify ?? false , useClasses ?? false )
250+ convertBundleIntoReact . bind (
251+ null ,
252+ linkify ?? false ,
253+ fuzzyLinks ?? false ,
254+ useClasses ?? false
255+ )
174256 )
175257 ) ;
176258}
0 commit comments