diff options
Diffstat (limited to 'node_modules/vuepress/lib')
82 files changed, 6268 insertions, 0 deletions
diff --git a/node_modules/vuepress/lib/app/.temp/enhanceApp.js b/node_modules/vuepress/lib/app/.temp/enhanceApp.js new file mode 100644 index 00000000..16a79b36 --- /dev/null +++ b/node_modules/vuepress/lib/app/.temp/enhanceApp.js @@ -0,0 +1 @@ +export { default } from "/Users/ruki/projects/personal/xmake-docs/src/.vuepress/enhanceApp.js"
\ No newline at end of file diff --git a/node_modules/vuepress/lib/app/.temp/override.styl b/node_modules/vuepress/lib/app/.temp/override.styl new file mode 100644 index 00000000..c7969f18 --- /dev/null +++ b/node_modules/vuepress/lib/app/.temp/override.styl @@ -0,0 +1 @@ +@import("/Users/ruki/projects/personal/xmake-docs/src/.vuepress/override.styl")
\ No newline at end of file diff --git a/node_modules/vuepress/lib/app/.temp/routes.js b/node_modules/vuepress/lib/app/.temp/routes.js new file mode 100644 index 00000000..625e7fb6 --- /dev/null +++ b/node_modules/vuepress/lib/app/.temp/routes.js @@ -0,0 +1,178 @@ +import Vue from 'vue' + +import ThemeLayout from '@themeLayout' +import ThemeNotFound from '@themeNotFound' +import { injectMixins } from '@app/util' +import rootMixins from '@app/root-mixins' + +injectMixins(ThemeLayout, rootMixins) +injectMixins(ThemeNotFound, rootMixins) + +export const routes = [ + { + name: "v-ddf4be195b958", + path: "/", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/README.md").then(comp => { + Vue.component("v-ddf4be195b958", comp.default) + next() + }) + } + }, + { + path: "/index.html", + redirect: "/" + }, + { + name: "v-8644851cf91dd", + path: "/api/introduction.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/api/introduction.md").then(comp => { + Vue.component("v-8644851cf91dd", comp.default) + next() + }) + } + }, + { + name: "v-b6ee77db64501", + path: "/guide/faq.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/guide/faq.md").then(comp => { + Vue.component("v-b6ee77db64501", comp.default) + next() + }) + } + }, + { + name: "v-fcba2a8896d43", + path: "/guide/getting-started.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/guide/getting-started.md").then(comp => { + Vue.component("v-fcba2a8896d43", comp.default) + next() + }) + } + }, + { + name: "v-cda17500cdb0c", + path: "/guide/introduction.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/guide/introduction.md").then(comp => { + Vue.component("v-cda17500cdb0c", comp.default) + next() + }) + } + }, + { + name: "v-745d22bfe3ef4", + path: "/guide/sponsors.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/guide/sponsors.md").then(comp => { + Vue.component("v-745d22bfe3ef4", comp.default) + next() + }) + } + }, + { + name: "v-360e42fea8e33", + path: "/plugin/introduction.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/plugin/introduction.md").then(comp => { + Vue.component("v-360e42fea8e33", comp.default) + next() + }) + } + }, + { + name: "v-1856b116362c7", + path: "/zh/", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/zh/README.md").then(comp => { + Vue.component("v-1856b116362c7", comp.default) + next() + }) + } + }, + { + path: "/zh/index.html", + redirect: "/zh/" + }, + { + name: "v-6812df0250aa3", + path: "/zh/api/introduction.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/zh/api/introduction.md").then(comp => { + Vue.component("v-6812df0250aa3", comp.default) + next() + }) + } + }, + { + name: "v-d3636509f8935", + path: "/zh/guide/faq.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/zh/guide/faq.md").then(comp => { + Vue.component("v-d3636509f8935", comp.default) + next() + }) + } + }, + { + name: "v-53a48e3593d7c", + path: "/zh/guide/getting-started.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/zh/guide/getting-started.md").then(comp => { + Vue.component("v-53a48e3593d7c", comp.default) + next() + }) + } + }, + { + name: "v-cd45bc6f8fdb", + path: "/zh/guide/introduction.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/zh/guide/introduction.md").then(comp => { + Vue.component("v-cd45bc6f8fdb", comp.default) + next() + }) + } + }, + { + name: "v-bef402580c1cc", + path: "/zh/guide/sponsors.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/zh/guide/sponsors.md").then(comp => { + Vue.component("v-bef402580c1cc", comp.default) + next() + }) + } + }, + { + name: "v-faf113f1b77ae", + path: "/zh/plugin/introduction.html", + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import("/Users/ruki/projects/personal/xmake-docs/src/zh/plugin/introduction.md").then(comp => { + Vue.component("v-faf113f1b77ae", comp.default) + next() + }) + } + }, + { + path: '*', + component: ThemeNotFound + } +]
\ No newline at end of file diff --git a/node_modules/vuepress/lib/app/.temp/siteData.js b/node_modules/vuepress/lib/app/.temp/siteData.js new file mode 100644 index 00000000..9af62f7a --- /dev/null +++ b/node_modules/vuepress/lib/app/.temp/siteData.js @@ -0,0 +1,634 @@ +export const siteData = { + "title": "", + "description": "", + "base": "/", + "pages": [ + { + "key": "v-ddf4be195b958", + "path": "/", + "lastUpdated": null, + "title": "Home", + "headers": [ + { + "level": 2, + "title": "Simple description", + "slug": "simple-description" + }, + { + "level": 2, + "title": "Package dependences", + "slug": "package-dependences" + }, + { + "level": 2, + "title": "Build project", + "slug": "build-project" + }, + { + "level": 2, + "title": "Run target", + "slug": "run-target" + }, + { + "level": 2, + "title": "Debug target", + "slug": "debug-target" + }, + { + "level": 2, + "title": "Configure platform", + "slug": "configure-platform" + }, + { + "level": 2, + "title": "Menu configuration", + "slug": "menu-configuration" + }, + { + "level": 2, + "title": "Package management", + "slug": "package-management" + }, + { + "level": 2, + "title": "Supported platforms", + "slug": "supported-platforms" + }, + { + "level": 2, + "title": "Supported Languages", + "slug": "supported-languages" + }, + { + "level": 2, + "title": "Supported Projects", + "slug": "supported-projects" + }, + { + "level": 2, + "title": "Builtin Plugins", + "slug": "builtin-plugins" + }, + { + "level": 2, + "title": "More Plugins", + "slug": "more-plugins" + }, + { + "level": 2, + "title": "IDE/Editor Integration", + "slug": "ide-editor-integration" + }, + { + "level": 2, + "title": "More Examples", + "slug": "more-examples" + }, + { + "level": 2, + "title": "Project Examples", + "slug": "project-examples" + }, + { + "level": 2, + "title": "Example Video", + "slug": "example-video" + }, + { + "level": 2, + "title": "Contacts", + "slug": "contacts" + } + ], + "frontmatter": { + "home": true, + "heroImage": "/hero.png", + "actionText": "Get Started →", + "actionLink": "/guide/getting-started", + "features": [ + { + "title": "Why", + "details": "Making development and building easier, so that any developer can quickly pick it up and enjoy the productivity boost when developing and building project." + }, + { + "title": "Powerful", + "details": "Provides lots of features (e.g. package, install, plugin, macro, action, option, task and etc)." + }, + { + "title": "Cross-platform", + "details": "Supports windows, macOS, linux, android, ios." + } + ], + "footer": "Apache-2.0 Licensed | Copyright © 2015-present tboox.org" + } + }, + { + "key": "v-8644851cf91dd", + "path": "/api/introduction.html", + "lastUpdated": null, + "title": "Title1", + "headers": [ + { + "level": 2, + "title": "Title2", + "slug": "title2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-2" + }, + { + "level": 2, + "title": "Title2", + "slug": "title2-2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-3" + } + ] + }, + { + "key": "v-b6ee77db64501", + "path": "/guide/faq.html", + "lastUpdated": null, + "title": "FAQ", + "headers": [ + { + "level": 2, + "title": "How to get verbose command-line arguments info?", + "slug": "how-to-get-verbose-command-line-arguments-info" + }, + { + "level": 2, + "title": "How to suppress all output info?", + "slug": "how-to-suppress-all-output-info" + }, + { + "level": 2, + "title": "How to do if xmake fails?", + "slug": "how-to-do-if-xmake-fails" + }, + { + "level": 2, + "title": "How to see verbose compiling warnings?", + "slug": "how-to-see-verbose-compiling-warnings" + }, + { + "level": 2, + "title": "How to scan source code and generate xmake.lua automaticlly", + "slug": "how-to-scan-source-code-and-generate-xmake-lua-automaticlly" + } + ] + }, + { + "key": "v-fcba2a8896d43", + "path": "/guide/getting-started.html", + "lastUpdated": null, + "title": "Getting Started", + "headers": [ + { + "level": 2, + "title": "Installation", + "slug": "installation" + }, + { + "level": 2, + "title": "Quick Start", + "slug": "quick-start" + }, + { + "level": 2, + "title": "Project Examples", + "slug": "project-examples" + }, + { + "level": 2, + "title": "Configuration", + "slug": "configuration" + }, + { + "level": 2, + "title": "Dependency Package Management", + "slug": "dependency-package-management" + } + ] + }, + { + "key": "v-cda17500cdb0c", + "path": "/guide/introduction.html", + "lastUpdated": null, + "title": "Introduction", + "headers": [ + { + "level": 2, + "title": "Introduction", + "slug": "introduction" + } + ] + }, + { + "key": "v-745d22bfe3ef4", + "path": "/guide/sponsors.html", + "lastUpdated": null, + "title": "Sponsors" + }, + { + "key": "v-360e42fea8e33", + "path": "/plugin/introduction.html", + "lastUpdated": null, + "title": "Title1", + "headers": [ + { + "level": 2, + "title": "Title2", + "slug": "title2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-2" + }, + { + "level": 2, + "title": "Title2", + "slug": "title2-2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-3" + } + ] + }, + { + "key": "v-1856b116362c7", + "path": "/zh/", + "lastUpdated": null, + "title": "Home", + "headers": [ + { + "level": 2, + "title": "简单的工程描述", + "slug": "简单的工程描述" + }, + { + "level": 2, + "title": "包依赖描述", + "slug": "包依赖描述" + }, + { + "level": 2, + "title": "构建工程", + "slug": "构建工程" + }, + { + "level": 2, + "title": "运行目标", + "slug": "运行目标" + }, + { + "level": 2, + "title": "调试程序", + "slug": "调试程序" + }, + { + "level": 2, + "title": "配置平台", + "slug": "配置平台" + }, + { + "level": 2, + "title": "图形化菜单配置", + "slug": "图形化菜单配置" + }, + { + "level": 2, + "title": "包依赖管理", + "slug": "包依赖管理" + }, + { + "level": 2, + "title": "支持平台", + "slug": "支持平台" + }, + { + "level": 2, + "title": "支持语言", + "slug": "支持语言" + }, + { + "level": 2, + "title": "工程类型", + "slug": "工程类型" + }, + { + "level": 2, + "title": "内置插件", + "slug": "内置插件" + }, + { + "level": 2, + "title": "更多插件", + "slug": "更多插件" + }, + { + "level": 2, + "title": "IDE和编辑器插件", + "slug": "ide和编辑器插件" + }, + { + "level": 2, + "title": "更多例子", + "slug": "更多例子" + }, + { + "level": 2, + "title": "项目例子", + "slug": "项目例子" + }, + { + "level": 2, + "title": "演示视频", + "slug": "演示视频" + }, + { + "level": 2, + "title": "联系方式", + "slug": "联系方式" + } + ], + "frontmatter": { + "home": true, + "heroImage": "/hero.png", + "actionText": "快速上手 →", + "actionLink": "/zh/guide/getting-started", + "features": [ + { + "title": "为什么使用", + "details": "让开发者更加关注于项目本身开发,简化项目的描述和构建,并且提供平台无关性,使得一次编写,随处构建" + }, + { + "title": "强大", + "details": "提供大量的实用特性(例如:插件扩展、脚本宏记录、批量打包、自动文档生成等常用插件)" + }, + { + "title": "跨平台", + "details": "支持windows, macOS, linux, android, ios" + } + ], + "footer": "Apache-2.0 Licensed | Copyright © 2015-present tboox.org" + } + }, + { + "key": "v-6812df0250aa3", + "path": "/zh/api/introduction.html", + "lastUpdated": null, + "title": "Title1", + "headers": [ + { + "level": 2, + "title": "Title2", + "slug": "title2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-2" + }, + { + "level": 2, + "title": "Title2", + "slug": "title2-2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-3" + } + ] + }, + { + "key": "v-d3636509f8935", + "path": "/zh/guide/faq.html", + "lastUpdated": null + }, + { + "key": "v-53a48e3593d7c", + "path": "/zh/guide/getting-started.html", + "lastUpdated": null, + "title": "快速开始", + "headers": [ + { + "level": 2, + "title": "编译", + "slug": "编译" + }, + { + "level": 2, + "title": "例子", + "slug": "例子" + } + ] + }, + { + "key": "v-cd45bc6f8fdb", + "path": "/zh/guide/introduction.html", + "lastUpdated": null, + "title": "简介", + "headers": [ + { + "level": 2, + "title": "特性", + "slug": "特性" + }, + { + "level": 2, + "title": "项目例子", + "slug": "项目例子" + }, + { + "level": 2, + "title": "联系方式", + "slug": "联系方式" + } + ] + }, + { + "key": "v-bef402580c1cc", + "path": "/zh/guide/sponsors.html", + "lastUpdated": null + }, + { + "key": "v-faf113f1b77ae", + "path": "/zh/plugin/introduction.html", + "lastUpdated": null, + "title": "Title1", + "headers": [ + { + "level": 2, + "title": "Title2", + "slug": "title2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-2" + }, + { + "level": 2, + "title": "Title2", + "slug": "title2-2" + }, + { + "level": 3, + "title": "Title3", + "slug": "title3-3" + } + ] + } + ], + "themeConfig": { + "repo": "tboox/xmake", + "docsRepo": "tboox/xmake-docs", + "docsDir": "src", + "editLinks": true, + "sidebarDepth": 2, + "locales": { + "/": { + "label": "English", + "selectText": "Languages", + "editLinkText": "Edit this page on GitHub", + "lastUpdated": "Last Updated", + "nav": [ + { + "text": "Guide", + "link": "/guide/introduction" + }, + { + "text": "Plugin", + "link": "/plugin/introduction" + }, + { + "text": "API", + "link": "/api/introduction" + }, + { + "text": "Articles", + "link": "http://www.tboox.org/category/#xmake" + }, + { + "text": "Feedback", + "link": "https://github.com/tboox/xmake/issues" + }, + { + "text": "Community", + "link": "https://www.reddit.com/r/tboox/" + }, + { + "text": "Donation", + "link": "http://tboox.org/cn/donation/" + } + ], + "sidebar": { + "/guide/": [ + "introduction", + "getting-started", + "faq", + "sponsors" + ], + "/plugin/": [ + "introduction" + ], + "/api/": [ + "introduction" + ] + } + }, + "/zh/": { + "label": "简体中文", + "selectText": "选择语言", + "editLinkText": "在 GitHub 上编辑此页", + "lastUpdated": "上次更新", + "nav": [ + { + "text": "指南", + "link": "/zh/guide/introduction" + }, + { + "text": "插件", + "link": "/zh/plugin/introduction" + }, + { + "text": "接口", + "link": "/zh/api/introduction" + }, + { + "text": "文章", + "link": "http://www.tboox.org/cn/category/#xmake" + }, + { + "text": "反馈", + "link": "https://github.com/tboox/xmake/issues" + }, + { + "text": "社区", + "link": "https://www.reddit.com/r/tboox/" + }, + { + "text": "捐助", + "link": "http://tboox.org/cn/donation/" + } + ], + "sidebar": { + "/zh/guide/": [ + "introduction", + "getting-started", + "faq", + "sponsors" + ], + "/zh/plugin/": [ + "introduction" + ], + "/zh/api/": [ + "introduction" + ] + } + } + } + }, + "locales": { + "/": { + "lang": "en-US", + "title": "xmake", + "description": "A cross-platform build utility based on Lua" + }, + "/zh/": { + "lang": "zh-CN", + "title": "xmake", + "description": "一个基于Lua的轻量级跨平台自动构建工具" + } + } +}
\ No newline at end of file diff --git a/node_modules/vuepress/lib/app/.temp/style.styl b/node_modules/vuepress/lib/app/.temp/style.styl new file mode 100644 index 00000000..b3deab99 --- /dev/null +++ b/node_modules/vuepress/lib/app/.temp/style.styl @@ -0,0 +1 @@ +@import("/Users/ruki/projects/personal/xmake-docs/src/.vuepress/style.styl")
\ No newline at end of file diff --git a/node_modules/vuepress/lib/app/.temp/themeEnhanceApp.js b/node_modules/vuepress/lib/app/.temp/themeEnhanceApp.js new file mode 100644 index 00000000..03c095d5 --- /dev/null +++ b/node_modules/vuepress/lib/app/.temp/themeEnhanceApp.js @@ -0,0 +1 @@ +export default function () {}
\ No newline at end of file diff --git a/node_modules/vuepress/lib/app/SWUpdateEvent.js b/node_modules/vuepress/lib/app/SWUpdateEvent.js new file mode 100644 index 00000000..fe6ab31c --- /dev/null +++ b/node_modules/vuepress/lib/app/SWUpdateEvent.js @@ -0,0 +1,43 @@ +export default class SWUpdateEvent { + constructor (registration) { + Object.defineProperty(this, 'registration', { + value: registration, + configurable: true, + writable: true + }) + } + + /** + * Check if the new service worker exists or not. + */ + update () { + return this.registration.update() + } + + /** + * Activate new service worker to work 'location.reload()' with new data. + */ + skipWaiting () { + const worker = this.registration.waiting + if (!worker) { + return Promise.resolve() + } + + console.log('[vuepress:sw] Doing worker.skipWaiting().') + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + console.log('[vuepress:sw] Done worker.skipWaiting().') + if (event.data.error) { + reject(event.data.error) + } else { + resolve(event.data) + } + } + + worker.postMessage({ type: 'skip-waiting' }, [channel.port2]) + }) + } +} + diff --git a/node_modules/vuepress/lib/app/app.js b/node_modules/vuepress/lib/app/app.js new file mode 100644 index 00000000..511abbd4 --- /dev/null +++ b/node_modules/vuepress/lib/app/app.js @@ -0,0 +1,102 @@ +import Vue from 'vue' +import Router from 'vue-router' +import dataMixin from './dataMixin' +import store from './store' +import { routes } from '@temp/routes' +import { siteData } from '@temp/siteData' +import enhanceApp from '@temp/enhanceApp' +import themeEnhanceApp from '@temp/themeEnhanceApp' + +// generated from user config +import('@temp/style.styl') + +// built-in components +import Content from './components/Content' +import OutboundLink from './components/OutboundLink.vue' +import ClientOnly from './components/ClientOnly' + +// suggest dev server restart on base change +if (module.hot) { + const prevBase = siteData.base + module.hot.accept('./.temp/siteData', () => { + if (siteData.base !== prevBase) { + window.alert( + `[vuepress] Site base has changed. ` + + `Please restart dev server to ensure correct asset paths.` + ) + } + }) +} + +Vue.config.productionTip = false +Vue.use(Router) +// mixin for exposing $site and $page +Vue.mixin(dataMixin(siteData)) +// component for rendering markdown content and setting title etc. +Vue.component('Content', Content) +Vue.component('OutboundLink', OutboundLink) +Vue.component('Badge', () => import('./components/Badge.vue')) +// component for client-only content +Vue.component('ClientOnly', ClientOnly) + +// global helper for adding base path to absolute urls +Vue.prototype.$withBase = function (path) { + const base = this.$site.base + if (path.charAt(0) === '/') { + return base + path.slice(1) + } else { + return path + } +} + +export function createApp () { + const router = new Router({ + base: siteData.base, + mode: 'history', + fallback: false, + routes, + scrollBehavior: (to, from, saved) => { + if (saved) { + return saved + } else if (to.hash) { + if (store.disableScrollBehavior) { + return false + } + return { + selector: to.hash + } + } else { + return { x: 0, y: 0 } + } + } + }) + + // redirect /foo to /foo/ + router.beforeEach((to, from, next) => { + if (!/(\/|\.html)$/.test(to.path)) { + next(Object.assign({}, to, { + path: to.path + '/' + })) + } else { + next() + } + }) + + const options = {} + + themeEnhanceApp({ Vue, options, router, siteData }) + enhanceApp({ Vue, options, router, siteData }) + + const app = new Vue( + Object.assign(options, { + router, + render (h) { + return h('div', { attrs: { id: 'app' }}, [ + h('router-view', { ref: 'layout' }) + ]) + } + }) + ) + + return { app, router } +} diff --git a/node_modules/vuepress/lib/app/clientEntry.js b/node_modules/vuepress/lib/app/clientEntry.js new file mode 100644 index 00000000..dc86314a --- /dev/null +++ b/node_modules/vuepress/lib/app/clientEntry.js @@ -0,0 +1,74 @@ +/* global BASE_URL, GA_ID, ga, SW_ENABLED, VUEPRESS_VERSION, LAST_COMMIT_HASH*/ + +import { createApp } from './app' +import SWUpdateEvent from './SWUpdateEvent' +import { register } from 'register-service-worker' + +const { app, router } = createApp() + +window.__VUEPRESS_VERSION__ = { + version: VUEPRESS_VERSION, + hash: LAST_COMMIT_HASH +} + +// Google analytics integration +if (process.env.NODE_ENV === 'production' && GA_ID) { + (function (i, s, o, g, r, a, m) { + i['GoogleAnalyticsObject'] = r + i[r] = i[r] || function () { + (i[r].q = i[r].q || []).push(arguments) + } + i[r].l = 1 * new Date() + a = s.createElement(o) + m = s.getElementsByTagName(o)[0] + a.async = 1 + a.src = g + m.parentNode.insertBefore(a, m) + })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga') + + ga('create', GA_ID, 'auto') + ga('send', 'pageview') + + router.afterEach(function (to) { + ga('set', 'page', app.$withBase(to.fullPath)) + ga('send', 'pageview') + }) +} + +router.onReady(() => { + app.$mount('#app') + + // Register service worker + if (process.env.NODE_ENV === 'production' && + SW_ENABLED && + window.location.protocol === 'https:') { + register(`${BASE_URL}service-worker.js`, { + ready () { + console.log('[vuepress:sw] Service worker is active.') + app.$refs.layout.$emit('sw-ready') + }, + cached (registration) { + console.log('[vuepress:sw] Content has been cached for offline use.') + app.$refs.layout.$emit('sw-cached', new SWUpdateEvent(registration)) + }, + updated (registration) { + console.log('[vuepress:sw] Content updated.') + app.$refs.layout.$emit('sw-updated', new SWUpdateEvent(registration)) + }, + offline () { + console.log('[vuepress:sw] No internet connection found. App is running in offline mode.') + app.$refs.layout.$emit('sw-offline') + }, + error (err) { + console.error('[vuepress:sw] Error during service worker registration:', err) + app.$refs.layout.$emit('sw-error', err) + if (GA_ID) { + ga('send', 'exception', { + exDescription: err.message, + exFatal: false + }) + } + } + }) + } +}) diff --git a/node_modules/vuepress/lib/app/components/Badge.vue b/node_modules/vuepress/lib/app/components/Badge.vue new file mode 100644 index 00000000..17e6ba6e --- /dev/null +++ b/node_modules/vuepress/lib/app/components/Badge.vue @@ -0,0 +1,46 @@ +<script> +export default { + functional: true, + props: { + type: { + type: String, + default: 'tip' + }, + text: String, + vertical: { + type: String, + default: 'top' + } + }, + render (h, { props, slots }) { + return h('span', { + class: ['badge', props.type, props.vertical] + }, props.text || slots().default) + } +} +</script> + +<style lang="stylus" scoped> +@import '../../default-theme/styles/config.styl' + +.badge + display inline-block + font-size 14px + height 18px + line-height 18px + border-radius 3px + padding 0 6px + color white + margin-right 5px + background-color #42b983 + &.middle + vertical-align middle + &.top + vertical-align top + &.tip, &.green + background-color #42b983 + &.error + background-color #DA5961 //#f66 + &.warning, &.warn, &.yellow + background-color darken(#ffe564, 35%) +</style> diff --git a/node_modules/vuepress/lib/app/components/ClientOnly.js b/node_modules/vuepress/lib/app/components/ClientOnly.js new file mode 100644 index 00000000..c786d1af --- /dev/null +++ b/node_modules/vuepress/lib/app/components/ClientOnly.js @@ -0,0 +1,12 @@ +export default { + functional: true, + render (h, { parent, children }) { + if (parent._isMounted) { + return children + } else { + parent.$once('hook:mounted', () => { + parent.$forceUpdate() + }) + } + } +} diff --git a/node_modules/vuepress/lib/app/components/Content.js b/node_modules/vuepress/lib/app/components/Content.js new file mode 100644 index 00000000..46628195 --- /dev/null +++ b/node_modules/vuepress/lib/app/components/Content.js @@ -0,0 +1,17 @@ +export default { + functional: true, + + props: { + custom: { + type: Boolean, + default: true + } + }, + + render (h, { parent, props, data }) { + return h(parent.$page.key, { + class: [props.custom ? 'custom' : '', data.class, data.staticClass], + style: data.style + }) + } +} diff --git a/node_modules/vuepress/lib/app/components/OutboundLink.vue b/node_modules/vuepress/lib/app/components/OutboundLink.vue new file mode 100644 index 00000000..a3aa7f2d --- /dev/null +++ b/node_modules/vuepress/lib/app/components/OutboundLink.vue @@ -0,0 +1,12 @@ +<template functional> + <svg class="icon outbound" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15"> + <path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> + <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon> + </svg> +</template> + +<style lang="stylus"> +.icon.outbound + color #aaa + display inline-block +</style> diff --git a/node_modules/vuepress/lib/app/dataMixin.js b/node_modules/vuepress/lib/app/dataMixin.js new file mode 100644 index 00000000..fd003d92 --- /dev/null +++ b/node_modules/vuepress/lib/app/dataMixin.js @@ -0,0 +1,90 @@ +import Vue from 'vue' +import { findPageForPath } from './util' + +export default function dataMixin (siteData) { + prepare(siteData) + const store = new Vue({ + data: { siteData } + }) + + if (module.hot) { + module.hot.accept('./.temp/siteData', () => { + prepare(siteData) + store.siteData = siteData + }) + } + + return { + computed: { + $site () { + return store.siteData + }, + $localeConfig () { + const { locales = {}} = this.$site + let targetLang + let defaultLang + for (const path in locales) { + if (path === '/') { + defaultLang = locales[path] + } else if (this.$page.path.indexOf(path) === 0) { + targetLang = locales[path] + } + } + return targetLang || defaultLang || {} + }, + $siteTitle () { + return this.$localeConfig.title || this.$site.title || '' + }, + $title () { + const page = this.$page + const siteTitle = this.$siteTitle + const selfTitle = page.frontmatter.home ? null : ( + page.frontmatter.title || // explicit title + page.title // inferred title + ) + return siteTitle + ? selfTitle + ? (selfTitle + ' | ' + siteTitle) + : siteTitle + : selfTitle || 'VuePress' + }, + $description () { + // #565 hoist description from meta + if (this.$page.frontmatter.meta) { + const descriptionMeta = this.$page.frontmatter.meta.filter(item => item.name === 'description')[0] + if (descriptionMeta) return descriptionMeta.content + } + return this.$page.frontmatter.description || this.$localeConfig.description || this.$site.description || '' + }, + $lang () { + return this.$page.frontmatter.lang || this.$localeConfig.lang || 'en-US' + }, + $localePath () { + return this.$localeConfig.path || '/' + }, + $themeLocaleConfig () { + return (this.$site.themeConfig.locales || {})[this.$localePath] || {} + }, + $page () { + return findPageForPath( + this.$site.pages, + this.$route.path + ) + } + } + } +} + +function prepare (siteData) { + siteData.pages.forEach(page => { + if (!page.frontmatter) { + page.frontmatter = {} + } + }) + if (siteData.locales) { + Object.keys(siteData.locales).forEach(path => { + siteData.locales[path].path = path + }) + } + Object.freeze(siteData) +} diff --git a/node_modules/vuepress/lib/app/index.dev.html b/node_modules/vuepress/lib/app/index.dev.html new file mode 100644 index 00000000..06f743c5 --- /dev/null +++ b/node_modules/vuepress/lib/app/index.dev.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <title></title> + </head> + <body> + <div id="app"></div> + </body> +</html> diff --git a/node_modules/vuepress/lib/app/index.ssr.html b/node_modules/vuepress/lib/app/index.ssr.html new file mode 100644 index 00000000..32d310a9 --- /dev/null +++ b/node_modules/vuepress/lib/app/index.ssr.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="{{ lang }}"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <title>{{ title }}</title> + <meta name="description" content="{{ description }}"> + {{{ userHeadTags }}} + {{{ pageMeta }}} + {{{ renderResourceHints() }}} + {{{ renderStyles() }}} + </head> + <body> + <!--vue-ssr-outlet--> + {{{ renderScripts() }}} + </body> +</html> diff --git a/node_modules/vuepress/lib/app/root-mixins/activeHeaderLinks.js b/node_modules/vuepress/lib/app/root-mixins/activeHeaderLinks.js new file mode 100644 index 00000000..6cad69d5 --- /dev/null +++ b/node_modules/vuepress/lib/app/root-mixins/activeHeaderLinks.js @@ -0,0 +1,47 @@ +import store from '@app/store' +import throttle from 'lodash.throttle' + +export default { + mounted () { + window.addEventListener('scroll', this.onScroll) + }, + methods: { + onScroll: throttle(function () { + this.setActiveHash() + }, 300), + setActiveHash () { + const sidebarLinks = [].slice.call(document.querySelectorAll('.sidebar-link')) + const anchors = [].slice.call(document.querySelectorAll('.header-anchor')) + .filter(anchor => sidebarLinks.some(sidebarLink => sidebarLink.hash === anchor.hash)) + + const scrollTop = Math.max( + window.pageYOffset, + document.documentElement.scrollTop, + document.body.scrollTop + ) + + for (let i = 0; i < anchors.length; i++) { + const anchor = anchors[i] + const nextAnchor = anchors[i + 1] + + const isActive = i === 0 && scrollTop === 0 || + (scrollTop >= anchor.parentElement.offsetTop + 10 && + (!nextAnchor || scrollTop < nextAnchor.parentElement.offsetTop - 10)) + + if (isActive && decodeURIComponent(this.$route.hash) !== decodeURIComponent(anchor.hash)) { + store.disableScrollBehavior = true + this.$router.replace(decodeURIComponent(anchor.hash), () => { + // execute after scrollBehavior handler. + this.$nextTick(() => { + store.disableScrollBehavior = false + }) + }) + return + } + } + } + }, + beforeDestroy () { + window.removeEventListener('scroll', this.onScroll) + } +} diff --git a/node_modules/vuepress/lib/app/root-mixins/index.js b/node_modules/vuepress/lib/app/root-mixins/index.js new file mode 100644 index 00000000..fd966f39 --- /dev/null +++ b/node_modules/vuepress/lib/app/root-mixins/index.js @@ -0,0 +1,7 @@ +import updateMeta from './updateMeta' +import activeHeaderLinks from '@activeHeaderLinks' + +export default [ + updateMeta, // required + activeHeaderLinks // optional +] diff --git a/node_modules/vuepress/lib/app/root-mixins/updateMeta.js b/node_modules/vuepress/lib/app/root-mixins/updateMeta.js new file mode 100644 index 00000000..ff2703ac --- /dev/null +++ b/node_modules/vuepress/lib/app/root-mixins/updateMeta.js @@ -0,0 +1,59 @@ +export default { + created () { + if (this.$ssrContext) { + this.$ssrContext.title = this.$title + this.$ssrContext.lang = this.$lang + this.$ssrContext.description = this.$page.description || this.$description + } + }, + + mounted () { + // update title / meta tags + this.currentMetaTags = new Set() + + const updateMeta = () => { + document.title = this.$title + document.documentElement.lang = this.$lang + const userMeta = this.$page.frontmatter.meta || [] + const meta = userMeta.slice(0) + const useGlobalDescription = userMeta.filter(m => m.name === 'description').length === 0 + + // #665 Avoid duplicate description meta at runtime. + if (useGlobalDescription) { + meta.push({ name: 'description', content: this.$description }) + } + + // Including description meta coming from SSR. + const descriptionMetas = document.querySelectorAll('meta[name="description"]') + if (descriptionMetas.length) { + descriptionMetas.forEach(m => this.currentMetaTags.add(m)) + } + + this.currentMetaTags = new Set(updateMetaTags(meta, this.currentMetaTags)) + } + this.$watch('$page', updateMeta) + updateMeta() + }, + + beforeDestroy () { + updateMetaTags(null, this.currentMetaTags) + } +} + +function updateMetaTags (meta, current) { + if (current) { + [...current].forEach(c => { + document.head.removeChild(c) + }) + } + if (meta) { + return meta.map(m => { + const tag = document.createElement('meta') + Object.keys(m).forEach(key => { + tag.setAttribute(key, m[key]) + }) + document.head.appendChild(tag) + return tag + }) + } +} diff --git a/node_modules/vuepress/lib/app/serverEntry.js b/node_modules/vuepress/lib/app/serverEntry.js new file mode 100644 index 00000000..715fc956 --- /dev/null +++ b/node_modules/vuepress/lib/app/serverEntry.js @@ -0,0 +1,14 @@ +import { createApp } from './app' + +export default context => new Promise((resolve, reject) => { + const { app, router } = createApp() + const { url } = context + const { fullPath } = router.resolve(url).route + + if (fullPath !== url) { + return reject({ url: fullPath }) + } + + router.push(url) + router.onReady(() => resolve(app)) +}) diff --git a/node_modules/vuepress/lib/app/store.js b/node_modules/vuepress/lib/app/store.js new file mode 100644 index 00000000..115fe0b9 --- /dev/null +++ b/node_modules/vuepress/lib/app/store.js @@ -0,0 +1,7 @@ +// It is not yet time to use Vuex to manage the global state +// singleton object as a global store. +const state = { + disableScrollBehavior: false +} + +export default state diff --git a/node_modules/vuepress/lib/app/util.js b/node_modules/vuepress/lib/app/util.js new file mode 100644 index 00000000..c1117224 --- /dev/null +++ b/node_modules/vuepress/lib/app/util.js @@ -0,0 +1,19 @@ +export function injectMixins (options, mixins) { + if (!options.mixins) { + options.mixins = [] + } + options.mixins.push(...mixins) +} + +export function findPageForPath (pages, path) { + for (let i = 0; i < pages.length; i++) { + const page = pages[i] + if (page.path === path) { + return page + } + } + return { + path: '', + frontmatter: {} + } +} diff --git a/node_modules/vuepress/lib/build.js b/node_modules/vuepress/lib/build.js new file mode 100644 index 00000000..f7efb49b --- /dev/null +++ b/node_modules/vuepress/lib/build.js @@ -0,0 +1,200 @@ +module.exports = async function build (sourceDir, cliOptions = {}) { + process.env.NODE_ENV = 'production' + + const fs = require('fs-extra') + const path = require('path') + const chalk = require('chalk') + const webpack = require('webpack') + const readline = require('readline') + const escape = require('escape-html') + + const logger = require('./util/logger') + const prepare = require('./prepare') + const createClientConfig = require('./webpack/createClientConfig') + const createServerConfig = require('./webpack/createServerConfig') + const { createBundleRenderer } = require('vue-server-renderer') + const { normalizeHeadTag, applyUserWebpackConfig } = require('./util') + + logger.wait('\nExtracting site metadata...') + const options = await prepare(sourceDir) + if (cliOptions.outDir) { + options.outDir = cliOptions.outDir + } + + const { outDir } = options + if (path.resolve() === outDir) { + return console.error(logger.error(chalk.red('Unexpected option: outDir cannot be set to the current working directory.\n'), false)) + } + await fs.remove(outDir) + + let clientConfig = createClientConfig(options, cliOptions).toConfig() + let serverConfig = createServerConfig(options, cliOptions).toConfig() + + // apply user config... + const userConfig = options.siteConfig.configureWebpack + if (userConfig) { + clientConfig = applyUserWebpackConfig(userConfig, clientConfig, false) + serverConfig = applyUserWebpackConfig(userConfig, serverConfig, true) + } + + // compile! + const stats = await compile([clientConfig, serverConfig]) + + const serverBundle = require(path.resolve(outDir, 'manifest/server.json')) + const clientManifest = require(path.resolve(outDir, 'manifest/client.json')) + + // remove manifests after loading them. + await fs.remove(path.resolve(outDir, 'manifest')) + + // find and remove empty style chunk caused by + // https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85 + // TODO remove when it's fixed + await workaroundEmptyStyleChunk() + + // create server renderer using built manifests + const renderer = createBundleRenderer(serverBundle, { + clientManifest, + runInNewContext: false, + inject: false, + shouldPrefetch: options.siteConfig.shouldPrefetch || (() => true), + template: await fs.readFile(path.resolve(__dirname, 'app/index.ssr.html'), 'utf-8') + }) + + // pre-render head tags from user config + const userHeadTags = (options.siteConfig.head || []) + .map(renderHeadTag) + .join('\n ') + + // render pages + logger.wait('Rendering static HTML...') + for (const page of options.siteData.pages) { + await renderPage(page) + } + + // if the user does not have a custom 404.md, generate the theme's default + if (!options.siteData.pages.some(p => p.path === '/404.html')) { + await renderPage({ path: '/404.html' }) + } + + readline.clearLine(process.stdout, 0) + readline.cursorTo(process.stdout, 0) + + if (options.siteConfig.serviceWorker) { + logger.wait('\nGenerating service worker...') + const wbb = require('workbox-build') + await wbb.generateSW({ + swDest: path.resolve(outDir, 'service-worker.js'), + globDirectory: outDir, + globPatterns: ['**\/*.{js,css,html,png,jpg,jpeg,gif,svg,woff,woff2,eot,ttf,otf}'] + }) + await fs.writeFile( + path.resolve(outDir, 'service-worker.js'), + await fs.readFile(path.resolve(__dirname, 'service-worker/skip-waiting.js'), 'utf8'), + { flag: 'a' } + ) + } + + // DONE. + const relativeDir = path.relative(process.cwd(), outDir) + logger.success(`\n${chalk.green('Success!')} Generated static files in ${chalk.cyan(relativeDir)}.\n`) + + // --- helpers --- + + function compile (config) { + return new Promise((resolve, reject) => { + webpack(config, (err, stats) => { + if (err) { + return reject(err) + } + if (stats.hasErrors()) { + stats.toJson().errors.forEach(err => { + console.error(err) + }) + reject(new Error(`Failed to compile with errors.`)) + return + } + if (cliOptions.debug && stats.hasWarnings()) { + stats.toJson().warnings.forEach(warning => { + console.warn(warning) + }) + } + resolve(stats.toJson({ modules: false })) + }) + }) + } + + function renderHeadTag (tag) { + const { tagName, attributes, innerHTML, closeTag } = normalizeHeadTag(tag) + return `<${tagName}${renderAttrs(attributes)}>${innerHTML}${closeTag ? `</${tagName}>` : ``}` + } + + function renderAttrs (attrs = {}) { + const keys = Object.keys(attrs) + if (keys.length) { + return ' ' + keys.map(name => `${name}="${escape(attrs[name])}"`).join(' ') + } else { + return '' + } + } + + async function renderPage (page) { + const pagePath = page.path + readline.clearLine(process.stdout, 0) + readline.cursorTo(process.stdout, 0) + process.stdout.write(`Rendering page: ${pagePath}`) + + // #565 Avoid duplicate description meta at SSR. + const meta = (page.frontmatter && page.frontmatter.meta || []).filter(item => item.name !== 'description') + const pageMeta = renderPageMeta(meta) + + const context = { + url: pagePath, + userHeadTags, + pageMeta, + title: 'VuePress', + lang: 'en', + description: '' + } + + let html + try { + html = await renderer.renderToString(context) + } catch (e) { + console.error(logger.error(chalk.red(`Error rendering ${pagePath}:`), false)) + throw e + } + const filename = decodeURIComponent(pagePath.replace(/\/$/, '/index.html').replace(/^\//, '')) + const filePath = path.resolve(outDir, filename) + await fs.ensureDir(path.dirname(filePath)) + await fs.writeFile(filePath, html) + } + + function renderPageMeta (meta) { + if (!meta) return '' + return meta.map(m => { + let res = `<meta` + Object.keys(m).forEach(key => { + res += ` ${key}="${escape(m[key])}"` + }) + return res + `>` + }).join('') + } + + async function workaroundEmptyStyleChunk () { + const styleChunk = stats.children[0].assets.find(a => { + return /styles\.\w{8}\.js$/.test(a.name) + }) + if (!styleChunk) return + const styleChunkPath = path.resolve(outDir, styleChunk.name) + const styleChunkContent = await fs.readFile(styleChunkPath, 'utf-8') + await fs.remove(styleChunkPath) + // prepend it to app.js. + // this is necessary for the webpack runtime to work properly. + const appChunk = stats.children[0].assets.find(a => { + return /app\.\w{8}\.js$/.test(a.name) + }) + const appChunkPath = path.resolve(outDir, appChunk.name) + const appChunkContent = await fs.readFile(appChunkPath, 'utf-8') + await fs.writeFile(appChunkPath, styleChunkContent + appChunkContent) + } +} diff --git a/node_modules/vuepress/lib/default-theme/AlgoliaSearchBox.vue b/node_modules/vuepress/lib/default-theme/AlgoliaSearchBox.vue new file mode 100644 index 00000000..0334ae0b --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/AlgoliaSearchBox.vue @@ -0,0 +1,156 @@ +<template> + <form + id="search-form" + class="algolia-search-wrapper search-box" + > + <input + id="algolia-search-input" + class="search-query" + > + </form> +</template> + +<script> +export default { + props: ['options'], + + mounted () { + this.initialize(this.options, this.$lang) + }, + + methods: { + initialize (userOptions, lang) { + Promise.all([ + import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'), + import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css') + ]).then(([docsearch]) => { + docsearch = docsearch.default + const { algoliaOptions = {}} = userOptions + docsearch(Object.assign( + {}, + userOptions, + { + inputSelector: '#algolia-search-input', + // #697 Make docsearch work well at i18n mode. + algoliaOptions: Object.assign({ + 'facetFilters': [`lang:${lang}`].concat(algoliaOptions.facetFilters || []) + }, algoliaOptions) + } + )) + }) + }, + + update (options, lang) { + this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">' + this.initialize(options, lang) + } + }, + + watch: { + $lang (newValue) { + this.update(this.options, newValue) + }, + + options (newValue) { + this.update(newValue, this.$lang) + } + } +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.algolia-search-wrapper + & > span + vertical-align middle + .algolia-autocomplete + line-height normal + .ds-dropdown-menu + background-color #fff + border 1px solid #999 + border-radius 4px + font-size 16px + margin 6px 0 0 + padding 4px + text-align left + &:before + border-color #999 + [class*=ds-dataset-] + border none + padding 0 + .ds-suggestions + margin-top 0 + .ds-suggestion + border-bottom 1px solid $borderColor + .algolia-docsearch-suggestion--highlight + color #2c815b + .algolia-docsearch-suggestion + border-color $borderColor + padding 0 + .algolia-docsearch-suggestion--category-header + padding 5px 10px + margin-top 0 + background $accentColor + color #fff + font-weight 600 + .algolia-docsearch-suggestion--highlight + background rgba(255, 255, 255, 0.6) + .algolia-docsearch-suggestion--wrapper + padding 0 + .algolia-docsearch-suggestion--title + font-weight 600 + margin-bottom 0 + color $textColor + .algolia-docsearch-suggestion--subcategory-column + vertical-align top + padding 5px 7px 5px 5px + border-color $borderColor + background #f1f3f5 + &:after + display none + .algolia-docsearch-suggestion--subcategory-column-text + color #555 + .algolia-docsearch-footer + border-color $borderColor + .ds-cursor .algolia-docsearch-suggestion--content + background-color #e7edf3 !important + color $textColor + +@media (min-width: $MQMobile) + .algolia-search-wrapper + .algolia-autocomplete + .algolia-docsearch-suggestion + .algolia-docsearch-suggestion--subcategory-column + float none + width 150px + min-width 150px + display table-cell + .algolia-docsearch-suggestion--content + float none + display table-cell + width 100% + vertical-align top + .ds-dropdown-menu + min-width 515px !important + +@media (max-width: $MQMobile) + .algolia-search-wrapper + .ds-dropdown-menu + min-width calc(100vw - 4rem) !important + max-width calc(100vw - 4rem) !important + .algolia-docsearch-suggestion--wrapper + padding 5px 7px 5px 5px !important + .algolia-docsearch-suggestion--subcategory-column + padding 0 !important + background white !important + .algolia-docsearch-suggestion--subcategory-column-text:after + content " > " + font-size 10px + line-height 14.4px + display inline-block + width 5px + margin -3px 3px 0 + vertical-align middle + +</style> diff --git a/node_modules/vuepress/lib/default-theme/DropdownLink.vue b/node_modules/vuepress/lib/default-theme/DropdownLink.vue new file mode 100644 index 00000000..e6000a60 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/DropdownLink.vue @@ -0,0 +1,181 @@ +<template> + <div + class="dropdown-wrapper" + :class="{ open }" + > + <a + class="dropdown-title" + @click="toggle" + > + <span class="title">{{ item.text }}</span> + <span + class="arrow" + :class="open ? 'down' : 'right'" + ></span> + </a> + + <DropdownTransition> + <ul + class="nav-dropdown" + v-show="open" + > + <li + class="dropdown-item" + :key="subItem.link || index" + v-for="(subItem, index) in item.items" + > + <h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4> + + <ul + class="dropdown-subitem-wrapper" + v-if="subItem.type === 'links'" + > + <li + class="dropdown-subitem" + :key="childSubItem.link" + v-for="childSubItem in subItem.items" + > + <NavLink :item="childSubItem"/> + </li> + </ul> + + <NavLink + v-else + :item="subItem" + /> + </li> + </ul> + </DropdownTransition> + </div> +</template> + +<script> +import NavLink from './NavLink.vue' +import DropdownTransition from './DropdownTransition.vue' + +export default { + components: { NavLink, DropdownTransition }, + + data () { + return { + open: false + } + }, + + props: { + item: { + required: true + } + }, + + methods: { + toggle () { + this.open = !this.open + } + } +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.dropdown-wrapper + cursor pointer + .dropdown-title + display block + &:hover + border-color transparent + .arrow + vertical-align middle + margin-top -1px + margin-left 0.4rem + .nav-dropdown + .dropdown-item + color inherit + line-height 1.7rem + h4 + margin 0.45rem 0 0 + border-top 1px solid #eee + padding 0.45rem 1.5rem 0 1.25rem + .dropdown-subitem-wrapper + padding 0 + list-style none + .dropdown-subitem + font-size 0.9em + a + display block + line-height 1.7rem + position relative + border-bottom none + font-weight 400 + margin-bottom 0 + padding 0 1.5rem 0 1.25rem + &:hover + color $accentColor + &.router-link-active + color $accentColor + &::after + content "" + width 0 + height 0 + border-left 5px solid $accentColor + border-top 3px solid transparent + border-bottom 3px solid transparent + position absolute + top calc(50% - 2px) + left 9px + &:first-child h4 + margin-top 0 + padding-top 0 + border-top 0 + +@media (max-width: $MQMobile) + .dropdown-wrapper + &.open .dropdown-title + margin-bottom 0.5rem + .nav-dropdown + transition height .1s ease-out + overflow hidden + .dropdown-item + h4 + border-top 0 + margin-top 0 + padding-top 0 + h4, & > a + font-size 15px + line-height 2rem + .dropdown-subitem + font-size 14px + padding-left 1rem + +@media (min-width: $MQMobile) + .dropdown-wrapper + height 1.8rem + &:hover .nav-dropdown + // override the inline style. + display block !important + .dropdown-title .arrow + // make the arrow always down at desktop + border-left 4px solid transparent + border-right 4px solid transparent + border-top 6px solid $arrowBgColor + border-bottom 0 + .nav-dropdown + display none + // Avoid height shaked by clicking + height auto !important + box-sizing border-box; + max-height calc(100vh - 2.7rem) + overflow-y auto + position absolute + top 100% + right 0 + background-color #fff + padding 0.6rem 0 + border 1px solid #ddd + border-bottom-color #ccc + text-align left + border-radius 0.25rem + white-space nowrap + margin 0 +</style> diff --git a/node_modules/vuepress/lib/default-theme/DropdownTransition.vue b/node_modules/vuepress/lib/default-theme/DropdownTransition.vue new file mode 100644 index 00000000..8c711a15 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/DropdownTransition.vue @@ -0,0 +1,33 @@ +<template> + <transition + name="dropdown" + @enter="setHeight" + @after-enter="unsetHeight" + @before-leave="setHeight" + > + <slot/> + </transition> +</template> + +<script> +export default { + name: 'DropdownTransition', + + methods: { + setHeight (items) { + // explicitly set height so that it can be transitioned + items.style.height = items.scrollHeight + 'px' + }, + + unsetHeight (items) { + items.style.height = '' + } + } +} +</script> + +<style lang="stylus"> +.dropdown-enter, .dropdown-leave-to + height 0 !important + +</style> diff --git a/node_modules/vuepress/lib/default-theme/Home.vue b/node_modules/vuepress/lib/default-theme/Home.vue new file mode 100644 index 00000000..a172eb9b --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/Home.vue @@ -0,0 +1,162 @@ +<template> + <div class="home"> + <div class="hero"> + <img + v-if="data.heroImage" + :src="$withBase(data.heroImage)" + alt="hero" + > + + <h1>{{ data.heroText || $title || 'Hello' }}</h1> + + <p class="description"> + {{ data.tagline || $description || 'Welcome to your VuePress site' }} + </p> + + <p + class="action" + v-if="data.actionText && data.actionLink" + > + <NavLink + class="action-button" + :item="actionLink" + /> + </p> + </div> + + <div + class="features" + v-if="data.features && data.features.length" + > + <div + class="feature" + v-for="(feature, index) in data.features" + :key="index" + > + <h2>{{ feature.title }}</h2> + <p>{{ feature.details }}</p> + </div> + </div> + + <Content custom/> + + <div + class="footer" + v-if="data.footer" + > + {{ data.footer }} + </div> + </div> +</template> + +<script> +import NavLink from './NavLink.vue' + +export default { + components: { NavLink }, + + computed: { + data () { + return this.$page.frontmatter + }, + + actionLink () { + return { + link: this.data.actionLink, + text: this.data.actionText + } + } + } +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.home + padding $navbarHeight 2rem 0 + max-width 960px + margin 0px auto + .hero + text-align center + img + max-height 280px + display block + margin 3rem auto 1.5rem + h1 + font-size 3rem + h1, .description, .action + margin 1.8rem auto + .description + max-width 35rem + font-size 1.6rem + line-height 1.3 + color lighten($textColor, 40%) + .action-button + display inline-block + font-size 1.2rem + color #fff + background-color $accentColor + padding 0.8rem 1.6rem + border-radius 4px + transition background-color .1s ease + box-sizing border-box + border-bottom 1px solid darken($accentColor, 10%) + &:hover + background-color lighten($accentColor, 10%) + .features + border-top 1px solid $borderColor + padding 1.2rem 0 + margin-top 2.5rem + display flex + flex-wrap wrap + align-items flex-start + align-content stretch + justify-content space-between + .feature + flex-grow 1 + flex-basis 30% + max-width 30% + h2 + font-size 1.4rem + font-weight 500 + border-bottom none + padding-bottom 0 + color lighten($textColor, 10%) + p + color lighten($textColor, 25%) + .footer + padding 2.5rem + border-top 1px solid $borderColor + text-align center + color lighten($textColor, 25%) + +@media (max-width: $MQMobile) + .home + .features + flex-direction column + .feature + max-width 100% + padding 0 2.5rem + +@media (max-width: $MQMobileNarrow) + .home + padding-left 1.5rem + padding-right 1.5rem + .hero + img + max-height 210px + margin 2rem auto 1.2rem + h1 + font-size 2rem + h1, .description, .action + margin 1.2rem auto + .description + font-size 1.2rem + .action-button + font-size 1rem + padding 0.6rem 1.2rem + .feature + h2 + font-size 1.25rem +</style> diff --git a/node_modules/vuepress/lib/default-theme/Layout.vue b/node_modules/vuepress/lib/default-theme/Layout.vue new file mode 100644 index 00000000..ee2e6abe --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/Layout.vue @@ -0,0 +1,183 @@ +<template> + <div + class="theme-container" + :class="pageClasses" + @touchstart="onTouchStart" + @touchend="onTouchEnd" + > + <Navbar + v-if="shouldShowNavbar" + @toggle-sidebar="toggleSidebar" + /> + + <div + class="sidebar-mask" + @click="toggleSidebar(false)" + ></div> + + <Sidebar + :items="sidebarItems" + @toggle-sidebar="toggleSidebar" + > + <slot + name="sidebar-top" + slot="top" + /> + <slot + name="sidebar-bottom" + slot="bottom" + /> + </Sidebar> + + <div + class="custom-layout" + v-if="$page.frontmatter.layout" + > + <component :is="$page.frontmatter.layout"/> + </div> + + <Home v-else-if="$page.frontmatter.home"/> + + <Page + v-else + :sidebar-items="sidebarItems" + > + <slot + name="page-top" + slot="top" + /> + <slot + name="page-bottom" + slot="bottom" + /> + </Page> + + <SWUpdatePopup :updateEvent="swUpdateEvent"/> + </div> +</template> + +<script> +import Vue from 'vue' +import nprogress from 'nprogress' +import Home from './Home.vue' +import Navbar from './Navbar.vue' +import Page from './Page.vue' +import Sidebar from './Sidebar.vue' +import SWUpdatePopup from './SWUpdatePopup.vue' +import { resolveSidebarItems } from './util' + +export default { + components: { Home, Page, Sidebar, Navbar, SWUpdatePopup }, + + data () { + return { + isSidebarOpen: false, + swUpdateEvent: null + } + }, + + computed: { + shouldShowNavbar () { + const { themeConfig } = this.$site + const { frontmatter } = this.$page + if ( + frontmatter.navbar === false || + themeConfig.navbar === false) { + return false + } + return ( + this.$title || + themeConfig.logo || + themeConfig.repo || + themeConfig.nav || + this.$themeLocaleConfig.nav + ) + }, + + shouldShowSidebar () { + const { frontmatter } = this.$page + return ( + !frontmatter.layout && + !frontmatter.home && + frontmatter.sidebar !== false && + this.sidebarItems.length + ) + }, + + sidebarItems () { + return resolveSidebarItems( + this.$page, + this.$route, + this.$site, + this.$localePath + ) + }, + + pageClasses () { + const userPageClass = this.$page.frontmatter.pageClass + return [ + { + 'no-navbar': !this.shouldShowNavbar, + 'sidebar-open': this.isSidebarOpen, + 'no-sidebar': !this.shouldShowSidebar + }, + userPageClass + ] + } + }, + + mounted () { + window.addEventListener('scroll', this.onScroll) + + // configure progress bar + nprogress.configure({ showSpinner: false }) + + this.$router.beforeEach((to, from, next) => { + if (to.path !== from.path && !Vue.component(to.name)) { + nprogress.start() + } + next() + }) + + this.$router.afterEach(() => { + nprogress.done() + this.isSidebarOpen = false + }) + + this.$on('sw-updated', this.onSWUpdated) + }, + + methods: { + toggleSidebar (to) { + this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen + }, + + // side swipe + onTouchStart (e) { + this.touchStart = { + x: e.changedTouches[0].clientX, + y: e.changedTouches[0].clientY + } + }, + + onTouchEnd (e) { + const dx = e.changedTouches[0].clientX - this.touchStart.x + const dy = e.changedTouches[0].clientY - this.touchStart.y + if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) { + if (dx > 0 && this.touchStart.x <= 80) { + this.toggleSidebar(true) + } else { + this.toggleSidebar(false) + } + } + }, + + onSWUpdated (e) { + this.swUpdateEvent = e + } + } +} +</script> + +<style src="prismjs/themes/prism-tomorrow.css"></style> +<style src="./styles/theme.styl" lang="stylus"></style> diff --git a/node_modules/vuepress/lib/default-theme/NavLink.vue b/node_modules/vuepress/lib/default-theme/NavLink.vue new file mode 100644 index 00000000..d9fa4886 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/NavLink.vue @@ -0,0 +1,49 @@ +<template> + <router-link + class="nav-link" + :to="link" + v-if="!isExternal(link)" + :exact="exact" + >{{ item.text }}</router-link> + <a + v-else + :href="link" + class="nav-link external" + :target="isMailto(link) || isTel(link) ? null : '_blank'" + :rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'" + > + {{ item.text }} + <OutboundLink/> + </a> +</template> + +<script> +import { isExternal, isMailto, isTel, ensureExt } from './util' + +export default { + props: { + item: { + required: true + } + }, + + computed: { + link () { + return ensureExt(this.item.link) + }, + + exact () { + if (this.$site.locales) { + return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link) + } + return this.link === '/' + } + }, + + methods: { + isExternal, + isMailto, + isTel + } +} +</script> diff --git a/node_modules/vuepress/lib/default-theme/NavLinks.vue b/node_modules/vuepress/lib/default-theme/NavLinks.vue new file mode 100644 index 00000000..4037f288 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/NavLinks.vue @@ -0,0 +1,151 @@ +<template> + <nav + class="nav-links" + v-if="userLinks.length || repoLink" + > + <!-- user links --> + <div + class="nav-item" + v-for="item in userLinks" + :key="item.link" + > + <DropdownLink + v-if="item.type === 'links'" + :item="item" + /> + <NavLink + v-else + :item="item" + /> + </div> + + <!-- repo link --> + <a + v-if="repoLink" + :href="repoLink" + class="repo-link" + target="_blank" + rel="noopener noreferrer" + > + {{ repoLabel }} + <OutboundLink/> + </a> + </nav> +</template> + +<script> +import DropdownLink from './DropdownLink.vue' +import { resolveNavLinkItem } from './util' +import NavLink from './NavLink.vue' + +export default { + components: { NavLink, DropdownLink }, + + computed: { + userNav () { + return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || [] + }, + + nav () { + const { locales } = this.$site + if (locales && Object.keys(locales).length > 1) { + const currentLink = this.$page.path + const routes = this.$router.options.routes + const themeLocales = this.$site.themeConfig.locales || {} + const languageDropdown = { + text: this.$themeLocaleConfig.selectText || 'Languages', + items: Object.keys(locales).map(path => { + const locale = locales[path] + const text = themeLocales[path] && themeLocales[path].label || locale.lang + let link + // Stay on the current page + if (locale.lang === this.$lang) { + link = currentLink + } else { + // Try to stay on the same page + link = currentLink.replace(this.$localeConfig.path, path) + // fallback to homepage + if (!routes.some(route => route.path === link)) { + link = path + } + } + return { text, link } + }) + } + return [...this.userNav, languageDropdown] + } + return this.userNav + }, + + userLinks () { + return (this.nav || []).map(link => { + return Object.assign(resolveNavLinkItem(link), { + items: (link.items || []).map(resolveNavLinkItem) + }) + }) + }, + + repoLink () { + const { repo } = this.$site.themeConfig + if (repo) { + return /^https?:/.test(repo) + ? repo + : `https://github.com/${repo}` + } + }, + + repoLabel () { + if (!this.repoLink) return + if (this.$site.themeConfig.repoLabel) { + return this.$site.themeConfig.repoLabel + } + + const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0] + const platforms = ['GitHub', 'GitLab', 'Bitbucket'] + for (let i = 0; i < platforms.length; i++) { + const platform = platforms[i] + if (new RegExp(platform, 'i').test(repoHost)) { + return platform + } + } + + return 'Source' + } + } +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.nav-links + display inline-block + a + line-height 1.4rem + color inherit + &:hover, &.router-link-active + color $accentColor + .nav-item + position relative + display inline-block + margin-left 1.5rem + line-height 2rem + &:first-child + margin-left 0 + .repo-link + margin-left 1.5rem + +@media (max-width: $MQMobile) + .nav-links + .nav-item, .repo-link + margin-left 0 + +@media (min-width: $MQMobile) + .nav-links a + &:hover, &.router-link-active + color $textColor + .nav-item > a:not(.external) + &:hover, &.router-link-active + margin-bottom -2px + border-bottom 2px solid lighten($accentColor, 8%) +</style> diff --git a/node_modules/vuepress/lib/default-theme/Navbar.vue b/node_modules/vuepress/lib/default-theme/Navbar.vue new file mode 100644 index 00000000..12795665 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/Navbar.vue @@ -0,0 +1,133 @@ +<template> + <header class="navbar"> + <SidebarButton @toggle-sidebar="$emit('toggle-sidebar')"/> + + <router-link + :to="$localePath" + class="home-link" + > + <img + class="logo" + v-if="$site.themeConfig.logo" + :src="$withBase($site.themeConfig.logo)" + :alt="$siteTitle" + > + <span + ref="siteName" + class="site-name" + v-if="$siteTitle" + :class="{ 'can-hide': $site.themeConfig.logo }" + >{{ $siteTitle }}</span> + </router-link> + + <div + class="links" + :style="{ + 'max-width': linksWrapMaxWidth + 'px' + }" + > + <AlgoliaSearchBox + v-if="isAlgoliaSearch" + :options="algolia" + /> + <SearchBox v-else-if="$site.themeConfig.search !== false"/> + <NavLinks class="can-hide"/> + </div> + </header> +</template> + +<script> +import SidebarButton from './SidebarButton.vue' +import AlgoliaSearchBox from '@AlgoliaSearchBox' +import SearchBox from './SearchBox.vue' +import NavLinks from './NavLinks.vue' + +export default { + components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox }, + + data () { + return { + linksWrapMaxWidth: null + } + }, + + mounted () { + const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl + const NAVBAR_VERTICAL_PADDING = parseInt(css(this.$el, 'paddingLeft')) + parseInt(css(this.$el, 'paddingRight')) + const handleLinksWrapWidth = () => { + if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) { + this.linksWrapMaxWidth = null + } else { + this.linksWrapMaxWidth = this.$el.offsetWidth - NAVBAR_VERTICAL_PADDING - + (this.$refs.siteName && this.$refs.siteName.offsetWidth || 0) + } + } + handleLinksWrapWidth() + window.addEventListener('resize', handleLinksWrapWidth, false) + }, + + computed: { + algolia () { + return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {} + }, + + isAlgoliaSearch () { + return this.algolia && this.algolia.apiKey && this.algolia.indexName + } + } +} + +function css (el, property) { + // NOTE: Known bug, will return 'auto' if style value is 'auto' + const win = el.ownerDocument.defaultView + // null means not to return pseudo styles + return win.getComputedStyle(el, null)[property] +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +$navbar-vertical-padding = 0.7rem +$navbar-horizontal-padding = 1.5rem + +.navbar + padding $navbar-vertical-padding $navbar-horizontal-padding + line-height $navbarHeight - 1.4rem + position relative + a, span, img + display inline-block + .logo + height $navbarHeight - 1.4rem + min-width $navbarHeight - 1.4rem + margin-right 0.8rem + vertical-align top + .site-name + font-size 1.3rem + font-weight 600 + color $textColor + position relative + .links + padding-left 1.5rem + box-sizing border-box + background-color white + white-space nowrap + font-size 0.9rem + position absolute + right $navbar-horizontal-padding + top $navbar-vertical-padding + display flex + .search-box + flex: 0 0 auto + vertical-align top + .nav-links + flex 1 + +@media (max-width: $MQMobile) + .navbar + padding-left 4rem + .can-hide + display none + .links + padding-left 1.5rem +</style> diff --git a/node_modules/vuepress/lib/default-theme/NotFound.vue b/node_modules/vuepress/lib/default-theme/NotFound.vue new file mode 100644 index 00000000..6aefe797 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/NotFound.vue @@ -0,0 +1,26 @@ +<template> + <div class="theme-container"> + <div class="content"> + <h1>404</h1> + <blockquote>{{ getMsg() }}</blockquote> + <router-link to="/">Take me home.</router-link> + </div> + </div> +</template> + +<script> +const msgs = [ + `There's nothing here.`, + `How did we get here?`, + `That's a Four-Oh-Four.`, + `Looks like we've got some broken links.` +] + +export default { + methods: { + getMsg () { + return msgs[Math.floor(Math.random() * msgs.length)] + } + } +} +</script> diff --git a/node_modules/vuepress/lib/default-theme/Page.vue b/node_modules/vuepress/lib/default-theme/Page.vue new file mode 100644 index 00000000..37d65b41 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/Page.vue @@ -0,0 +1,246 @@ +<template> + <div class="page"> + <slot name="top"/> + + <Content :custom="false"/> + + <div class="page-edit"> + <div + class="edit-link" + v-if="editLink" + > + <a + :href="editLink" + target="_blank" + rel="noopener noreferrer" + >{{ editLinkText }}</a> + <OutboundLink/> + </div> + + <div + class="last-updated" + v-if="lastUpdated" + > + <span class="prefix">{{ lastUpdatedText }}: </span> + <span class="time">{{ lastUpdated }}</span> + </div> + </div> + + <div class="page-nav" v-if="prev || next"> + <p class="inner"> + <span + v-if="prev" + class="prev" + > + ← + <router-link + v-if="prev" + class="prev" + :to="prev.path" + > + {{ prev.title || prev.path }} + </router-link> + </span> + + <span + v-if="next" + class="next" + > + <router-link + v-if="next" + :to="next.path" + > + {{ next.title || next.path }} + </router-link> + → + </span> + </p> + </div> + + <slot name="bottom"/> + </div> +</template> + +<script> +import { resolvePage, normalize, outboundRE, endingSlashRE } from './util' + +export default { + props: ['sidebarItems'], + + computed: { + lastUpdated () { + if (this.$page.lastUpdated) { + return new Date(this.$page.lastUpdated).toLocaleString(this.$lang) + } + }, + + lastUpdatedText () { + if (typeof this.$themeLocaleConfig.lastUpdated === 'string') { + return this.$themeLocaleConfig.lastUpdated + } + if (typeof this.$site.themeConfig.lastUpdated === 'string') { + return this.$site.themeConfig.lastUpdated + } + return 'Last Updated' + }, + + prev () { + const prev = this.$page.frontmatter.prev + if (prev === false) { + return + } else if (prev) { + return resolvePage(this.$site.pages, prev, this.$route.path) + } else { + return resolvePrev(this.$page, this.sidebarItems) + } + }, + + next () { + const next = this.$page.frontmatter.next + if (next === false) { + return + } else if (next) { + return resolvePage(this.$site.pages, next, this.$route.path) + } else { + return resolveNext(this.$page, this.sidebarItems) + } + }, + + editLink () { + if (this.$page.frontmatter.editLink === false) { + return + } + const { + repo, + editLinks, + docsDir = '', + docsBranch = 'master', + docsRepo = repo + } = this.$site.themeConfig + + let path = normalize(this.$page.path) + if (endingSlashRE.test(path)) { + path += 'README.md' + } else { + path += '.md' + } + if (docsRepo && editLinks) { + return this.createEditLink(repo, docsRepo, docsDir, docsBranch, path) + } + }, + + editLinkText () { + return ( + this.$themeLocaleConfig.editLinkText || + this.$site.themeConfig.editLinkText || + `Edit this page` + ) + } + }, + + methods: { + createEditLink (repo, docsRepo, docsDir, docsBranch, path) { + const bitbucket = /bitbucket.org/ + if (bitbucket.test(repo)) { + const base = outboundRE.test(docsRepo) + ? docsRepo + : repo + return ( + base.replace(endingSlashRE, '') + + `/${docsBranch}` + + (docsDir ? '/' + docsDir.replace(endingSlashRE, '') : '') + + path + + `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default` + ) + } + + const base = outboundRE.test(docsRepo) + ? docsRepo + : `https://github.com/${docsRepo}` + + return ( + base.replace(endingSlashRE, '') + + `/edit/${docsBranch}` + + (docsDir ? '/' + docsDir.replace(endingSlashRE, '') : '') + + path + ) + } + } +} + +function resolvePrev (page, items) { + return find(page, items, -1) +} + +function resolveNext (page, items) { + return find(page, items, 1) +} + +function find (page, items, offset) { + const res = [] + items.forEach(item => { + if (item.type === 'group') { + res.push(...item.children || []) + } else { + res.push(item) + } + }) + for (let i = 0; i < res.length; i++) { + const cur = res[i] + if (cur.type === 'page' && cur.path === page.path) { + return res[i + offset] + } + } +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' +@require './styles/wrapper.styl' + +.page + padding-bottom 2rem + +.page-edit + @extend $wrapper + padding-top 1rem + padding-bottom 1rem + overflow auto + .edit-link + display inline-block + a + color lighten($textColor, 25%) + margin-right 0.25rem + .last-updated + float right + font-size 0.9em + .prefix + font-weight 500 + color lighten($textColor, 25%) + .time + font-weight 400 + color #aaa + +.page-nav + @extend $wrapper + padding-top 1rem + padding-bottom 0 + .inner + min-height 2rem + margin-top 0 + border-top 1px solid $borderColor + padding-top 1rem + overflow auto // clear float + .next + float right + +@media (max-width: $MQMobile) + .page-edit + .edit-link + margin-bottom .5rem + .last-updated + font-size .8em + float none + text-align left + +</style> diff --git a/node_modules/vuepress/lib/default-theme/SWUpdatePopup.vue b/node_modules/vuepress/lib/default-theme/SWUpdatePopup.vue new file mode 100644 index 00000000..b224db31 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/SWUpdatePopup.vue @@ -0,0 +1,85 @@ +<template> + <transition name="sw-update-popup"> + <div + v-if="enabled" + class="sw-update-popup" + > + {{message}}<br> + <button @click="reload">{{buttonText}}</button> + </div> + </transition> +</template> + +<script> +export default { + props: { + updateEvent: { + type: Object, + default: null + } + }, + + computed: { + popupConfig () { + for (const config of [this.$themeLocaleConfig, this.$site.themeConfig]) { + const sw = config.serviceWorker + if (sw && sw.updatePopup) { + return typeof sw.updatePopup === 'object' ? sw.updatePopup : {} + } + } + return null + }, + + enabled () { + return Boolean(this.popupConfig && this.updateEvent) + }, + + message () { + const c = this.popupConfig + return (c && c.message) || 'New content is available.' + }, + + buttonText () { + const c = this.popupConfig + return (c && c.buttonText) || 'Refresh' + } + }, + + methods: { + reload () { + if (this.updateEvent) { + this.updateEvent.skipWaiting().then(() => { + location.reload(true) + }) + this.updateEvent = null + } + } + } +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.sw-update-popup + position fixed + right 1em + bottom 1em + padding 1em + border 1px solid $accentColor + border-radius 3px + background #fff + box-shadow 0 4px 16px rgba(0, 0, 0, 0.5) + text-align center + + button + margin-top 0.5em + padding 0.25em 2em + +.sw-update-popup-enter-active, .sw-update-popup-leave-active + transition opacity 0.3s, transform 0.3s + +.sw-update-popup-enter, .sw-update-popup-leave-to + opacity 0 + transform translate(0, 50%) scale(0.5) +</style> diff --git a/node_modules/vuepress/lib/default-theme/SearchBox.vue b/node_modules/vuepress/lib/default-theme/SearchBox.vue new file mode 100644 index 00000000..98608552 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/SearchBox.vue @@ -0,0 +1,238 @@ +<template> + <div class="search-box"> + <input + @input="query = $event.target.value" + aria-label="Search" + :value="query" + :class="{ 'focused': focused }" + autocomplete="off" + spellcheck="false" + @focus="focused = true" + @blur="focused = false" + @keyup.enter="go(focusIndex)" + @keyup.up="onUp" + @keyup.down="onDown" + > + <ul + class="suggestions" + v-if="showSuggestions" + :class="{ 'align-right': alignRight }" + @mouseleave="unfocus" + > + <li + class="suggestion" + v-for="(s, i) in suggestions" + :class="{ focused: i === focusIndex }" + @mousedown="go(i)" + @mouseenter="focus(i)" + > + <a :href="s.path" @click.prevent> + <span class="page-title">{{ s.title || s.path }}</span> + <span v-if="s.header" class="header">> {{ s.header.title }}</span> + </a> + </li> + </ul> + </div> +</template> + +<script> +export default { + data () { + return { + query: '', + focused: false, + focusIndex: 0 + } + }, + + computed: { + showSuggestions () { + return ( + this.focused && + this.suggestions && + this.suggestions.length + ) + }, + + suggestions () { + const query = this.query.trim().toLowerCase() + if (!query) { + return + } + + const { pages, themeConfig } = this.$site + const max = themeConfig.searchMaxSuggestions || 5 + const localePath = this.$localePath + const matches = item => ( + item.title && + item.title.toLowerCase().indexOf(query) > -1 + ) + const res = [] + for (let i = 0; i < pages.length; i++) { + if (res.length >= max) break + const p = pages[i] + // filter out results that do not match current locale + if (this.getPageLocalePath(p) !== localePath) { + continue + } + if (matches(p)) { + res.push(p) + } else if (p.headers) { + for (let j = 0; j < p.headers.length; j++) { + if (res.length >= max) break + const h = p.headers[j] + if (matches(h)) { + res.push(Object.assign({}, p, { + path: p.path + '#' + h.slug, + header: h + })) + } + } + } + } + return res + }, + + // make suggestions align right when there are not enough items + alignRight () { + const navCount = (this.$site.themeConfig.nav || []).length + const repo = this.$site.repo ? 1 : 0 + return navCount + repo <= 2 + } + }, + + methods: { + getPageLocalePath (page) { + for (const localePath in this.$site.locales || {}) { + if (localePath !== '/' && page.path.indexOf(localePath) === 0) { + return localePath + } + } + return '/' + }, + + onUp () { + if (this.showSuggestions) { + if (this.focusIndex > 0) { + this.focusIndex-- + } else { + this.focusIndex = this.suggestions.length - 1 + } + } + }, + + onDown () { + if (this.showSuggestions) { + if (this.focusIndex < this.suggestions.length - 1) { + this.focusIndex++ + } else { + this.focusIndex = 0 + } + } + }, + + go (i) { + if (!this.showSuggestions) { + return + } + this.$router.push(this.suggestions[i].path) + this.query = '' + this.focusIndex = 0 + }, + + focus (i) { + this.focusIndex = i + }, + + unfocus () { + this.focusIndex = -1 + } + } +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.search-box + display inline-block + position relative + margin-right 1rem + input + cursor text + width 10rem + color lighten($textColor, 25%) + display inline-block + border 1px solid darken($borderColor, 10%) + border-radius 2rem + font-size 0.9rem + line-height 2rem + padding 0 0.5rem 0 2rem + outline none + transition all .2s ease + background #fff url(./search.svg) 0.6rem 0.5rem no-repeat + background-size 1rem + &:focus + cursor auto + border-color $accentColor + .suggestions + background #fff + width 20rem + position absolute + top 1.5rem + border 1px solid darken($borderColor, 10%) + border-radius 6px + padding 0.4rem + list-style-type none + &.align-right + right 0 + .suggestion + line-height 1.4 + padding 0.4rem 0.6rem + border-radius 4px + cursor pointer + a + white-space normal + color lighten($textColor, 35%) + .page-title + font-weight 600 + .header + font-size 0.9em + margin-left 0.25em + &.focused + background-color #f3f4f5 + a + color $accentColor + +@media (max-width: $MQNarrow) + .search-box + input + cursor pointer + width 0 + border-color transparent + position relative + &:focus + cursor text + left 0 + width 10rem + +@media (max-width: $MQNarrow) and (min-width: $MQMobile) + .search-box + .suggestions + left 0 + +@media (max-width: $MQMobile) + .search-box + margin-right 0 + input + left 1rem + .suggestions + right 0 + +@media (max-width: $MQMobileNarrow) + .search-box + .suggestions + width calc(100vw - 4rem) + input:focus + width 8rem +</style> diff --git a/node_modules/vuepress/lib/default-theme/Sidebar.vue b/node_modules/vuepress/lib/default-theme/Sidebar.vue new file mode 100644 index 00000000..7bcf0059 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/Sidebar.vue @@ -0,0 +1,113 @@ +<template> + <div class="sidebar"> + <NavLinks/> + <slot name="top"/> + <ul class="sidebar-links" v-if="items.length"> + <li v-for="(item, i) in items" :key="i"> + <SidebarGroup + v-if="item.type === 'group'" + :item="item" + :first="i === 0" + :open="i === openGroupIndex" + :collapsable="item.collapsable || item.collapsible" + @toggle="toggleGroup(i)" + /> + <SidebarLink v-else :item="item"/> + </li> + </ul> + <slot name="bottom"/> + </div> +</template> + +<script> +import SidebarGroup from './SidebarGroup.vue' +import SidebarLink from './SidebarLink.vue' +import NavLinks from './NavLinks.vue' +import { isActive } from './util' + +export default { + components: { SidebarGroup, SidebarLink, NavLinks }, + + props: ['items'], + + data () { + return { + openGroupIndex: 0 + } + }, + + created () { + this.refreshIndex() + }, + + watch: { + '$route' () { + this.refreshIndex() + } + }, + + methods: { + refreshIndex () { + const index = resolveOpenGroupIndex( + this.$route, + this.items + ) + if (index > -1) { + this.openGroupIndex = index + } + }, + + toggleGroup (index) { + this.openGroupIndex = index === this.openGroupIndex ? -1 : index + }, + + isActive (page) { + return isActive(this.$route, page.path) + } + } +} + +function resolveOpenGroupIndex (route, items) { + for (let i = 0; i < items.length; i++) { + const item = items[i] + if (item.type === 'group' && item.children.some(c => isActive(route, c.path))) { + return i + } + } + return -1 +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.sidebar + ul + padding 0 + margin 0 + list-style-type none + a + display inline-block + .nav-links + display none + border-bottom 1px solid $borderColor + padding 0.5rem 0 0.75rem 0 + a + font-weight 600 + .nav-item, .repo-link + display block + line-height 1.25rem + font-size 1.1em + padding 0.5rem 0 0.5rem 1.5rem + .sidebar-links + padding 1.5rem 0 + +@media (max-width: $MQMobile) + .sidebar + .nav-links + display block + .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after + top calc(1rem - 2px) + .sidebar-links + padding 1rem 0 +</style> diff --git a/node_modules/vuepress/lib/default-theme/SidebarButton.vue b/node_modules/vuepress/lib/default-theme/SidebarButton.vue new file mode 100644 index 00000000..0a222434 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/SidebarButton.vue @@ -0,0 +1,28 @@ +<template> + <div class="sidebar-button" @click="$emit('toggle-sidebar')"> + <svg class="icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512"> + <path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z" class=""></path> + </svg> + </div> +</template> + +<style lang="stylus"> +@import './styles/config.styl' + +.sidebar-button + display none + width 1.25rem + height 1.25rem + position absolute + padding 0.6rem + top 0.6rem + left 1rem + .icon + display block + width 1.25rem + height 1.25rem + +@media (max-width: $MQMobile) + .sidebar-button + display block +</style> diff --git a/node_modules/vuepress/lib/default-theme/SidebarGroup.vue b/node_modules/vuepress/lib/default-theme/SidebarGroup.vue new file mode 100644 index 00000000..119dfa14 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/SidebarGroup.vue @@ -0,0 +1,77 @@ +<template> + <div + class="sidebar-group" + :class="{ first, collapsable }" + > + <p + class="sidebar-heading" + :class="{ open }" + @click="$emit('toggle')" + > + <span>{{ item.title }}</span> + <span + class="arrow" + v-if="collapsable" + :class="open ? 'down' : 'right'"> + </span> + </p> + + <DropdownTransition> + <ul + ref="items" + class="sidebar-group-items" + v-if="open || !collapsable" + > + <li v-for="child in item.children"> + <SidebarLink :item="child"/> + </li> + </ul> + </DropdownTransition> + </div> +</template> + +<script> +import SidebarLink from './SidebarLink.vue' +import DropdownTransition from './DropdownTransition.vue' + +export default { + name: 'SidebarGroup', + props: ['item', 'first', 'open', 'collapsable'], + components: { SidebarLink, DropdownTransition } +} +</script> + +<style lang="stylus"> +.sidebar-group + &:not(.first) + margin-top 1em + .sidebar-group + padding-left 0.5em + &:not(.collapsable) + .sidebar-heading + cursor auto + color inherit + +.sidebar-heading + color #999 + transition color .15s ease + cursor pointer + font-size 1.1em + font-weight bold + // text-transform uppercase + padding 0 1.5rem + margin-top 0 + margin-bottom 0.5rem + &.open, &:hover + color inherit + .arrow + position relative + top -0.12em + left 0.5em + &:.open .arrow + top -0.18em + +.sidebar-group-items + transition height .1s ease-out + overflow hidden +</style> diff --git a/node_modules/vuepress/lib/default-theme/SidebarLink.vue b/node_modules/vuepress/lib/default-theme/SidebarLink.vue new file mode 100644 index 00000000..8288bf96 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/SidebarLink.vue @@ -0,0 +1,91 @@ +<script> +import { isActive, hashRE, groupHeaders } from './util' + +export default { + functional: true, + + props: ['item'], + + render (h, { parent: { $page, $site, $route }, props: { item }}) { + // use custom active class matching logic + // due to edge case of paths ending with / + hash + const selfActive = isActive($route, item.path) + // for sidebar: auto pages, a hash link should be active if one of its child + // matches + const active = item.type === 'auto' + ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug)) + : selfActive + const link = renderLink(h, item.path, item.title || item.path, active) + const configDepth = $page.frontmatter.sidebarDepth != null + ? $page.frontmatter.sidebarDepth + : $site.themeConfig.sidebarDepth + const maxDepth = configDepth == null ? 1 : configDepth + const displayAllHeaders = !!$site.themeConfig.displayAllHeaders + if (item.type === 'auto') { + return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)] + } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) { + const children = groupHeaders(item.headers) + return [link, renderChildren(h, children, item.path, $route, maxDepth)] + } else { + return link + } + } +} + +function renderLink (h, to, text, active) { + return h('router-link', { + props: { + to, + activeClass: '', + exactActiveClass: '' + }, + class: { + active, + 'sidebar-link': true + } + }, text) +} + +function renderChildren (h, children, path, route, maxDepth, depth = 1) { + if (!children || depth > maxDepth) return null + return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => { + const active = isActive(route, path + '#' + c.slug) + return h('li', { class: 'sidebar-sub-header' }, [ + renderLink(h, path + '#' + c.slug, c.title, active), + renderChildren(h, c.children, path, route, maxDepth, depth + 1) + ]) + })) +} +</script> + +<style lang="stylus"> +@import './styles/config.styl' + +.sidebar .sidebar-sub-headers + padding-left 1rem + font-size 0.95em + +a.sidebar-link + font-weight 400 + display inline-block + color $textColor + border-left 0.25rem solid transparent + padding 0.35rem 1rem 0.35rem 1.25rem + line-height 1.4 + width: 100% + box-sizing: border-box + &:hover + color $accentColor + &.active + font-weight 600 + color $accentColor + border-left-color $accentColor + .sidebar-group & + padding-left 2rem + .sidebar-sub-headers & + padding-top 0.25rem + padding-bottom 0.25rem + border-left none + &.active + font-weight 500 +</style> diff --git a/node_modules/vuepress/lib/default-theme/search.svg b/node_modules/vuepress/lib/default-theme/search.svg new file mode 100644 index 00000000..03d83913 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/search.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="12" height="13"><g stroke-width="2" stroke="#aaa" fill="none"><path d="M11.29 11.71l-4-4"/><circle cx="5" cy="5" r="4"/></g></svg> diff --git a/node_modules/vuepress/lib/default-theme/styles/arrow.styl b/node_modules/vuepress/lib/default-theme/styles/arrow.styl new file mode 100644 index 00000000..20bffc0d --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/arrow.styl @@ -0,0 +1,22 @@ +@require './config' + +.arrow + display inline-block + width 0 + height 0 + &.up + border-left 4px solid transparent + border-right 4px solid transparent + border-bottom 6px solid $arrowBgColor + &.down + border-left 4px solid transparent + border-right 4px solid transparent + border-top 6px solid $arrowBgColor + &.right + border-top 4px solid transparent + border-bottom 4px solid transparent + border-left 6px solid $arrowBgColor + &.left + border-top 4px solid transparent + border-bottom 4px solid transparent + border-right 6px solid $arrowBgColor diff --git a/node_modules/vuepress/lib/default-theme/styles/code.styl b/node_modules/vuepress/lib/default-theme/styles/code.styl new file mode 100644 index 00000000..8383c6e3 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/code.styl @@ -0,0 +1,129 @@ +@require './config' + +.content + code + color lighten($textColor, 20%) + padding 0.25rem 0.5rem + margin 0 + font-size 0.85em + background-color rgba(27,31,35,0.05) + border-radius 3px + +.content + pre, pre[class*="language-"] + line-height 1.4 + padding 1.25rem 1.5rem + margin 0.85rem 0 + background-color $codeBgColor + border-radius 6px + overflow auto + code + color #fff + padding 0 + background-color transparent + border-radius 0 + +div[class*="language-"] + position relative + background-color $codeBgColor + border-radius 6px + .highlight-lines + user-select none + padding-top 1.3rem + position absolute + top 0 + left 0 + width 100% + line-height 1.4 + .highlighted + background-color rgba(0, 0, 0, 66%) + pre, pre[class*="language-"] + background transparent + position relative + z-index 1 + &::before + position absolute + z-index 3 + top 0.8em + right 1em + font-size 0.75rem + color rgba(255, 255, 255, 0.4) + &:not(.line-numbers-mode) + .line-numbers-wrapper + display none + &.line-numbers-mode + .highlight-lines .highlighted + position relative + &:before + content ' ' + position absolute + z-index 3 + left 0 + top 0 + display block + width $lineNumbersWrapperWidth + height 100% + background-color rgba(0, 0, 0, 66%) + pre + padding-left $lineNumbersWrapperWidth + 1 rem + vertical-align middle + .line-numbers-wrapper + position absolute + top 0 + width $lineNumbersWrapperWidth + text-align center + color rgba(255, 255, 255, 0.3) + padding 1.25rem 0 + line-height 1.4 + br + user-select none + .line-number + position relative + z-index 4 + user-select none + font-size 0.85em + &::after + content '' + position absolute + z-index 2 + top 0 + left 0 + width $lineNumbersWrapperWidth + height 100% + border-radius 6px 0 0 6px + border-right 1px solid rgba(0, 0, 0, 66%) + background-color $codeBgColor + + +for lang in $codeLang + div{'[class~="language-' + lang + '"]'} + &:before + content ('' + lang) + +div[class~="language-javascript"] + &:before + content "js" + +div[class~="language-typescript"] + &:before + content "ts" + +div[class~="language-markup"] + &:before + content "html" + +div[class~="language-markdown"] + &:before + content "md" + +div[class~="language-json"]:before + content "json" + +div[class~="language-ruby"]:before + content "rb" + +div[class~="language-python"]:before + content "py" + +div[class~="language-bash"]:before + content "sh" diff --git a/node_modules/vuepress/lib/default-theme/styles/config.styl b/node_modules/vuepress/lib/default-theme/styles/config.styl new file mode 100644 index 00000000..a57d4484 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/config.styl @@ -0,0 +1,22 @@ +// colors +$accentColor = #3eaf7c +$textColor = #2c3e50 +$borderColor = #eaecef +$codeBgColor = #282c34 +$arrowBgColor = #ccc + +// layout +$navbarHeight = 3.6rem +$sidebarWidth = 20rem +$contentWidth = 740px + +// responsive breakpoints +$MQNarrow = 959px +$MQMobile = 719px +$MQMobileNarrow = 419px + +// code +$lineNumbersWrapperWidth = 3.5rem +$codeLang = js ts html md vue css sass scss less stylus go java c sh yaml py + +@import '~@temp/override.styl' diff --git a/node_modules/vuepress/lib/default-theme/styles/custom-blocks.styl b/node_modules/vuepress/lib/default-theme/styles/custom-blocks.styl new file mode 100644 index 00000000..3ccc2df2 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/custom-blocks.styl @@ -0,0 +1,28 @@ +.custom-block + .custom-block-title + font-weight 600 + margin-bottom -0.4rem + &.tip, &.warning, &.danger + padding .1rem 1.5rem + border-left-width .5rem + border-left-style solid + margin 1rem 0 + &.tip + background-color #f3f5f7 + border-color #42b983 + &.warning + background-color rgba(255,229,100,.3) + border-color darken(#ffe564, 35%) + color darken(#ffe564, 70%) + .custom-block-title + color darken(#ffe564, 50%) + a + color $textColor + &.danger + background-color #ffe6e6 + border-color darken(red, 20%) + color darken(red, 70%) + .custom-block-title + color darken(red, 40%) + a + color $textColor diff --git a/node_modules/vuepress/lib/default-theme/styles/mobile.styl b/node_modules/vuepress/lib/default-theme/styles/mobile.styl new file mode 100644 index 00000000..b35e59c5 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/mobile.styl @@ -0,0 +1,37 @@ +@require './config' + +$mobileSidebarWidth = $sidebarWidth * 0.82 + +// narrow desktop / iPad +@media (max-width: $MQNarrow) + .sidebar + font-size 15px + width $mobileSidebarWidth + .page + padding-left $mobileSidebarWidth + +// wide mobile +@media (max-width: $MQMobile) + .sidebar + top 0 + padding-top $navbarHeight + transform translateX(-100%) + transition transform .2s ease + .page + padding-left 0 + .theme-container + &.sidebar-open + .sidebar + transform translateX(0) + &.no-navbar + .sidebar + padding-top: 0 + +// narrow mobile +@media (max-width: $MQMobileNarrow) + h1 + font-size 1.9rem + .content + div[class*="language-"] + margin 0.85rem -1.5rem + border-radius 0 diff --git a/node_modules/vuepress/lib/default-theme/styles/nprogress.styl b/node_modules/vuepress/lib/default-theme/styles/nprogress.styl new file mode 100644 index 00000000..f186a67e --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/nprogress.styl @@ -0,0 +1,48 @@ +#nprogress + pointer-events none + .bar + background $accentColor + position fixed + z-index 1031 + top 0 + left 0 + width 100% + height 2px + .peg + display block + position absolute + right 0px + width 100px + height 100% + box-shadow 0 0 10px $accentColor, 0 0 5px $accentColor + opacity 1.0 + transform rotate(3deg) translate(0px, -4px) + .spinner + display block + position fixed + z-index 1031 + top 15px + right 15px + .spinner-icon + width 18px + height 18px + box-sizing border-box + border solid 2px transparent + border-top-color $accentColor + border-left-color $accentColor + border-radius 50% + animation nprogress-spinner 400ms linear infinite + +.nprogress-custom-parent + overflow hidden + position relative + +.nprogress-custom-parent #nprogress .spinner, +.nprogress-custom-parent #nprogress .bar + position absolute + +@keyframes nprogress-spinner + 0% + transform rotate(0deg) + 100% + transform rotate(360deg) diff --git a/node_modules/vuepress/lib/default-theme/styles/theme.styl b/node_modules/vuepress/lib/default-theme/styles/theme.styl new file mode 100644 index 00000000..a733861f --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/theme.styl @@ -0,0 +1,190 @@ +@require './config' +@require './nprogress' +@require './code' +@require './custom-blocks' +@require './arrow' +@require './wrapper' +@require './toc' + +html, body + padding 0 + margin 0 + +body + font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif + -webkit-font-smoothing antialiased + -moz-osx-font-smoothing grayscale + font-size 16px + color $textColor + +.page + padding-left $sidebarWidth + +.navbar + position fixed + z-index 20 + top 0 + left 0 + right 0 + height $navbarHeight + background-color #fff + box-sizing border-box + border-bottom 1px solid $borderColor + +.sidebar-mask + position fixed + z-index 9 + top 0 + left 0 + width 100vw + height 100vh + display none + +.sidebar + font-size 15px + background-color #fff + width $sidebarWidth + position fixed + z-index 10 + margin 0 + top $navbarHeight + left 0 + bottom 0 + box-sizing border-box + border-right 1px solid $borderColor + overflow-y auto + +.content:not(.custom) + @extend $wrapper + > *:first-child + margin-top $navbarHeight + a:hover + text-decoration underline + p.demo + padding 1rem 1.5rem + border 1px solid #ddd + border-radius 4px + img + max-width 100% + +.content.custom + padding 0 + margin 0 + img + max-width 100% + +a + font-weight 500 + color $accentColor + text-decoration none + +p a code + font-weight 400 + color $accentColor + +kbd + background #eee + border solid 0.15rem #ddd + border-bottom solid 0.25rem #ddd + border-radius 0.15rem + padding 0 0.15em + +blockquote + font-size 1.2rem + color #999 + border-left .25rem solid #dfe2e5 + margin-left 0 + padding-left 1rem + +ul, ol + padding-left 1.2em + +strong + font-weight 600 + +h1, h2, h3, h4, h5, h6 + font-weight 600 + line-height 1.25 + .content:not(.custom) > & + margin-top (0.5rem - $navbarHeight) + padding-top ($navbarHeight + 1rem) + margin-bottom 0 + &:first-child + margin-top -1.5rem + margin-bottom 1rem + + p, + pre, + .custom-block + margin-top 2rem + &:hover .header-anchor + opacity: 1 + +h1 + font-size 2.2rem + +h2 + font-size 1.65rem + padding-bottom .3rem + border-bottom 1px solid $borderColor + +h3 + font-size 1.35rem + +a.header-anchor + font-size 0.85em + float left + margin-left -0.87em + padding-right 0.23em + margin-top 0.125em + opacity 0 + &:hover + text-decoration none + +code, kbd, .line-number + font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace + +p, ul, ol + line-height 1.7 + +hr + border 0 + border-top 1px solid $borderColor + +table + border-collapse collapse + margin 1rem 0 + display: block + overflow-x: auto + +tr + border-top 1px solid #dfe2e5 + &:nth-child(2n) + background-color #f6f8fa + +th, td + border 1px solid #dfe2e5 + padding .6em 1em + +.custom-layout + padding-top $navbarHeight + +.theme-container + &.sidebar-open + .sidebar-mask + display: block + &.no-navbar + .content:not(.custom) > h1, h2, h3, h4, h5, h6 + margin-top 1.5rem + padding-top 0 + .sidebar + top 0 + .custom-layout + padding-top 0 + + +@media (min-width: ($MQMobile + 1px)) + .theme-container.no-sidebar + .sidebar + display none + .page + padding-left 0 + +@require './mobile.styl' diff --git a/node_modules/vuepress/lib/default-theme/styles/toc.styl b/node_modules/vuepress/lib/default-theme/styles/toc.styl new file mode 100644 index 00000000..d3e71069 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/toc.styl @@ -0,0 +1,3 @@ +.table-of-contents + .badge + vertical-align middle diff --git a/node_modules/vuepress/lib/default-theme/styles/wrapper.styl b/node_modules/vuepress/lib/default-theme/styles/wrapper.styl new file mode 100644 index 00000000..a99262c7 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/styles/wrapper.styl @@ -0,0 +1,9 @@ +$wrapper + max-width $contentWidth + margin 0 auto + padding 2rem 2.5rem + @media (max-width: $MQNarrow) + padding 2rem + @media (max-width: $MQMobileNarrow) + padding 1.5rem + diff --git a/node_modules/vuepress/lib/default-theme/util.js b/node_modules/vuepress/lib/default-theme/util.js new file mode 100644 index 00000000..ef95bea5 --- /dev/null +++ b/node_modules/vuepress/lib/default-theme/util.js @@ -0,0 +1,216 @@ +export const hashRE = /#.*$/ +export const extRE = /\.(md|html)$/ +export const endingSlashRE = /\/$/ +export const outboundRE = /^(https?:|mailto:|tel:)/ + +export function normalize (path) { + return decodeURI(path) + .replace(hashRE, '') + .replace(extRE, '') +} + +export function getHash (path) { + const match = path.match(hashRE) + if (match) { + return match[0] + } +} + +export function isExternal (path) { + return outboundRE.test(path) +} + +export function isMailto (path) { + return /^mailto:/.test(path) +} + +export function isTel (path) { + return /^tel:/.test(path) +} + +export function ensureExt (path) { + if (isExternal(path)) { + return path + } + const hashMatch = path.match(hashRE) + const hash = hashMatch ? hashMatch[0] : '' + const normalized = normalize(path) + + if (endingSlashRE.test(normalized)) { + return path + } + return normalized + '.html' + hash +} + +export function isActive (route, path) { + const routeHash = route.hash + const linkHash = getHash(path) + if (linkHash && routeHash !== linkHash) { + return false + } + const routePath = normalize(route.path) + const pagePath = normalize(path) + return routePath === pagePath +} + +export function resolvePage (pages, rawPath, base) { + if (base) { + rawPath = resolvePath(rawPath, base) + } + const path = normalize(rawPath) + for (let i = 0; i < pages.length; i++) { + if (normalize(pages[i].path) === path) { + return Object.assign({}, pages[i], { + type: 'page', + path: ensureExt(rawPath) + }) + } + } + console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`) + return {} +} + +function resolvePath (relative, base, append) { + const firstChar = relative.charAt(0) + if (firstChar === '/') { + return relative + } + + if (firstChar === '?' || firstChar === '#') { + return base + relative + } + + const stack = base.split('/') + + // remove trailing segment if: + // - not appending + // - appending to trailing slash (last segment is empty) + if (!append || !stack[stack.length - 1]) { + stack.pop() + } + + // resolve relative path + const segments = relative.replace(/^\//, '').split('/') + for (let i = 0; i < segments.length; i++) { + const segment = segments[i] + if (segment === '..') { + stack.pop() + } else if (segment !== '.') { + stack.push(segment) + } + } + + // ensure leading slash + if (stack[0] !== '') { + stack.unshift('') + } + + return stack.join('/') +} + +export function resolveSidebarItems (page, route, site, localePath) { + const { pages, themeConfig } = site + + const localeConfig = localePath && themeConfig.locales + ? themeConfig.locales[localePath] || themeConfig + : themeConfig + + const pageSidebarConfig = page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar + if (pageSidebarConfig === 'auto') { + return resolveHeaders(page) + } + + const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar + if (!sidebarConfig) { + return [] + } else { + const { base, config } = resolveMatchingConfig(route, sidebarConfig) + return config + ? config.map(item => resolveItem(item, pages, base)) + : [] + } +} + +function resolveHeaders (page) { + const headers = groupHeaders(page.headers || []) + return [{ + type: 'group', + collapsable: false, + title: page.title, + children: headers.map(h => ({ + type: 'auto', + title: h.title, + basePath: page.path, + path: page.path + '#' + h.slug, + children: h.children || [] + })) + }] +} + +export function groupHeaders (headers) { + // group h3s under h2 + headers = headers.map(h => Object.assign({}, h)) + let lastH2 + headers.forEach(h => { + if (h.level === 2) { + lastH2 = h + } else if (lastH2) { + (lastH2.children || (lastH2.children = [])).push(h) + } + }) + return headers.filter(h => h.level === 2) +} + +export function resolveNavLinkItem (linkItem) { + return Object.assign(linkItem, { + type: linkItem.items && linkItem.items.length ? 'links' : 'link' + }) +} + +export function resolveMatchingConfig (route, config) { + if (Array.isArray(config)) { + return { + base: '/', + config: config + } + } + for (const base in config) { + if (ensureEndingSlash(route.path).indexOf(base) === 0) { + return { + base, + config: config[base] + } + } + } + return {} +} + +function ensureEndingSlash (path) { + return /(\.html|\/)$/.test(path) + ? path + : path + '/' +} + +function resolveItem (item, pages, base, isNested) { + if (typeof item === 'string') { + return resolvePage(pages, item, base) + } else if (Array.isArray(item)) { + return Object.assign(resolvePage(pages, item[0], base), { + title: item[1] + }) + } else { + if (isNested) { + console.error( + '[vuepress] Nested sidebar groups are not supported. ' + + 'Consider using navbar + categories instead.' + ) + } + const children = item.children || [] + return { + type: 'group', + title: item.title, + children: children.map(child => resolveItem(child, pages, base, true)), + collapsable: item.collapsable !== false + } + } +} diff --git a/node_modules/vuepress/lib/dev.js b/node_modules/vuepress/lib/dev.js new file mode 100644 index 00000000..ba909d99 --- /dev/null +++ b/node_modules/vuepress/lib/dev.js @@ -0,0 +1,149 @@ +module.exports = async function dev (sourceDir, cliOptions = {}) { + const fs = require('fs') + const path = require('path') + const chalk = require('chalk') + const webpack = require('webpack') + const chokidar = require('chokidar') + const serve = require('webpack-serve') + const convert = require('koa-connect') + const mount = require('koa-mount') + const range = require('koa-range') + const serveStatic = require('koa-static') + const history = require('connect-history-api-fallback') + + const prepare = require('./prepare') + const logger = require('./util/logger') + const HeadPlugin = require('./webpack/HeadPlugin') + const DevLogPlugin = require('./webpack/DevLogPlugin') + const createClientConfig = require('./webpack/createClientConfig') + const { applyUserWebpackConfig } = require('./util') + const { frontmatterEmitter } = require('./webpack/markdownLoader') + + logger.wait('\nExtracting site metadata...') + const options = await prepare(sourceDir) + + // setup watchers to update options and dynamically generated files + const update = () => { + prepare(sourceDir).catch(err => { + console.error(logger.error(chalk.red(err.stack), false)) + }) + } + + // watch add/remove of files + const pagesWatcher = chokidar.watch([ + '**/*.md', + '.vuepress/components/**/*.vue' + ], { + cwd: sourceDir, + ignored: '.vuepress/**/*.md', + ignoreInitial: true + }) + pagesWatcher.on('add', update) + pagesWatcher.on('unlink', update) + pagesWatcher.on('addDir', update) + pagesWatcher.on('unlinkDir', update) + + // watch config file + const configWatcher = chokidar.watch([ + '.vuepress/config.js', + '.vuepress/config.yml', + '.vuepress/config.toml' + ], { + cwd: sourceDir, + ignoreInitial: true + }) + configWatcher.on('change', update) + + // also listen for frontmatter changes from markdown files + frontmatterEmitter.on('update', update) + + // resolve webpack config + let config = createClientConfig(options, cliOptions) + + config + .plugin('html') + // using a fork of html-webpack-plugin to avoid it requiring webpack + // internals from an incompatible version. + .use(require('vuepress-html-webpack-plugin'), [{ + template: path.resolve(__dirname, 'app/index.dev.html') + }]) + + config + .plugin('site-data') + .use(HeadPlugin, [{ + tags: options.siteConfig.head || [] + }]) + + const port = await resolvePort(cliOptions.port || options.siteConfig.port) + const { host, displayHost } = await resolveHost(cliOptions.host || options.siteConfig.host) + + config + .plugin('vuepress-log') + .use(DevLogPlugin, [{ + port, + displayHost, + publicPath: options.publicPath + }]) + + config = config.toConfig() + const userConfig = options.siteConfig.configureWebpack + if (userConfig) { + config = applyUserWebpackConfig(userConfig, config, false /* isServer */) + } + + const compiler = webpack(config) + + const nonExistentDir = path.resolve(__dirname, 'non-existent') + await serve({ + // avoid project cwd from being served. Otherwise if the user has index.html + // in cwd it would break the server + content: [nonExistentDir], + compiler, + host, + dev: { logLevel: 'warn' }, + hot: { + port: port + 1, + logLevel: 'error' + }, + logLevel: 'error', + port, + add: app => { + const userPublic = path.resolve(sourceDir, '.vuepress/public') + + // enable range request + app.use(range) + + // respect base when serving static files... + if (fs.existsSync(userPublic)) { + app.use(mount(options.publicPath, serveStatic(userPublic))) + } + + app.use(convert(history({ + rewrites: [ + { from: /\.html$/, to: '/' } + ] + }))) + } + }) +} + +function resolveHost (host) { + // webpack-serve hot updates doesn't work properly over 0.0.0.0 on Windows, + // but localhost does not allow visiting over network :/ + const defaultHost = process.platform === 'win32' ? 'localhost' : '0.0.0.0' + host = host || defaultHost + const displayHost = host === defaultHost && process.platform !== 'win32' + ? 'localhost' + : host + return { + displayHost, + host + } +} + +async function resolvePort (port) { + const portfinder = require('portfinder') + portfinder.basePort = parseInt(port) || 8080 + port = await portfinder.getPortPromise() + return port +} diff --git a/node_modules/vuepress/lib/eject.js b/node_modules/vuepress/lib/eject.js new file mode 100644 index 00000000..e96b00fd --- /dev/null +++ b/node_modules/vuepress/lib/eject.js @@ -0,0 +1,16 @@ +const fs = require('fs-extra') +const path = require('path') +const chalk = require('chalk') +const logger = require('./util/logger') + +module.exports = async (dir) => { + const source = path.resolve(__dirname, 'default-theme') + const target = path.resolve(dir, '.vuepress/theme') + await fs.copy(source, target) + // remove the import to default theme override + const styleConfig = path.resolve(target, 'styles/config.styl') + const content = await fs.readFile(styleConfig, 'utf-8') + const transformed = content.split('\n').slice(0, -2).join('\n') + await fs.writeFile(styleConfig, transformed) + logger.success(`\nCopied default theme into ${chalk.cyan(target)}.\n`) +} diff --git a/node_modules/vuepress/lib/index.js b/node_modules/vuepress/lib/index.js new file mode 100644 index 00000000..8e4d4a19 --- /dev/null +++ b/node_modules/vuepress/lib/index.js @@ -0,0 +1,4 @@ +exports.dev = require('./dev') +exports.build = require('./build') +exports.eject = require('./eject') +Object.assign(exports, require('./util')) diff --git a/node_modules/vuepress/lib/markdown/component.js b/node_modules/vuepress/lib/markdown/component.js new file mode 100644 index 00000000..1aab66e8 --- /dev/null +++ b/node_modules/vuepress/lib/markdown/component.js @@ -0,0 +1,81 @@ +// Replacing the default htmlBlock rule to allow using custom components at +// root level + +const blockNames = require('markdown-it/lib/common/html_blocks') +const HTML_OPEN_CLOSE_TAG_RE = require('markdown-it/lib/common/html_re').HTML_OPEN_CLOSE_TAG_RE + +// An array of opening and corresponding closing sequences for html tags, +// last argument defines whether it can terminate a paragraph or not +const HTML_SEQUENCES = [ + [/^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true], + [/^<!--/, /-->/, true], + [/^<\?/, /\?>/, true], + [/^<![A-Z]/, />/, true], + [/^<!\[CDATA\[/, /\]\]>/, true], + // PascalCase Components + [/^<[A-Z]/, />/, true], + // custom elements with hyphens + [/^<\w+\-/, />/, true], + [new RegExp('^</?(' + blockNames.join('|') + ')(?=(\\s|/?>|$))', 'i'), /^$/, true], + [new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false] +] + +module.exports = md => { + md.block.ruler.at('html_block', htmlBlock) +} + +function htmlBlock (state, startLine, endLine, silent) { + let i, nextLine, lineText + let pos = state.bMarks[startLine] + state.tShift[startLine] + let max = state.eMarks[startLine] + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false } + + if (!state.md.options.html) { return false } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false } + + lineText = state.src.slice(pos, max) + + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { break } + } + + if (i === HTML_SEQUENCES.length) { + console.log(lineText) + return false + } + + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2] + } + + nextLine = startLine + 1 + + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break } + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + lineText = state.src.slice(pos, max) + + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { nextLine++ } + break + } + } + } + + state.line = nextLine + + const token = state.push('html_block', '', 0) + token.map = [startLine, nextLine] + token.content = state.getLines(startLine, nextLine, state.blkIndent, true) + + return true +} diff --git a/node_modules/vuepress/lib/markdown/containers.js b/node_modules/vuepress/lib/markdown/containers.js new file mode 100644 index 00000000..215c4534 --- /dev/null +++ b/node_modules/vuepress/lib/markdown/containers.js @@ -0,0 +1,28 @@ +const container = require('markdown-it-container') + +module.exports = md => { + md + .use(...createContainer('tip', 'TIP')) + .use(...createContainer('warning', 'WARNING')) + .use(...createContainer('danger', 'WARNING')) + // explicitly escape Vue syntax + .use(container, 'v-pre', { + render: (tokens, idx) => tokens[idx].nesting === 1 + ? `<div v-pre>\n` + : `</div>\n` + }) +} + +function createContainer (klass, defaultTitle) { + return [container, klass, { + render (tokens, idx) { + const token = tokens[idx] + const info = token.info.trim().slice(klass.length).trim() + if (token.nesting === 1) { + return `<div class="${klass} custom-block"><p class="custom-block-title">${info || defaultTitle}</p>\n` + } else { + return `</div>\n` + } + } + }] +} diff --git a/node_modules/vuepress/lib/markdown/highlight.js b/node_modules/vuepress/lib/markdown/highlight.js new file mode 100644 index 00000000..02d5e3ff --- /dev/null +++ b/node_modules/vuepress/lib/markdown/highlight.js @@ -0,0 +1,47 @@ +const chalk = require('chalk') +const prism = require('prismjs') +const loadLanguages = require('prismjs/components/index') +const escapeHtml = require('escape-html') +const logger = require('../util/logger') + +// required to make embedded highlighting work... +loadLanguages(['markup', 'css', 'javascript']) + +function wrap (code, lang) { + if (lang === 'text') { + code = escapeHtml(code) + } + return `<pre v-pre class="language-${lang}"><code>${code}</code></pre>` +} + +module.exports = (str, lang) => { + if (!lang) { + return wrap(str, 'text') + } + lang = lang.toLowerCase() + const rawLang = lang + if (lang === 'vue' || lang === 'html') { + lang = 'markup' + } + if (lang === 'md') { + lang = 'markdown' + } + if (lang === 'ts') { + lang = 'typescript' + } + if (lang === 'py') { + lang = 'python' + } + if (!prism.languages[lang]) { + try { + loadLanguages([lang]) + } catch (e) { + logger.warn(chalk.yellow(`[vuepress] Syntax highlight for language "${lang}" is not supported.`)) + } + } + if (prism.languages[lang]) { + const code = prism.highlight(str, prism.languages[lang], lang) + return wrap(code, rawLang) + } + return wrap(str, 'text') +} diff --git a/node_modules/vuepress/lib/markdown/highlightLines.js b/node_modules/vuepress/lib/markdown/highlightLines.js new file mode 100644 index 00000000..dd833432 --- /dev/null +++ b/node_modules/vuepress/lib/markdown/highlightLines.js @@ -0,0 +1,49 @@ +// Modified from https://github.com/egoist/markdown-it-highlight-lines + +const RE = /{([\d,-]+)}/ +const wrapperRE = /^<pre .*?><code>/ + +module.exports = md => { + const fence = md.renderer.rules.fence + md.renderer.rules.fence = (...args) => { + const [tokens, idx, options] = args + const token = tokens[idx] + + const rawInfo = token.info + if (!rawInfo || !RE.test(rawInfo)) { + return fence(...args) + } + + const langName = rawInfo.replace(RE, '').trim() + // ensure the next plugin get the correct lang. + token.info = langName + + const lineNumbers = RE.exec(rawInfo)[1] + .split(',') + .map(v => v.split('-').map(v => parseInt(v, 10))) + + const code = options.highlight + ? options.highlight(token.content, langName) + : token.content + + const rawCode = code.replace(wrapperRE, '') + const highlightLinesCode = rawCode.split('\n').map((split, index) => { + const lineNumber = index + 1 + const inRange = lineNumbers.some(([start, end]) => { + if (start && end) { + return lineNumber >= start && lineNumber <= end + } + return lineNumber === start + }) + if (inRange) { + return `<div class="highlighted"> </div>` + } + return '<br>' + }).join('') + + const highlightLinesWrapperCode = + `<div class="highlight-lines">${highlightLinesCode}</div>` + + return highlightLinesWrapperCode + code + } +} diff --git a/node_modules/vuepress/lib/markdown/hoist.js b/node_modules/vuepress/lib/markdown/hoist.js new file mode 100644 index 00000000..a6383398 --- /dev/null +++ b/node_modules/vuepress/lib/markdown/hoist.js @@ -0,0 +1,14 @@ +module.exports = md => { + const RE = /^<(script|style)(?=(\s|>|$))/i + + md.renderer.rules.html_block = (tokens, idx) => { + const content = tokens[idx].content + const hoistedTags = md.__data.hoistedTags || (md.__data.hoistedTags = []) + if (RE.test(content.trim())) { + hoistedTags.push(content) + return '' + } else { + return content + } + } +} diff --git a/node_modules/vuepress/lib/markdown/index.js b/node_modules/vuepress/lib/markdown/index.js new file mode 100644 index 00000000..f4942f6c --- /dev/null +++ b/node_modules/vuepress/lib/markdown/index.js @@ -0,0 +1,78 @@ +const highlight = require('./highlight') +const highlightLines = require('./highlightLines') +const preWrapper = require('./preWrapper') +const lineNumbers = require('./lineNumbers') +const component = require('./component') +const hoistScriptStyle = require('./hoist') +const convertRouterLink = require('./link') +const containers = require('./containers') +const snippet = require('./snippet') +const emoji = require('markdown-it-emoji') +const anchor = require('markdown-it-anchor') +const toc = require('markdown-it-table-of-contents') +const _slugify = require('./slugify') +const { parseHeaders } = require('../util/parseHeaders') + +module.exports = ({ markdown = {}} = {}) => { + // allow user config slugify + const slugify = markdown.slugify || _slugify + + const md = require('markdown-it')({ + html: true, + highlight + }) + // custom plugins + .use(component) + .use(highlightLines) + .use(preWrapper) + .use(snippet) + .use(convertRouterLink, Object.assign({ + target: '_blank', + rel: 'noopener noreferrer' + }, markdown.externalLinks)) + .use(hoistScriptStyle) + .use(containers) + + // 3rd party plugins + .use(emoji) + .use(anchor, Object.assign({ + slugify, + permalink: true, + permalinkBefore: true, + permalinkSymbol: '#' + }, markdown.anchor)) + .use(toc, Object.assign({ + slugify, + includeLevel: [2, 3], + format: parseHeaders + }, markdown.toc)) + + // apply user config + if (markdown.config) { + markdown.config(md) + } + + if (markdown.lineNumbers) { + md.use(lineNumbers) + } + + module.exports.dataReturnable(md) + + // expose slugify + md.slugify = slugify + + return md +} + +module.exports.dataReturnable = function dataReturnable (md) { + // override render to allow custom plugins return data + const render = md.render + md.render = (...args) => { + md.__data = {} + const html = render.call(md, ...args) + return { + html, + data: md.__data + } + } +} diff --git a/node_modules/vuepress/lib/markdown/lineNumbers.js b/node_modules/vuepress/lib/markdown/lineNumbers.js new file mode 100644 index 00000000..bc95d2b0 --- /dev/null +++ b/node_modules/vuepress/lib/markdown/lineNumbers.js @@ -0,0 +1,26 @@ +// markdown-it plugin for generating line numbers. +// It depends on preWrapper plugin. + +module.exports = md => { + const fence = md.renderer.rules.fence + md.renderer.rules.fence = (...args) => { + const rawCode = fence(...args) + const code = rawCode.slice( + rawCode.indexOf('<code>'), + rawCode.indexOf('</code>') + ) + + const lines = code.split('\n') + const lineNumbersCode = [...Array(lines.length - 1)] + .map((line, index) => `<span class="line-number">${index + 1}</span><br>`).join('') + + const lineNumbersWrapperCode = + `<div class="line-numbers-wrapper">${lineNumbersCode}</div>` + + const finalCode = rawCode + .replace('<!--beforeend-->', `${lineNumbersWrapperCode}<!--beforeend-->`) + .replace('extra-class', 'line-numbers-mode') + + return finalCode + } +} diff --git a/node_modules/vuepress/lib/markdown/link.js b/node_modules/vuepress/lib/markdown/link.js new file mode 100644 index 00000000..bbe9550d --- /dev/null +++ b/node_modules/vuepress/lib/markdown/link.js @@ -0,0 +1,91 @@ +// markdown-it plugin for: +// 1. adding target="_blank" to external links +// 2. converting internal links to <router-link> + +const indexRE = /(.*)(index|readme).md(#?.*)$/i + +module.exports = (md, externalAttrs) => { + let hasOpenRouterLink = false + let hasOpenExternalLink = false + + md.renderer.rules.link_open = (tokens, idx, options, env, self) => { + const token = tokens[idx] + const hrefIndex = token.attrIndex('href') + if (hrefIndex >= 0) { + const link = token.attrs[hrefIndex] + const href = link[1] + const isExternal = /^https?:/.test(href) + const isSourceLink = /(\/|\.md|\.html)(#.*)?$/.test(href) + if (isExternal) { + Object.entries(externalAttrs).forEach(([key, val]) => { + token.attrSet(key, val) + }) + if (/_blank/i.test(externalAttrs['target'])) { + hasOpenExternalLink = true + } + } else if (isSourceLink) { + hasOpenRouterLink = true + tokens[idx] = toRouterLink(token, link) + } + } + return self.renderToken(tokens, idx, options) + } + + function toRouterLink (token, link) { + link[0] = 'to' + let to = link[1] + + // convert link to filename and export it for existence check + const links = md.__data.links || (md.__data.links = []) + links.push(to) + + const indexMatch = to.match(indexRE) + if (indexMatch) { + const [, path, , hash] = indexMatch + to = path + hash + } else { + to = to + .replace(/\.md$/, '.html') + .replace(/\.md(#.*)$/, '.html$1') + } + + // relative path usage. + if (!to.startsWith('/')) { + to = ensureBeginningDotSlash(to) + } + + // markdown-it encodes the uri + link[1] = decodeURI(to) + + // export the router links for testing + const routerLinks = md.__data.routerLinks || (md.__data.routerLinks = []) + routerLinks.push(to) + + return Object.assign({}, token, { + tag: 'router-link' + }) + } + + md.renderer.rules.link_close = (tokens, idx, options, env, self) => { + const token = tokens[idx] + if (hasOpenRouterLink) { + token.tag = 'router-link' + hasOpenRouterLink = false + } + if (hasOpenExternalLink) { + hasOpenExternalLink = false + // add OutBoundLink to the beforeend of this link if it opens in _blank. + return '<OutboundLink/>' + self.renderToken(tokens, idx, options) + } + return self.renderToken(tokens, idx, options) + } +} + +const beginningSlashRE = /^\.\// + +function ensureBeginningDotSlash (path) { + if (beginningSlashRE.test(path)) { + return path + } + return './' + path +} diff --git a/node_modules/vuepress/lib/markdown/preWrapper.js b/node_modules/vuepress/lib/markdown/preWrapper.js new file mode 100644 index 00000000..8f1b1d73 --- /dev/null +++ b/node_modules/vuepress/lib/markdown/preWrapper.js @@ -0,0 +1,19 @@ +// markdown-it plugin for wrapping <pre> ... </pre>. +// +// If your plugin was chained before preWrapper, you can add additional eleemnt directly. +// If your plugin was chained after preWrapper, you can use these slots: +// 1. <!--beforebegin--> +// 2. <!--afterbegin--> +// 3. <!--beforeend--> +// 4. <!--afterend--> + +module.exports = md => { + const fence = md.renderer.rules.fence + md.renderer.rules.fence = (...args) => { + const [tokens, idx] = args + const token = tokens[idx] + const rawCode = fence(...args) + return `<!--beforebegin--><div class="language-${token.info.trim()} extra-class">` + + `<!--afterbegin-->${rawCode}<!--beforeend--></div><!--afterend-->` + } +} diff --git a/node_modules/vuepress/lib/markdown/slugify.js b/node_modules/vuepress/lib/markdown/slugify.js new file mode 100644 index 00000000..47a9d6e9 --- /dev/null +++ b/node_modules/vuepress/lib/markdown/slugify.js @@ -0,0 +1,22 @@ +// string.js slugify drops non ascii chars so we have to +// use a custom implementation here +const removeDiacritics = require('diacritics').remove +// eslint-disable-next-line no-control-regex +const rControl = /[\u0000-\u001f]/g +const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'<>,.?/]+/g + +module.exports = function slugify (str) { + return removeDiacritics(str) + // Remove control characters + .replace(rControl, '') + // Replace special characters + .replace(rSpecial, '-') + // Remove continous separators + .replace(/\-{2,}/g, '-') + // Remove prefixing and trailing separtors + .replace(/^\-+|\-+$/g, '') + // ensure it doesn't start with a number (#121) + .replace(/^(\d)/, '_$1') + // lowercase + .toLowerCase() +} diff --git a/node_modules/vuepress/lib/markdown/snippet.js b/node_modules/vuepress/lib/markdown/snippet.js new file mode 100644 index 00000000..a677e12f --- /dev/null +++ b/node_modules/vuepress/lib/markdown/snippet.js @@ -0,0 +1,43 @@ +const fs = require('fs') + +module.exports = function snippet (md, options = {}) { + const root = options.root || process.cwd() + function parser (state, startLine, endLine, silent) { + const CH = '<'.charCodeAt(0) + const pos = state.bMarks[startLine] + state.tShift[startLine] + const max = state.eMarks[startLine] + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { + return false + } + + for (let i = 0; i < 3; ++i) { + const ch = state.src.charCodeAt(pos + i) + if (ch !== CH || pos + i >= max) return false + } + + if (silent) { + return true + } + + const start = pos + 3 + const end = state.skipSpacesBack(max, pos) + const rawPath = state.src.slice(start, end).trim().replace(/^@/, root) + const filename = rawPath.split(/[{:\s]/).shift() + const content = fs.existsSync(filename) ? fs.readFileSync(filename).toString() : 'Not found: ' + filename + const meta = rawPath.replace(filename, '') + + state.line = startLine + 1 + + const token = state.push('fence', 'code', 0) + token.info = filename.split('.').pop() + meta + token.content = content + token.markup = '```' + token.map = [startLine, startLine + 1] + + return true + } + + md.block.ruler.before('fence', 'snippet', parser) +} diff --git a/node_modules/vuepress/lib/prepare/codegen.js b/node_modules/vuepress/lib/prepare/codegen.js new file mode 100644 index 00000000..52e85355 --- /dev/null +++ b/node_modules/vuepress/lib/prepare/codegen.js @@ -0,0 +1,74 @@ +const path = require('path') +const { fileToComponentName, resolveComponents } = require('./util') + +exports.genRoutesFile = async function ({ + siteData: { pages }, + sourceDir, + pageFiles +}) { + function genRoute ({ path: pagePath, key: componentName }, index) { + const file = pageFiles[index] + const filePath = path.resolve(sourceDir, file) + let code = ` + { + name: ${JSON.stringify(componentName)}, + path: ${JSON.stringify(pagePath)}, + component: ThemeLayout, + beforeEnter: (to, from, next) => { + import(${JSON.stringify(filePath)}).then(comp => { + Vue.component(${JSON.stringify(componentName)}, comp.default) + next() + }) + } + }` + + const dncodedPath = decodeURIComponent(pagePath) + if (dncodedPath !== pagePath) { + code += `, + { + path: ${JSON.stringify(dncodedPath)}, + redirect: ${JSON.stringify(pagePath)} + }` + } + + if (/\/$/.test(pagePath)) { + code += `, + { + path: ${JSON.stringify(pagePath + 'index.html')}, + redirect: ${JSON.stringify(pagePath)} + }` + } + + return code + } + + const notFoundRoute = `, + { + path: '*', + component: ThemeNotFound + }` + + return ( + `import ThemeLayout from '@themeLayout'\n` + + `import ThemeNotFound from '@themeNotFound'\n` + + `import { injectMixins } from '@app/util'\n` + + `import rootMixins from '@app/root-mixins'\n\n` + + `injectMixins(ThemeLayout, rootMixins)\n` + + `injectMixins(ThemeNotFound, rootMixins)\n\n` + + `export const routes = [${pages.map(genRoute).join(',')}${notFoundRoute}\n]` + ) +} + +exports.genComponentRegistrationFile = async function ({ sourceDir }) { + function genImport (file) { + const name = fileToComponentName(file) + const baseDir = path.resolve(sourceDir, '.vuepress/components') + const absolutePath = path.resolve(baseDir, file) + const code = `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))` + return code + } + + const components = (await resolveComponents(sourceDir)) || [] + return `import Vue from 'vue'\n` + components.map(genImport).join('\n') +} + diff --git a/node_modules/vuepress/lib/prepare/index.js b/node_modules/vuepress/lib/prepare/index.js new file mode 100644 index 00000000..7561f791 --- /dev/null +++ b/node_modules/vuepress/lib/prepare/index.js @@ -0,0 +1,51 @@ +const path = require('path') +const fs = require('fs-extra') +const resolveOptions = require('./resolveOptions') +const { genRoutesFile, genComponentRegistrationFile } = require('./codegen') +const { writeTemp, writeEnhanceTemp } = require('./util') +const logger = require('../util/logger') +const chalk = require('chalk') + +module.exports = async function prepare (sourceDir) { + // 1. load options + const options = await resolveOptions(sourceDir) + + // 2. generate routes & user components registration code + const routesCode = await genRoutesFile(options) + const componentCode = await genComponentRegistrationFile(options) + + await writeTemp('routes.js', [ + componentCode, + routesCode + ].join('\n')) + + // 3. generate siteData + const dataCode = `export const siteData = ${JSON.stringify(options.siteData, null, 2)}` + await writeTemp('siteData.js', dataCode) + + // 4. handle user override + const overridePath = path.resolve(sourceDir, '.vuepress/override.styl').replace(/[\\]+/g, '/') + const hasUserOverride = fs.existsSync(overridePath) + await writeTemp('override.styl', hasUserOverride ? `@import(${JSON.stringify(overridePath)})` : ``) + + const stylePath = path.resolve(sourceDir, '.vuepress/style.styl').replace(/[\\]+/g, '/') + const hasUserStyle = fs.existsSync(stylePath) + await writeTemp('style.styl', hasUserStyle ? `@import(${JSON.stringify(stylePath)})` : ``) + + // Temporary tip, will be removed at next release. + if (hasUserOverride && !hasUserStyle) { + logger.tip( + `${chalk.magenta('override.styl')} has been split into 2 APIs, we recommend you upgrade to continue.\n` + + ` See: ${chalk.magenta('https://vuepress.vuejs.org/default-theme-config/#simple-css-override')}` + ) + } + + // 5. handle enhanceApp.js + const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js') + await writeEnhanceTemp('enhanceApp.js', enhanceAppPath) + + // 6. handle the theme enhanceApp.js + await writeEnhanceTemp('themeEnhanceApp.js', options.themeEnhanceAppPath) + + return options +} diff --git a/node_modules/vuepress/lib/prepare/loadConfig.js b/node_modules/vuepress/lib/prepare/loadConfig.js new file mode 100644 index 00000000..aa913ba0 --- /dev/null +++ b/node_modules/vuepress/lib/prepare/loadConfig.js @@ -0,0 +1,56 @@ +const fs = require('fs-extra') +const path = require('path') +const yamlParser = require('js-yaml') +const tomlParser = require('toml') + +module.exports = function loadConfig (vuepressDir, bustCache = true) { + const configPath = path.resolve(vuepressDir, 'config.js') + const configYmlPath = path.resolve(vuepressDir, 'config.yml') + const configTomlPath = path.resolve(vuepressDir, 'config.toml') + + if (bustCache) { + delete require.cache[configPath] + } + + // resolve siteConfig + let siteConfig = {} + if (fs.existsSync(configYmlPath)) { + siteConfig = parseConfig(configYmlPath) + } else if (fs.existsSync(configTomlPath)) { + siteConfig = parseConfig(configTomlPath) + } else if (fs.existsSync(configPath)) { + siteConfig = require(configPath) + } + + return siteConfig +} + +function parseConfig (file) { + const content = fs.readFileSync(file, 'utf-8') + const [extension] = /.\w+$/.exec(file) + let data + + switch (extension) { + case '.yml': + case '.yaml': + data = yamlParser.safeLoad(content) + break + + case '.toml': + data = tomlParser.parse(content) + // reformat to match config since TOML does not allow different data type + // https://github.com/toml-lang/toml#array + const format = [] + if (data.head) { + Object.keys(data.head).forEach(meta => { + data.head[meta].forEach(values => { + format.push([meta, values]) + }) + }) + } + data.head = format + break + } + + return data || {} +} diff --git a/node_modules/vuepress/lib/prepare/resolveOptions.js b/node_modules/vuepress/lib/prepare/resolveOptions.js new file mode 100644 index 00000000..9b97ca8e --- /dev/null +++ b/node_modules/vuepress/lib/prepare/resolveOptions.js @@ -0,0 +1,194 @@ +const fs = require('fs-extra') +const path = require('path') +const globby = require('globby') +const createMarkdown = require('../markdown') +const loadConfig = require('./loadConfig') +const { encodePath, fileToPath, sort, getGitLastUpdatedTimeStamp } = require('./util') +const { + inferTitle, + extractHeaders, + parseFrontmatter +} = require('../util/index') + +module.exports = async function resolveOptions (sourceDir) { + const vuepressDir = path.resolve(sourceDir, '.vuepress') + const siteConfig = loadConfig(vuepressDir) + + // normalize head tag urls for base + const base = siteConfig.base || '/' + if (base !== '/' && siteConfig.head) { + siteConfig.head.forEach(tag => { + const attrs = tag[1] + if (attrs) { + for (const name in attrs) { + if (name === 'src' || name === 'href') { + const value = attrs[name] + if (value.charAt(0) === '/') { + attrs[name] = base + value.slice(1) + } + } + } + } + }) + } + + // resolve outDir + const outDir = siteConfig.dest + ? path.resolve(siteConfig.dest) + : path.resolve(sourceDir, '.vuepress/dist') + + // resolve theme + const useDefaultTheme = ( + !siteConfig.theme && + !fs.existsSync(path.resolve(vuepressDir, 'theme')) + ) + const defaultThemePath = path.resolve(__dirname, '../default-theme') + let themePath = null + let themeLayoutPath = null + let themeNotFoundPath = null + let themeEnhanceAppPath = null + + if (useDefaultTheme) { + // use default theme + themePath = defaultThemePath + themeLayoutPath = path.resolve(defaultThemePath, 'Layout.vue') + themeNotFoundPath = path.resolve(defaultThemePath, 'NotFound.vue') + } else { + // resolve theme Layout + if (siteConfig.theme) { + // use external theme + try { + themeLayoutPath = require.resolve(`vuepress-theme-${siteConfig.theme}/Layout.vue`, { + paths: [ + path.resolve(__dirname, '../../node_modules'), + path.resolve(sourceDir) + ] + }) + themePath = path.dirname(themeLayoutPath) + } catch (e) { + throw new Error(`[vuepress] Failed to load custom theme "${ + siteConfig.theme + }". File vuepress-theme-${siteConfig.theme}/Layout.vue does not exist.`) + } + } else { + // use custom theme + themePath = path.resolve(vuepressDir, 'theme') + themeLayoutPath = path.resolve(themePath, 'Layout.vue') + if (!fs.existsSync(themeLayoutPath)) { + throw new Error(`[vuepress] Cannot resolve Layout.vue file in .vuepress/theme.`) + } + } + + // resolve theme NotFound + themeNotFoundPath = path.resolve(themePath, 'NotFound.vue') + if (!fs.existsSync(themeNotFoundPath)) { + themeNotFoundPath = path.resolve(defaultThemePath, 'NotFound.vue') + } + + // resolve theme enhanceApp + themeEnhanceAppPath = path.resolve(themePath, 'enhanceApp.js') + if (!fs.existsSync(themeEnhanceAppPath)) { + themeEnhanceAppPath = null + } + } + + // resolve theme config + const themeConfig = siteConfig.themeConfig || {} + + // resolve algolia + const isAlgoliaSearch = ( + themeConfig.algolia || + Object.keys(siteConfig.locales && themeConfig.locales || {}) + .some(base => themeConfig.locales[base].algolia) + ) + + // resolve markdown + const markdown = createMarkdown(siteConfig) + + // resolve pageFiles + const patterns = ['**/*.md', '!.vuepress', '!node_modules'] + if (siteConfig.dest) { + // #654 exclude dest folder when dest dir was set in + // sourceDir but not in '.vuepress' + const outDirRelative = path.relative(sourceDir, outDir) + if (!outDirRelative.includes('..')) { + patterns.push('!' + outDirRelative) + } + } + const pageFiles = sort(await globby(patterns, { cwd: sourceDir })) + + // resolve lastUpdated + const shouldResolveLastUpdated = ( + themeConfig.lastUpdated || + Object.keys(siteConfig.locales && themeConfig.locales || {}) + .some(base => themeConfig.locales[base].lastUpdated) + ) + + // resolve pagesData + const pagesData = await Promise.all(pageFiles.map(async (file) => { + const filepath = path.resolve(sourceDir, file) + const key = 'v-' + Math.random().toString(16).slice(2) + const data = { + key, + path: encodePath(fileToPath(file)) + } + + if (shouldResolveLastUpdated) { + data.lastUpdated = getGitLastUpdatedTimeStamp(filepath) + } + + // extract yaml frontmatter + const content = await fs.readFile(filepath, 'utf-8') + const frontmatter = parseFrontmatter(content) + // infer title + const title = inferTitle(frontmatter) + if (title) { + data.title = title + } + const headers = extractHeaders( + frontmatter.content, + ['h2', 'h3'], + markdown + ) + if (headers.length) { + data.headers = headers + } + if (Object.keys(frontmatter.data).length) { + data.frontmatter = frontmatter.data + } + if (frontmatter.excerpt) { + const { html } = markdown.render(frontmatter.excerpt) + data.excerpt = html + } + return data + })) + + // resolve site data + const siteData = { + title: siteConfig.title || '', + description: siteConfig.description || '', + base, + pages: pagesData, + themeConfig, + locales: siteConfig.locales + } + + const options = { + siteConfig, + siteData, + sourceDir, + outDir, + publicPath: base, + pageFiles, + pagesData, + themePath, + themeLayoutPath, + themeNotFoundPath, + themeEnhanceAppPath, + useDefaultTheme, + isAlgoliaSearch, + markdown + } + + return options +} diff --git a/node_modules/vuepress/lib/prepare/util.js b/node_modules/vuepress/lib/prepare/util.js new file mode 100644 index 00000000..efa96cc9 --- /dev/null +++ b/node_modules/vuepress/lib/prepare/util.js @@ -0,0 +1,80 @@ +const path = require('path') +const spawn = require('cross-spawn') +const fs = require('fs-extra') +const globby = require('globby') + +const tempPath = path.resolve(__dirname, '../app/.temp') +fs.ensureDirSync(tempPath) + +const tempCache = new Map() +exports.writeTemp = async function (file, content) { + // cache write to avoid hitting the dist if it didn't change + const cached = tempCache.get(file) + if (cached !== content) { + await fs.writeFile(path.join(tempPath, file), content) + tempCache.set(file, content) + } +} + +exports.writeEnhanceTemp = async function (destName, srcPath) { + await exports.writeTemp( + destName, + fs.existsSync(srcPath) + ? `export { default } from ${JSON.stringify(srcPath)}` + : `export default function () {}` + ) +} + +const indexRE = /(^|.*\/)(index|readme)\.md$/i +const extRE = /\.(vue|md)$/ + +exports.fileToPath = function (file) { + if (exports.isIndexFile(file)) { + // README.md -> / + // foo/README.md -> /foo/ + return file.replace(indexRE, '/$1') + } else { + // foo.md -> /foo.html + // foo/bar.md -> /foo/bar.html + return `/${file.replace(extRE, '').replace(/\\/g, '/')}.html` + } +} + +exports.fileToComponentName = function (file) { + let normalizedName = file + .replace(/\/|\\/g, '-') + .replace(extRE, '') + if (exports.isIndexFile(file)) { + normalizedName = normalizedName.replace(/readme$/i, 'index') + } + const pagePrefix = /\.md$/.test(file) ? `page-` : `` + return `${pagePrefix}${normalizedName}` +} + +exports.isIndexFile = function (file) { + return indexRE.test(file) +} + +exports.resolveComponents = async function (sourceDir) { + const componentDir = path.resolve(sourceDir, '.vuepress/components') + if (!fs.existsSync(componentDir)) { + return + } + return exports.sort(await globby(['**/*.vue'], { cwd: componentDir })) +} + +exports.sort = function (arr) { + return arr.sort((a, b) => { + if (a < b) return -1 + if (a > b) return 1 + return 0 + }) +} + +exports.encodePath = function (userpath) { + return userpath.split('/').map(item => encodeURIComponent(item)).join('/') +} + +exports.getGitLastUpdatedTimeStamp = function (filepath) { + return parseInt(spawn.sync('git', ['log', '-1', '--format=%ct', filepath]).stdout.toString('utf-8')) * 1000 +} diff --git a/node_modules/vuepress/lib/service-worker/skip-waiting.js b/node_modules/vuepress/lib/service-worker/skip-waiting.js new file mode 100644 index 00000000..54fd8d37 --- /dev/null +++ b/node_modules/vuepress/lib/service-worker/skip-waiting.js @@ -0,0 +1,12 @@ +addEventListener('message', event => { + const replyPort = event.ports[0] + const message = event.data + if (replyPort && message && message.type === 'skip-waiting') { + event.waitUntil( + self.skipWaiting().then( + () => replyPort.postMessage({ error: null }), + error => replyPort.postMessage({ error }) + ) + ) + } +}) diff --git a/node_modules/vuepress/lib/util/index.js b/node_modules/vuepress/lib/util/index.js new file mode 100644 index 00000000..52044a8f --- /dev/null +++ b/node_modules/vuepress/lib/util/index.js @@ -0,0 +1,83 @@ +const { deeplyParseHeaders } = require('./parseHeaders') + +exports.normalizeHeadTag = function (tag) { + if (typeof tag === 'string') { + tag = [tag] + } + const tagName = tag[0] + return { + tagName, + attributes: tag[1] || {}, + innerHTML: tag[2] || '', + closeTag: !(tagName === 'meta' || tagName === 'link') + } +} + +exports.applyUserWebpackConfig = function (userConfig, config, isServer) { + const merge = require('webpack-merge') + if (typeof userConfig === 'object') { + return merge(config, userConfig) + } + if (typeof userConfig === 'function') { + const res = userConfig(config, isServer) + if (res && typeof res === 'object') { + return merge(config, res) + } + } + return config +} + +exports.inferTitle = function (frontmatter) { + if (frontmatter.data.home) { + return 'Home' + } + if (frontmatter.data.title) { + return deeplyParseHeaders(frontmatter.data.title) + } + const match = frontmatter.content.trim().match(/^#+\s+(.*)/m) + if (match) { + return deeplyParseHeaders(match[1]) + } +} + +exports.parseFrontmatter = function (content) { + const matter = require('gray-matter') + const toml = require('toml') + + return matter(content, { + excerpt_separator: '<!-- more -->', + engines: { + toml: toml.parse.bind(toml), + excerpt: false + } + }) +} + +const LRU = require('lru-cache') +const cache = LRU({ max: 1000 }) + +exports.extractHeaders = function (content, include = [], md) { + const key = content + include.join(',') + const hit = cache.get(key) + if (hit) { + return hit + } + + const tokens = md.parse(content, {}) + + const res = [] + tokens.forEach((t, i) => { + if (t.type === 'heading_open' && include.includes(t.tag)) { + const title = tokens[i + 1].content + const slug = t.attrs.find(([name]) => name === 'id')[1] + res.push({ + level: parseInt(t.tag.slice(1), 10), + title: deeplyParseHeaders(title), + slug: slug || md.slugify(title) + }) + } + }) + + cache.set(key, res) + return res +} diff --git a/node_modules/vuepress/lib/util/logger.js b/node_modules/vuepress/lib/util/logger.js new file mode 100644 index 00000000..84e07e86 --- /dev/null +++ b/node_modules/vuepress/lib/util/logger.js @@ -0,0 +1,47 @@ +const chalk = require('chalk') + +const logger = {} + +const logTypes = { + success: { + color: 'green', + label: 'DONE' + }, + error: { + color: 'red', + label: 'FAIL' + }, + warn: { + color: 'yellow', + label: 'WARN' + }, + tip: { + color: 'cyan', + label: 'TIP' + }, + wait: { + color: 'blue', + label: 'WAIT' + } +} + +const getLoggerFn = (color, label) => (msg, log = true) => { + let newLine = false + if (msg.startsWith('\n')) { + if (log) msg = msg.slice(1) + newLine = true + } + msg = chalk.reset.inverse.bold[color](` ${label} `) + ' ' + msg + if (log) { + console.log(newLine ? '\n' + msg : msg) + } else { + return msg + } +} + +for (const type in logTypes) { + const { color, label } = logTypes[type] + logger[type] = getLoggerFn(color, label) +} + +module.exports = logger diff --git a/node_modules/vuepress/lib/util/parseHeaders.js b/node_modules/vuepress/lib/util/parseHeaders.js new file mode 100644 index 00000000..2c9fe641 --- /dev/null +++ b/node_modules/vuepress/lib/util/parseHeaders.js @@ -0,0 +1,56 @@ +// Since VuePress needs to extract the header from the markdown source +// file and display it in the sidebar or title (#238), this file simply +// removes some unnecessary elements to make header displays well at +// sidebar or title. +// +// But header's parsing in the markdown content is done by the markdown +// loader based on markdown-it. markdown-it parser will will always keep +// HTML in headers, so in VuePress, after being parsed by the markdiwn +// loader, the raw HTML in headers will finally be parsed by Vue-loader. +// so that we can write HTML/Vue in the header. One exception is the HTML +// wrapped by <code>(markdown token: '`') tag. + +const { compose } = require('./shared') + +const parseEmojis = str => { + const emojiData = require('markdown-it-emoji/lib/data/full.json') + return String(str).replace(/:(.+?):/g, (placeholder, key) => emojiData[key] || placeholder) +} + +const unescapeHtml = html => String(html) + .replace(/"/g, '"') + .replace(/'/g, '\'') + .replace(/:/g, ':') + .replace(/</g, '<') + .replace(/>/g, '>') + +const removeMarkdownTokens = str => String(str) + .replace(/\[(.*)\]\(.*\)/, '$1') // []() + .replace(/(`|\*{1,3}|_)(.*?[^\\])\1/g, '$2') // `{t}` | *{t}* | **{t}** | ***{t}*** | _{t}_ + .replace(/(\\)(\*|_|`|\!)/g, '$2') // remove escape char '\' + +const trim = str => str.trim() + +// This method remove the raw HTML but reserve the HTML wrapped by `<code>`. +// e.g. +// Input: "<a> b", Output: "b" +// Input: "`<a>` b", Output: "`<a>` b" +exports.removeNonCodeWrappedHTML = (str) => { + return String(str).replace(/(^|[^><`])<.*>([^><`]|$)/g, '$1$2') +} + +// Unescape html, parse emojis and remove some md tokens. +exports.parseHeaders = compose( + unescapeHtml, + parseEmojis, + removeMarkdownTokens, + trim +) + +// Also clean the html that isn't wrapped by code. +// Because we want to support using VUE components in headers. +// e.g. https://vuepress.vuejs.org/guide/using-vue.html#badge +exports.deeplyParseHeaders = compose( + exports.removeNonCodeWrappedHTML, + exports.parseHeaders, +) diff --git a/node_modules/vuepress/lib/util/shared.js b/node_modules/vuepress/lib/util/shared.js new file mode 100644 index 00000000..023f63bd --- /dev/null +++ b/node_modules/vuepress/lib/util/shared.js @@ -0,0 +1,7 @@ +exports.compose = (...processors) => { + if (processors.length === 0) return input => input + if (processors.length === 1) return processors[0] + return processors.reduce((prev, next) => { + return (...args) => next(prev(...args)) + }) +} diff --git a/node_modules/vuepress/lib/webpack/ClientPlugin.js b/node_modules/vuepress/lib/webpack/ClientPlugin.js new file mode 100644 index 00000000..82028475 --- /dev/null +++ b/node_modules/vuepress/lib/webpack/ClientPlugin.js @@ -0,0 +1,87 @@ +// Temporarily hacked dev build + +var isJS = function (file) { return /\.js(\?[^.]+)?$/.test(file) } + +var isCSS = function (file) { return /\.css(\?[^.]+)?$/.test(file) } + +var onEmit = function (compiler, name, hook) { + if (compiler.hooks) { + // Webpack >= 4.0.0 + compiler.hooks.emit.tapAsync(name, hook) + } else { + // Webpack < 4.0.0 + compiler.plugin('emit', hook) + } +} + +var hash = require('hash-sum') +var uniq = require('lodash.uniq') +var VueSSRClientPlugin = function VueSSRClientPlugin (options) { + if (options === void 0) options = {} + + this.options = Object.assign({ + filename: 'vue-ssr-client-manifest.json' + }, options) +} + +VueSSRClientPlugin.prototype.apply = function apply (compiler) { + var this$1 = this + + onEmit(compiler, 'vue-client-plugin', function (compilation, cb) { + var stats = compilation.getStats().toJson() + + var allFiles = uniq(stats.assets + .map(function (a) { return a.name })) + // Avoid preloading / injecting the style chunk + .filter(file => !/styles\.\w{8}\.js$/.test(file)) + + var initialFiles = uniq(Object.keys(stats.entrypoints) + .map(function (name) { return stats.entrypoints[name].assets }) + .reduce(function (assets, all) { return all.concat(assets) }, []) + .filter(function (file) { return isJS(file) || isCSS(file) })) + // Avoid preloading / injecting the style chunk + .filter(file => !/styles\.\w{8}\.js$/.test(file)) + + var asyncFiles = allFiles + .filter(function (file) { return isJS(file) || isCSS(file) }) + .filter(function (file) { return initialFiles.indexOf(file) < 0 }) + + var manifest = { + publicPath: stats.publicPath, + all: allFiles, + initial: initialFiles, + async: asyncFiles, + modules: { /* [identifier: string]: Array<index: number> */ } + } + + var assetModules = stats.modules.filter(function (m) { return m.assets.length }) + var fileToIndex = function (file) { return manifest.all.indexOf(file) } + stats.modules.forEach(function (m) { + // ignore modules duplicated in multiple chunks + if (m.chunks.length === 1) { + var cid = m.chunks[0] + var chunk = stats.chunks.find(function (c) { return c.id === cid }) + if (!chunk || !chunk.files) { + return + } + var id = m.identifier.replace(/\s\w+$/, '') // remove appended hash + var files = manifest.modules[hash(id)] = chunk.files.map(fileToIndex) + // find all asset modules associated with the same chunk + assetModules.forEach(function (m) { + if (m.chunks.some(function (id) { return id === cid })) { + files.push.apply(files, m.assets.map(fileToIndex)) + } + }) + } + }) + + var json = JSON.stringify(manifest, null, 2) + compilation.assets[this$1.options.filename] = { + source: function () { return json }, + size: function () { return json.length } + } + cb() + }) +} + +module.exports = VueSSRClientPlugin diff --git a/node_modules/vuepress/lib/webpack/DevLogPlugin.js b/node_modules/vuepress/lib/webpack/DevLogPlugin.js new file mode 100644 index 00000000..8fd18d4a --- /dev/null +++ b/node_modules/vuepress/lib/webpack/DevLogPlugin.js @@ -0,0 +1,38 @@ +const chalk = require('chalk') +const logger = require('../util/logger') + +module.exports = class DevLogPlugin { + constructor (options) { + this.options = options + } + + apply (compiler) { + let isFirst = true + compiler.hooks.done.tap('vuepress-log', stats => { + clearScreen() + + const { displayHost, port, publicPath } = this.options + const time = new Date().toTimeString().match(/^[\d:]+/)[0] + const displayUrl = `http://${displayHost}:${port}${publicPath}` + + logger.success( + `\n${chalk.gray(`[${time}]`)} Build ${chalk.italic(stats.hash.slice(0, 6))} ` + + `finished in ${stats.endTime - stats.startTime} ms! ` + + ( + isFirst + ? '' + : `${chalk.gray(`(${displayUrl})`)}` + ) + ) + if (isFirst) { + isFirst = false + console.log(`\n${chalk.gray('>')} VuePress dev server listening at ${chalk.cyan(displayUrl)}`) + } + }) + compiler.hooks.invalid.tap('vuepress-log', clearScreen) + } +} + +function clearScreen () { + process.stdout.write('\x1Bc') +} diff --git a/node_modules/vuepress/lib/webpack/HeadPlugin.js b/node_modules/vuepress/lib/webpack/HeadPlugin.js new file mode 100644 index 00000000..2ccb46e0 --- /dev/null +++ b/node_modules/vuepress/lib/webpack/HeadPlugin.js @@ -0,0 +1,22 @@ +const { normalizeHeadTag } = require('../util') + +module.exports = class SiteDataPlugin { + constructor ({ tags }) { + this.tags = tags + } + + apply (compiler) { + compiler.hooks.compilation.tap('vuepress-site-data', compilation => { + compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('vuepress-site-data', (data, cb) => { + try { + this.tags.forEach(tag => { + data.head.push(normalizeHeadTag(tag)) + }) + } catch (e) { + return cb(e) + } + cb(null, data) + }) + }) + } +} diff --git a/node_modules/vuepress/lib/webpack/createBaseConfig.js b/node_modules/vuepress/lib/webpack/createBaseConfig.js new file mode 100644 index 00000000..dcf7fab3 --- /dev/null +++ b/node_modules/vuepress/lib/webpack/createBaseConfig.js @@ -0,0 +1,315 @@ +const path = require('path') + +module.exports = function createBaseConfig ({ + siteConfig, + siteData, + sourceDir, + outDir, + publicPath, + themePath, + themeLayoutPath, + themeNotFoundPath, + isAlgoliaSearch, + markdown +}, { debug } = {}, isServer) { + const Config = require('webpack-chain') + const { VueLoaderPlugin } = require('vue-loader') + const CSSExtractPlugin = require('mini-css-extract-plugin') + + const isProd = process.env.NODE_ENV === 'production' + const inlineLimit = 10000 + + const config = new Config() + + config + .mode(isProd && !debug ? 'production' : 'development') + .output + .path(outDir) + .filename(isProd ? 'assets/js/[name].[chunkhash:8].js' : 'assets/js/[name].js') + .publicPath(isProd ? publicPath : '/') + + if (debug) { + config.devtool('source-map') + } else if (!isProd) { + config.devtool('cheap-module-eval-source-map') + } + + config.resolve + .set('symlinks', true) + .alias + .set('@theme', themePath) + .set('@themeLayout', themeLayoutPath) + .set('@themeNotFound', themeNotFoundPath) + .set('@source', sourceDir) + .set('@app', path.resolve(__dirname, '../app')) + .set('@temp', path.resolve(__dirname, '../app/.temp')) + .set('@default-theme', path.resolve(__dirname, '../default-theme')) + .set('@AlgoliaSearchBox', isAlgoliaSearch + ? path.resolve(__dirname, '../default-theme/AlgoliaSearchBox.vue') + : path.resolve(__dirname, './noopModule.js')) + .end() + .extensions + .merge(['.js', '.jsx', '.vue', '.json', '.styl']) + .end() + .modules + // prioritize our own + .add(path.resolve(__dirname, '../../node_modules')) + .add(path.resolve(__dirname, '../../../')) + .add('node_modules') + + // Load extra root mixins on demand. + const { activeHeaderLinks = true } = siteData.themeConfig + const rootMixinsLoadingConfig = { activeHeaderLinks } + for (const mixinName in rootMixinsLoadingConfig) { + const enabled = rootMixinsLoadingConfig[mixinName] + config.resolve + .alias.set(`@${mixinName}`, enabled + ? path.resolve(__dirname, `../app/root-mixins/${mixinName}.js`) + : path.resolve(__dirname, './noopModule.js') + ) + } + + config.resolveLoader + .set('symlinks', true) + .modules + // prioritize our own + .add(path.resolve(__dirname, '../../node_modules')) + .add(path.resolve(__dirname, '../../../')) + .add('node_modules') + + config.module + .noParse(/^(vue|vue-router|vuex|vuex-router-sync)$/) + + const cacheDirectory = path.resolve(__dirname, '../../node_modules/.cache/vuepress') + const cacheIdentifier = JSON.stringify({ + vuepress: require('../../package.json').version, + 'cache-loader': require('cache-loader').version, + 'vue-loader': require('vue-loader').version, + isProd, + isServer, + config: ( + (siteConfig.markdown ? JSON.stringify(siteConfig.markdown) : '') + + (siteConfig.chainWebpack || '').toString() + + (siteConfig.configureWebpack || '').toString() + ) + }) + + function applyVuePipeline (rule) { + rule + .use('cache-loader') + .loader('cache-loader') + .options({ + cacheDirectory, + cacheIdentifier + }) + + rule + .use('vue-loader') + .loader('vue-loader') + .options({ + compilerOptions: { + preserveWhitespace: true + }, + cacheDirectory, + cacheIdentifier + }) + } + + const vueRule = config.module + .rule('vue') + .test(/\.vue$/) + + applyVuePipeline(vueRule) + + const mdRule = config.module + .rule('markdown') + .test(/\.md$/) + + applyVuePipeline(mdRule) + + mdRule + .use('markdown-loader') + .loader(require.resolve('./markdownLoader')) + .options({ sourceDir, markdown }) + + config.module + .rule('pug') + .test(/\.pug$/) + .use('pug-plain-loader') + .loader('pug-plain-loader') + .end() + + if (!siteConfig.evergreen) { + const libDir = path.join(__dirname, '..') + config.module + .rule('js') + .test(/\.js$/) + .exclude.add(filepath => { + // Always transpile lib directory + if (filepath.startsWith(libDir)) { + return false + } + // always transpile js in vue files + if (/\.vue\.js$/.test(filepath)) { + return false + } + // Don't transpile node_modules + return /node_modules/.test(filepath) + }).end() + .use('cache-loader') + .loader('cache-loader') + .options({ + cacheDirectory, + cacheIdentifier + }) + .end() + .use('babel-loader') + .loader('babel-loader') + .options({ + // do not pick local project babel config (.babelrc) + babelrc: false, + // do not pick local project babel config (babel.config.js) + // ref: http://babeljs.io/docs/en/config-files + configFile: false, + presets: [ + require.resolve('@vue/babel-preset-app') + ] + }) + } + + config.module + .rule('images') + .test(/\.(png|jpe?g|gif)(\?.*)?$/) + .use('url-loader') + .loader('url-loader') + .options({ + limit: inlineLimit, + name: `assets/img/[name].[hash:8].[ext]` + }) + + // do not base64-inline SVGs. + // https://github.com/facebookincubator/create-react-app/pull/1180 + config.module + .rule('svg') + .test(/\.(svg)(\?.*)?$/) + .use('file-loader') + .loader('file-loader') + .options({ + name: `assets/img/[name].[hash:8].[ext]` + }) + + config.module + .rule('media') + .test(/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/) + .use('url-loader') + .loader('url-loader') + .options({ + limit: inlineLimit, + name: `assets/media/[name].[hash:8].[ext]` + }) + + config.module + .rule('fonts') + .test(/\.(woff2?|eot|ttf|otf)(\?.*)?$/i) + .use('url-loader') + .loader('url-loader') + .options({ + limit: inlineLimit, + name: `assets/fonts/[name].[hash:8].[ext]` + }) + + function createCSSRule (lang, test, loader, options) { + const baseRule = config.module.rule(lang).test(test) + const modulesRule = baseRule.oneOf('modules').resourceQuery(/module/) + const normalRule = baseRule.oneOf('normal') + + applyLoaders(modulesRule, true) + applyLoaders(normalRule, false) + + function applyLoaders (rule, modules) { + if (!isServer) { + if (isProd) { + rule.use('extract-css-loader').loader(CSSExtractPlugin.loader) + } else { + rule.use('vue-style-loader').loader('vue-style-loader') + } + } + + rule.use('css-loader') + .loader(isServer ? 'css-loader/locals' : 'css-loader') + .options({ + modules, + localIdentName: `[local]_[hash:base64:8]`, + importLoaders: 1, + sourceMap: !isProd + }) + + rule.use('postcss-loader').loader('postcss-loader').options(Object.assign({ + plugins: [require('autoprefixer')], + sourceMap: !isProd + }, siteConfig.postcss)) + + if (loader) { + rule.use(loader).loader(loader).options(options) + } + } + } + + createCSSRule('css', /\.css$/) + createCSSRule('postcss', /\.p(ost)?css$/) + createCSSRule('scss', /\.scss$/, 'sass-loader', siteConfig.scss) + createCSSRule('sass', /\.sass$/, 'sass-loader', Object.assign({ indentedSyntax: true }, siteConfig.sass)) + createCSSRule('less', /\.less$/, 'less-loader', siteConfig.less) + createCSSRule('stylus', /\.styl(us)?$/, 'stylus-loader', Object.assign({ + preferPathResolver: 'webpack' + }, siteConfig.stylus)) + + config + .plugin('vue-loader') + .use(VueLoaderPlugin) + + if (isProd && !isServer) { + config + .plugin('extract-css') + .use(CSSExtractPlugin, [{ + filename: 'assets/css/styles.[chunkhash:8].css' + }]) + + // ensure all css are extracted together. + // since most of the CSS will be from the theme and very little + // CSS will be from async chunks + config.optimization.splitChunks({ + cacheGroups: { + styles: { + name: 'styles', + // necessary to ensure async chunks are also extracted + test: m => /css-extract/.test(m.type), + chunks: 'all', + enforce: true + } + } + }) + } + + // inject constants + config + .plugin('injections') + .use(require('webpack/lib/DefinePlugin'), [{ + BASE_URL: JSON.stringify(siteConfig.base || '/'), + GA_ID: siteConfig.ga ? JSON.stringify(siteConfig.ga) : false, + SW_ENABLED: !!siteConfig.serviceWorker, + VUEPRESS_VERSION: JSON.stringify(require('../../package.json').version), + LAST_COMMIT_HASH: JSON.stringify(getLastCommitHash()) + }]) + + return config +} + +function getLastCommitHash () { + const spawn = require('cross-spawn') + let hash + try { + hash = spawn.sync('git', ['log', '-1', '--format=%h']).stdout.toString('utf-8').trim() + } catch (error) {} + return hash +} diff --git a/node_modules/vuepress/lib/webpack/createClientConfig.js b/node_modules/vuepress/lib/webpack/createClientConfig.js new file mode 100644 index 00000000..8c276633 --- /dev/null +++ b/node_modules/vuepress/lib/webpack/createClientConfig.js @@ -0,0 +1,69 @@ +module.exports = function createClientConfig (options, cliOptions) { + const path = require('path') + const WebpackBar = require('webpackbar') + const createBaseConfig = require('./createBaseConfig') + + const config = createBaseConfig(options, cliOptions) + + config + .entry('app') + .add(path.resolve(__dirname, '../app/clientEntry.js')) + + config.node + .merge({ + // prevent webpack from injecting useless setImmediate polyfill because Vue + // source contains it (although only uses it if it's native). + setImmediate: false, + global: false, + process: false, + // prevent webpack from injecting mocks to Node native modules + // that does not make sense for the client + dgram: 'empty', + fs: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty' + }) + + // generate client manifest only during build + if (process.env.NODE_ENV === 'production') { + // This is a temp build of vue-server-renderer/client-plugin. + // TODO Switch back to original after problems are resolved. + // Fixes two things: + // 1. Include CSS in preload files + // 2. filter out useless styles.xxxxx.js chunk from mini-css-extract-plugin + // https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85 + config + .plugin('ssr-client') + .use(require('./ClientPlugin'), [{ + filename: 'manifest/client.json' + }]) + + config + .plugin('optimize-css') + .use(require('optimize-css-assets-webpack-plugin'), [{ + canPrint: false, + cssProcessorOptions: { + safe: true, + autoprefixer: { disable: true }, + mergeLonghand: false + } + }]) + } + + if (!cliOptions.debug) { + config + .plugin('bar') + .use(WebpackBar, [{ + name: 'Client', + color: '#41b883', + compiledIn: false + }]) + } + + if (options.siteConfig.chainWebpack) { + options.siteConfig.chainWebpack(config, false /* isServer */) + } + + return config +} diff --git a/node_modules/vuepress/lib/webpack/createServerConfig.js b/node_modules/vuepress/lib/webpack/createServerConfig.js new file mode 100644 index 00000000..e9f09835 --- /dev/null +++ b/node_modules/vuepress/lib/webpack/createServerConfig.js @@ -0,0 +1,58 @@ +module.exports = function createServerConfig (options, cliOptions) { + const fs = require('fs') + const path = require('path') + const WebpackBar = require('webpackbar') + const createBaseConfig = require('./createBaseConfig') + const VueSSRServerPlugin = require('vue-server-renderer/server-plugin') + const CopyPlugin = require('copy-webpack-plugin') + + const config = createBaseConfig(options, cliOptions, true /* isServer */) + const { sourceDir, outDir } = options + + config + .target('node') + .externals([/^vue|vue-router$/]) + .devtool('source-map') + + // no need to minimize server build + config.optimization.minimize(false) + + config + .entry('app') + .add(path.resolve(__dirname, '../app/serverEntry.js')) + + config.output + .filename('server-bundle.js') + .libraryTarget('commonjs2') + + config + .plugin('ssr-server') + .use(VueSSRServerPlugin, [{ + filename: 'manifest/server.json' + }]) + + const publicDir = path.resolve(sourceDir, '.vuepress/public') + if (fs.existsSync(publicDir)) { + config + .plugin('copy') + .use(CopyPlugin, [[ + { from: publicDir, to: outDir } + ]]) + } + + if (!cliOptions.debug) { + config + .plugin('bar') + .use(WebpackBar, [{ + name: 'Server', + color: 'blue', + compiledIn: false + }]) + } + + if (options.siteConfig.chainWebpack) { + options.siteConfig.chainWebpack(config, true /* isServer */) + } + + return config +} diff --git a/node_modules/vuepress/lib/webpack/markdownLoader.js b/node_modules/vuepress/lib/webpack/markdownLoader.js new file mode 100644 index 00000000..3c49b747 --- /dev/null +++ b/node_modules/vuepress/lib/webpack/markdownLoader.js @@ -0,0 +1,101 @@ +const fs = require('fs') +const path = require('path') +const hash = require('hash-sum') +const { EventEmitter } = require('events') +const { getOptions } = require('loader-utils') +const { inferTitle, extractHeaders, parseFrontmatter } = require('../util') +const LRU = require('lru-cache') + +const cache = LRU({ max: 1000 }) +const devCache = LRU({ max: 1000 }) + +module.exports = function (src) { + const isProd = process.env.NODE_ENV === 'production' + const isServer = this.target === 'node' + const { markdown, sourceDir } = getOptions(this) + + // we implement a manual cache here because this loader is chained before + // vue-loader, and will be applied on the same file multiple times when + // selecting the individual blocks. + const file = this.resourcePath + const key = hash(file + src) + const cached = cache.get(key) + if (cached && (isProd || /\?vue/.test(this.resourceQuery))) { + return cached + } + + const frontmatter = parseFrontmatter(src) + const content = frontmatter.content + + if (!isProd && !isServer) { + const inferredTitle = inferTitle(frontmatter) + const headers = extractHeaders(content, ['h2', 'h3'], markdown) + delete frontmatter.content + + // diff frontmatter and title, since they are not going to be part of the + // returned component, changes in frontmatter do not trigger proper updates + const cachedData = devCache.get(file) + if (cachedData && ( + cachedData.inferredTitle !== inferredTitle || + JSON.stringify(cachedData.frontmatter) !== JSON.stringify(frontmatter) || + headersChanged(cachedData.headers, headers) + )) { + // frontmatter changed... need to do a full reload + module.exports.frontmatterEmitter.emit('update') + } + + devCache.set(file, { + headers, + frontmatter, + inferredTitle + }) + } + + // the render method has been augmented to allow plugins to + // register data during render + const { html, data: { hoistedTags, links }} = markdown.render(content) + + // check if relative links are valid + links && links.forEach(link => { + link = decodeURIComponent(link) + const shortname = link + .replace(/#.*$/, '') + .replace(/\.html$/, '.md') + const filename = shortname + .replace(/\/$/, '/README.md') + .replace(/^\//, sourceDir + '/') + const altname = shortname + .replace(/\/$/, '/index.md') + .replace(/^\//, sourceDir + '/') + const dir = path.dirname(this.resourcePath) + const file = path.resolve(dir, filename) + const altfile = altname !== filename ? path.resolve(dir, altname) : null + if (!fs.existsSync(file) && (!altfile || !fs.existsSync(altfile))) { + this.emitWarning( + new Error( + `\nFile for relative link "${link}" does not exist.\n` + + `(Resolved file: ${file})\n` + ) + ) + } + }) + + const res = ( + `<template>\n` + + `<div class="content">${html}</div>\n` + + `</template>\n` + + (hoistedTags || []).join('\n') + ) + cache.set(key, res) + return res +} + +function headersChanged (a, b) { + if (a.length !== b.length) return true + return a.some((h, i) => ( + h.title !== b[i].title || + h.level !== b[i].level + )) +} + +module.exports.frontmatterEmitter = new EventEmitter() diff --git a/node_modules/vuepress/lib/webpack/noopModule.js b/node_modules/vuepress/lib/webpack/noopModule.js new file mode 100644 index 00000000..b1c6ea43 --- /dev/null +++ b/node_modules/vuepress/lib/webpack/noopModule.js @@ -0,0 +1 @@ +export default {} |
