## 特性
- **简单**:Zero-Markdown,只需配置你的语雀 repo 地址,就能获得一个 VuePress 站点;
- **高效**:自动生成的文档主页、侧边栏让你享受 “高效” 的文档生成体验;
- **缓存**:完美地解决语雀 Open API 调用次数超限的问题;
想了解更多,请移步:
- **案例**
- [视频演示](https://player.youku.com/embed/XNDA1MzAwMDIzNg==)
- [**Site**](https://antd-course.ulivz.com/)
- [语雀](https://www.yuque.com/ant-design/course)
- [源码](https://github.com/ulivz/vuepress-plugin-yuque/tree/master/example/.vuepress)
- **文档**
- [**Site**](https://vuepress-plugin-yuque.ulivz.com/)
- [语雀](https://www.yuque.com/vuepress/vuepress-plugin-yuque)
- [源码](https://github.com/ulivz/vuepress-plugin-yuque/tree/master/docs/.vuepress)
## 快速上手
在 VuePress 中配置 `repoUrl`:
```js
// .vuepress/config.js
module.exports = {
title: 'Ant Design 实战教程',
description: '基于 umi 的 Ant Design 实战教程',
plugins: [
['vuepress-plugin-yuque', {
repoUrl: 'https://www.yuque.com/ant-design/course',
}]
]
}
```
你就能获得这个[**Site**](https://antd-course.ulivz.com/),更多使用方法,可参见[文档](https://vuepress-plugin-yuque.ulivz.com/)。
## Contributing
1. Fork it!
2. Create your feature branch: `git checkout -b my-new-feature`
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request :D
## Author
**vuepress-plugin-yuque** © [ULVIZ](https://github.com/ulivz), Released under the [MIT](./LICENSE) License.
<\/p>/g, '')
.replace(/
<\/span><\/p>/g, '')
}
const CODE_FENCE = '```'
function highlight($) {
$('pre').replaceWith((i, el) => {
const $el = $(el)
const $code = $el.children('code')
const lang = $el.attr('data-lang')
const code = $code.length ? $code.text() : $el.text()
return `\n\n ${CODE_FENCE} ${lang} \n ${code} \n ${CODE_FENCE} \n\n`
})
return $
}
function escapeHTMLTagInCode($) {
$('code').replaceWith((i, el) => {
const content = $(el).text()
return escapeHtml(`${escapeHtml(content)}`)
})
return $
}
function removeTableStyls($) {
$('table').css('width', '')
return $
}
================================================
FILE: lib/index.js
================================================
/**
* Module dependencies.
*/
const assert = require('assert')
const { chalk, parseFrontmatter, path } = require('@vuepress/shared-utils')
const Yuque = require('./yuque')
const { getSidebarByToc } = require('./toc')
const spinner = require('./spinner')
const { prettify } = require('./html')
const { PACKAGE_NAME } = require('./constant')
const debug = require('debug')(PACKAGE_NAME)
/**
* Expose vuepress-plugin-yuque
*/
module.exports = (opts, ctx) => {
return {
name: PACKAGE_NAME,
enhanceAppFiles: [
path.join(__dirname, 'client.js')
],
plugins: [
['@vuepress/medium-zoom', true],
],
chainMarkdown(config) {
if (opts.html) {
return
}
// Disable `html` for now. Many yuque writers would write raw HTML in the markdown
// But only these markups under inline code(`) will be transpiled successfully when
// `html` is enabled.
config
.options
.html(false)
},
async ready() {
let { repoId, repoUrl, authToken, source } = opts
if (repoId) {
assert(
typeof repoId === 'string' ||
typeof repoId === 'number',
`[${PACKAGE_NAME}] repoId should be string or number, but got ${repoId}`
)
} else if (repoUrl) {
assert(
typeof repoUrl === 'string',
`[${PACKAGE_NAME}] repoUrl should be string, but got ${repoId}`
)
} else {
throw new Error(`Expected repoId or repoUrl`)
}
Yuque.setAuthToken(authToken)
if (repoUrl) {
const targetRepo = await Yuque.inferRepoByUrl(repoUrl)
repoId = targetRepo.id
}
Yuque.setRepoId(repoId)
const yuque = Yuque.getInstance()
spinner.start(`Fetching repo detail ...`)
const repoDetail = await yuque.getRepoDetail()
if (!(repoDetail && repoDetail.data && repoDetail.data.name)) {
spinner.fail(
`Please check your repoId: ${chalk.yellow(repoId)} ` +
`response: ${JSON.stringify(repoDetail)}`
)
process.exit(1)
}
spinner.succeed(`Retrieved repo detail`)
const { name, description, user } = repoDetail.data
spinner.succeed(
`Your Yuque website: ${chalk.green(name)} ` +
`${chalk.gray(description)} ...`
)
spinner.start(`Fetching TOC ... `)
const toc = await yuque.getToc()
assert(
toc && toc.data,
`[${PACKAGE_NAME}] cannot get toc of ${repoId}`
)
debug('toc', toc.data)
spinner.succeed(`Retrieved TOC`)
// Apply sidebar config
const sidebar = getSidebarByToc(toc.data)
ctx.siteConfig.themeConfig = ctx.siteConfig.themeConfig || {}
ctx.siteConfig.themeConfig.sidebar = sidebar
if (!ctx.siteConfig.title) {
ctx.siteConfig.title = name
}
if (!ctx.siteConfig.description) {
ctx.siteConfig.description = description
}
let defaultActionLink
// Apply pages
for (const page of toc.data) {
const { title, slug } = page
// Once the slug is equal to '#', it means that current node
// is an empty node.
if (!slug || slug === '#') {
continue
}
spinner.start(`Fetching ${chalk.cyan(title)} ... `)
const { status, data } = await yuque.getPage(slug)
debug('status = ', status)
let postContent
if (status && status === 404) {
postContent = `# ${title}\n > 此文档尚未创建`
} else {
const useMarkdown = typeof source === 'string'
? source === 'markdown'
: typeof source === 'function'
? source(page) === 'markdown'
: false
if (useMarkdown || data.format === 'markdown') {
postContent = data.body || ''
} else {
postContent = prettify(data.body_html || '')
}
}
const {
data: frontmatter,
content: strippedContent
} = parseFrontmatter(postContent)
const inferredTitle = inferTitle({}, strippedContent)
let content = inferredTitle
? postContent
: `# ${title} \n\n ${postContent}`
if (opts.yuqueLink) {
content += opts.yuqueLinkHtml || `
使用语雀查看`
}
const permalink = `/${slug}.html`
await ctx.addPage({
content,
frontmatter,
permalink
})
if (!defaultActionLink) {
defaultActionLink = permalink
}
spinner.succeed(`Retrieved ${chalk.cyan(title)} ... `)
}
if (ctx.pages.every(page => page.path !== '/')) {
const { home = {} } = opts
const { actionText, actionLink, heroImage, features, footer } = home
spinner.info(`Apply default homepage`)
await ctx.addPage({
content: '',
permalink: '/',
frontmatter: {
home: true,
heroImage: heroImage || user.large_avatar_url,
actionText: actionText || 'Getting Started →',
actionLink: actionLink || defaultActionLink,
features: features || undefined,
footer: footer || `Copyright © ${user.name}`,
}
})
}
}
}
}
/**
* Infer title, forked from VuePress.
*/
function inferTitle(frontmatter, strippedContent) {
if (frontmatter.home) {
return 'Home';
}
if (frontmatter.title) {
return frontmatter.title
}
const match = strippedContent.trim().match(/^#\s+(.*)/);
if (match) {
return match[1];
}
};
================================================
FILE: lib/spinner.js
================================================
/**
* Module dependencies.
*/
const ora = require('ora')
const { chalk } = require('@vuepress/shared-utils')
/**
* Expose a singleton of spinner
*/
module.exports = !process.env.DEBUG
? ora()
: [
'frame',
'clear',
'render',
'start',
'stop',
'succeed',
'fail',
'warn',
'info',
'stopAndPersist'
].reduce((memo, key) => {
memo[key] = curry(key)
return memo
}, {})
function curry(name) {
return (...args) => console.log(chalk.cyan(`spinner:${name}`), ...args)
}
================================================
FILE: lib/stack.js
================================================
/**
* Expose a simple stack
*/
module.exports = class Stack {
constructor() {
this.elements = []
}
push(element) {
this.elements.push(element)
}
pop() {
return this.elements.pop()
}
peek() {
return this.elements[this.size() - 1]
}
size() {
return this.elements.length
}
}
================================================
FILE: lib/store.js
================================================
/**
* Module dependencies.
*/
const Conf = require('conf')
/**
* Expose a object containing the utilities of cache.
*/
module.exports = {
get,
init
}
let store
function init(configName) {
store = new Conf({
configName: configName
})
return store
}
function get(configName) {
if (store) {
return store
}
return init(configName)
}
================================================
FILE: lib/style.styl
================================================
.yuque-link
border: 1px solid #333;
padding: 5px 10px;
color: #333;
border-radius: 3px;
font-weight: 200;
text-decoration none
svg
line-height: 20px;
vertical-align: text-bottom;
================================================
FILE: lib/toc.js
================================================
/**
* Module dependencies.
*/
const Stack = require('./stack')
/**
* Expose a object containing the utilities of TOC.
*/
module.exports = {
getSidebarByToc
}
/**
* Transform yuque's toc to vuepress's sidebar config.
*
* @param {Array