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,4 +1,4 @@
# VitePress (alpha) 📝💨
# VitePress (beta) 📝💨
[![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)
@ -8,7 +8,7 @@
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

@ -18,7 +18,7 @@ All **static** path references, including absolute paths, should be based on you
## 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`.

@ -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.

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

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

File diff suppressed because it is too large Load Diff

@ -1,13 +1,15 @@
import { readFileSync, writeFileSync } from 'fs'
import { resolve } from 'path'
import { fileURLToPath } from 'url'
import { createRequire } from 'module'
import c from 'picocolors'
import prompts from 'prompts'
import { execa } from 'execa'
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 versionIncrements = ['patch', 'minor', 'major']

@ -303,6 +303,8 @@ function handleHMR(route: Route): void {
function shouldHotReload(payload: PageDataPayload): boolean {
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
}

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

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

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

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

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

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

@ -61,9 +61,12 @@ onMounted(() => {
preconnect()
const handleSearchHotKey = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
const handleSearchHotKey = (event: KeyboardEvent) => {
if (
(event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) ||
(!isEditingContent(event) && event.key === '/')
) {
event.preventDefault()
load()
remove()
}
@ -101,6 +104,18 @@ function poll() {
}, 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
const showSearch = ref(false)
@ -112,6 +127,13 @@ if (__VP_LOCAL_SEARCH__) {
showSearch.value = true
}
})
onKeyStroke('/', (event) => {
if (!isEditingContent(event)) {
event.preventDefault()
showSearch.value = true
}
})
}
const metaKey = ref(`'Meta'`)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -5,7 +5,7 @@
import type MarkdownIt from 'markdown-it'
import type { MarkdownEnv } from '../env'
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
@ -26,8 +26,7 @@ export const linkPlugin = (
if (hrefIndex >= 0) {
const hrefAttr = token.attrs![hrefIndex]
const url = hrefAttr[1]
const isExternal = EXTERNAL_URL_RE.test(url)
if (isExternal) {
if (isExternal(url)) {
Object.entries(externalAttrs).forEach(([key, val]) => {
token.attrSet(key, val)
})

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

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

@ -60,7 +60,7 @@ export async function localSearchPlugin(
}
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)
return siteData?.localeIndex ?? 'root'
}
@ -97,7 +97,7 @@ export async function localSearchPlugin(
function getDocId(file: string) {
let relFile = slash(path.relative(siteConfig.srcDir, file))
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(/\.md$/, siteConfig.cleanUrls ? '' : '.html')
return id

@ -1,5 +1,5 @@
import compression from 'compression'
import fs from 'fs'
import fs from 'fs-extra'
import path from 'path'
import polka, { type IOptions } from 'polka'
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