import marked from 'marked' import Prism from 'prismjs' import * as tpl from './tpl' import * as event from './event' import { genTree, getRoute, isMobile, slugify, merge } from './util' let OPTIONS = {} let markdown = marked let toc = [] const CACHE = {} const TIP_RE = /^!\s/ const renderTo = function (dom, content) { dom = typeof dom === 'object' ? dom : document.querySelector(dom) dom.innerHTML = content slugify.clear() return dom } /** * init render * @param {Object} options */ export function init (options) { OPTIONS = options const renderer = new marked.Renderer() /** * render anchor tag * @link https://github.com/chjj/marked#overriding-renderer-methods */ renderer.heading = function (text, level) { const slug = slugify(text) let route = '' if (OPTIONS.router) { route = `#/${getRoute()}` } toc.push({ level, slug: `${route}#${encodeURIComponent(slug)}`, title: text }) return `${text}` } // highlight code renderer.code = function (code, lang = '') { const hl = Prism.highlight(code, Prism.languages[lang] || Prism.languages.markup) .replace(/{{/g, '{{') return `
${hl}
` } renderer.link = function (href, title, text) { if (OPTIONS.router && !/:/.test(href)) { href = `#/${href}`.replace(/\/\//g, '/') } return `${text}` } renderer.paragraph = function (text) { const isTip = TIP_RE.test(text) return isTip ? `

${text.replace(TIP_RE, '')}

` : `

${text}

` } if (typeof OPTIONS.markdown === 'function') { markdown.setOptions({ renderer }) markdown = OPTIONS.markdown.call(this, markdown) } else { markdown.setOptions(merge({ renderer }, OPTIONS.markdown)) } } /** * App */ export function renderApp (dom, replace) { const nav = document.querySelector('nav') || document.createElement('nav') if (!OPTIONS.repo) nav.classList.add('no-badge') dom[replace ? 'outerHTML' : 'innerHTML'] = tpl.corner(OPTIONS.repo) + (OPTIONS.coverpage ? tpl.cover() : '') + tpl.main(OPTIONS.sidebarToggle ? tpl.toggle() : '') document.body.insertBefore(nav, document.body.children[0]) // bind toggle event.bindToggle('button.sidebar-toggle') // bind sticky effect if (OPTIONS.coverpage) { !isMobile() && window.addEventListener('scroll', event.sticky) } else { document.body.classList.add('sticky') } } /** * article */ export function renderArticle (content) { renderTo('article', content ? markdown(content) : 'not found') if (!OPTIONS.sidebar && !OPTIONS.loadSidebar) renderSidebar() if (content && typeof Vue !== 'undefined' && typeof Vuep !== 'undefined') { const vm = new Vue({ el: 'main' }) // eslint-disable-line vm.$nextTick(_ => event.scrollActiveSidebar()) } if (OPTIONS.auto2top) setTimeout(() => event.scroll2Top(OPTIONS.auto2top), 0) } /** * navbar */ export function renderNavbar (content) { if (CACHE.navbar && CACHE.navbar === content) return CACHE.navbar = content if (content) renderTo('nav', markdown(content)) event.activeLink('nav') } /** * sidebar */ export function renderSidebar (content) { let html if (content) { html = markdown(content) // find url tag html = html.match(/]*>([\s\S]+)<\/ul>/g)[0] } else if (OPTIONS.sidebar) { html = tpl.tree(OPTIONS.sidebar, '