pull/20/head
Evan You 5 years ago
parent 796a1a6730
commit 255a2c4853

@ -22,15 +22,15 @@ export default {
<style>
.debug {
position: fixed;
z-index: 999;
cursor: pointer;
top: 0;
bottom: 0;
right: 0;
width: 40px;
height: 25px;
width: 50px;
height: 20px;
padding: 5px;
overflow: hidden;
color: #eeeeee;
margin-top: -15px;
transition: all .15s ease;
background-color: rgba(0,0,0,0.85);
}
@ -46,5 +46,6 @@ export default {
.debug pre {
font-family: Hack, monospace;
font-size: 13px;
margin: 0;
}
</style>

@ -1,7 +1,6 @@
import serialized from '@siteData'
import { ref, readonly, Ref } from 'vue'
import { SiteData } from '../../../../types/shared'
import { hot } from 'vite/hmr'
const parse = (data: string) => readonly(JSON.parse(data) as SiteData)
@ -12,8 +11,8 @@ export function useSiteData() {
}
// hmr
if (__DEV__) {
hot.accept('/@siteData', (m) => {
if (import.meta.hot) {
import.meta.hot.acceptDeps('/@siteData', (m) => {
siteDataRef.value = parse(m.default)
})
}

@ -6,7 +6,6 @@ import { pageDataSymbol } from './composables/pageData'
import { Content } from './components/Content'
import Debug from './components/Debug.vue'
import Theme from '/@theme/index'
import { hot } from 'vite/hmr'
import { inBrowser, pathToFile } from './utils'
const NotFound = Theme.NotFound || (() => '404 Not Found')
@ -21,9 +20,9 @@ export function createApp() {
useUpdateHead(pageDataRef)
}
if (__DEV__ && inBrowser) {
if (import.meta.hot) {
// hot reload pageData
hot.on('vitepress:pageData', (data) => {
import.meta.hot.on('vitepress:pageData', (data) => {
if (
data.path.replace(/(\bindex)?\.md$/, '') ===
location.pathname.replace(/(\bindex)?\.html$/, '')
@ -87,6 +86,11 @@ export function createApp() {
get() {
return pageDataRef.value
}
},
$theme: {
get() {
return siteDataRef.value.themeConfig
}
}
})

@ -96,18 +96,19 @@ export function createRouter(
protocol === currentUrl.protocol &&
hostname === currentUrl.hostname
) {
e.preventDefault()
if (pathname === currentUrl.pathname) {
// smooth scroll bewteen hash anchors in the same page
if (hash !== currentUrl.hash) {
e.preventDefault()
// calculate the offset based on app's offset
const pageOffset = document.getElementById('app')!.offsetTop
window.scrollTo({
left: 0,
top: link.offsetTop,
top: link.offsetTop - pageOffset - 15,
behavior: 'smooth'
})
}
} else {
e.preventDefault()
go(href)
}
}

@ -17,11 +17,12 @@ export function pathToFile(path: string): string {
// the path conversion scheme.
// /foo/bar.html -> ./foo_bar.md
if (inBrowser) {
pagePath = pagePath.slice(__BASE__.length).replace(/\//g, '_') + '.md'
const base = process.env.BASE_URL
pagePath = pagePath.slice(base.length).replace(/\//g, '_') + '.md'
// client production build needs to account for page hash, which is
// injected directly in the page's html
const pageHash = __VP_HASH_MAP__[pagePath]
pagePath = `${__BASE__}_assets/${pagePath}.${pageHash}.js`
pagePath = `${base}_assets/${pagePath}.${pageHash}.js`
} else {
// ssr build uses much simpler name mapping
pagePath = `./${pagePath.slice(1).replace(/\//g, '_')}.md.js`

@ -1,6 +1,9 @@
declare const __DEV__: boolean
declare const __BASE__: string
declare const __VP_HASH_MAP__: Record<string, string>
declare const process: {
env: Record<string, string>
}
declare const require: (id: string) => any
declare module '*.vue' {
import { ComponentOptions } from 'vue'

@ -26,61 +26,3 @@ export default {
}
}
</script>
<style>
body {
margin: 0;
}
* {
box-sizing: border-box;
}
.theme {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial,
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
--border-color: rgb(226, 232, 240);
--header-height: 4rem;
--sidebar-width: 18rem;
--text-color: #2c3e50;
--accent-color: #3eaf7c;
color: var(--text-color);
}
header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background-color: #fff;
border-bottom: 1px solid var(--border-color);
z-index: 4;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.5rem;
}
aside {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: var(--sidebar-width);
padding-top: calc(var(--header-height) + 1.5rem);
border-right: 1px solid var(--border-color);
background-color: #fff;
z-index: 3;
overflow-y: auto;
}
main {
margin-top: var(--header-height);
margin-left: var(--sidebar-width);
}
a {
text-decoration: none;
}
</style>

@ -0,0 +1,26 @@
<template>
<div class="theme">
<h1>404</h1>
<blockquote>{{ getMsg() }}</blockquote>
<a :href="$site.base" aria-label="go to home">
Take me home.
</a>
</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 {
setup: () => ({
getMsg() {
return msgs[Math.floor(Math.random() * msgs.length)]
}
})
}
</script>

@ -1,19 +1,94 @@
<template>
<a
class="site-title"
class="title"
:aria-label="$site.title + ', back to home'"
:href="$site.base"
>{{ $site.title }}</a
>
<ul class="nav-links">
<li><a href="/hello/index">hello</a></li>
</ul>
<img
class="logo"
v-if="$theme.logo"
:src="withBase($theme.logo)"
alt="logo"
/>
<span>{{ $site.title }}</span>
</a>
<nav class="nav-links" v-if="navData">
<a
class="nav-link"
v-for="{ text, link, target, rel, ariaLabel } of navData"
:class="{ active: isActiveLink(link) }"
:href="withBase(link)"
:target="target"
:rel="rel"
:aria-label="ariaLabel"
>{{ text }}</a
>
</nav>
</template>
<script>
import { computed } from 'vue'
import { useSiteData, useRoute } from 'vitepress'
import { withBase } from '../utils'
const normalizePath = (path) => {
path = path
.replace(/#.*$/, '')
.replace(/\?.*$/, '')
.replace(/\.html$/, '')
if (path.endsWith('/')) {
path += 'index'
}
return path
}
export default {
setup() {
const route = useRoute()
const isActiveLink = (link) => {
return normalizePath(withBase(link)) === normalizePath(route.path)
}
return {
withBase,
isActiveLink,
// use computed in dev for hot reload
navData: __DEV__
? computed(() => useSiteData().value.themeConfig.nav)
: useSiteData().value.themeConfig.nav
}
}
}
</script>
<style>
.site-title {
.title {
font-size: 1.3rem;
font-weight: 600;
color: var(--text-color);
}
.logo {
margin-right: 0.75rem;
height: 1.3rem;
vertical-align: bottom;
}
.nav-links {
list-style-type: none;
}
.nav-link {
color: var(--text-color);
margin-left: 1.5rem;
font-weight: 500;
display: inline-block;
height: 1.75rem;
line-height: 1.75rem;
}
.nav-link:hover,
.nav-link.active {
border-bottom: 2px solid var(--accent-color);
}
</style>

@ -1,5 +1,7 @@
<template>
<Content class="content" />
<div class="content">
<Content />
</div>
</template>
<style>

@ -0,0 +1,77 @@
export namespace DefaultTheme {
export interface Config {
logo?: string
nav?: NavItem[] | false
sidebar?: SideBarConfig | MultiSideBarConfig
search?: SearchConfig | false
editLink?: EditLinkConfig | false
lastUpdated?: string | boolean
prevLink?: boolean
nextLink?: boolean
}
// navbar --------------------------------------------------------------------
export type NavItem = NavItemWithLink | NavItemWithChildren
export interface NavItemWithLink extends NavItemBase {
link: string
}
export interface NavItemWithChildren extends NavItemBase {
items: NavItem[]
}
export interface NavItemBase {
text: string
target?: string
rel?: string
ariaLabel?: string
}
// sidebar -------------------------------------------------------------------
export type SideBarConfig = SideBarItem[] | 'auto' | false
export interface MultiSideBarConfig {
[path: string]: SideBarConfig
}
export type SideBarItem = string | [string, string] | SideBarGroup
export interface SideBarGroup {
title: string
path?: string
/**
* @default false
*/
collapsable?: boolean
children: SideBarItem[]
}
// search --------------------------------------------------------------------
export interface SearchConfig {
/**
* @default 5
*/
maxSuggestions?: number
/**
* @default ''
*/
placeholder?: string
algolia?: {
apiKey: string
indexName: string
}
}
// edit link -----------------------------------------------------------------
export interface EditLinkConfig {
repo: string
dir?: string
branch?: string
text?: string
}
}

@ -1,8 +1,11 @@
import './layout.css'
import Layout from './Layout.vue'
import NotFound from './NotFound.vue'
import { Theme } from '../app/theme'
const theme: Theme = {
Layout
Layout,
NotFound
}
export default theme

@ -0,0 +1,59 @@
body {
margin: 0;
}
* {
box-sizing: border-box;
}
.theme {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial,
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
--border-color: rgb(226, 232, 240);
--header-height: 4rem;
--sidebar-width: 18rem;
--text-color: #2c3e50;
--accent-color: #3eaf7c;
color: var(--text-color);
}
header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
background-color: #fff;
border-bottom: 1px solid var(--border-color);
z-index: 4;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.75rem 0 1.5rem;
}
aside {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: var(--sidebar-width);
padding-top: calc(var(--header-height) + 1.5rem);
border-right: 1px solid var(--border-color);
background-color: #fff;
z-index: 3;
overflow-y: auto;
}
main {
margin-top: var(--header-height);
margin-left: var(--sidebar-width);
}
a {
text-decoration: none;
}
h1, h2, h3, h4, h5, h6, strong, b {
font-weight: 600;
}

@ -0,0 +1,5 @@
import { useSiteData } from 'vitepress'
export function withBase(path: string) {
return (useSiteData().value.base + path).replace(/\/+/g, '/')
}

@ -5,6 +5,7 @@
"outDir": "../../dist/client",
"module": "esnext",
"lib": ["ESNext", "DOM"],
"types": ["vite/hmr"],
"paths": {
"/@app/*": ["app/*"],
"/@theme/*": ["theme-default/*"],

@ -43,20 +43,21 @@ export async function bundle(
const VitePressPlugin: Plugin = {
name: 'vitepress',
resolveId(id) {
if (id === SITE_DATA_REQUEST_PATH || id.endsWith('.md.vue')) {
if (id === SITE_DATA_REQUEST_PATH) {
return id
}
},
async load(id) {
if (id === SITE_DATA_REQUEST_PATH) {
return `export default ${JSON.stringify(JSON.stringify(config.site))}`
}
// compile md into vue src for .md.vue virtual files
// compile md into vue src
if (id.endsWith('.md')) {
const filePath = id.replace(/\.vue$/, '')
const content = await fs.readFile(filePath, 'utf-8')
const lastUpdated = (await fs.stat(filePath)).mtimeMs
const { vueSrc } = markdownToVue(content, filePath, lastUpdated)
const content = await fs.readFile(id, 'utf-8')
// TODO use git timestamp
const lastUpdated = (await fs.stat(id)).mtimeMs
const { vueSrc } = markdownToVue(content, id, lastUpdated)
return vueSrc
}
},

@ -67,29 +67,20 @@ export const createMarkdownRenderer = (
// 3rd party plugins
.use(emoji)
.use(
anchor,
Object.assign(
{
slugify,
permalink: true,
permalinkBefore: true,
permalinkSymbol: '#'
},
options.anchor
)
)
.use(
toc,
Object.assign(
{
slugify,
includeLevel: [2, 3],
format: parseHeader
},
options.toc
)
)
.use(anchor, {
slugify,
permalink: true,
permalinkBefore: true,
permalinkSymbol: '#',
permalinkAttrs: () => ({ 'aria-hidden': true }),
...options.anchor
})
.use(toc, {
slugify,
includeLevel: [2, 3],
format: parseHeader,
...options.toc
})
// apply user config
if (options.config) {

3
types/index.d.ts vendored

@ -1,3 +1,4 @@
export * from './shared'
export * from '../dist/client/app/exports'
export * from '../dist/node/index'
export * from '../dist/client/app/exports'
export * from '../dist/client/theme-default/config'

Loading…
Cancel
Save