const path = require('path') const fse = require('fs-extra') const marked = require('marked') const camelCase = require('lodash/camelCase') const createRenderer = require('./md-renderer') const projectPath = require('./project-path') const mdRenderer = createRenderer() async function resolveDemoTitle (fileName, demoEntryPath) { const demoStr = await fse.readFile( path.resolve(projectPath, demoEntryPath, '..', fileName), 'utf-8' ) return demoStr.match(/# ([^\n]+)/)[1] } async function resolveDemoInfos (literal, url, env) { const ids = literal .split('\n') .map((line) => line.trim()) .filter((id) => id.length) const infos = [] for (const id of ids) { const debug = id.includes('debug') || id.includes('Debug') if (env === 'production' && debug) { continue } const fileName = `${id}.demo.md` const variable = `${camelCase(id)}Demo` infos.push({ id, variable, fileName, title: await resolveDemoTitle(fileName, url), tag: `<${variable} />`, debug }) } return infos } function genDemosTemplate (demoInfos, colSpan) { return `${demoInfos .map(({ tag }) => tag) .join('\n')}` } function genAnchorTemplate (children, options = {}) { return ` ${children} ` } function genDemosAnchorTemplate (demoInfos) { const links = demoInfos.map( ({ id, title, debug }) => `` ) return genAnchorTemplate(links.join('\n')) } function genPageAnchorTemplate (tokens) { const titles = tokens .filter((token) => token.type === 'heading' && token.depth === 2) .map((token) => token.text) const links = titles.map((title) => { const href = title.replace(/ /g, '-') return `` }) return genAnchorTemplate(links.join('\n'), { ignoreGap: true }) } function genScript (demoInfos, components = [], url, forceShowAnchor) { const showAnchor = !!(demoInfos.length || forceShowAnchor) const importStmts = demoInfos .map(({ variable, fileName }) => `import ${variable} from './${fileName}'`) .concat( components.map( ({ importStmt }) => importStmt ) ) .join('\n') const componentStmts = demoInfos .map(({ variable }) => variable) .concat(components.map( ({ ids }) => ids ).flat()) .join(',\n') const script = `` return script } async function convertMd2ComponentDocumentation ( text, url, env = 'development' ) { const forceShowAnchor = !!~text.search('') const colSpan = ~text.search('') ? 1 : 2 const tokens = marked.lexer(text) // resolve external components const componentsIndex = tokens.findIndex( (token) => token.type === 'code' && token.lang === 'component' ) let components = [] if (~componentsIndex) { components = tokens[componentsIndex].text components = components .split('\n') .map((component) => { const [ids, importStmt] = component.split(':') if (!ids.trim()) throw new Error('No component id') if (!importStmt.trim()) throw new Error('No component source url') return { ids: ids.split(',').map(id => id.trim()), importStmt: importStmt.trim() } }) .filter(({ids, importStmt}) => ids && importStmt) tokens.splice(componentsIndex, 1) } // add edit on github button on title const titleIndex = tokens.findIndex( (token) => token.type === 'heading' && token.depth === 1 ) if (titleIndex > -1) { const titleText = JSON.stringify(tokens[titleIndex].text) const btnTemplate = `` tokens.splice(titleIndex, 1, { type: 'html', pre: false, text: btnTemplate }) } // resolve demos, debug demos are removed from production build const demosIndex = tokens.findIndex( (token) => token.type === 'code' && token.lang === 'demo' ) let demoInfos = [] if (~demosIndex) { demoInfos = await resolveDemoInfos(tokens[demosIndex].text, url, env) tokens.splice(demosIndex, 1, { type: 'html', pre: false, text: genDemosTemplate(demoInfos, colSpan) }) } const docMainTemplate = marked.parser(tokens, { gfm: true, renderer: mdRenderer }) // generate page const docTemplate = ` ${docMainTemplate} ${ demoInfos.length ? genDemosAnchorTemplate(demoInfos) : genPageAnchorTemplate(tokens) } ` const docScript = await genScript(demoInfos, components, url, forceShowAnchor) return `${docTemplate}\n\n${docScript}` } module.exports = convertMd2ComponentDocumentation