import type { Content } from 'mdast' ; import remark from 'remark' ; import type { Plugin , Transformer } from 'unified' ; import { logger } from '../../logger' ; import { hasKey } from '../../util/object' ; import { regEx } from '../../util/regex' ; interface UrlMatch { start : number ; end : number ; replaceTo : string ; } const urlRegex = /(?:https?:)?(?:\/\/)?(?:www\.)?(?<!api\.)(?:to)?github\.com\/[-_a-z0-9]+\/[-_a-z0-9]+\/(?:discussions|issues|pull)\/[0-9]+(?:#[-_a-z0-9]+)?/i ; function massageLink ( input ) { return input . replace ( regEx ( /(?:to)?github\.com/i ) , 'togithub.com' ) ; } function collectLinkPosition ( input , matches ) { const transformer = ( tree ) : void => { const startOffset = tree . position . start . offset ; const endOffset = tree . position . end . offset ; if ( tree . type === 'link' ) { const substr = input . slice ( startOffset , endOffset ) ; const url = tree . url ; const offset = startOffset + substr . lastIndexOf ( url ) ; if ( urlRegex . test ( url ) ) { matches . push ( { start : offset , end : offset + url . length , replaceTo : massageLink ( url ) , } ) ; } } else if ( tree . type === 'text' ) { const globalUrlReg = new RegExp ( urlRegex , 'g' ) ; const urlMatches = [ ... tree . value . matchAll ( globalUrlReg ) ] ; for ( const match of urlMatches ) { const [ url ] = match ; const start = startOffset + match . index ; const end = start + url . length ; const newUrl = massageLink ( url ) ; matches . push ( { start , end , replaceTo : `[${ url }](${ newUrl })` } ) ; } } else if ( hasKey ( 'children' , tree ) ) { tree . children . forEach ( ( child ) => { transformer ( child ) ; } ) ; } } ; return ( ) => transformer as Transformer ; } export function massageMarkdownLinks ( content ) { try { const rightSpaces = content . replace ( content . trimRight ( ) , '' ) ; const matches = [ ] ; remark ( ) . use ( collectLinkPosition ( content , matches ) ) . processSync ( content ) ; const result = matches . reduceRight ( ( acc , { start , end , replaceTo } ) => { const leftPart = acc . slice ( 0 , start ) ; const rightPart = acc . slice ( end ) ; return leftPart + replaceTo + rightPart ; } , content ) ; return result . trimRight ( ) + rightSpaces ; } catch ( err ) { logger . warn ( { err } , `Unable to massage markdown text` ) ; return content ; } }