import { readFileSync } from 'fs'; import { resolve, basename } from 'path'; import resolvePathname from 'resolve-pathname'; import { AbstractHistory } from '../../src/core/router/history/abstract'; import { Compiler } from '../../src/core/render/compiler'; import { isAbsolutePath } from '../../src/core/router/util'; import * as tpl from '../../src/core/render/tpl'; import { prerenderEmbed } from '../../src/core/render/embed'; import fetch from 'node-fetch'; import debug from 'debug'; function cwd(...args) { return resolve(process.cwd(), ...args); } function mainTpl(config) { let html = ``; if (config.repo) { html += tpl.corner(config.repo); } if (config.coverpage) { html += tpl.cover(); } html += tpl.main(config); return html; } export default class Renderer { constructor({ template, config, cache }) { this.html = template; this.config = config = Object.assign({}, config, { routerMode: 'history', }); this.cache = cache; this.router = new AbstractHistory(config); this.compiler = new Compiler(config, this.router); this.router.getCurrentPath = () => this.url; this._renderHtml( 'inject-config', `` ); this._renderHtml('inject-app', mainTpl(config)); this.template = this.html; } _getPath(url) { const file = this.router.getFile(url); return isAbsolutePath(file) ? file : cwd(`./${file}`); } async renderToString(url) { this.url = url = this.router.parse(url).path; const { loadSidebar, loadNavbar, coverpage } = this.config; const mainFile = this._getPath(url); this._renderHtml('main', await this._render(mainFile, 'main')); if (loadSidebar) { const name = loadSidebar === true ? '_sidebar.md' : loadSidebar; const sidebarFile = this._getPath(resolve(url, `./${name}`)); this._renderHtml('sidebar', await this._render(sidebarFile, 'sidebar')); } if (loadNavbar) { const name = loadNavbar === true ? '_navbar.md' : loadNavbar; const navbarFile = this._getPath(resolve(url, `./${name}`)); this._renderHtml('navbar', await this._render(navbarFile, 'navbar')); } if (coverpage) { let path = null; if (typeof coverpage === 'string') { if (url === '/') { path = coverpage; } } else if (Array.isArray(coverpage)) { path = coverpage.indexOf(url) > -1 && '_coverpage.md'; } else { const cover = coverpage[url]; path = cover === true ? '_coverpage.md' : cover; } const coverFile = this._getPath(resolve(url, `./${path}`)); this._renderHtml('cover', await this._render(coverFile), 'cover'); } const html = this.html; this.html = this.template; return html; } _renderHtml(match, content) { this.html = this.html.replace(new RegExp(``, 'g'), content); return this.html; } async _render(path, type) { let html = await this._loadFile(path); const { subMaxLevel, maxLevel } = this.config; let tokens; switch (type) { case 'sidebar': html = this.compiler.sidebar(html, maxLevel) + ``; break; case 'cover': html = this.compiler.cover(html); break; case 'main': tokens = await new Promise(r => { prerenderEmbed( { fetch: url => this._loadFile(this._getPath(url)), compiler: this.compiler, raw: html, }, r ); }); html = this.compiler.compile(tokens); break; case 'navbar': case 'article': default: html = this.compiler.compile(html); break; } return html; } async _loadFile(filePath) { debug('docsify')(`load > ${filePath}`); let content; try { if (isAbsolutePath(filePath)) { const res = await fetch(filePath); if (!res.ok) { throw Error(); } content = await res.text(); this.lock = 0; } else { content = await readFileSync(filePath, 'utf8'); this.lock = 0; } return content; } catch (e) { this.lock = this.lock || 0; if (++this.lock > 10) { this.lock = 0; return; } const fileName = basename(filePath); const result = await this._loadFile( resolvePathname(`../${fileName}`, filePath) ); return result; } } } Renderer.version = '__VERSION__';