import React , { useState , useEffect } from 'react' ; import { Collapse , Skeleton } from 'antd' ; import Icon from '@ant-design/icons' ; import { last , map } from 'lodash-es' ; import Mark from 'mark.js' ; import { useTranslation } from 'react-i18next' ; import Tabs , { CollapseDataProp } from './Tabs' ; import EmptySvg from '../images/empty.svg' ; import styles from './APIDoc.module.less' ; import docStyles from '../templates/markdown.module.less' ; const { Panel } = Collapse ; interface APIDocProps { markdownRemark : any ; githubUrl : string ; relativePath : string ; exampleSections : any ; description : string ; codeQuery : string ; showAPISearch : boolean ; } const APIDoc = ( { markdownRemark , exampleSections , codeQuery , description , showAPISearch , } ) => { const { t } = useTranslation ( ) ; const [ collapseData , updateCollapseData ] = useState < CollapseDataProp [ ] > ( [ ] ) ; const [ searchQuery , updateSearchQuery ] = useState < string > ( '' ) ; const { frontmatter } = markdownRemark ; const [ outsideActiveKeys , updateOutsideActiveKeys ] = useState < string [ ] > ( [ ] ) ; const [ insideActiveKeys , updateInsideActiveKeys ] = useState < string [ ] > ( [ ] ) ; const [ active , updateActive ] = useState < 'API' | 'design' > ( 'API' ) ; const outsideHandleChange = ( activeKey ) => { updateOutsideActiveKeys ( activeKey ) ; } ; const insideHandleChange = ( activeKey ) => { updateInsideActiveKeys ( activeKey ) ; } ; const createApiStructure = ( html ) => { const div = document . createElement ( 'div' ) ; div . innerHTML = html ; let nodes = div . childNodes ; if ( nodes . length === 1 && nodes [ 0 ] . nodeName === 'DIV' ) { nodes = nodes [ 0 ] . childNodes ; } const result = [ ] ; let htmlList = [ ] ; let item = null ; let depth = 0 ; let firstDepth = 0 ; const getHeading = ( node ) => { if ( /[hH]\d/ . test ( node . nodeName ) ) { return { depth : + node . nodeName . slice ( 1 ) , name : node . innerText , } ; } return null ; } ; const isExpandable = ( level ) => { if ( firstDepth === 0 ) { return true ; } return level < firstDepth + 3 ; } ; const getDescription = ( node ) => { const childs = node . childNodes ; if ( childs . length > 0 && childs [ 0 ] ) { const first = childs [ 0 ] ; if ( first . nodeName === 'DESCRIPTION' ) { const res = { } ; first . childNodes . forEach ( ( c ) => { const text = ( c as HTMLElement ) . innerText ; if ( c . nodeName === 'STRONG' ) { res . require = text ; } else if ( c . nodeName === 'EM' ) { if ( text !== 'default:' ) { res . type = text ; } } else if ( c . nodeName === 'CODE' ) { res . default = text ; } } ) ; return res ; } } return null ; } ; const recordResult = ( node , level ) => { if ( level === firstDepth ) { result . push ( node ) ; } if ( level === firstDepth + 1 && result . length ) { result [ result . length - 1 ] . children . push ( node ) ; } else if ( level === firstDepth + 2 ) { const pNode = last ( result [ result . length - 1 ] . children ) ; if ( pNode && pNode . children ) { pNode . children . push ( node ) ; } } } ; for ( let i = 0 , len = nodes . length ; i < len ; i += 1 ) { const heading = getHeading ( nodes [ i ] as HTMLElement ) ; if ( heading && isExpandable ( heading . depth ) ) { item = { title : heading . name , options : { } , children : [ ] , content : '' , } ; depth = heading . depth ; firstDepth = firstDepth || depth ; htmlList = [ ] ; } else { htmlList . push ( ( nodes [ i ] as HTMLElement ) . outerHTML ) ; const des = getDescription ( nodes [ i ] as HTMLElement ) ; if ( item && des ) { item . options = des ; } } const isLast = i === len - 1 ; let isBoundary = false ; if ( ! isLast ) { const nextHeading = getHeading ( nodes [ i + 1 ] as HTMLElement ) ; isBoundary = ! ! nextHeading && isExpandable ( nextHeading . depth ) ; } if ( item && ( isLast || isBoundary ) ) { item . content = htmlList . length > 0 ? htmlList . join ( '' ) : '' ; recordResult ( item , depth ) ; } } return result ; } ; useEffect ( ( ) => { if ( ! exampleSections ?. API ) { updateActive ( 'design' ) ; return ; } const initData = createApiStructure ( exampleSections ?. API ?. node ?. html ) ; initData . forEach ( ( node ) => { const element = node ; if ( element . title ) { element . show = true ; } } ) ; const defaultKey = initData [ 0 ] ?. title ; if ( defaultKey ) updateOutsideActiveKeys ( [ `outside-${ defaultKey }-0` ] ) ; updateCollapseData ( initData ) ; if ( initData . length <= 0 ) { updateActive ( 'design' ) ; } } , [ exampleSections ] ) ; useEffect ( ( ) => { if ( ! collapseData ) return ; const context = document . getElementById ( 'apiStructure' ) ; if ( context ) { const instance = new Mark ( context ) ; if ( searchQuery ) { const reg = new RegExp ( searchQuery , 'gi' ) ; instance . markRegExp ( reg ) ; } else { instance . unmark ( ) ; } } } , [ searchQuery ] ) ; const genExtra = ( options ) => ( < > { options ?. require && ( < span className = { styles . require } > { options ?. require } < /span>)} { options ?. type && < span className = { styles . type } > { options ?. type } < /span>} { options ?. default && ( < span className = { styles . default } > { options ?. default } < /span>)} < />); const empty = ( < div className = { styles . emptyContainer } > < div className = { styles . empty } > < Icon component = { EmptySvg } /> < div > { t ( '正在施工中...' ) } < /div> < /div> < /div>); const renderCollapse = ( ) => { const renderInner = ( data ) => { return map ( data ?. children , ( child , idx ) => ( < Collapse bordered = { false } activeKey = { insideActiveKeys } onChange = { insideHandleChange } key = { `collapse-${ child . title }-${ idx }` } > < Panel header = { child . title } key = { `inside-${ child . title }-${ idx }` } extra = { genExtra ( child . options ) } > { child . content && ( < div dangerouslySetInnerHTML = { { __html : child . content , } } />)} { renderInner ( child ) } < /Panel> < /Collapse>)); } ; return ( < div id = "apiStructure" className = { docStyles . markdown } > < Collapse className = { styles . rootCollapse } activeKey = { outsideActiveKeys } onChange = { outsideHandleChange } > { collapseData . map ( ( data , i ) => ( < Panel key = { `outside-${ data . title }-${ i }` } header = { data . title } className = { data . show ? styles . rootItem : styles . hidden } > { data . content && ( < div dangerouslySetInnerHTML = { { __html : data . content , } } />)} { renderInner ( data ) } < /Panel>))} < /Collapse> < /div>); } ; return ( < div className = { styles . docPane } > < Tabs title = { frontmatter . title } active = { active } updateActive = { updateActive } updateOutsideActiveKeys = { updateOutsideActiveKeys } updateInsideActiveKeys = { updateInsideActiveKeys } updateSearchQuery = { updateSearchQuery } updateCollapseData = { updateCollapseData } content = { collapseData } codeQuery = { codeQuery } showAPISearch = { showAPISearch } / > { ! exampleSections ? ( < Skeleton className = { styles . skeleton } paragraph = { { rows : 16 } } />) : (<div className={styles.docContent}> { active === 'API' && collapseData . length > 0 && renderCollapse ( ) } { collapseData . length <= 0 && active === 'API' && empty } { active === 'design' ? ( < div className = { styles . designContent } > < div dangerouslySetInnerHTML = { { __html : description , } } /> < div dangerouslySetInnerHTML = { { __html : exampleSections ?. design ?. node ?. html , } } /> { ! exampleSections ?. design ?. node ?. html && ! description && empty } { ' ' } < /div>) : null} < /div>)} < /div>); } ; export default APIDoc ;