pages/_.vue (185 lines of code) (raw):

<template> <div> <div class="post-inner"> <div v-if="toc.length" class="table-of-contents"> <h4 class="toc-container-header">{{ $t('inThisPage') }}</h4> <ul> <li v-for="link of toc" :key="link.id" :class="{ toc2: link.depth === 2, toc3: link.depth === 3 }" > <NuxtLink :to="`#${link.id}`">{{ link.title }}</NuxtLink> </li> </ul> </div> <!-- 'nuxt-content' class for DocSearch https://github.com/algolia/docsearch-configs/blob/master/configs/apache_echarts.json --> <div class="nuxt-content"> <post-content :content="html"></post-content> </div> </div> <contributors :path="postPath"></contributors> </div> </template> <script lang="ts"> import '~/components/markdown/global' import markdown from 'markdown-it' import anchor from 'markdown-it-anchor' import Contributors from '~/components/partials/Contributors.vue' import PostContent from '~/components/partials/PostContent' import * as base64 from 'js-base64' import config from '~/configs/config' import LazyLoad from 'vanilla-lazyload' function parseLiveCodeBlocks(md: string) { return md.replace( /^```(\w+?)\s+live\s*({.*?})?\s*?\n([\s\S]+?)^```/gm, (full, lang = 'js', options = '{}', code: string = '') => { lang = lang.trim() options = options.trim() || '{}' const encoded = base64.encode(code.trim(), true) return `<md-live lang="${lang}" code="'${encoded}'" v-bind="${options}" />` } ) } function parseCodeBlocks(md: string) { return md.replace( /^```(\w+?)\s*({.*?})?\s*?\n([\s\S]+?)^```/gm, (full, lang = 'js', lineHighlights = '', code: string = '') => { lang = lang.trim() const encoded = base64.encode(code.trim(), true) return `<md-code-block lang="${lang}" code="'${encoded}'" line-highlights="'${lineHighlights}'" />` } ) } function replaceVars(md: string, lang: string) { // Replace variables ;[ 'optionPath', 'mainSitePath', 'exampleViewPath', 'exampleEditorPath' ].forEach(p => { const val = config[p].replace('${lang}', lang) md = md.replace(new RegExp('\\$\\{' + p + '\\}', 'g'), val) }) md = md.replace(/\$\{lang\}/g, lang) return md } function slugify(s: string) { return encodeURIComponent( String(s) .trim() .toLowerCase() .replace(/[\s+:]/g, '-') ) } function findPostByDir(posts: any[], dir: string) { for (let i = 0, len = posts.length, post; i < len; i++) { post = posts[i] if (post.dir === dir) { return post } } } function composePostTitle(posts: any[], path: string) { const parts: string[] = [] const slugs = path.split('/') for (let i = 0, post; i < slugs.length; i++) { post = findPostByDir(post ? post.children : posts, slugs[i]) post && parts.unshift(post.title) } return parts.join(' - ') } export default { components: { Contributors, PostContent }, data() { return { toc: [] as { title: string id: string depth: number }[] } }, mounted() { // @ts-ignore this.toc = [] const headers = // @ts-ignore this.$el.querySelector('.post-inner')?.querySelectorAll('h2,h3') || [] for (let i = 0; i < headers.length; i++) { const title = (headers[i] as HTMLHeadingElement).innerText // @ts-ignore this.toc.push({ title, depth: +headers[i].nodeName.replace(/\D/g, ''), id: slugify(title) }) } setTimeout(() => { // FIXME not sure why this needs to be in the setTimeout // init lazy load // @ts-ignore this._lazyload = new LazyLoad({ // container: this.$el.querySelector('.post-inner') as HTMLElement, elements_selector: 'img[data-src], iframe[data-src]', threshold: 300 }) }) }, destroyed() { // @ts-ignore this._lazyload && this._lazyload.destroy() }, head() { return { // @ts-ignore title: this.title, meta: [ { hid: 'docsearch:language', name: 'docsearch:language', // @ts-ignore content: this.$i18n.locale } ] } }, async asyncData({ params, i18n, store }: any) { const posts = store.state.posts[i18n.locale] const path = params.pathMatch; const title = composePostTitle(posts, path); const postPath = `${i18n.locale}/${path}` const fileContent = await import(`~/contents/${postPath}.md`) const content = replaceVars( parseCodeBlocks(parseLiveCodeBlocks(fileContent.default)), i18n.locale ) const md = markdown({ html: true, linkify: true }) .use(anchor, { slugify, permalink: false, permalinkAfter: true, permalinkSymbol: '#', permalinkClass: 'permalink' }) .use(function(md) { const defaultImageRenderer = md.renderer.rules.image! md.renderer.rules.image = function(tokens, idx, options, env, self) { const token = tokens[idx] const srcValue = token.attrGet('src') token.attrPush(['data-src', srcValue!]) token.attrSet('src', '') return defaultImageRenderer(tokens, idx, options, env, self) } }) // lazyload return { html: md.render(content), postPath, title } } } </script> <style lang="scss"></style>