import m from 'markdownscript' ; import GithubSlugger from 'github-slugger' ; import { HeadingNode , ListItemNode , ListNode , Node } from 'mos-core' ; import { Plugin , Processor , PluginOptions } from 'mos-processor' ; import readPkgUp from 'mos-read-pkg-up' ; const HEADING = 'heading' ; const LIST = 'list' ; const LIST_ITEM = 'listItem' ; const DEFAULT_HEADING = 'toc|table[ -]of[ -]contents?' ; param { string } value return { RegExp } function toExpression ( value ) { return new RegExp ( '^(' + value + ')$' , 'i' ) ; } param { Node } node param { number } depth param { RegExp } expression return { boolean } function isOpeningHeading ( node , depth , expression ) { return depth === null && node && node . type === HEADING && expression . test ( toString ( node ) ) ; } param { Node } node param { number } depth return { boolean } function isClosingHeading ( node , depth ) { return node && ( depth && node . type === HEADING && node . depth <= depth || node . type === 'markdownScript' ) ; } return { Object } function list ( ) { return m . list ( { ordered : false } , [ ] ) ; } return { Object } function listItem ( ) { return m . listItem ( { loose : false } , [ ] ) ; } param { Object } node param { Object } parent function insert ( node , parent ) { const children = parent . children ; const length = children . length ; const last = children [ length - 1 ] ; let index ; let item ; if ( node . depth === 1 ) { item = listItem ( ) ; item . children . push ( m . paragraph ( [ m . link ( { title : null , url : `#${ node [ 'id' ] }` } , [ node . value ] ) , ] ) ) ; children . push ( item ) ; } else if ( last && last . type === LIST_ITEM ) { insert ( node , last ) ; } else if ( last && last . type === LIST ) { node . depth -- ; insert ( node , last ) ; } else if ( parent . type === LIST ) { item = listItem ( ) ; insert ( node , item ) ; children . push ( item ) ; } else { item = list ( ) ; node . depth -- ; insert ( node , item ) ; children . push ( item ) ; } if ( parent . type === LIST_ITEM ) { ( < ListItemNode > parent ) . loose = false ; return ; } index = - 1 ; while ( ++ index < length ) { ( < ListNode | ListItemNode > children [ index ] ) . loose = false ; } } type HeadingWithId = { depth : number ; id : string ; value : string ; } ; param { Array . < Object > } map return { Object } function contents ( map ) { let minDepth = Infinity ; let index = - 1 ; while ( ++ index < map . length ) { if ( map [ index ] . depth < minDepth ) { minDepth = map [ index ] . depth ; } } index = - 1 ; while ( ++ index < map . length ) { map [ index ] . depth -= minDepth - 1 ; } const table = list ( ) ; index = - 1 ; while ( ++ index < map . length ) { insert ( map [ index ] , table ) ; } return table ; } function valueOf ( node ) { return node && node . value || '' ; } function toString ( node ) { return ( valueOf ( node ) || ( node . children && node . children . filter ( child => child . type !== 'html' ) . map ( toString ) . join ( '' ) ) || '' ) . trim ( ) ; } const plugin = Object . assign ( function plugin ( mos , md ) { const settings = md . options || { } ; let heading = toExpression ( settings . heading || DEFAULT_HEADING ) ; const depth = settings . maxDepth || 6 ; mos . compile . pre ( ( next , node , opts ) => { const result = search ( node , heading , depth ) ; if ( result . index === null || ! result . map . length ) { return next . applySame ( ) ; } node . children = [ ] . concat ( node . children . slice ( 0 , result . index ) , contents ( result . map ) , node . children . slice ( result . index ) ) ; return next . applySame ( ) ; } ) ; param { Node } root param { RegExp } expression param { number } maxDepth return { Object } function search ( root , expression , maxDepth ) { let index = - 1 ; let depth = null ; let map = [ ] ; let headingIndex ; let closingIndex ; while ( ++ index < root . children . length ) { const child = root . children [ index ] ; if ( [ HEADING , 'markdownScript' ] . indexOf ( child . type ) === - 1 ) { continue ; } if ( headingIndex && isClosingHeading ( < HeadingNode > child , depth ) ) { closingIndex = index ; searchChildren ( root . children . slice ( index ) ) ; break ; } if ( isOpeningHeading ( < HeadingNode > child , depth , expression ) ) { headingIndex = index + 1 ; depth = ( < HeadingNode > child ) . depth ; } } function searchChildren ( children ) { if ( ! children || ! children . length ) { return ; } const child = children . shift ( ) ; if ( [ HEADING , 'markdownScript' ] . indexOf ( child . type ) === - 1 ) { return searchChildren ( children ) ; } if ( child . type === 'markdownScript' ) { return searchChildren ( child . children . concat ( children ) ) ; } const value = toString ( child ) ; const depth = ( < HeadingNode > child ) . depth ; if ( value && depth <= maxDepth ) { map . push ( { depth , value , id : getId ( child ) , } ) ; } return searchChildren ( children ) ; } if ( headingIndex ) { if ( ! closingIndex ) { closingIndex = root . children . length + 1 ; } root . children . splice ( headingIndex , closingIndex - headingIndex ) ; } return { index : headingIndex || null , map , } ; } const slugs = new GithubSlugger ( ) ; function getId ( child ) { if ( child . children && child . children [ 0 ] . type === 'html' ) { const match = child . children [ 0 ] . value . match ( /name="([a-zA-Z0-9\-_]+)"/ ) ; if ( match ) { return match [ 1 ] ; } } return slugs . slug ( toString ( child ) ) ; } } , { attributes : { pkg : readPkgUp . sync ( __dirname ) . pkg } } ) ; export default plugin ;