Merge branch 'master' into feat/algoliasearch

pull/153/head
Eduardo San Martin Morote 5 years ago committed by GitHub
commit 59cbd25547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,6 +11,12 @@ module.exports = {
editLinkText: 'Edit this page on GitHub', editLinkText: 'Edit this page on GitHub',
lastUpdated: 'Last Updated', lastUpdated: 'Last Updated',
carbonAds: {
carbon: 'CEBDT27Y',
custom: 'CKYD62QM',
placement: 'vuejsorg'
},
nav: [ nav: [
{ text: 'Guide', link: '/' }, { text: 'Guide', link: '/' },
{ text: 'Config Reference', link: '/config/basics' }, { text: 'Config Reference', link: '/config/basics' },
@ -36,6 +42,7 @@ function getGuideSidebar() {
{ text: 'What is VitePress?', link: '/' }, { text: 'What is VitePress?', link: '/' },
{ text: 'Getting Started', link: '/guide/getting-started' }, { text: 'Getting Started', link: '/guide/getting-started' },
{ text: 'Configuration', link: '/guide/configuration' }, { text: 'Configuration', link: '/guide/configuration' },
{ text: 'Asset Handling', link: '/guide/assets' },
{ text: 'Markdown Extensions', link: '/guide/markdown' }, { text: 'Markdown Extensions', link: '/guide/markdown' },
{ text: 'Deploying', link: '/guide/deploy' } { text: 'Deploying', link: '/guide/deploy' }
] ]
@ -44,6 +51,7 @@ function getGuideSidebar() {
text: 'Advanced', text: 'Advanced',
children: [ children: [
{ text: 'Frontmatter', link: '/guide/frontmatter' }, { text: 'Frontmatter', link: '/guide/frontmatter' },
{ text: 'Global Computed', link: '/guide/global-computed' },
{ text: 'Customization', link: '/guide/customization' } { text: 'Customization', link: '/guide/customization' }
] ]
} }
@ -54,7 +62,10 @@ function getConfigSidebar() {
return [ return [
{ {
text: 'App Config', text: 'App Config',
children: [{ text: 'Basics', link: '/config/basics' }] children: [
{ text: 'Basics', link: '/config/basics' },
{ text: 'Carbon Ads', link: '/config/carbon-ads' }
]
} }
] ]
} }

@ -43,7 +43,7 @@ module.exports = {
} }
``` ```
## Description ## description
- Type: `string` - Type: `string`
- Default: `A VitePress site` - Default: `A VitePress site`
@ -52,6 +52,6 @@ Description for the site. This will render as a `<meta>` tag in the page HTML.
```js ```js
module.exports = { module.exports = {
title: 'A VitePress site' description: 'A VitePress site'
} }
``` ```

@ -0,0 +1,13 @@
# App Config: Carbon Ads
VitePress has built in native support for [Carbon Ads](https://www.carbonads.net). By defining the Carbon Ads credentials in config, VitePress will display ads on the page.
```js
module.exports = {
carbonAds: {
carbon: 'your-carbon-key',
custom: 'your-carbon-custom',
placement: 'your-carbon-placement'
}
}
```

@ -0,0 +1,35 @@
# Asset Handling
All Markdown files are compiled into Vue components and processed by [Vite](https://github.com/vitejs/vite). You can, **and should**, reference any assets using relative URLs:
```md
![An image](./image.png)
```
You can reference static assets in your markdown files, your `*.vue` components in the theme, styles and plain `.css` files either using absolute public paths (based on project root) or relative paths (based on your file system). The latter is similar to the behavior you are used to if you have used `vue-cli` or webpack's `file-loader`.
Common image, media, and font filetypes are detected and included as assets automatically.
All referenced assets, including those using absolute paths, will be copied to the dist folder with a hashed file name in the production build. Never-referenced assets will not be copied. Similar to `vue-cli`, image assets smaller than 4kb will be base64 inlined.
All **static** path references, including absolute paths, should be based on your working directory structure.
## Public Files
Sometimes you may need to provide static assets that are not directly referenced in any of your Markdown or theme components (for example, favicons and PWA icons). The `public` directory under project root can be used as an escape hatch to provide static assets that either are never referenced in source code (e.g. `robots.txt`), or must retain the exact same file name (without hashing).
Assets placed in `public` will be copied to the root of the dist directory as-is.
Note that you should reference files placed in `public` using root absolute path - for example, `public/icon.png` should always be referenced in source code as `/icon.png`.
## Base URL
If your site is deployed to a non-root URL, you will need to set the `base` option in `.vitepress/config.js`. For example, if you plan to deploy your site to `https://foo.github.io/bar/`, then `base` should be set to `'/bar/'` (it should always start and end with a slash).
With a base URL, to reference an image in `public`, you'd have to use URLs like `/bar/image.png`. But this is brittle if you ever decide to change the base. To help with that, VitePress provides a built-in helper `$withBase` (injected onto Vue's prototype) that generates the correct path:
```html
<img :src="$withBase('/foo.png')" alt="foo">
```
Note you can use the above syntax not only in theme components, but in your Markdown files as well.

@ -20,4 +20,4 @@ module.exports = {
} }
``` ```
Check out the [Config Reference](/config/) for a full list of options. Check out the [Config Reference](/config/basics) for a full list of options.

@ -9,7 +9,7 @@ editLink: true
--- ---
``` ```
Between the triple-dashed lines, you can set [predefined variables](#predefined-variables), or even create custom ones of your own. These variables can be used via the <code>\$page.frontmatter</code> variable. Between the triple-dashed lines, you can set [predefined variables](#predefined-variables), or even create custom ones of your own. These variables can be used via the <code>$frontmatter</code> variable.
Heres an example of how you could use it in your Markdown file: Heres an example of how you could use it in your Markdown file:
@ -19,7 +19,7 @@ title: Docs with VitePress
editLink: true editLink: true
--- ---
# {{ $page.frontmatter.title }} # {{ $frontmatter.title }}
Guide content Guide content
``` ```

@ -0,0 +1,81 @@
# Global Computed
In VitePress, some core [computed properties](https://v3.vuejs.org/guide/computed.html#computed-properties) can be used by the default theme or custom themes. Or directly in Markdown pages using vue, for example using `$frontmatter.title` to access the title defined in the frontmatter section of the page.
## $site
This is the `$site` value of the site you're currently reading:
```js
{
base: '/',
lang: 'en-US',
title: 'VitePress',
description: 'Vite & Vue powered static site generator.',
head: [],
locales: {},
themeConfig: $themeConfig
}
```
## $themeConfig
Refers to `$site.themeConfig`.
```js
{
locales: {},
repo: 'vuejs/vitepress',
docsDir: 'docs',
editLinks: true,
editLinkText: 'Edit this page on GitHub',
lastUpdated: 'Last Updated',
nav: [...],
sidebar: { ... }
}
```
## $page
This is the `$page` value of the page you're currently reading:
```js
{
relativePath: 'guide/global-computed.md',
title: 'Global Computed',
headers: [
{ level: 2, title: '$site', slug: 'site' },
{ level: 2, title: '$page', slug: '$page' },
...
],
frontmatter: $frontmatter,
lastUpdated: 1606297645000
}
```
## $frontmatter
Reference of `$page.frontmatter`.
```js
{
title: 'Docs with VitePress',
editLink: true
}
```
## $title
Value of the `<title>` label used for the current page.
## $description
The content value of the `<meta name= "description" content= "...">` for the current page.
## $withBase
Helper method to generate correct path by prepending the `base` path configured in `.vitepress/config.js`. It's useful when you want to link to [public files with base path](./assets#public-files).
```html
<img :src="$withBase('/foo.png')" alt="foo">
```

@ -5,10 +5,12 @@ import { usePrefetch } from '../composables/preFetch'
export const Content = { export const Content = {
setup() { setup() {
const route = useRoute() const route = useRoute()
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
// in prod mode, enable intersectionObserver based pre-fetch. // in prod mode, enable intersectionObserver based pre-fetch
usePrefetch() usePrefetch()
} }
return () => (route.component ? h(route.component) : null) return () => (route.component ? h(route.component) : null)
} }
} }

@ -1,8 +1,11 @@
import { computed } from 'vue' import { Ref, computed } from 'vue'
import { useRoute } from '../router' import { PageData } from '/@types/shared'
import { Route, useRoute } from '../router'
export function usePageData() { export type PageDataRef = Ref<PageData>
const route = useRoute()
return computed(() => route.data) export function usePageData(route?: Route) {
const r = route || useRoute()
return computed(() => r.data)
} }

@ -1,8 +1,6 @@
import serialized from '@siteData' import serialized from '@siteData'
import { ref, readonly, Ref } from 'vue' import { SiteData } from '/@types/shared'
import { SiteData } from '../../../../types/shared' import { Ref, ref, readonly } from 'vue'
const parse = (data: string) => readonly(JSON.parse(data)) as SiteData
export type SiteDataRef<T = any> = Ref<SiteData<T>> export type SiteDataRef<T = any> = Ref<SiteData<T>>
@ -12,6 +10,10 @@ export function useSiteData<T = any>() {
return siteDataRef as Ref<SiteData<T>> return siteDataRef as Ref<SiteData<T>>
} }
function parse(data: string): SiteData {
return readonly(JSON.parse(data)) as SiteData
}
// hmr // hmr
if (import.meta.hot) { if (import.meta.hot) {
import.meta.hot!.acceptDeps('/@siteData', (m) => { import.meta.hot!.acceptDeps('/@siteData', (m) => {

@ -1,10 +1,12 @@
import { computed } from 'vue' import { computed } from 'vue'
import { resolveSiteDataByRoute } from '/@shared/config' import { resolveSiteDataByRoute } from '/@shared/config'
import { siteDataRef } from './siteData' import { siteDataRef } from './siteData'
import { useRoute } from '../router' import { Route, useRoute } from '../router'
export function useSiteDataByRoute(route?: Route) {
const r = route || useRoute()
export function useSiteDataByRoute(route = useRoute()) {
return computed(() => { return computed(() => {
return resolveSiteDataByRoute(siteDataRef.value, route.path) return resolveSiteDataByRoute(siteDataRef.value, r.path)
}) })
} }

@ -1,5 +1,7 @@
// exports in this file are exposed to themes and md files via 'vitepress' // exports in this file are exposed to themes and md files via 'vitepress'
// so the user can do `import { useRoute, useSiteData } from 'vitepress'`. // so the user can do `import { useRoute, useSiteData } from 'vitepress'`
import { ComponentOptions } from 'vue'
// generic types // generic types
export type { SiteData, PageData } from '/@types/shared' export type { SiteData, PageData } from '/@types/shared'
@ -17,6 +19,7 @@ export { usePageData } from './composables/pageData'
export { Content } from './components/Content' export { Content } from './components/Content'
import _Debug from './components/Debug.vue' import _Debug from './components/Debug.vue'
import { ComponentOptions } from 'vue'
const Debug = _Debug as ComponentOptions const Debug = _Debug as ComponentOptions
export { Debug } export { Debug }

@ -1,20 +1,57 @@
import { createApp as createClientApp, createSSRApp } from 'vue' import { App, createApp as createClientApp, createSSRApp } from 'vue'
import { inBrowser, pathToFile } from './utils' import { inBrowser, pathToFile } from './utils'
import { createRouter, RouterSymbol } from './router' import { Router, RouterSymbol, createRouter } from './router'
import { mixinGlobalComputed, mixinGlobalComponents } from './mixin'
import { siteDataRef } from './composables/siteData' import { siteDataRef } from './composables/siteData'
import { useSiteDataByRoute } from './composables/siteDataByRoute' import { useSiteDataByRoute } from './composables/siteDataByRoute'
import { usePageData } from './composables/pageData'
import { useUpdateHead } from './composables/head' import { useUpdateHead } from './composables/head'
import { Content } from './components/Content'
import Debug from './components/Debug.vue'
import Theme from '/@theme/index' import Theme from '/@theme/index'
const NotFound = Theme.NotFound || (() => '404 Not Found') const NotFound = Theme.NotFound || (() => '404 Not Found')
export function createApp() { export function createApp() {
const router = newRouter()
handleHMR(router)
const app = newApp()
app.provide(RouterSymbol, router)
const siteDataByRouteRef = useSiteDataByRoute(router.route)
const pageDataRef = usePageData(router.route)
if (inBrowser) {
// dynamically update head tags
useUpdateHead(router.route, siteDataByRouteRef)
}
mixinGlobalComputed(app, siteDataRef, siteDataByRouteRef, pageDataRef)
mixinGlobalComponents(app)
if (Theme.enhanceApp) {
Theme.enhanceApp({
app,
router,
siteData: siteDataRef
})
}
return { app, router }
}
function newApp(): App {
return process.env.NODE_ENV === 'production'
? createSSRApp(Theme.Layout)
: createClientApp(Theme.Layout)
}
function newRouter(): Router {
let isInitialPageLoad = inBrowser let isInitialPageLoad = inBrowser
let initialPath: string let initialPath: string
const router = createRouter((path) => { return createRouter((path) => {
let pageFilePath = pathToFile(path) let pageFilePath = pathToFile(path)
if (isInitialPageLoad) { if (isInitialPageLoad) {
@ -23,90 +60,45 @@ export function createApp() {
// use lean build if this is the initial page load or navigating back // use lean build if this is the initial page load or navigating back
// to the initial loaded path (the static vnodes already adopted the // to the initial loaded path (the static vnodes already adopted the
// static content on that load so no need to re-fetch the page). // static content on that load so no need to re-fetch the page)
if (isInitialPageLoad || initialPath === pageFilePath) { if (isInitialPageLoad || initialPath === pageFilePath) {
pageFilePath = pageFilePath.replace(/\.js$/, '.lean.js') pageFilePath = pageFilePath.replace(/\.js$/, '.lean.js')
} }
// in browser: native dynamic import
if (inBrowser) { if (inBrowser) {
isInitialPageLoad = false isInitialPageLoad = false
// in browser: native dynamic import
return import(/*@vite-ignore*/ pageFilePath) return import(/*@vite-ignore*/ pageFilePath)
} else {
// SSR, sync require
return require(pageFilePath)
} }
// SSR: sync require
return require(pageFilePath)
}, NotFound) }, NotFound)
}
function handleHMR(router: Router): void {
// update route.data on HMR updates of active page // update route.data on HMR updates of active page
if (import.meta.hot) { if (import.meta.hot) {
// hot reload pageData // hot reload pageData
import.meta.hot!.on('vitepress:pageData', (payload) => { import.meta.hot!.on('vitepress:pageData', (payload) => {
if ( if (shouldHotReload(payload)) {
payload.path.replace(/(\bindex)?\.md$/, '') ===
location.pathname.replace(/(\bindex)?\.html$/, '')
) {
router.route.data = payload.pageData router.route.data = payload.pageData
} }
}) })
} }
}
const app = function shouldHotReload(payload: any): boolean {
process.env.NODE_ENV === 'production' const payloadPath = payload.path.replace(/(\bindex)?\.md$/, '')
? createSSRApp(Theme.Layout) const locationPath = location.pathname.replace(/(\bindex)?\.html$/, '')
: createClientApp(Theme.Layout)
app.provide(RouterSymbol, router)
app.component('Content', Content)
app.component(
'Debug',
process.env.NODE_ENV === 'production' ? () => null : Debug
)
const siteDataByRouteRef = useSiteDataByRoute(router.route)
if (inBrowser) {
// dynamically update head tags
useUpdateHead(router.route, siteDataByRouteRef)
}
Object.defineProperties(app.config.globalProperties, {
$site: {
get() {
return siteDataRef.value
}
},
$siteByRoute: {
get() {
return siteDataByRouteRef.value
}
},
$page: {
get() {
return router.route.data
}
},
$theme: {
get() {
return siteDataByRouteRef.value.themeConfig
}
}
})
if (Theme.enhanceApp) {
Theme.enhanceApp({
app,
router,
siteData: siteDataByRouteRef
})
}
return { app, router } return payloadPath === locationPath
} }
if (inBrowser) { if (inBrowser) {
const { app, router } = createApp() const { app, router } = createApp()
// wait unitl page component is fetched before mounting // wait unitl page component is fetched before mounting
router.go().then(() => { router.go().then(() => {
app.mount('#app') app.mount('#app')

@ -0,0 +1,73 @@
import { App } from 'vue'
import { joinPath } from './utils'
import { SiteDataRef } from './composables/siteData'
import { PageDataRef } from './composables/pageData'
import { Content } from './components/Content'
import Debug from './components/Debug.vue'
export function mixinGlobalComputed(
app: App,
site: SiteDataRef,
siteByRoute: SiteDataRef,
page: PageDataRef
): void {
Object.defineProperties(app.config.globalProperties, {
$site: {
get() {
return site.value
}
},
$siteByRoute: {
get() {
return siteByRoute.value
}
},
$themeConfig: {
get() {
return siteByRoute.value.themeConfig
}
},
$page: {
get() {
return page.value
}
},
$frontmatter: {
get() {
return page.value.frontmatter
}
},
$title: {
get() {
return page.value.title
? page.value.title + ' | ' + siteByRoute.value.title
: siteByRoute.value.title
}
},
$description: {
get() {
return page.value.description || siteByRoute.value.description
}
},
$withBase: {
value(path: string) {
return joinPath(site.value.base, path)
}
}
})
}
export function mixinGlobalComponents(app: App) {
const isProd = process.env.NODE_ENV === 'production'
app.component('Content', Content)
app.component('Debug', isProd ? () => null : Debug)
}

@ -1,5 +1,12 @@
export const inBrowser = typeof window !== 'undefined' export const inBrowser = typeof window !== 'undefined'
/**
* Join two paths by resolving the slash collision.
*/
export function joinPath(base: string, path: string): string {
return `${base}${path}`.replace(/\/+/g, '/')
}
/** /**
* Converts a url path to the corresponding js chunk filename. * Converts a url path to the corresponding js chunk filename.
*/ */

@ -39,10 +39,26 @@
<main v-else> <main v-else>
<Page> <Page>
<template #top> <template #top>
<slot name="page-top-ads">
<CarbonAds
v-if="$site.themeConfig.carbonAds"
:key="'carbon' + $page.path"
:code="$site.themeConfig.carbonAds.carbon"
:placement="$site.themeConfig.carbonAds.placement"
/>
</slot>
<slot name="page-top" /> <slot name="page-top" />
</template> </template>
<template #bottom> <template #bottom>
<slot name="page-bottom" /> <slot name="page-bottom" />
<slot name="page-bottom-ads">
<BuySellAds
v-if="$site.themeConfig.carbonAds"
:key="'custom' + $page.path"
:code="$site.themeConfig.carbonAds.custom"
:placement="$site.themeConfig.carbonAds.placement"
/>
</slot>
</template> </template>
</Page> </Page>
</main> </main>
@ -59,6 +75,8 @@ import SideBar from './components/SideBar.vue'
import Page from './components/Page.vue' import Page from './components/Page.vue'
import { useRoute, useSiteData, useSiteDataByRoute } from 'vitepress' import { useRoute, useSiteData, useSiteDataByRoute } from 'vitepress'
import AlgoliaSearchBox from './components/AlgoliaSearchBox.vue' import AlgoliaSearchBox from './components/AlgoliaSearchBox.vue'
import CarbonAds from './components/CarbonAds.vue'
import BuySellAds from './components/BuySellAds.vue'
const route = useRoute() const route = useRoute()
const siteData = useSiteData() const siteData = useSiteData()

@ -0,0 +1,146 @@
<template>
<div class="buy-sell-ads">
<div class="bsa-cpc" />
</div>
</template>
<script setup lang="ts">
import { defineProps, onMounted } from 'vue'
// global _bsa
const ID = 'bsa-cpc-script'
declare global {
var _bsa: BSA | undefined
interface BSA {
init(
name: string,
code: string,
placement: string,
options: {
target: string
align: string
disable_css?: 'true' | 'false'
}
): void
}
}
const { code, placement } = defineProps<{
code: string
placement: string
}>()
onMounted(() => {
if (!document.getElementById(ID)) {
const s = document.createElement('script')
s.id = ID
s.src = '//m.servedby-buysellads.com/monetization.js'
document.head.appendChild(s)
s.onload = () => { load() }
} else {
load()
}
})
function load() {
if (typeof _bsa !== 'undefined' && _bsa) {
_bsa.init('default', code, `placement:${placement}`, {
target: '.bsa-cpc',
align: 'horizontal',
disable_css: 'true'
})
}
}
</script>
<style scoped>
.buy-sell-ads {
margin: 0 auto;
padding-top: 2rem;
font-size: .85rem;
}
.bsa-cpc {
border-radius: 6px;
background-color: #f8f8f8;
}
.bsa-cpc ::v-deep(a._default_) {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
margin-bottom: 20px;
padding: 12px;
text-decoration: none;
line-height: 1.4;
font-weight: 400;
color: var(--c-text-light);
}
@media (min-width: 512px) {
.bsa-cpc ::v-deep(a._default_) {
flex-wrap: nowrap;
}
}
.bsa-cpc ::v-deep(.default-ad) {
display: none;
}
.bsa-cpc ::v-deep(a._default_ .default-image) {
flex-shrink: 0;
margin-right: 12px;
width: 24px;
}
.bsa-cpc ::v-deep(a._default_ .default-image img) {
border-radius: 4px;
height: 24px;
vertical-align: middle;
}
.bsa-cpc ::v-deep(._default_::after) {
border: 1px solid #1c90f3;
border-radius: 4px;
margin-top: 8px;
margin-left: 36px;
padding: 0 8px;
line-height: 22px;
font-size: .85em;
font-weight: 500;
color: #1c90f3;
content: 'Sponsored';
}
@media (min-width: 512px) {
.bsa-cpc ::v-deep(._default_::after) {
margin-top: 0px;
margin-left: 12px;
}
}
.bsa-cpc ::v-deep(.default-text) {
flex-grow: 1;
align-self: center;
width: calc(100% - 36px);
}
@media (min-width: 512px) {
.bsa-cpc ::v-deep(.default-text) {
width: auto;
}
}
.bsa-cpc ::v-deep(.default-title) {
font-weight: 600;
}
.bsa-cpc ::v-deep(.default-description) {
padding-left: 8px;
}
</style>

@ -0,0 +1,102 @@
<template>
<div class="carbon-ads" ref="el" />
</template>
<script setup lang="ts">
import { defineProps, ref, onMounted } from 'vue'
const { code, placement } = defineProps<{
code: string
placement: string
}>()
const el = ref()
onMounted(() => {
const s = document.createElement('script')
s.id = '_carbonads_js'
s.src = `//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}`
el.value.appendChild(s)
})
</script>
<style scoped>
.carbon-ads {
padding: 1.75rem 0 0;
border-radius: 4px;
margin: 0 auto;
max-width: 280px;
font-size: .75rem;
background-color: rgba(255, 255, 255, .8);
}
.carbon-ads::after {
clear: both;
display: block;
content: "";
}
@media (min-width: 420px) {
.carbon-ads {
position: relative;
right: -8px;
z-index: 1;
float: right;
margin: -8px -8px 24px 24px;
padding: 8px;
width: 146px;
max-width: 100%;
}
}
@media (min-width: 1400px) {
.carbon-ads {
position: fixed;
top: auto;
right: 8px;
bottom: 8px;
float: none;
margin: 0;
}
}
.carbon-ads ::v-deep(.carbon-img) {
float: left;
margin-right: .75rem;
max-width: 100px;
border: 1px solid var(--c-divider);
}
@media (min-width: 420px) {
.carbon-ads ::v-deep(.carbon-img) {
float: none;
display: block;
margin-right: 0;
max-width: 130px;
}
}
.carbon-ads ::v-deep(.carbon-img img) {
display: block;
width: 100%;
}
@media (min-width: 420px) {
.carbon-ads ::v-deep(.carbon-text) {
padding-top: 8px;
}
}
.carbon-ads ::v-deep(.carbon-text) {
display: block;
font-weight: 400;
color: var(--c-text-light);
}
.carbon-ads ::v-deep(.carbon-poweredby) {
display: block;
padding-top: 2px;
font-weight: 400;
color: var(--c-text-lighter);
}
</style>

@ -2,7 +2,7 @@
<header class="hero"> <header class="hero">
<img <img
v-if="data.heroImage" v-if="data.heroImage"
:src="heroImageSrc" :src="$withBase(heroImageSrc)"
:alt="data.heroAlt || 'hero'" :alt="data.heroAlt || 'hero'"
/> />
@ -39,7 +39,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute, useSiteData } from 'vitepress' import { useRoute, useSiteData } from 'vitepress'
import { withBase } from '../utils'
const route = useRoute() const route = useRoute()
const siteData = useSiteData() const siteData = useSiteData()
@ -48,7 +47,7 @@ const actionLink = computed(() => ({
link: data.value.actionLink, link: data.value.actionLink,
text: data.value.actionText text: data.value.actionText
})) }))
const heroImageSrc = computed(() => withBase(data.value.heroImage)) const heroImageSrc = computed(() => data.value.heroImage)
const siteTitle = computed(() => siteData.value.title) const siteTitle = computed(() => siteData.value.title)
const siteDescription = computed(() => siteData.value.description) const siteDescription = computed(() => siteData.value.description)
</script> </script>

@ -16,8 +16,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineProps } from 'vue' import { computed, defineProps } from 'vue'
import { useRoute } from 'vitepress' import { useRoute } from 'vitepress'
import { withBase, isExternal } from '../utils' import { isExternal } from '../utils'
import type { DefaultTheme } from '../config' import type { DefaultTheme } from '../config'
import { useUrl } from '../composables/url'
import OutboundLink from './icons/OutboundLink.vue' import OutboundLink from './icons/OutboundLink.vue'
const { item } = defineProps<{ const { item } = defineProps<{
@ -35,6 +36,7 @@ const normalizePath = (path: string): string => {
return path return path
} }
const { withBase } = useUrl()
const route = useRoute() const route = useRoute()
const classes = computed(() => ({ const classes = computed(() => ({

@ -5,26 +5,15 @@
:aria-label="`${$site.title}, back to home`" :aria-label="`${$site.title}, back to home`"
> >
<img <img
v-if="$theme.logo" v-if="$themeConfig.logo"
class="logo" class="logo"
:src="withBase($theme.logo)" :src="$withBase($themeConfig.logo)"
alt="Logo" alt="Logo"
/> />
{{ $site.title }} {{ $site.title }}
</a> </a>
</template> </template>
<script lang="ts">
import { defineComponent } from 'vue'
import { withBase } from '../utils'
export default defineComponent({
setup() {
return { withBase }
}
})
</script>
<style scoped> <style scoped>
.nav-bar-title { .nav-bar-title {
font-size: 1.3rem; font-size: 1.3rem;

@ -2,13 +2,13 @@
<div v-if="hasLinks" class="next-and-prev-link"> <div v-if="hasLinks" class="next-and-prev-link">
<div class="container"> <div class="container">
<div class="prev"> <div class="prev">
<a v-if="prev" class="link" :href="withBase(prev.link)"> <a v-if="prev" class="link" :href="$withBase(prev.link)">
<ArrowLeft class="icon icon-prev" /> <ArrowLeft class="icon icon-prev" />
<span class="text">{{ prev.text }}</span> <span class="text">{{ prev.text }}</span>
</a> </a>
</div> </div>
<div class="next"> <div class="next">
<a v-if="next" class="link" :href="withBase(next.link)"> <a v-if="next" class="link" :href="$withBase(next.link)">
<span class="text">{{ next.text }}</span> <span class="text">{{ next.text }}</span>
<ArrowRight class="icon icon-next" /> <ArrowRight class="icon icon-next" />
</a> </a>
@ -18,7 +18,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { withBase } from '../utils'
import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks' import { useNextAndPrevLinks } from '../composables/nextAndPrevLinks'
import ArrowLeft from './icons/ArrowLeft.vue' import ArrowLeft from './icons/ArrowLeft.vue'
import ArrowRight from './icons/ArrowRight.vue' import ArrowRight from './icons/ArrowRight.vue'

@ -1,13 +1,13 @@
<template functional> <template functional>
<svg <svg
class="icon" class="icon outbound"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
aria-hidden="true" aria-hidden="true"
x="0px" x="0px"
y="0px" y="0px"
viewBox="0 0 100 100" viewBox="0 0 100 100"
width="16" width="15"
height="16" height="15"
> >
<path <path
fill="currentColor" fill="currentColor"
@ -21,11 +21,11 @@
</template> </template>
<style> <style>
.icon { .icon.outbound {
position: relative; position: relative;
top: -1px; top: -1px;
display: inline-block; display: inline-block;
color: #aaa;
vertical-align: middle; vertical-align: middle;
color: var(--c-text-lighter);
} }
</style> </style>

@ -0,0 +1,14 @@
import { useSiteData } from 'vitepress'
import { joinPath } from '/@app/utils'
export function useUrl() {
const site = useSiteData()
function withBase(path: string): string {
return joinPath(site.value.base, path)
}
return {
withBase
}
}

@ -20,6 +20,7 @@ div[class*='language-'] {
position: relative; position: relative;
margin: 1rem -1.5rem; margin: 1rem -1.5rem;
background-color: var(--code-bg-color); background-color: var(--code-bg-color);
overflow-x: auto;
} }
li > div[class*='language-'] { li > div[class*='language-'] {

@ -4,6 +4,7 @@
margin: 1rem 0; margin: 1rem 0;
border-left: .5rem solid; border-left: .5rem solid;
padding: .1rem 1.5rem; padding: .1rem 1.5rem;
overflow-x: auto;
} }
.custom-block.tip { .custom-block.tip {

@ -86,6 +86,7 @@ h2 {
padding-bottom: .3rem; padding-bottom: .3rem;
line-height: 1.25; line-height: 1.25;
font-size: 1.65rem; font-size: 1.65rem;
overflow-x: auto;
} }
h2 + h3 { h2 + h3 {

@ -13,6 +13,7 @@
--c-text-light-1: #2c3e50; --c-text-light-1: #2c3e50;
--c-text-light-2: #476582; --c-text-light-2: #476582;
--c-text-light-3: #90a4b7;
--c-brand: #3eaf7c; --c-brand: #3eaf7c;
--c-brand-light: #4abf8a; --c-brand-light: #4abf8a;
@ -46,6 +47,7 @@
--c-text: var(--c-text-light-1); --c-text: var(--c-text-light-1);
--c-text-light: var(--c-text-light-2); --c-text-light: var(--c-text-light-2);
--c-text-lighter: var(--c-text-light-3);
--c-bg: var(--c-white); --c-bg: var(--c-white);

@ -1,4 +1,4 @@
import { useSiteData, Route } from 'vitepress' import { Route } from 'vitepress'
export const hashRE = /#.*$/ export const hashRE = /#.*$/
export const extRE = /(index)?\.(md|html)$/ export const extRE = /(index)?\.(md|html)$/
@ -13,10 +13,6 @@ export function isArray(value: any): value is any[] {
return Array.isArray(value) return Array.isArray(value)
} }
export function withBase(path: string) {
return (useSiteData().value.base + path).replace(/\/+/g, '/')
}
export function isExternal(path: string): boolean { export function isExternal(path: string): boolean {
return outboundRE.test(path) return outboundRE.test(path)
} }

@ -3,7 +3,7 @@ import matter from 'gray-matter'
import LRUCache from 'lru-cache' import LRUCache from 'lru-cache'
import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown' import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown'
import { deeplyParseHeader } from './utils/parseHeader' import { deeplyParseHeader } from './utils/parseHeader'
import { PageData } from '../../types/shared' import { PageData, HeadConfig } from '../../types/shared'
const debug = require('debug')('vitepress:md') const debug = require('debug')('vitepress:md')
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 }) const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })
@ -41,6 +41,7 @@ export function createMarkdownToVueRenderFn(
// inject page data // inject page data
const pageData: PageData = { const pageData: PageData = {
title: inferTitle(frontmatter, content), title: inferTitle(frontmatter, content),
description: inferDescription(frontmatter),
frontmatter, frontmatter,
headers: data.headers, headers: data.headers,
relativePath: file.replace(/\\/g, '/'), relativePath: file.replace(/\\/g, '/'),
@ -106,3 +107,26 @@ const inferTitle = (frontmatter: any, content: string) => {
} }
return '' return ''
} }
const inferDescription = (frontmatter: Record<string, any>) => {
if (!frontmatter.head) {
return ''
}
return getHeadMetaContent(frontmatter.head, 'description') || ''
}
const getHeadMetaContent = (
head: HeadConfig[],
name: string
): string | undefined => {
if (!head || !head.length) {
return undefined
}
const meta = head.find(([tag, attrs = {}]) => {
return tag === 'meta' && attrs.name === name && attrs.content
})
return meta && meta[1].content
}

9
types/shared.d.ts vendored

@ -1,4 +1,4 @@
// types shared between server and client. // types shared between server and client
export interface LocaleConfig { export interface LocaleConfig {
lang: string lang: string
@ -10,10 +10,10 @@ export interface LocaleConfig {
} }
export interface SiteData<ThemeConfig = any> { export interface SiteData<ThemeConfig = any> {
base: string
lang: string lang: string
title: string title: string
description: string description: string
base: string
head: HeadConfig[] head: HeadConfig[]
themeConfig: ThemeConfig themeConfig: ThemeConfig
locales: Record<string, LocaleConfig> locales: Record<string, LocaleConfig>
@ -24,10 +24,11 @@ export type HeadConfig =
| [string, Record<string, string>, string] | [string, Record<string, string>, string]
export interface PageData { export interface PageData {
relativePath: string
title: string title: string
frontmatter: Record<string, any> description: string
headers: Header[] headers: Header[]
relativePath: string frontmatter: Record<string, any>
lastUpdated: number lastUpdated: number
} }

Loading…
Cancel
Save