--- /dev/null
+<template>
+ <p class="bit-sponsor">
+ <a href="https://www.bitsrc.io/?utm_source=vue&utm_medium=vue&utm_campaign=vue&utm_term=vue&utm_content=vue" target="_blank">
+ <span>This project is sponsored by</span>
+ <img alt="bit" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/bit-wide.png">
+ </a>
+ </p>
+</template>
--- /dev/null
+<template>
+ <div class="demo" :class="containerClasses">
+ <Promised :promise="examplePromise">
+ <span></span>
+ <span slot="catch" slot-scope="error"></span>
+ <ExamplePreviewBar slot-scope="then" :router="router" :view-code.sync="viewCode" :files="files" :current-file.sync="currentFile" :codesandbox-params="codesandboxParams" />
+ </Promised>
+ <Promised :promise="examplePromise">
+ <div class="example">Loading example...</div>
+ <div slot-scope="then">
+ <template v-if="viewCode">
+ <!-- <ExamplePreviewFilesTabs :files="files" :current-file.sync="currentFile" :view-code.sync="viewCode" /> -->
+ <ExamplePreviewExplorer v-if="currentFile" :file="currentFile" />
+ </template>
+ <template v-else>
+ <div class="example">
+ <component :is="page" />
+ </div>
+ </template>
+ </div>
+ <div class="example error" slot="catch" slot-scope="error">
+ <p>There was an error loading the example</p>
+
+ <button class="action-button" @click="loadPage">Retry</button>
+ </div>
+ </Promised>
+ </div>
+</template>
+
+<script>
+import Router from 'vue-router'
+import Promised from 'vue-promised'
+import ExamplePreviewBar from '../example-preview/ExamplePreviewBar'
+import 'focus-visible'
+import ExamplePreviewExplorer from '../example-preview/ExamplePreviewExplorer'
+import { getCodesandboxParameters, removeScriptSection } from '../example-preview/utils'
+
+export default {
+ props: {
+ name: String,
+ initialView: {
+ type: String,
+ default: 'demo'
+ }
+ },
+
+ data() {
+ return {
+ viewCode: this.initialView === 'code',
+ router: null,
+ page: null,
+ files: [],
+ currentFile: null,
+ codesandboxParams: null,
+
+ examplePromise: null
+ }
+ },
+
+ methods: {
+ async loadPage () {
+ if (!this.name) return
+ this.examplePromise = (this.pagePath ? import(`@docs/${this.pagePath}/examples/${this.name}/index.js`) : import(`@docs/examples/${this.name}/index.js`))
+ const Page = await this.examplePromise
+ if (!Page || !Page.App) return
+ let { App, files, codesandbox } = Page
+
+ // Do not create a new Router if the example has been loaded before
+ // otherwise, visiting a new page and coming back will break the navigation bar
+ if (App._exampleLoaded) {
+ this.router = App.router
+ } else {
+ // create the router again but force the mode to abstract
+ this.router = App.router = new Router({
+ ...App.router.options,
+ mode: 'abstract'
+ })
+ App._exampleLoaded = true
+ }
+
+ this.router.push('/')
+ this.page = App
+ // reset file
+ this.currentFile = null
+ this.codesandboxParams = null
+
+ this.files = []
+ // files is usually an object, transform if it's not
+ if (Array.isArray(files)) {
+ files = files.reduce((filesDict, name) => ({...filesDict, [name]: name}), {})
+ }
+
+ // load the content of all the files
+ Object.keys(files).forEach(name => {
+ const handleImport = ({ default: content }) => {
+ // remove the script part that contains the router
+ if (name === 'App.vue') {
+ content = removeScriptSection(content)
+ }
+ this.files.push({ name, content })
+ if (!this.currentFile) this.currentFile = this.files[this.files.length - 1]
+ }
+ if (this.pagePath) {
+ import(`!raw-loader!@docs/${this.pagePath}/examples/${this.name}/${files[name]}`).then(handleImport)
+ } else {
+ import(`!raw-loader!@docs/examples/${this.name}/${files[name]}`).then(handleImport)
+ }
+ })
+
+ if (!codesandbox || !codesandbox.length) return
+ const allFiles = codesandbox.map(filename => {
+ const transformFile = ({ default: content }) => {
+ // remove the script part from App.vue because it has the router
+ if (filename === 'App.vue') {
+ content = removeScriptSection(content)
+ }
+ return {
+ [filename]: { content }
+ }
+ }
+ if (this.pagePath) {
+ return import(`!raw-loader!@docs/${this.pagePath}/examples/${this.name}/${filename}`).then(transformFile)
+ } else {
+ return import(`!raw-loader!@docs/examples/${this.name}/${filename}`).then(transformFile)
+ }
+ })
+
+ // add the entry point (common for all examples) main.js
+ if (codesandbox.indexOf('main.js') < 0) {
+ allFiles.push(import(`!raw-loader!@docs/examples/common/main.js`).then(({ default: content })=> ({
+ 'main.js': { content }
+ })))
+ }
+
+ this.codesandboxParams = await Promise.all(allFiles).then(getCodesandboxParameters)
+ }
+ },
+
+ computed: {
+ containerClasses() {
+ return { explorer: this.viewCode }
+ },
+ // this seems to be necessary to correctly code split
+ // it allows import path to have the slashes in them
+ pagePath () {
+ return this.$localePath.replace(/^\//, '').replace(/\/$/, '')
+ }
+ },
+
+ watch: {
+ name: {
+ handler: 'loadPage',
+ immediate: true
+ }
+ },
+
+ components: { ExamplePreviewBar, ExamplePreviewExplorer, Promised }
+}
+</script>
+
+<style scoped lang="stylus">
+@import '~@default-theme/styles/config.styl';
+
+.demo {
+ border: 1px solid #ddd;
+ border-radius: 4px;
+
+ & .example {
+ padding: 1rem 1.5rem;
+ overflow: hidden;
+
+ &.error {
+ color: #ff2828;
+ }
+ }
+}
+
+@media (max-width: $MQMobileNarrow) {
+ .demo {
+ margin-left: -1.5rem;
+ margin-right: -1.5rem;
+ border-radius: 0;
+ }
+}
+
+.action-button {
+ display: inline-block;
+ font-size: 1.2rem;
+ color: #fff;
+ background-color: #3eaf7c;
+ padding: 0.8rem 1.6rem;
+ border-radius: 4px;
+ transition: background-color 0.1s ease;
+ box-sizing: border-box;
+ border-bottom: 1px solid #389d70;
+
+ &:hover {
+ background-color: #4abf8a;
+ cursor: pointer;
+ }
+}
+</style>
+
--- /dev/null
+<template>
+ <button class="button">
+ <component :is="svgComponent" />
+ </button>
+</template>
+
+<script>
+import { defineComponent, shallowRef, watch } from 'vue'
+
+export default defineComponent({
+ props: {
+ icon: String,
+ },
+
+ setup(props) {
+ const svgComponent = shallowRef()
+
+ watch(
+ () => props.icon,
+ async icon => {
+ svgComponent.value = (
+ await import(`../example-preview/icons/${icon}.vue`)
+ ).default
+ },
+ { immediate: true }
+ )
+
+ return { svgComponent }
+ },
+})
+</script>
+
+<style scoped>
+.button {
+ /* reset button style */
+ border: none;
+ padding: 0;
+ width: auto;
+ overflow: visible;
+ background: transparent;
+ font: inherit;
+ /* acutal styles */
+ color: rgb(135, 135, 135);
+ font-size: 1.5rem;
+ line-height: 0.5;
+ vertical-align: middle;
+ text-align: center;
+ margin: 0px 0.1rem;
+}
+
+.button:not([disabled]):hover {
+ background-color: rgb(226, 226, 226);
+ cursor: pointer;
+}
+
+.button[disabled] {
+ color: rgb(192, 192, 192);
+}
+</style>
--- /dev/null
+<template>
+ <div class="bar-container">
+ <template v-if="!viewCode">
+ <div class="actions-container">
+ <ExamplePreviewBarButton
+ icon="left-arrow"
+ @click.native="back"
+ :disabled="!previousPage"
+ title="Go back"
+ />
+ <ExamplePreviewBarButton
+ icon="right-arrow"
+ @click.native="forward"
+ :disabled="!nextPage"
+ title="Go forward"
+ />
+ </div>
+ <div class="uri-container">
+ <input
+ class="uri"
+ :disabled="!router"
+ :value="uri"
+ @keyup.enter="navigate"
+ />
+ </div>
+ </template>
+ <div class="tabs-container" v-else>
+ <button
+ class="reset-button tab"
+ v-for="file in files"
+ :class="file === currentFile ? 'is-selected' : ''"
+ @click="$emit('update:currentFile', file)"
+ >
+ {{ file.name }}
+ </button>
+ </div>
+ <div class="actions-container">
+ <form
+ v-if="viewCode && codesandboxParams"
+ action="https://codesandbox.io/api/v1/sandboxes/define"
+ method="POST"
+ target="_blank"
+ >
+ <input type="hidden" name="parameters" :value="codesandboxParams" />
+ <ExamplePreviewBarButton
+ icon="codesandbox"
+ title="Edit on CodeSandbox"
+ />
+ </form>
+ <ExamplePreviewBarButton
+ :icon="viewCode ? 'play-window' : 'brackets'"
+ @click.native="$emit('update:viewCode', !viewCode)"
+ :title="viewCode ? 'View Demo' : 'View Code'"
+ />
+ </div>
+ </div>
+</template>
+
+<script>
+import ExamplePreviewBarButton from '../components/ExamplePreviewBarButton'
+
+export default {
+ props: {
+ router: Object,
+ files: Array,
+ currentFile: Object,
+ viewCode: Boolean,
+ codesandboxParams: String,
+ },
+
+ computed: {
+ uri() {
+ return this.router ? this.router.currentRoute.fullPath : '/'
+ },
+ history() {
+ return this.router && this.router.history
+ },
+ previousPage() {
+ return this.history ? this.history.stack[this.history.index - 1] : ''
+ },
+ nextPage() {
+ return this.history ? this.history.stack[this.history.index + 1] : ''
+ },
+ },
+
+ methods: {
+ back() {
+ this.router.go(-1)
+ },
+ forward() {
+ this.router.go(1)
+ },
+ navigate({ target }) {
+ this.router.push(target.value)
+ },
+ },
+
+ components: { ExamplePreviewBarButton },
+}
+</script>
+
+<style scoped lang="stylus">
+@import '~@default-theme/styles/config.styl';
+
+.bar-container {
+ display: flex;
+ border-radius: 4px 4px 0 0;
+ background-color: rgb(242, 242, 242);
+ padding: 0 0.5rem;
+ align-items: center;
+ line-height: 1;
+ box-shadow: rgb(221, 221, 221) 0px 1px 3px;
+ height: 2.5rem;
+ min-height: 2.5rem;
+ margin-bottom: 0;
+}
+
+.actions-container {
+ // Make sure they appar in line
+ display: flex;
+}
+
+.uri-container, .tabs-container {
+ flex-grow: 1;
+}
+
+.tabs-container {
+ display: flex;
+ height: 100%;
+ margin-left: -0.5rem;
+ overflow-x: auto;
+}
+
+.tab {
+ height: 100%;
+ font-size: 0.9rem;
+ padding: 0 0.7rem;
+ line-height: 2.5rem;
+ color: #666;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &.is-selected {
+ color: inherit;
+ font-weight: 500;
+ border-bottom: 3px solid $accentColor;
+ background-color: rgba(0, 0, 0, 0.075);
+ }
+}
+
+.uri {
+ box-sizing: border-box;
+ width: 100%;
+ color: rgba(0, 0, 0, 0.8);
+ box-sizing: border-box;
+ border-radius: 4px;
+ outline: none;
+ border-width: 1px;
+ border-style: solid;
+ border-color: rgb(204, 204, 204);
+ padding: 0.2rem 0.5rem;
+ vertical-align: middle;
+ font-size: 1rem;
+}
+
+.button {
+ color: rgb(135, 135, 135);
+ font-size: 1.5rem;
+ line-height: 0.5;
+ vertical-align: middle;
+ text-align: center;
+ margin: 0px 0.1rem;
+
+ &:not([disabled]):hover {
+ background-color: rgb(226, 226, 226);
+ cursor: pointer;
+ }
+
+ &[disabled] {
+ color: rgb(192, 192, 192);
+ }
+}
+</style>
--- /dev/null
+<template>
+ <div v-html="content" class="code" :class="'language-' + language"></div>
+</template>
+
+<script>
+// pretty much copied from vuepress sourcecode
+const prism = require('prismjs')
+// const loadLanguages = require('prismjs/components/index')
+const escapeHtml = require('escape-html')
+
+// required to make embedded highlighting work...
+// loadLanguages(['markup', 'css', 'javascript'])
+
+function wrap (code, lang) {
+ if (lang === 'text') {
+ code = escapeHtml(code)
+ }
+ return `<pre class="language-${lang}" style="margin: 0"><code>${code}</code></pre>`
+}
+
+function highlightCode (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]) {
+ const code = prism.highlight(str, prism.languages[lang], lang)
+ return wrap(code, rawLang)
+ } else {
+ console.warn(`[vuepress] Syntax highlight for language "${lang}" is not supported.`)
+ }
+ return wrap(str, 'text')
+}
+
+export default {
+ props: {
+ file: Object
+ },
+
+ computed: {
+ language: ({ file: { name }}) => name.substring(name.lastIndexOf('.') + 1, name.length),
+ content: ({ file, language}) => highlightCode(file.content, language)
+ }
+}
+</script>
+
+<style scoped>
+.code {
+ border-radius: 0 0 4px 4px;
+}
+
+.code pre {
+ margin: 0;
+}
+
+@media (max-width: 419px) {
+ .code.code.code {
+ margin: 0;
+ }
+}
+</style>
--- /dev/null
+<template>
+ <svg
+ fill="currentColor"
+ x="0px"
+ y="0px"
+ width="24px"
+ height="24px"
+ viewBox="0 0 24 24"
+ enable-background="new 0 0 24 24"
+ >
+ <g>
+ <path
+ id="left-bracket"
+ d="M4,12v-1h1c1,0,1,0,1-1V7.614C6,7.1,6.024,6.718,6.073,6.472C6.127,6.22,6.212,6.009,6.33,5.839 C6.534,5.56,6.803,5.364,7.138,5.255C7.473,5.14,8.01,5,8.973,5H10v1H9.248c-0.457,0-0.77,0.191-0.936,0.408 C8.145,6.623,8,6.853,8,7.476v1.857c0,0.729-0.041,1.18-0.244,1.493c-0.2,0.307-0.562,0.529-1.09,0.667 c0.535,0.155,0.9,0.385,1.096,0.688C7.961,12.484,8,12.938,8,13.665v1.862c0,0.619,0.145,0.848,0.312,1.062 c0.166,0.22,0.479,0.407,0.936,0.407L10,17l0,0v1H8.973c-0.963,0-1.5-0.133-1.835-0.248c-0.335-0.109-0.604-0.307-0.808-0.591 c-0.118-0.165-0.203-0.374-0.257-0.625C6.024,16.283,6,15.9,6,15.387V13c0-1,0-1-1-1H4z"
+ />
+ <use
+ transform="matrix(-1,0,0,1,24,0)"
+ id="right-bracket"
+ x="0"
+ y="0"
+ width="24"
+ height="24"
+ xlink:href="#left-bracket"
+ />
+ </g>
+ </svg>
+</template>
--- /dev/null
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 1063 1063" fill="none">
+ <path d="M0 317.198V106.046L182.999 0V211.465L0 317.198Z" transform="translate(739.001 551.802)" fill="currentColor" />
+ <path d="M179.915 104.303L0 0V208.606L179.915 313.438V104.303Z" transform="translate(142.167 557.135)" fill="currentColor" />
+ <path d="M183.546 212.795L366.503 106.633L183.624 0L0 106.987L183.546 212.795Z" transform="translate(348.436 81)" fill="currentColor" />
+ <path d="M390 0L0 225.167V675.167" transform="translate(529 305.833)" stroke="currentColor" stroke-width="100" stroke-miterlimit="10" />
+ <path d="M0 0L389.333 224" transform="translate(142.167 307)" stroke="currentColor" stroke-width="100" stroke-miterlimit="10" />
+ <path d="M0 677.083L389.917 901.042L780 676.333V226L390 0L0 227V677.083Z" transform="translate(141 80)" stroke="currentColor" stroke-width="100" stroke-miterlimit="10" />
+</svg>
--- /dev/null
+<svg fill="currentColor" preserveAspectRatio="xMidYMid meet" height="1em" width="1em" viewBox="0 0 40 40" style="vertical-align: middle;">
+ <g>
+ <path d="m26.5 12.1q0 0.3-0.2 0.6l-8.8 8.7 8.8 8.8q0.2 0.2 0.2 0.5t-0.2 0.5l-1.1 1.1q-0.3 0.3-0.6 0.3t-0.5-0.3l-10.4-10.4q-0.2-0.2-0.2-0.5t0.2-0.5l10.4-10.4q0.3-0.2 0.5-0.2t0.6 0.2l1.1 1.1q0.2 0.2 0.2 0.5z"></path>
+ </g>
+</svg>
--- /dev/null
+<svg fill="currentColor" preserveAspectRatio="xMidYMid meet" height="1em" width="1em" viewBox="0 0 200 200" style="vertical-align: middle;">
+ <path d="M27,33 L173,33 C178.522847,33 183,37.4771525 183,43 L183,157 C183,162.522847 178.522847,167 173,167 L27,167 C21.4771525,167 17,162.522847 17,157 L17,43 C17,37.4771525 21.4771525,33 27,33 Z M29,64 L29,156 L171,156 L171,64 L29,64 Z M84.260356,82.6998802 L119.974518,107.161635 C121.797124,108.409995 122.262642,110.899505 121.014282,112.722111 C120.734924,113.129973 120.38238,113.482517 119.974518,113.761875 L84.260356,138.223629 C82.4377502,139.471989 79.9482404,139.006471 78.6998802,137.183866 C78.2439706,136.518238 78,135.730302 78,134.92351 L78,86 C78,83.790861 79.790861,82 82,82 C82.8067925,82 83.594728,82.2439706 84.260356,82.6998802 Z" fill="inherit"></path>
+</svg>
--- /dev/null
+<svg fill="currentColor" preserveAspectRatio="xMidYMid meet" height="1em" width="1em" viewBox="0 0 40 40" style="vertical-align: middle;">
+ <g>
+ <path d="m26.3 21.4q0 0.3-0.2 0.5l-10.4 10.4q-0.3 0.3-0.6 0.3t-0.5-0.3l-1.1-1.1q-0.2-0.2-0.2-0.5t0.2-0.5l8.8-8.8-8.8-8.7q-0.2-0.3-0.2-0.6t0.2-0.5l1.1-1.1q0.3-0.2 0.5-0.2t0.6 0.2l10.4 10.4q0.2 0.2 0.2 0.5z"></path>
+ </g>
+</svg>
--- /dev/null
+import { getParameters } from 'codesandbox/lib/api/define'
+
+export function getCodesandboxParameters(files) {
+ const data = {
+ files: {
+ 'package.json': {
+ content: {
+ main: 'main.js',
+ dependencies: {
+ vue: 'latest',
+ 'vue-router': 'latest',
+ },
+ },
+ },
+ ...files.reduce(
+ (fileMap, file) => ({
+ ...fileMap,
+ ...file,
+ }),
+ {}
+ ),
+ },
+ }
+
+ return getParameters(data)
+}
+
+const scriptRE = /\s*<script[^>]*>[\s\S]*<\/script>\s*/m
+export function removeScriptSection(content) {
+ return content.replace(scriptRE, '')
+}
--- /dev/null
+import DefaultTheme from 'vitepress/dist/client/theme-default'
+import Bit from '../components/Bit.vue'
+import ExampleButton from '../components/ExamplePreviewBarButton.vue'
+
+/** @type {import('vitepress').Theme} */
+const config = {
+ ...DefaultTheme,
+ enhanceApp({ app }) {
+ app.component('Bit', Bit)
+ app.component('ExampleButton', ExampleButton)
+ },
+}
+
+export default config
<!-- TODO: provide examples or remove this -->
[Get started](./guide/) or play with the [playground](https://github.com/vuejs/vue-router-next/tree/master/playground) (see [`README.md`](https://github.com/vuejs/vue-router-next) to run them).
+
+<ExampleButton icon="brackets"/>