Merge branch 'main' into doc-zh

pull/2249/head
vanch 3 years ago
commit 46551ea2b5

@ -1,3 +1,34 @@
# [1.0.0-beta.1](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.76...v1.0.0-beta.1) (2023-05-22)
### Bug Fixes
* **config:** set scrollOffset to 0 is not effect ([#2395](https://github.com/vuejs/vitepress/issues/2395)) ([8153f23](https://github.com/vuejs/vitepress/commit/8153f23c901a6200661813e65f0d8eb602ad46da))
* **theme:** features section layout is not consistent ([#2382](https://github.com/vuejs/vitepress/issues/2382)) ([26f21d9](https://github.com/vuejs/vitepress/commit/26f21d95dfbd671477d425e6b8ac5b0172a846ac))
* **theme:** some global properties in localSearch are missing ([#2396](https://github.com/vuejs/vitepress/issues/2396)) ([4896811](https://github.com/vuejs/vitepress/commit/489681117f46a803704b6ec80546a5e787e19df2))
* **theme:** custom target and rel in navbar links for mobile is missing ([#2400](https://github.com/vuejs/vitepress/issues/2400)) ([f364a5d](https://github.com/vuejs/vitepress/commit/f364a5d1d3c066c9728beb5d07576d6cb4b0640d))
# [1.0.0-alpha.76](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.75...v1.0.0-alpha.76) (2023-05-18)
### Bug Fixes
* **a11y:** mobile and theme switcher ([#2354](https://github.com/vuejs/vitepress/issues/2354)) ([d6c0985](https://github.com/vuejs/vitepress/commit/d6c0985002ee792b1e8e052f71cdd6bd72c315ad))
* **build:** uniform handling of windows slash in localSearchPlugin ([#2358](https://github.com/vuejs/vitepress/issues/2358)) ([b31933f](https://github.com/vuejs/vitepress/commit/b31933fbdd7aabfe080234407153aefa8f6a3f30))
* hmr when `base` is set ([#2375](https://github.com/vuejs/vitepress/issues/2375)) ([484ff5d](https://github.com/vuejs/vitepress/commit/484ff5dd4bd2e5c2d5168437895d400a39f2bfa8))
* **theme:** don't update opacity on hover ([#2326](https://github.com/vuejs/vitepress/issues/2326)) ([35f8b89](https://github.com/vuejs/vitepress/commit/35f8b896372e75e62882df613a49e8945e7bc832))
### Features
* **cli:** add shortcuts ([#2353](https://github.com/vuejs/vitepress/issues/2353)) ([97065ce](https://github.com/vuejs/vitepress/commit/97065cefc22e4772c0295c5ad23a87eea286f46b))
* **theme:** add focus trap to local search dialog ([#2324](https://github.com/vuejs/vitepress/issues/2324)) ([2f482af](https://github.com/vuejs/vitepress/commit/2f482afaabdb4206b87e2453d0099257693c4653))
* **theme:** open search box on pressing slash too ([#2328](https://github.com/vuejs/vitepress/issues/2328)) ([c20bd28](https://github.com/vuejs/vitepress/commit/c20bd283319158135e2d850485970dfc5fe82812))
# [1.0.0-alpha.75](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.74...v1.0.0-alpha.75) (2023-04-30) # [1.0.0-alpha.75](https://github.com/vuejs/vitepress/compare/v1.0.0-alpha.74...v1.0.0-alpha.75) (2023-04-30)

@ -1,4 +1,4 @@
# VitePress (alpha) 📝💨 # VitePress (beta) 📝💨
[![Test](https://github.com/vuejs/vitepress/workflows/Test/badge.svg)](https://github.com/vuejs/vitepress/actions) [![Test](https://github.com/vuejs/vitepress/workflows/Test/badge.svg)](https://github.com/vuejs/vitepress/actions)
[![npm](https://img.shields.io/npm/v/vitepress)](https://www.npmjs.com/package/vitepress) [![npm](https://img.shields.io/npm/v/vitepress)](https://www.npmjs.com/package/vitepress)
@ -8,7 +8,7 @@
VitePress is [VuePress](https://vuepress.vuejs.org)' spiritual successor, built on top of [vite](https://github.com/vitejs/vite). VitePress is [VuePress](https://vuepress.vuejs.org)' spiritual successor, built on top of [vite](https://github.com/vitejs/vite).
Currently, it's in the `alpha` stage. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases. Currently, it is in the `beta` stage. It is already suitable for out-of-the-box documentation use. There still might be issues, however, we do not plan to introduce any breaking changes from here on until the stable release.
## Documentation ## Documentation

@ -18,7 +18,7 @@ All **static** path references, including absolute paths, should be based on you
## The Public Directory ## The Public Directory
Sometimes you may need to provide static assets that are not directly referenced in any of your Markdown or theme components, or you may want to serve certain files with the original filename. Examples of such files include `robot.txt`, favicons, and PWA icons. Sometimes you may need to provide static assets that are not directly referenced in any of your Markdown or theme components, or you may want to serve certain files with the original filename. Examples of such files include `robots.txt`, favicons, and PWA icons.
You can place these files in the `public` directory under the [source directory](./routing#source-directory). For example, if your project root is `./docs` and using default source directory location, then your public directory will be `./docs/public`. You can place these files in the `public` directory under the [source directory](./routing#source-directory). For example, if your project root is `./docs` and using default source directory location, then your public directory will be `./docs/public`.

@ -36,4 +36,8 @@ export default {
} }
``` ```
::: warning
Only inline elements can be used in `message` and `copyright` as they are rendered inside a `<p>` element. If you want to add block elements, consider using [`layout-bottom`](../guide/extending-default-theme#layout-slots) slot instead.
:::
Note that footer will not be displayed when the [SideBar](./default-theme-sidebar) is visible. Note that footer will not be displayed when the [SideBar](./default-theme-sidebar) is visible.

@ -384,7 +384,7 @@ export default {
// adjust how header anchors are generated, // adjust how header anchors are generated,
// useful for integrating with tools that use different conventions // useful for integrating with tools that use different conventions
anchors: { anchor: {
slugify(str) { slugify(str) {
return encodeURIComponent(str) return encodeURIComponent(str)
} }

@ -1,9 +1,9 @@
{ {
"name": "vitepress", "name": "vitepress",
"version": "1.0.0-alpha.75", "version": "1.0.0-beta.1",
"description": "Vite & Vue powered static site generator", "description": "Vite & Vue powered static site generator",
"type": "module", "type": "module",
"packageManager": "pnpm@8.3.1", "packageManager": "pnpm@8.5.1",
"main": "dist/node/index.js", "main": "dist/node/index.js",
"types": "types/index.d.ts", "types": "types/index.d.ts",
"exports": { "exports": {
@ -23,7 +23,7 @@
"default": "./dist/client/theme-default/index.js" "default": "./dist/client/theme-default/index.js"
}, },
"./theme-without-fonts": { "./theme-without-fonts": {
"types": "./theme.d.ts", "types": "./theme-without-fonts.d.ts",
"default": "./dist/client/theme-default/without-fonts.js" "default": "./dist/client/theme-default/without-fonts.js"
} }
}, },
@ -36,7 +36,8 @@
"types", "types",
"template", "template",
"client.d.ts", "client.d.ts",
"theme.d.ts" "theme.d.ts",
"theme-without-fonts.d.ts"
], ],
"repository": { "repository": {
"type": "git", "type": "git",
@ -87,17 +88,19 @@
"docs-preview": "node ./bin/vitepress preview docs" "docs-preview": "node ./bin/vitepress preview docs"
}, },
"dependencies": { "dependencies": {
"@docsearch/css": "^3.3.4", "@docsearch/css": "^3.4.0",
"@docsearch/js": "^3.3.4", "@docsearch/js": "^3.4.0",
"@vitejs/plugin-vue": "^4.2.1", "@vitejs/plugin-vue": "^4.2.3",
"@vue/devtools-api": "^6.5.0", "@vue/devtools-api": "^6.5.0",
"@vueuse/core": "^10.1.0", "@vueuse/core": "^10.1.2",
"@vueuse/integrations": "^10.1.2",
"body-scroll-lock": "4.0.0-beta.0", "body-scroll-lock": "4.0.0-beta.0",
"focus-trap": "^7.4.3",
"mark.js": "8.11.1", "mark.js": "8.11.1",
"minisearch": "^6.0.1", "minisearch": "^6.1.0",
"shiki": "^0.14.2", "shiki": "^0.14.2",
"vite": "^4.3.3", "vite": "^4.3.9",
"vue": "^3.2.47" "vue": "^3.3.4"
}, },
"devDependencies": { "devDependencies": {
"@clack/prompts": "^0.6.3", "@clack/prompts": "^0.6.3",
@ -109,14 +112,14 @@
"@mdit-vue/plugin-toc": "^0.12.0", "@mdit-vue/plugin-toc": "^0.12.0",
"@mdit-vue/shared": "^0.12.0", "@mdit-vue/shared": "^0.12.0",
"@rollup/plugin-alias": "^5.0.0", "@rollup/plugin-alias": "^5.0.0",
"@rollup/plugin-commonjs": "^24.1.0", "@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-json": "^6.0.0", "@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.2", "@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-replace": "^5.0.2",
"@types/body-scroll-lock": "^3.1.0", "@types/body-scroll-lock": "^3.1.0",
"@types/compression": "^1.7.2", "@types/compression": "^1.7.2",
"@types/cross-spawn": "^6.0.2", "@types/cross-spawn": "^6.0.2",
"@types/debug": "^4.1.7", "@types/debug": "^4.1.8",
"@types/escape-html": "^1.0.2", "@types/escape-html": "^1.0.2",
"@types/fs-extra": "^11.0.1", "@types/fs-extra": "^11.0.1",
"@types/mark.js": "^8.11.8", "@types/mark.js": "^8.11.8",
@ -126,15 +129,14 @@
"@types/markdown-it-emoji": "^2.0.2", "@types/markdown-it-emoji": "^2.0.2",
"@types/micromatch": "^4.0.2", "@types/micromatch": "^4.0.2",
"@types/minimist": "^1.2.2", "@types/minimist": "^1.2.2",
"@types/node": "^18.16.3", "@types/node": "^20.2.5",
"@types/prompts": "^2.4.4", "@types/prompts": "^2.4.4",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"compression": "^1.7.4", "compression": "^1.7.4",
"conventional-changelog-cli": "^2.2.2", "conventional-changelog-cli": "^2.2.2",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.3",
"debug": "^4.3.4", "debug": "^4.3.4",
"enquirer": "^2.3.6", "esbuild": "^0.17.19",
"esbuild": "^0.17.18",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"execa": "^7.1.1", "execa": "^7.1.1",
"fast-glob": "^3.2.12", "fast-glob": "^3.2.12",
@ -153,27 +155,27 @@
"minimist": "^1.2.8", "minimist": "^1.2.8",
"nanoid": "^4.0.2", "nanoid": "^4.0.2",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"ora": "^6.3.0", "ora": "^6.3.1",
"path-to-regexp": "^6.2.1", "path-to-regexp": "^6.2.1",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"pkg-dir": "^7.0.0", "pkg-dir": "^7.0.0",
"playwright-chromium": "^1.33.0", "playwright-chromium": "^1.34.3",
"polka": "1.0.0-next.22", "polka": "1.0.0-next.22",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"punycode": "^2.3.0", "punycode": "^2.3.0",
"rimraf": "^5.0.0", "rimraf": "^5.0.1",
"rollup": "^3.21.1", "rollup": "^3.23.0",
"rollup-plugin-dts": "^5.3.0", "rollup-plugin-dts": "^5.3.0",
"rollup-plugin-esbuild": "^5.0.0", "rollup-plugin-esbuild": "^5.0.0",
"semver": "^7.5.0", "semver": "^7.5.1",
"shiki-processor": "^0.1.3", "shiki-processor": "^0.1.3",
"simple-git-hooks": "^2.8.1", "simple-git-hooks": "^2.8.1",
"sirv": "^2.0.3", "sirv": "^2.0.3",
"supports-color": "^9.3.1", "supports-color": "^9.3.1",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vitest": "^0.30.1", "vitest": "^0.31.1",
"vue-tsc": "^1.6.1", "vue-tsc": "^1.6.5",
"wait-on": "^7.0.1" "wait-on": "^7.0.1"
}, },
"pnpm": { "pnpm": {

File diff suppressed because it is too large Load Diff

@ -1,13 +1,15 @@
import { readFileSync, writeFileSync } from 'fs' import { readFileSync, writeFileSync } from 'fs'
import { resolve } from 'path' import { resolve } from 'path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import { createRequire } from 'module'
import c from 'picocolors' import c from 'picocolors'
import prompts from 'prompts' import prompts from 'prompts'
import { execa } from 'execa' import { execa } from 'execa'
import semver from 'semver' import semver from 'semver'
import pkg from '../package.json' assert { type: 'json' }
const { version: currentVersion } = pkg const { version: currentVersion } = createRequire(import.meta.url)(
'../package.json'
)
const { inc: _inc, valid } = semver const { inc: _inc, valid } = semver
const versionIncrements = ['patch', 'minor', 'major'] const versionIncrements = ['patch', 'minor', 'major']

@ -303,6 +303,8 @@ function handleHMR(route: Route): void {
function shouldHotReload(payload: PageDataPayload): boolean { function shouldHotReload(payload: PageDataPayload): boolean {
const payloadPath = payload.path.replace(/(\bindex)?\.md$/, '') const payloadPath = payload.path.replace(/(\bindex)?\.md$/, '')
const locationPath = location.pathname.replace(/(\bindex)?\.html$/, '') const locationPath = location.pathname
.replace(/(\bindex)?\.html$/, '')
.slice(siteDataRef.value.base.length - 1)
return payloadPath === locationPath return payloadPath === locationPath
} }

@ -26,7 +26,7 @@ const grid = computed(() => {
return 'grid-3' return 'grid-3'
} else if (length % 3 === 0) { } else if (length % 3 === 0) {
return 'grid-6' return 'grid-6'
} else if (length % 2 === 0) { } else if (length > 3) {
return 'grid-4' return 'grid-4'
} }
}) })

@ -9,6 +9,7 @@ import {
useScrollLock, useScrollLock,
useSessionStorage useSessionStorage
} from '@vueuse/core' } from '@vueuse/core'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import Mark from 'mark.js/src/vanilla.js' import Mark from 'mark.js/src/vanilla.js'
import MiniSearch, { type SearchResult } from 'minisearch' import MiniSearch, { type SearchResult } from 'minisearch'
import { useRouter } from 'vitepress' import { useRouter } from 'vitepress'
@ -26,11 +27,10 @@ import {
type Ref type Ref
} from 'vue' } from 'vue'
import type { ModalTranslations } from '../../../../types/local-search' import type { ModalTranslations } from '../../../../types/local-search'
import { dataSymbol } from '../../app/data'
import { pathToFile } from '../../app/utils' import { pathToFile } from '../../app/utils'
import { slash } from '../../shared'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { createTranslate } from '../support/translation' import { createTranslate } from '../support/translation'
import { dataSymbol } from '../../app/data'
defineProps<{ defineProps<{
placeholder: string placeholder: string
@ -64,6 +64,12 @@ interface Result {
} }
const vitePressData = useData() const vitePressData = useData()
const { activate } = useFocusTrap(el, {
immediate: true,
allowOutsideClick: true,
clickOutsideDeactivates: true,
escapeDeactivates: true
})
const { localeIndex, theme } = vitePressData const { localeIndex, theme } = vitePressData
const searchIndex = computedAsync(async () => const searchIndex = computedAsync(async () =>
markRaw( markRaw(
@ -84,14 +90,14 @@ const searchIndex = computedAsync(async () =>
const disableQueryPersistence = computed(() => { const disableQueryPersistence = computed(() => {
return ( return (
theme.value.search?.provider === 'local' && theme.value.search?.provider === 'local' &&
theme.value.search.options?.disableQueryPersistence === true theme.value.search.options?.disableQueryPersistence === true
) )
}) })
const filterText = disableQueryPersistence.value const filterText = disableQueryPersistence.value
? ref('') ? ref('')
: useSessionStorage('vitepress:local-search-filter', '') : useSessionStorage('vitepress:local-search-filter', '')
const showDetailedList = useLocalStorage( const showDetailedList = useLocalStorage(
'vitepress:local-search-detailed-list', 'vitepress:local-search-detailed-list',
@ -149,23 +155,36 @@ debouncedWatch(
if (canceled) return if (canceled) return
const c = new Map<string, Map<string, string>>() const c = new Map<string, Map<string, string>>()
for (const { id, mod } of mods) { for (const { id, mod } of mods) {
const mapId = id.slice(0, id.indexOf('#'))
let map = c.get(mapId)
if (map) continue
else {
map = new Map()
c.set(mapId, map)
}
const comp = mod.default ?? mod const comp = mod.default ?? mod
if (comp?.render) { if (comp?.render) {
const app = createApp(comp) const app = createApp(comp)
// Silence warnings about missing components // Silence warnings about missing components
app.config.warnHandler = () => {} app.config.warnHandler = () => {}
app.provide(dataSymbol, vitePressData) app.provide(dataSymbol, vitePressData)
Object.defineProperties(app.config.globalProperties, {
$frontmatter: {
get() {
return vitePressData.frontmatter.value
}
},
$params: {
get() {
return vitePressData.page.value.params
}
}
})
const div = document.createElement('div') const div = document.createElement('div')
app.mount(div) app.mount(div)
const sections = div.innerHTML.split(headingRegex) const sections = div.innerHTML.split(headingRegex)
app.unmount() app.unmount()
sections.shift() sections.shift()
const mapId = id.slice(0, id.indexOf('#'))
let map = c.get(mapId)
if (!map) {
map = new Map()
c.set(mapId, map)
}
for (let i = 0; i < sections.length; i += 3) { for (let i = 0; i < sections.length; i += 3) {
const anchor = sections[i + 1] const anchor = sections[i + 1]
const html = sections[i + 2] const html = sections[i + 2]
@ -211,7 +230,7 @@ debouncedWatch(
) )
async function fetchExcerpt(id: string) { async function fetchExcerpt(id: string) {
const file = pathToFile(slash(id.slice(0, id.indexOf('#')))) const file = pathToFile(id.slice(0, id.indexOf('#')))
try { try {
return { id, mod: await import(/*@vite-ignore*/ file) } return { id, mod: await import(/*@vite-ignore*/ file) }
} catch (e) { } catch (e) {
@ -334,6 +353,7 @@ onMounted(() => {
body.value = document.body body.value = document.body
nextTick(() => { nextTick(() => {
isLocked.value = true isLocked.value = true
nextTick().then(() => activate())
}) })
}) })
@ -474,6 +494,7 @@ function formMarkRegex(terms: Set<string>) {
}" }"
:aria-label="[...p.titles, p.title].join(' > ')" :aria-label="[...p.titles, p.title].join(' > ')"
@mouseenter="!disableMouseOver && (selectedIndex = index)" @mouseenter="!disableMouseOver && (selectedIndex = index)"
@focusin="selectedIndex = index"
@click="$emit('close')" @click="$emit('close')"
> >
<div> <div>
@ -498,7 +519,7 @@ function formMarkRegex(terms: Set<string>) {
</div> </div>
<div v-if="showDetailedList" class="excerpt-wrapper"> <div v-if="showDetailedList" class="excerpt-wrapper">
<div v-if="p.text" class="excerpt"> <div v-if="p.text" class="excerpt" inert>
<div class="vp-doc" v-html="p.text" /> <div class="vp-doc" v-html="p.text" />
</div> </div>
<div class="excerpt-gradient-bottom" /> <div class="excerpt-gradient-bottom" />
@ -727,6 +748,7 @@ function formMarkRegex(terms: Set<string>) {
transition: none; transition: none;
line-height: 1rem; line-height: 1rem;
border: solid 2px var(--vp-local-search-result-border); border: solid 2px var(--vp-local-search-result-border);
outline: none;
} }
.result > div { .result > div {

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { isActive } from '../support/utils' import { isActive } from '../../shared'
import VPLink from './VPLink.vue' import VPLink from './VPLink.vue'
defineProps<{ defineProps<{

@ -175,6 +175,12 @@ const classes = computed(() => ({
} }
} }
@media (max-width: 768px) {
.content-body {
column-gap: 0.5rem;
}
}
.menu + .translations::before, .menu + .translations::before,
.menu + .appearance::before, .menu + .appearance::before,
.menu + .social-links::before, .menu + .social-links::before,

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { isActive } from '../support/utils' import { isActive } from '../../shared'
import VPFlyout from './VPFlyout.vue' import VPFlyout from './VPFlyout.vue'
defineProps<{ defineProps<{

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { isActive } from '../support/utils' import { isActive } from '../../shared'
import VPLink from './VPLink.vue' import VPLink from './VPLink.vue'
defineProps<{ defineProps<{

@ -61,9 +61,12 @@ onMounted(() => {
preconnect() preconnect()
const handleSearchHotKey = (e: KeyboardEvent) => { const handleSearchHotKey = (event: KeyboardEvent) => {
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) { if (
e.preventDefault() (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) ||
(!isEditingContent(event) && event.key === '/')
) {
event.preventDefault()
load() load()
remove() remove()
} }
@ -101,6 +104,18 @@ function poll() {
}, 16) }, 16)
} }
function isEditingContent(event: KeyboardEvent): boolean {
const element = event.target as HTMLElement
const tagName = element.tagName
return (
element.isContentEditable ||
tagName === 'INPUT' ||
tagName === 'SELECT' ||
tagName === 'TEXTAREA'
)
}
// Local search // Local search
const showSearch = ref(false) const showSearch = ref(false)
@ -112,6 +127,13 @@ if (__VP_LOCAL_SEARCH__) {
showSearch.value = true showSearch.value = true
} }
}) })
onKeyStroke('/', (event) => {
if (!isEditingContent(event)) {
event.preventDefault()
showSearch.value = true
}
})
} }
const metaKey = ref(`'Meta'`) const metaKey = ref(`'Meta'`)

@ -61,7 +61,7 @@ defineProps<{
align-items: center; align-items: center;
margin: 0; margin: 0;
padding: 0; padding: 0;
width: 32px; width: 48px;
height: 55px; height: 55px;
background: transparent; background: transparent;
transition: border-color 0.25s; transition: border-color 0.25s;

@ -35,10 +35,6 @@ const { currentLang } = useLangs()
transition: opacity 0.25s; transition: opacity 0.25s;
} }
.title:hover {
opacity: 0.6;
}
@media (min-width: 960px) { @media (min-width: 960px) {
.title { .title {
flex-shrink: 0; flex-shrink: 0;

@ -11,8 +11,7 @@ const { theme } = useData()
<template v-for="item in theme.nav" :key="item.text"> <template v-for="item in theme.nav" :key="item.text">
<VPNavScreenMenuLink <VPNavScreenMenuLink
v-if="'link' in item" v-if="'link' in item"
:text="item.text" :item="item"
:link="item.link"
/> />
<VPNavScreenMenuGroup <VPNavScreenMenuGroup
v-else v-else

@ -35,10 +35,7 @@ function toggle() {
<div :id="groupId" class="items"> <div :id="groupId" class="items">
<template v-for="item in items" :key="item.text"> <template v-for="item in items" :key="item.text">
<div v-if="'link' in item" :key="item.text" class="item"> <div v-if="'link' in item" :key="item.text" class="item">
<VPNavScreenMenuGroupLink <VPNavScreenMenuGroupLink :item="item" />
:text="item.text"
:link="item.link"
/>
</div> </div>
<div v-else class="group"> <div v-else class="group">

@ -1,18 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { DefaultTheme } from 'vitepress/theme'
import { inject } from 'vue' import { inject } from 'vue'
import VPLink from './VPLink.vue' import VPLink from './VPLink.vue'
defineProps<{ defineProps<{
text: string item: DefaultTheme.NavItemWithLink
link: string
}>() }>()
const closeScreen = inject('close-screen') as () => void const closeScreen = inject('close-screen') as () => void
</script> </script>
<template> <template>
<VPLink class="VPNavScreenMenuGroupLink" :href="link" @click="closeScreen"> <VPLink
{{ text }} class="VPNavScreenMenuGroupLink"
:href="item.link"
:target="item.target"
:rel="item.rel"
@click="closeScreen"
>
{{ item.text }}
</VPLink> </VPLink>
</template> </template>

@ -14,8 +14,7 @@ defineProps<{
<VPNavScreenMenuGroupLink <VPNavScreenMenuGroupLink
v-for="item in items" v-for="item in items"
:key="item.text" :key="item.text"
:text="item.text" :item="item"
:link="item.link"
/> />
</div> </div>
</template> </template>

@ -1,18 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { DefaultTheme } from 'vitepress/theme'
import { inject } from 'vue' import { inject } from 'vue'
import VPLink from './VPLink.vue' import VPLink from './VPLink.vue'
defineProps<{ defineProps<{
text: string item: DefaultTheme.NavItemWithLink
link: string
}>() }>()
const closeScreen = inject('close-screen') as () => void const closeScreen = inject('close-screen') as () => void
</script> </script>
<template> <template>
<VPLink class="VPNavScreenMenuLink" :href="link" @click="closeScreen"> <VPLink
{{ text }} class="VPNavScreenMenuLink"
:href="item.link"
:target="item.target"
:rel="item.rel"
@click="closeScreen"
>
{{ item.text }}
</VPLink> </VPLink>
</template> </template>

@ -59,10 +59,6 @@ function focusOnTargetAnchor({ target }: Event) {
clip-path: none; clip-path: none;
} }
.dark .VPSkipLink {
color: var(--vp-c-green);
}
@media (min-width: 1280px) { @media (min-width: 1280px) {
.VPSkipLink { .VPSkipLink {
top: 14px; top: 14px;

@ -1,14 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, watch } from 'vue' import { ref, onMounted, watch } from 'vue'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { APPEARANCE_KEY } from '../../shared' import { inBrowser, APPEARANCE_KEY } from '../../shared'
import VPSwitch from './VPSwitch.vue' import VPSwitch from './VPSwitch.vue'
import VPIconSun from './icons/VPIconSun.vue' import VPIconSun from './icons/VPIconSun.vue'
import VPIconMoon from './icons/VPIconMoon.vue' import VPIconMoon from './icons/VPIconMoon.vue'
const { site, isDark } = useData() const { site, isDark } = useData()
const checked = ref(false) const checked = ref(false)
const toggle = typeof localStorage !== 'undefined' ? useAppearance() : () => {} const toggle = inBrowser ? useAppearance() : () => {}
onMounted(() => { onMounted(() => {
checked.value = document.documentElement.classList.contains('dark') checked.value = document.documentElement.classList.contains('dark')
@ -75,16 +75,15 @@ watch(checked, (newIsDark) => {
</script> </script>
<template> <template>
<label title="toggle dark mode"> <VPSwitch
<VPSwitch title="toggle dark mode"
class="VPSwitchAppearance" class="VPSwitchAppearance"
:aria-checked="checked" :aria-checked="checked"
@click="toggle" @click="toggle"
> >
<VPIconSun class="sun" /> <VPIconSun class="sun" />
<VPIconMoon class="moon" /> <VPIconMoon class="moon" />
</VPSwitch> </VPSwitch>
</label>
</template> </template>
<style scoped> <style scoped>

@ -1,6 +1,6 @@
import { computed } from 'vue' import { computed } from 'vue'
import { useData } from './data' import { useData } from './data'
import { isActive } from '../support/utils' import { isActive } from '../../shared'
import { getSidebar, getFlatSideBarLinks } from '../support/sidebar' import { getSidebar, getFlatSideBarLinks } from '../support/sidebar'
export function usePrevNext() { export function usePrevNext() {

@ -10,7 +10,7 @@ import {
import { useMediaQuery } from '@vueuse/core' import { useMediaQuery } from '@vueuse/core'
import { useRoute } from 'vitepress' import { useRoute } from 'vitepress'
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { isActive } from '../support/utils' import { isActive } from '../../shared'
import { import {
hasActiveLink as containsActiveLink, hasActiveLink as containsActiveLink,
getSidebar, getSidebar,
@ -60,8 +60,7 @@ export function useSidebar() {
const hasAside = computed(() => { const hasAside = computed(() => {
if (frontmatter.value.layout === 'home') return false if (frontmatter.value.layout === 'home') return false
if (frontmatter.value.aside != null) return !!frontmatter.value.aside if (frontmatter.value.aside != null) return !!frontmatter.value.aside
if (theme.value.aside === false) return false return theme.value.aside !== false
return true
}) })
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value) const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)

@ -56,7 +56,7 @@
right: 8px; right: 8px;
bottom: -1px; bottom: -1px;
left: 8px; left: 8px;
z-index: 10; z-index: 1;
height: 1px; height: 1px;
content: ''; content: '';
background-color: transparent; background-color: transparent;

@ -1,5 +1,6 @@
import type { DefaultTheme } from 'vitepress/theme' import type { DefaultTheme } from 'vitepress/theme'
import { ensureStartingSlash, isActive } from './utils' import { ensureStartingSlash } from './utils'
import { isActive } from '../../shared'
export interface SidebarLink { export interface SidebarLink {
text: string text: string

@ -2,8 +2,6 @@ import { withBase } from 'vitepress'
import { useData } from '../composables/data' import { useData } from '../composables/data'
import { isExternal, PATHNAME_PROTOCOL_RE } from '../../shared' import { isExternal, PATHNAME_PROTOCOL_RE } from '../../shared'
export { isExternal, isActive } from '../../shared'
export function throttleAndDebounce(fn: () => void, delay: number): () => void { export function throttleAndDebounce(fn: () => void, delay: number): () => void {
let timeoutId: NodeJS.Timeout let timeoutId: NodeJS.Timeout
let called = false let called = false

@ -165,13 +165,15 @@ export async function bundle(
if (config.mpa) { if (config.mpa) {
// in MPA mode, we need to copy over the non-js asset files from the // in MPA mode, we need to copy over the non-js asset files from the
// server build since there is no client-side build. // server build since there is no client-side build.
for (const chunk of serverResult.output) { await Promise.all(
if (!chunk.fileName.endsWith('.js')) { serverResult.output.map(async (chunk) => {
const tempPath = path.resolve(config.tempDir, chunk.fileName) if (!chunk.fileName.endsWith('.js')) {
const outPath = path.resolve(config.outDir, chunk.fileName) const tempPath = path.resolve(config.tempDir, chunk.fileName)
await fs.copy(tempPath, outPath) const outPath = path.resolve(config.outDir, chunk.fileName)
} await fs.copy(tempPath, outPath)
} }
})
)
// also copy over public dir // also copy over public dir
const publicDir = path.resolve(config.srcDir, 'public') const publicDir = path.resolve(config.srcDir, 'public')
if (fs.existsSync(publicDir)) { if (fs.existsSync(publicDir)) {

@ -4,6 +4,7 @@ import { createLogger } from 'vite'
import { build, createServer, serve } from '.' import { build, createServer, serve } from '.'
import { init } from './init/init' import { init } from './init/init'
import { version } from '../../package.json' import { version } from '../../package.json'
import { bindShortcuts } from './shortcuts'
const argv: any = minimist(process.argv.slice(2)) const argv: any = minimist(process.argv.slice(2))
@ -33,6 +34,7 @@ if (!command || command === 'dev') {
await server.listen() await server.listen()
logVersion(server.config.logger) logVersion(server.config.logger)
server.printUrls() server.printUrls()
bindShortcuts(server, createDevServer)
} }
createDevServer().catch((err) => { createDevServer().catch((err) => {
createLogger().error( createLogger().error(

@ -222,7 +222,7 @@ export async function resolveSiteData(
appearance: userConfig.appearance ?? true, appearance: userConfig.appearance ?? true,
themeConfig: userConfig.themeConfig || {}, themeConfig: userConfig.themeConfig || {},
locales: userConfig.locales || {}, locales: userConfig.locales || {},
scrollOffset: userConfig.scrollOffset || 90, scrollOffset: userConfig.scrollOffset ?? 90,
cleanUrls: !!userConfig.cleanUrls cleanUrls: !!userConfig.cleanUrls
} }
} }

@ -1,4 +1,4 @@
import fs from 'fs' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import glob from 'fast-glob' import glob from 'fast-glob'
import type { SiteConfig } from './config' import type { SiteConfig } from './config'

@ -5,7 +5,7 @@
import type MarkdownIt from 'markdown-it' import type MarkdownIt from 'markdown-it'
import type { MarkdownEnv } from '../env' import type { MarkdownEnv } from '../env'
import { URL } from 'url' import { URL } from 'url'
import { EXTERNAL_URL_RE, PATHNAME_PROTOCOL_RE } from '../../shared' import { EXTERNAL_URL_RE, PATHNAME_PROTOCOL_RE, isExternal } from '../../shared'
const indexRE = /(^|.*\/)index.md(#?.*)$/i const indexRE = /(^|.*\/)index.md(#?.*)$/i
@ -26,8 +26,7 @@ export const linkPlugin = (
if (hrefIndex >= 0) { if (hrefIndex >= 0) {
const hrefAttr = token.attrs![hrefIndex] const hrefAttr = token.attrs![hrefIndex]
const url = hrefAttr[1] const url = hrefAttr[1]
const isExternal = EXTERNAL_URL_RE.test(url) if (isExternal(url)) {
if (isExternal) {
Object.entries(externalAttrs).forEach(([key, val]) => { Object.entries(externalAttrs).forEach(([key, val]) => {
token.attrSet(key, val) token.attrSet(key, val)
}) })

@ -1,4 +1,4 @@
import fs from 'fs' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import type MarkdownIt from 'markdown-it' import type MarkdownIt from 'markdown-it'
import type { RuleBlock } from 'markdown-it/lib/parser_block' import type { RuleBlock } from 'markdown-it/lib/parser_block'

@ -1,6 +1,6 @@
import { resolveTitleFromToken } from '@mdit-vue/shared' import { resolveTitleFromToken } from '@mdit-vue/shared'
import _debug from 'debug' import _debug from 'debug'
import fs from 'fs' import fs from 'fs-extra'
import { LRUCache } from 'lru-cache' import { LRUCache } from 'lru-cache'
import path from 'path' import path from 'path'
import type { SiteConfig } from './config' import type { SiteConfig } from './config'

@ -60,7 +60,7 @@ export async function localSearchPlugin(
} }
function getLocaleForPath(file: string) { function getLocaleForPath(file: string) {
const relativePath = path.relative(siteConfig.srcDir, file) const relativePath = slash(path.relative(siteConfig.srcDir, file))
const siteData = resolveSiteDataByRoute(siteConfig.site, relativePath) const siteData = resolveSiteDataByRoute(siteConfig.site, relativePath)
return siteData?.localeIndex ?? 'root' return siteData?.localeIndex ?? 'root'
} }
@ -97,7 +97,7 @@ export async function localSearchPlugin(
function getDocId(file: string) { function getDocId(file: string) {
let relFile = slash(path.relative(siteConfig.srcDir, file)) let relFile = slash(path.relative(siteConfig.srcDir, file))
relFile = siteConfig.rewrites.map[relFile] || relFile relFile = siteConfig.rewrites.map[relFile] || relFile
let id = path.join(siteConfig.site.base, relFile) let id = slash(path.join(siteConfig.site.base, relFile))
id = id.replace(/\/index\.md$/, '/') id = id.replace(/\/index\.md$/, '/')
id = id.replace(/\.md$/, siteConfig.cleanUrls ? '' : '.html') id = id.replace(/\.md$/, siteConfig.cleanUrls ? '' : '.html')
return id return id

@ -1,5 +1,5 @@
import compression from 'compression' import compression from 'compression'
import fs from 'fs' import fs from 'fs-extra'
import path from 'path' import path from 'path'
import polka, { type IOptions } from 'polka' import polka, { type IOptions } from 'polka'
import sirv, { type RequestHandler } from 'sirv' import sirv, { type RequestHandler } from 'sirv'

@ -0,0 +1,117 @@
import type { ViteDevServer } from 'vite'
import c from 'picocolors'
import { clearCache } from './markdownToVue'
type CreateDevServer = () => Promise<void>
export type CLIShortcut = {
key: string
description: string
action(
server: ViteDevServer,
createDevServer: CreateDevServer
): void | Promise<void>
}
export function bindShortcuts(
server: ViteDevServer,
createDevServer: CreateDevServer
): void {
if (!server.httpServer || !process.stdin.isTTY || process.env.CI) {
return
}
server.config.logger.info(
c.dim(c.green(' ➜')) +
c.dim(' press ') +
c.bold('h') +
c.dim(' to show help')
)
let actionRunning = false
const onInput = async (input: string) => {
// ctrl+c or ctrl+d
if (input === '\x03' || input === '\x04') {
await server.close().finally(() => process.exit(1))
return
}
if (actionRunning) return
if (input === 'h') {
server.config.logger.info(
[
'',
c.bold(' Shortcuts'),
...SHORTCUTS.map(
(shortcut) =>
c.dim(' press ') +
c.bold(shortcut.key) +
c.dim(` to ${shortcut.description}`)
)
].join('\n')
)
}
const shortcut = SHORTCUTS.find((shortcut) => shortcut.key === input)
if (!shortcut) return
actionRunning = true
await shortcut.action(server, createDevServer)
actionRunning = false
}
process.stdin.setRawMode(true)
process.stdin.on('data', onInput).setEncoding('utf8').resume()
server.httpServer.on('close', () => {
process.stdin.off('data', onInput).pause()
})
}
const SHORTCUTS: CLIShortcut[] = [
{
key: 'r',
description: 'restart the server',
async action(server, createDevServer) {
server.config.logger.info(c.green(`restarting server...\n`), {
clear: true,
timestamp: true
})
clearCache()
await server.close()
await createDevServer()
}
},
{
key: 'u',
description: 'show server url',
action(server) {
server.config.logger.info('')
server.printUrls()
}
},
{
key: 'o',
description: 'open in browser',
action(server) {
server.openBrowser()
}
},
{
key: 'c',
description: 'clear console',
action(server) {
server.config.logger.clearScreen('error')
}
},
{
key: 'q',
description: 'quit',
async action(server) {
await server.close().finally(() => process.exit())
}
}
]

@ -0,0 +1 @@
export * from './theme'
Loading…
Cancel
Save